2 * Copyright 2000-2018 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package jetbrains.buildServer.buildTriggers.vcs.git;
19 import com.intellij.openapi.diagnostic.Logger;
20 import jetbrains.buildServer.util.FileUtil;
21 import org.eclipse.jgit.lib.Repository;
22 import org.eclipse.jgit.lib.RepositoryBuilder;
23 import org.eclipse.jgit.lib.StoredConfig;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
28 import java.io.FileFilter;
29 import java.io.IOException;
33 * @author dmitry.neverov
35 public class MirrorManagerImpl implements MirrorManager {
37 private static Logger LOG = Logger.getInstance(MirrorManagerImpl.class.getName());
39 private final File myBaseMirrorsDir;
40 private final File myMapFile;
41 private final File myInvalidDirsFile;
43 private final Map<String, String> myMirrorMap = new HashMap<String, String>();
44 private final Set<String> myInvalidDirNames = new HashSet<String>();
45 private final Object myLock = new Object();
46 private final HashCalculator myHashCalculator;
49 public MirrorManagerImpl(@NotNull MirrorConfig config, @NotNull HashCalculator hash) {
50 myHashCalculator = hash;
51 myBaseMirrorsDir = config.getCachesDir();
52 myMapFile = new File(myBaseMirrorsDir, "map");
53 myInvalidDirsFile = new File(myBaseMirrorsDir, "invalid");
60 public File getBaseMirrorsDir() {
61 return myBaseMirrorsDir;
66 public File getMirrorDir(@NotNull String repositoryUrl) {
67 return new File(myBaseMirrorsDir, getDirNameForUrl(repositoryUrl));
71 public void invalidate(@NotNull final File dir) {
72 synchronized (myLock) {
73 List<String> urlsMappedToDir = getUrlsMappedToDir(dir);
74 for (String url : urlsMappedToDir) {
75 String dirName = myMirrorMap.remove(url);
76 myInvalidDirNames.add(dirName);
79 saveInvalidDirsToFile();
85 public Map<String, File> getMappings() {
86 Map<String, String> mirrorMapSnapshot;
87 synchronized (myLock) {
88 mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
90 Map<String, File> result = new HashMap<String, File>();
91 for (Map.Entry<String, String> entry : mirrorMapSnapshot.entrySet()) {
92 String url = entry.getKey();
93 String dir = entry.getValue();
94 result.put(url, new File(myBaseMirrorsDir, dir));
101 public String getUrl(@NotNull String cloneDirName) {
102 Map<String, String> mirrorMapSnapshot;
103 synchronized (myLock) {
104 mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
106 for (Map.Entry<String, String> e : mirrorMapSnapshot.entrySet()) {
107 if (cloneDirName.equals(e.getValue()))
113 public long getLastUsedTime(@NotNull final File dir) {
114 File timestamp = new File(dir, "timestamp");
115 if (timestamp.exists()) {
117 List<String> lines = FileUtil.readFile(timestamp);
119 return dir.lastModified();
121 return Long.valueOf(lines.get(0));
122 } catch (IOException e) {
123 return dir.lastModified();
126 return dir.lastModified();
132 private List<String> getUrlsMappedToDir(@NotNull final File dir) {
133 synchronized (myLock) {
134 List<String> urlsMappedToDir = new ArrayList<String>();
135 for (Map.Entry<String, String> entry : myMirrorMap.entrySet()) {
136 String url = entry.getKey();
137 String dirName = entry.getValue();
138 if (dir.equals(new File(myBaseMirrorsDir, dirName)))
139 urlsMappedToDir.add(url);
141 return urlsMappedToDir;
147 * Returns repository dir name for specified url. Every url gets unique dir name.
148 * @param url url of interest
152 private String getDirNameForUrl(@NotNull final String url) {
153 synchronized (myLock) {
154 String dirName = myMirrorMap.get(url);
157 dirName = getUniqueDirNameForUrl(url);
158 myMirrorMap.put(url, dirName);
166 private String getUniqueDirNameForUrl(@NotNull final String url) {
167 String dirName = calculateDirNameForUrl(url);
169 synchronized (myLock) {
170 while (isOccupiedDirName(dirName) || isInvalidDirName(dirName)) {
171 dirName = calculateDirNameForUrl(url + i);
180 private String calculateDirNameForUrl(@NotNull String url) {
181 return String.format("git-%08X.git", myHashCalculator.getHash(url) & 0xFFFFFFFFL);
185 private boolean isOccupiedDirName(@NotNull final String dirName) {
186 synchronized (myLock) {
187 return myMirrorMap.values().contains(dirName)/* || new File(myBaseMirrorsDir, dirName).exists()*/;
192 private boolean isInvalidDirName(@NotNull final String dirName) {
193 synchronized (myLock) {
194 return myInvalidDirNames.contains(dirName);
199 private void saveMappingToFile() {
200 synchronized (myLock) {
201 LOG.debug("Save mapping to " + myMapFile.getAbsolutePath());
202 StringBuilder sb = new StringBuilder();
203 for (Map.Entry<String, String> mirror : myMirrorMap.entrySet()) {
204 String url = mirror.getKey();
205 String dir = mirror.getValue();
206 sb.append(url).append(" = ").append(dir).append("\n");
208 FileUtil.writeFile(myMapFile, sb.toString());
213 private void saveInvalidDirsToFile() {
214 synchronized (myLock) {
215 LOG.debug("Save invalid dirs to " + myInvalidDirsFile.getAbsolutePath());
216 StringBuilder sb = new StringBuilder();
217 for (String dirName : myInvalidDirNames) {
218 sb.append(dirName).append("\n");
220 FileUtil.writeFile(myInvalidDirsFile, sb.toString());
225 private void loadInvalidDirs() {
226 synchronized (myLock) {
227 LOG.debug("Parse invalid dirs file " + myInvalidDirsFile.getAbsolutePath());
228 if (myInvalidDirsFile.exists()) {
229 for (String line : readLines(myInvalidDirsFile)) {
230 String dirName = line.trim();
231 if (dirName.length() > 0)
232 myInvalidDirNames.add(dirName);
239 private void loadMappings() {
240 synchronized (myLock) {
241 LOG.debug("Parse mapping file " + myMapFile.getAbsolutePath());
242 if (myMapFile.exists()) {
251 private void readMappings() {
252 synchronized (myLock) {
253 for (String line : readLines(myMapFile)) {
254 int separatorIndex = line.lastIndexOf(" = ");
255 if (separatorIndex == -1) {
256 if (!line.equals(""))
257 LOG.warn("Cannot parse mapping '" + line + "', skip it.");
259 String url = line.substring(0, separatorIndex);
260 String dirName = line.substring(separatorIndex + 3);
261 if (myMirrorMap.values().contains(dirName)) {
262 LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url);
264 myMirrorMap.put(url, dirName);
272 private List<String> readLines(@NotNull final File file) {
273 synchronized (myLock) {
275 return FileUtil.readFile(file);
276 } catch (IOException e) {
277 LOG.error("Error while reading file " + file.getAbsolutePath() + " assume it is empty", e);
278 return new ArrayList<String>();
284 private void createMapFile() {
285 synchronized (myLock) {
286 LOG.info("No mapping file found at " + myMapFile.getAbsolutePath() + ", create a new one");
287 if (!myBaseMirrorsDir.exists() && !myBaseMirrorsDir.mkdirs()) {
288 LOG.error("Cannot create base mirrors dir at " + myBaseMirrorsDir.getAbsolutePath() + ", start with empty mapping");
291 if (myMapFile.createNewFile()) {
294 LOG.warn("Someone else creates a mapping file " + myMapFile.getAbsolutePath() + ", will use it");
297 } catch (IOException e) {
298 LOG.error("Cannot create a mapping file at " + myMapFile.getAbsolutePath() + ", start with empty mapping", e);
305 private void restoreMapFile() {
306 synchronized (myLock) {
307 LOG.info("Restore mapping from existing repositories");
308 Map<String, String> restoredMappings = restoreMappings();
309 myMirrorMap.putAll(restoredMappings);
316 private Map<String, String> restoreMappings() {
317 Map<String, String> result = new HashMap<String, String>();
318 File[] subDirs = findRepositoryDirs();
319 if (subDirs.length > 0) {
320 LOG.info(subDirs.length + " existing repositories found");
321 for (File dir : subDirs) {
322 String url = getRemoteRepositoryUrl(dir);
324 result.put(url, dir.getName());
326 LOG.warn("Cannot retrieve remote repository url for " + dir.getName() + ", skip it");
330 LOG.info("No existing repositories found");
337 private File[] findRepositoryDirs() {
338 return myBaseMirrorsDir.listFiles(new FileFilter() {
339 public boolean accept(File f) {
340 return f.isDirectory() && new File(f, "config").exists();
347 private String getRemoteRepositoryUrl(@NotNull final File repositoryDir) {
349 Repository r = new RepositoryBuilder().setBare().setGitDir(repositoryDir).build();
350 StoredConfig config = r.getConfig();
351 String teamcityRemote = config.getString("teamcity", null, "remote");
352 if (teamcityRemote != null)
353 return teamcityRemote;
354 return config.getString("remote", "origin", "url");
355 } catch (Exception e) {
356 LOG.warn("Error while trying to get remote repository url at " + repositoryDir.getAbsolutePath(), e);