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();
83 public void removeMirrorDir(@NotNull final File dir) {
84 synchronized (myLock) {
85 List<String> urlsMappedToDir = getUrlsMappedToDir(dir);
86 for (String url : urlsMappedToDir) {
87 myMirrorMap.remove(url);
95 public Map<String, File> getMappings() {
96 Map<String, String> mirrorMapSnapshot;
97 synchronized (myLock) {
98 mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
100 Map<String, File> result = new HashMap<String, File>();
101 for (Map.Entry<String, String> entry : mirrorMapSnapshot.entrySet()) {
102 String url = entry.getKey();
103 String dir = entry.getValue();
104 result.put(url, new File(myBaseMirrorsDir, dir));
111 public String getUrl(@NotNull String cloneDirName) {
112 Map<String, String> mirrorMapSnapshot;
113 synchronized (myLock) {
114 mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
116 for (Map.Entry<String, String> e : mirrorMapSnapshot.entrySet()) {
117 if (cloneDirName.equals(e.getValue()))
123 public long getLastUsedTime(@NotNull final File dir) {
124 File timestamp = new File(dir, "timestamp");
125 if (timestamp.exists()) {
127 List<String> lines = FileUtil.readFile(timestamp);
129 return dir.lastModified();
131 return Long.valueOf(lines.get(0));
132 } catch (IOException e) {
133 return dir.lastModified();
136 return dir.lastModified();
142 private List<String> getUrlsMappedToDir(@NotNull final File dir) {
143 synchronized (myLock) {
144 List<String> urlsMappedToDir = new ArrayList<String>();
145 for (Map.Entry<String, String> entry : myMirrorMap.entrySet()) {
146 String url = entry.getKey();
147 String dirName = entry.getValue();
148 if (dir.equals(new File(myBaseMirrorsDir, dirName)))
149 urlsMappedToDir.add(url);
151 return urlsMappedToDir;
157 * Returns repository dir name for specified url. Every url gets unique dir name.
158 * @param url url of interest
162 private String getDirNameForUrl(@NotNull final String url) {
163 synchronized (myLock) {
164 String dirName = myMirrorMap.get(url);
167 dirName = getUniqueDirNameForUrl(url);
168 myMirrorMap.put(url, dirName);
176 private String getUniqueDirNameForUrl(@NotNull final String url) {
177 String dirName = calculateDirNameForUrl(url);
179 synchronized (myLock) {
180 while (isOccupiedDirName(dirName) || isInvalidDirName(dirName)) {
181 dirName = calculateDirNameForUrl(url + i);
190 private String calculateDirNameForUrl(@NotNull String url) {
191 return String.format("git-%08X.git", myHashCalculator.getHash(url) & 0xFFFFFFFFL);
195 private boolean isOccupiedDirName(@NotNull final String dirName) {
196 synchronized (myLock) {
197 return myMirrorMap.values().contains(dirName)/* || new File(myBaseMirrorsDir, dirName).exists()*/;
202 private boolean isInvalidDirName(@NotNull final String dirName) {
203 synchronized (myLock) {
204 return myInvalidDirNames.contains(dirName);
209 private void saveMappingToFile() {
210 synchronized (myLock) {
211 LOG.debug("Save mapping to " + myMapFile.getAbsolutePath());
212 StringBuilder sb = new StringBuilder();
213 for (Map.Entry<String, String> mirror : myMirrorMap.entrySet()) {
214 String url = mirror.getKey();
215 String dir = mirror.getValue();
216 sb.append(url).append(" = ").append(dir).append("\n");
218 FileUtil.writeFile(myMapFile, sb.toString());
223 private void saveInvalidDirsToFile() {
224 synchronized (myLock) {
225 LOG.debug("Save invalid dirs to " + myInvalidDirsFile.getAbsolutePath());
226 StringBuilder sb = new StringBuilder();
227 for (String dirName : myInvalidDirNames) {
228 sb.append(dirName).append("\n");
230 FileUtil.writeFile(myInvalidDirsFile, sb.toString());
235 private void loadInvalidDirs() {
236 synchronized (myLock) {
237 LOG.debug("Parse invalid dirs file " + myInvalidDirsFile.getAbsolutePath());
238 if (myInvalidDirsFile.exists()) {
239 for (String line : readLines(myInvalidDirsFile)) {
240 String dirName = line.trim();
241 if (dirName.length() > 0)
242 myInvalidDirNames.add(dirName);
249 private void loadMappings() {
250 synchronized (myLock) {
251 LOG.debug("Parse mapping file " + myMapFile.getAbsolutePath());
252 if (myMapFile.exists()) {
261 private void readMappings() {
262 synchronized (myLock) {
263 boolean mappingsFileHasObsoleteDirs = false;
265 for (String line : readLines(myMapFile)) {
266 int separatorIndex = line.lastIndexOf(" = ");
267 if (separatorIndex == -1) {
268 if (!line.equals(""))
269 LOG.warn("Cannot parse mapping '" + line + "', skip it.");
271 String url = line.substring(0, separatorIndex);
272 String dirName = line.substring(separatorIndex + 3);
274 if (!new File(myBaseMirrorsDir, dirName).isDirectory()) {
275 LOG.info("Skip mapping " + line + ": " + dirName + " because the specified directory does not exist");
276 mappingsFileHasObsoleteDirs = true;
280 if (myMirrorMap.values().contains(dirName)) {
281 LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url);
283 myMirrorMap.put(url, dirName);
288 if (mappingsFileHasObsoleteDirs) {
295 private List<String> readLines(@NotNull final File file) {
296 synchronized (myLock) {
298 return FileUtil.readFile(file);
299 } catch (IOException e) {
300 LOG.error("Error while reading file " + file.getAbsolutePath() + " assume it is empty", e);
301 return new ArrayList<String>();
307 private void createMapFile() {
308 synchronized (myLock) {
309 LOG.info("No mapping file found at " + myMapFile.getAbsolutePath() + ", create a new one");
310 if (!myBaseMirrorsDir.exists() && !myBaseMirrorsDir.mkdirs()) {
311 LOG.error("Cannot create base mirrors dir at " + myBaseMirrorsDir.getAbsolutePath() + ", start with empty mapping");
314 if (myMapFile.createNewFile()) {
317 LOG.warn("Someone else creates a mapping file " + myMapFile.getAbsolutePath() + ", will use it");
320 } catch (IOException e) {
321 LOG.error("Cannot create a mapping file at " + myMapFile.getAbsolutePath() + ", start with empty mapping", e);
328 private void restoreMapFile() {
329 synchronized (myLock) {
330 LOG.info("Restore mapping from existing repositories");
331 Map<String, String> restoredMappings = restoreMappings();
332 myMirrorMap.putAll(restoredMappings);
339 private Map<String, String> restoreMappings() {
340 Map<String, String> result = new HashMap<String, String>();
341 File[] subDirs = findRepositoryDirs();
342 if (subDirs.length > 0) {
343 LOG.info(subDirs.length + " existing repositories found");
344 for (File dir : subDirs) {
345 String url = getRemoteRepositoryUrl(dir);
347 result.put(url, dir.getName());
349 LOG.warn("Cannot retrieve remote repository url for " + dir.getName() + ", skip it");
353 LOG.info("No existing repositories found");
360 private File[] findRepositoryDirs() {
361 final File[] dirs = myBaseMirrorsDir.listFiles(new FileFilter() {
362 public boolean accept(File f) {
363 return f.isDirectory() && new File(f, "config").exists();
366 return dirs != null ? dirs : new File[0];
371 private String getRemoteRepositoryUrl(@NotNull final File repositoryDir) {
373 Repository r = new RepositoryBuilder().setBare().setGitDir(repositoryDir).build();
374 StoredConfig config = r.getConfig();
375 String teamcityRemote = config.getString("teamcity", null, "remote");
376 if (teamcityRemote != null)
377 return teamcityRemote;
378 return config.getString("remote", "origin", "url");
379 } catch (Exception e) {
380 LOG.warn("Error while trying to get remote repository url at " + repositoryDir.getAbsolutePath(), e);