TW-57690: now when we load mirrors mapping file we ignore non existing directories...
[teamcity/git-plugin.git] / git-common / src / jetbrains / buildServer / buildTriggers / vcs / git / MirrorManagerImpl.java
1 /*
2  * Copyright 2000-2018 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package jetbrains.buildServer.buildTriggers.vcs.git;
18
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;
26
27 import java.io.File;
28 import java.io.FileFilter;
29 import java.io.IOException;
30 import java.util.*;
31
32 /**
33  * @author dmitry.neverov
34  */
35 public class MirrorManagerImpl implements MirrorManager {
36
37   private static Logger LOG = Logger.getInstance(MirrorManagerImpl.class.getName());
38
39   private final File myBaseMirrorsDir;
40   private final File myMapFile;
41   private final File myInvalidDirsFile;
42   /*url -> dir name*/
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;
47
48
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");
54     loadInvalidDirs();
55     loadMappings();
56   }
57
58
59   @NotNull
60   public File getBaseMirrorsDir() {
61     return myBaseMirrorsDir;
62   }
63
64
65   @NotNull
66   public File getMirrorDir(@NotNull String repositoryUrl) {
67     return new File(myBaseMirrorsDir, getDirNameForUrl(repositoryUrl));
68   }
69
70
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);
77       }
78       saveMappingToFile();
79       saveInvalidDirsToFile();
80     }
81   }
82
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);
88       }
89       saveMappingToFile();
90       FileUtil.delete(dir);
91     }
92   }
93
94   @NotNull
95   public Map<String, File> getMappings() {
96     Map<String, String> mirrorMapSnapshot;
97     synchronized (myLock) {
98       mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
99     }
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));
105     }
106     return result;
107   }
108
109   @Nullable
110   @Override
111   public String getUrl(@NotNull String cloneDirName) {
112     Map<String, String> mirrorMapSnapshot;
113     synchronized (myLock) {
114       mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
115     }
116     for (Map.Entry<String, String> e : mirrorMapSnapshot.entrySet()) {
117       if (cloneDirName.equals(e.getValue()))
118         return e.getKey();
119     }
120     return null;
121   }
122
123   public long getLastUsedTime(@NotNull final File dir) {
124     File timestamp = new File(dir, "timestamp");
125     if (timestamp.exists()) {
126       try {
127         List<String> lines = FileUtil.readFile(timestamp);
128         if (lines.isEmpty())
129           return dir.lastModified();
130         else
131           return Long.valueOf(lines.get(0));
132       } catch (IOException e) {
133         return dir.lastModified();
134       }
135     } else {
136       return dir.lastModified();
137     }
138   }
139
140
141   @NotNull
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);
150       }
151       return urlsMappedToDir;
152     }
153   }
154
155
156   /**
157    * Returns repository dir name for specified url. Every url gets unique dir name.
158    * @param url url of interest
159    * @return see above
160    */
161   @NotNull
162   private String getDirNameForUrl(@NotNull final String url) {
163     synchronized (myLock) {
164       String dirName = myMirrorMap.get(url);
165       if (dirName != null)
166         return dirName;
167       dirName = getUniqueDirNameForUrl(url);
168       myMirrorMap.put(url, dirName);
169       saveMappingToFile();
170       return dirName;
171     }
172   }
173
174
175   @NotNull
176   private String getUniqueDirNameForUrl(@NotNull final String url) {
177     String dirName = calculateDirNameForUrl(url);
178     int i = 0;
179     synchronized (myLock) {
180       while (isOccupiedDirName(dirName) || isInvalidDirName(dirName)) {
181         dirName = calculateDirNameForUrl(url + i);
182         i++;
183       }
184     }
185     return dirName;
186   }
187
188
189   @NotNull
190   private String calculateDirNameForUrl(@NotNull String url) {
191     return String.format("git-%08X.git", myHashCalculator.getHash(url) & 0xFFFFFFFFL);
192   }
193
194
195   private boolean isOccupiedDirName(@NotNull final String dirName) {
196     synchronized (myLock) {
197       return myMirrorMap.values().contains(dirName)/* || new File(myBaseMirrorsDir, dirName).exists()*/;
198     }
199   }
200
201
202   private boolean isInvalidDirName(@NotNull final String dirName) {
203     synchronized (myLock) {
204       return myInvalidDirNames.contains(dirName);
205     }
206   }
207
208
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");
217       }
218       FileUtil.writeFile(myMapFile, sb.toString());
219     }
220   }
221
222
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");
229       }
230       FileUtil.writeFile(myInvalidDirsFile, sb.toString());
231     }
232   }
233
234
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);
243         }
244       }
245     }
246   }
247
248
249   private void loadMappings() {
250     synchronized (myLock) {
251       LOG.debug("Parse mapping file " + myMapFile.getAbsolutePath());
252       if (myMapFile.exists()) {
253         readMappings();
254       } else {
255         createMapFile();
256       }
257     }
258   }
259
260
261   private void readMappings() {
262     synchronized (myLock) {
263       boolean mappingsFileHasObsoleteDirs = false;
264
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.");
270         } else {
271           String url = line.substring(0, separatorIndex);
272           String dirName = line.substring(separatorIndex + 3);
273
274           if (!new File(myBaseMirrorsDir, dirName).isDirectory()) {
275             LOG.info("Skip mapping " + line + ": " + dirName + " because the specified directory does not exist");
276             mappingsFileHasObsoleteDirs = true;
277             continue;
278           }
279
280           if (myMirrorMap.values().contains(dirName)) {
281             LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url);
282           } else {
283             myMirrorMap.put(url, dirName);
284           }
285         }
286       }
287
288       if (mappingsFileHasObsoleteDirs) {
289         saveMappingToFile();
290       }
291     }
292   }
293
294
295   private List<String> readLines(@NotNull final File file) {
296     synchronized (myLock) {
297       try {
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>();
302       }
303     }
304   }
305
306
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");
312       } else {
313         try {
314           if (myMapFile.createNewFile()) {
315             restoreMapFile();
316           } else {
317             LOG.warn("Someone else creates a mapping file " + myMapFile.getAbsolutePath() + ", will use it");
318             readMappings();
319           }
320         } catch (IOException e) {
321           LOG.error("Cannot create a mapping file at " + myMapFile.getAbsolutePath() + ", start with empty mapping", e);
322         }
323       }
324     }
325   }
326
327
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);
333       saveMappingToFile();
334     }
335   }
336
337
338   @NotNull
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);
346         if (url != null) {
347           result.put(url, dir.getName());
348         } else {
349           LOG.warn("Cannot retrieve remote repository url for " + dir.getName() + ", skip it");
350         }
351       }
352     } else {
353       LOG.info("No existing repositories found");
354     }
355     return result;
356   }
357
358
359   @NotNull
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();
364       }
365     });
366     return dirs != null ? dirs : new File[0];
367   }
368
369
370   @Nullable
371   private String getRemoteRepositoryUrl(@NotNull final File repositoryDir) {
372     try {
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);
381       return null;
382     }
383   }
384 }