061c6634c8a34fda96dc87ea1c8fc2866fde7556
[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
84   @NotNull
85   public Map<String, File> getMappings() {
86     Map<String, String> mirrorMapSnapshot;
87     synchronized (myLock) {
88       mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
89     }
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));
95     }
96     return result;
97   }
98
99   @Nullable
100   @Override
101   public String getUrl(@NotNull String cloneDirName) {
102     Map<String, String> mirrorMapSnapshot;
103     synchronized (myLock) {
104       mirrorMapSnapshot = new HashMap<String, String>(myMirrorMap);
105     }
106     for (Map.Entry<String, String> e : mirrorMapSnapshot.entrySet()) {
107       if (cloneDirName.equals(e.getValue()))
108         return e.getKey();
109     }
110     return null;
111   }
112
113   public long getLastUsedTime(@NotNull final File dir) {
114     File timestamp = new File(dir, "timestamp");
115     if (timestamp.exists()) {
116       try {
117         List<String> lines = FileUtil.readFile(timestamp);
118         if (lines.isEmpty())
119           return dir.lastModified();
120         else
121           return Long.valueOf(lines.get(0));
122       } catch (IOException e) {
123         return dir.lastModified();
124       }
125     } else {
126       return dir.lastModified();
127     }
128   }
129
130
131   @NotNull
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);
140       }
141       return urlsMappedToDir;
142     }
143   }
144
145
146   /**
147    * Returns repository dir name for specified url. Every url gets unique dir name.
148    * @param url url of interest
149    * @return see above
150    */
151   @NotNull
152   private String getDirNameForUrl(@NotNull final String url) {
153     synchronized (myLock) {
154       String dirName = myMirrorMap.get(url);
155       if (dirName != null)
156         return dirName;
157       dirName = getUniqueDirNameForUrl(url);
158       myMirrorMap.put(url, dirName);
159       saveMappingToFile();
160       return dirName;
161     }
162   }
163
164
165   @NotNull
166   private String getUniqueDirNameForUrl(@NotNull final String url) {
167     String dirName = calculateDirNameForUrl(url);
168     int i = 0;
169     synchronized (myLock) {
170       while (isOccupiedDirName(dirName) || isInvalidDirName(dirName)) {
171         dirName = calculateDirNameForUrl(url + i);
172         i++;
173       }
174     }
175     return dirName;
176   }
177
178
179   @NotNull
180   private String calculateDirNameForUrl(@NotNull String url) {
181     return String.format("git-%08X.git", myHashCalculator.getHash(url) & 0xFFFFFFFFL);
182   }
183
184
185   private boolean isOccupiedDirName(@NotNull final String dirName) {
186     synchronized (myLock) {
187       return myMirrorMap.values().contains(dirName)/* || new File(myBaseMirrorsDir, dirName).exists()*/;
188     }
189   }
190
191
192   private boolean isInvalidDirName(@NotNull final String dirName) {
193     synchronized (myLock) {
194       return myInvalidDirNames.contains(dirName);
195     }
196   }
197
198
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");
207       }
208       FileUtil.writeFile(myMapFile, sb.toString());
209     }
210   }
211
212
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");
219       }
220       FileUtil.writeFile(myInvalidDirsFile, sb.toString());
221     }
222   }
223
224
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);
233         }
234       }
235     }
236   }
237
238
239   private void loadMappings() {
240     synchronized (myLock) {
241       LOG.debug("Parse mapping file " + myMapFile.getAbsolutePath());
242       if (myMapFile.exists()) {
243         readMappings();
244       } else {
245         createMapFile();
246       }
247     }
248   }
249
250
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.");
258         } else {
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);
263           } else {
264             myMirrorMap.put(url, dirName);
265           }
266         }
267       }
268     }
269   }
270
271
272   private List<String> readLines(@NotNull final File file) {
273     synchronized (myLock) {
274       try {
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>();
279       }
280     }
281   }
282
283
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");
289       } else {
290         try {
291           if (myMapFile.createNewFile()) {
292             restoreMapFile();
293           } else {
294             LOG.warn("Someone else creates a mapping file " + myMapFile.getAbsolutePath() + ", will use it");
295             readMappings();
296           }
297         } catch (IOException e) {
298           LOG.error("Cannot create a mapping file at " + myMapFile.getAbsolutePath() + ", start with empty mapping", e);
299         }
300       }
301     }
302   }
303
304
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);
310       saveMappingToFile();
311     }
312   }
313
314
315   @NotNull
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);
323         if (url != null) {
324           result.put(url, dir.getName());
325         } else {
326           LOG.warn("Cannot retrieve remote repository url for " + dir.getName() + ", skip it");
327         }
328       }
329     } else {
330       LOG.info("No existing repositories found");
331     }
332     return result;
333   }
334
335
336   @NotNull
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();
341       }
342     });
343   }
344
345
346   @Nullable
347   private String getRemoteRepositoryUrl(@NotNull final File repositoryDir) {
348     try {
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);
357       return null;
358     }
359   }
360 }