OSDN Git Service

initial TGitCache support added
[tortoisegit/TortoiseGitJp.git] / src / TGitCache / SVNStatusCache.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // External Cache Copyright (C) 2005-2006,2008 - TortoiseSVN\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 \r
20 #include "StdAfx.h"\r
21 #include "GitStatus.h"\r
22 #include "GitStatuscache.h"\r
23 #include "CacheInterface.h"\r
24 #include "shlobj.h"\r
25 \r
26 //////////////////////////////////////////////////////////////////////////\r
27 \r
28 CGitStatusCache* CGitStatusCache::m_pInstance;\r
29 \r
30 CGitStatusCache& CGitStatusCache::Instance()\r
31 {\r
32         ATLASSERT(m_pInstance != NULL);\r
33         return *m_pInstance;\r
34 }\r
35 \r
36 void CGitStatusCache::Create()\r
37 {\r
38         ATLASSERT(m_pInstance == NULL);\r
39         m_pInstance = new CGitStatusCache;\r
40 \r
41         m_pInstance->watcher.SetFolderCrawler(&m_pInstance->m_folderCrawler);\r
42 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto exit;\r
43 #define LOADVALUEFROMFILE2(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto error;\r
44         unsigned int value = (unsigned int)-1;\r
45         FILE * pFile = NULL;\r
46         // find the location of the cache\r
47         TCHAR path[MAX_PATH];           //MAX_PATH ok here.\r
48         TCHAR path2[MAX_PATH];\r
49         if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)\r
50         {\r
51                 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));\r
52                 if (!PathIsDirectory(path))\r
53                 {\r
54                         if (CreateDirectory(path, NULL)==0)\r
55                                 goto error;\r
56                 }\r
57                 _tcscat_s(path, MAX_PATH, _T("\\cache"));\r
58                 // in case the cache file is corrupt, we could crash while\r
59                 // reading it! To prevent crashing every time once that happens,\r
60                 // we make a copy of the cache file and use that copy to read from.\r
61                 // if that copy is corrupt, the original file won't exist anymore\r
62                 // and the second time we start up and try to read the file,\r
63                 // it's not there anymore and we start from scratch without a crash.\r
64                 _tcscpy_s(path2, MAX_PATH, path);\r
65                 _tcscat_s(path2, MAX_PATH, _T("2"));\r
66                 DeleteFile(path2);\r
67                 CopyFile(path, path2, FALSE);\r
68                 DeleteFile(path);\r
69                 pFile = _tfsopen(path2, _T("rb"), _SH_DENYNO);\r
70                 if (pFile)\r
71                 {\r
72                         try\r
73                         {\r
74                                 LOADVALUEFROMFILE(value);\r
75                                 if (value != 2)\r
76                                 {\r
77                                         goto error;\r
78                                 }\r
79                                 int mapsize = 0;\r
80                                 LOADVALUEFROMFILE(mapsize);\r
81                                 for (int i=0; i<mapsize; ++i)\r
82                                 {\r
83                                         LOADVALUEFROMFILE2(value);      \r
84                                         if (value > MAX_PATH)\r
85                                                 goto error;\r
86                                         if (value)\r
87                                         {\r
88                                                 CString sKey;\r
89                                                 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)\r
90                                                 {\r
91                                                         sKey.ReleaseBuffer(0);\r
92                                                         goto error;\r
93                                                 }\r
94                                                 sKey.ReleaseBuffer(value);\r
95                                                 CCachedDirectory * cacheddir = new CCachedDirectory();\r
96                                                 if (cacheddir == NULL)\r
97                                                         goto error;\r
98                                                 if (!cacheddir->LoadFromDisk(pFile))\r
99                                                         goto error;\r
100                                                 CTGitPath KeyPath = CTGitPath(sKey);\r
101                                                 if (m_pInstance->IsPathAllowed(KeyPath))\r
102                                                 {\r
103                                                         m_pInstance->m_directoryCache[KeyPath] = cacheddir;\r
104                                                         // only add the path to the watch list if it is versioned\r
105                                                         if ((cacheddir->GetCurrentFullStatus() != git_wc_status_unversioned)&&(cacheddir->GetCurrentFullStatus() != git_wc_status_none))\r
106                                                                 m_pInstance->watcher.AddPath(KeyPath);\r
107                                                         // do *not* add the paths for crawling!\r
108                                                         // because crawled paths will trigger a shell\r
109                                                         // notification, which makes the desktop flash constantly\r
110                                                         // until the whole first time crawling is over\r
111                                                         // m_pInstance->AddFolderForCrawling(KeyPath);\r
112                                                 }\r
113                                         }\r
114                                 }\r
115                         }\r
116                         catch (CAtlException)\r
117                         {\r
118                                 goto error;\r
119                         }\r
120                 }\r
121         }\r
122 exit:\r
123         if (pFile)\r
124                 fclose(pFile);\r
125         DeleteFile(path2);\r
126         ATLTRACE("cache loaded from disk successfully!\n");\r
127         return;\r
128 error:\r
129         fclose(pFile);\r
130         DeleteFile(path2);\r
131         if (m_pInstance)\r
132         {\r
133                 m_pInstance->Stop();\r
134                 Sleep(100);\r
135         }\r
136         delete m_pInstance;\r
137         m_pInstance = new CGitStatusCache;\r
138         ATLTRACE("cache not loaded from disk\n");\r
139 }\r
140 \r
141 bool CGitStatusCache::SaveCache()\r
142 {\r
143 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) goto error;\r
144         unsigned int value = 0;\r
145         // save the cache to disk\r
146         FILE * pFile = NULL;\r
147         // find a location to write the cache to\r
148         TCHAR path[MAX_PATH];           //MAX_PATH ok here.\r
149         if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)\r
150         {\r
151                 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));\r
152                 if (!PathIsDirectory(path))\r
153                         CreateDirectory(path, NULL);\r
154                 _tcscat_s(path, MAX_PATH, _T("\\cache"));\r
155                 _tfopen_s(&pFile, path, _T("wb"));\r
156                 if (pFile)\r
157                 {\r
158                         value = 2;              // 'version'\r
159                         WRITEVALUETOFILE(value);\r
160                         value = (int)m_pInstance->m_directoryCache.size();\r
161                         WRITEVALUETOFILE(value);\r
162                         for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)\r
163                         {\r
164                                 if (I->second == NULL)\r
165                                 {\r
166                                         value = 0;\r
167                                         WRITEVALUETOFILE(value);\r
168                                         continue;\r
169                                 }\r
170                                 const CString& key = I->first.GetWinPathString();\r
171                                 value = key.GetLength();\r
172                                 WRITEVALUETOFILE(value);\r
173                                 if (value)\r
174                                 {\r
175                                         if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)\r
176                                                 goto error;\r
177                                         if (!I->second->SaveToDisk(pFile))\r
178                                                 goto error;\r
179                                 }\r
180                         }\r
181                         fclose(pFile);\r
182                 }\r
183         }\r
184         ATLTRACE(_T("cache saved to disk at %s\n"), path);\r
185         return true;\r
186 error:\r
187         fclose(pFile);\r
188         if (m_pInstance)\r
189         {\r
190                 m_pInstance->Stop();\r
191                 Sleep(100);\r
192         }\r
193         delete m_pInstance;\r
194         m_pInstance = NULL;\r
195         DeleteFile(path);\r
196         return false;\r
197 }\r
198 \r
199 void CGitStatusCache::Destroy()\r
200 {\r
201         if (m_pInstance)\r
202         {\r
203                 m_pInstance->Stop();\r
204                 Sleep(100);\r
205         }\r
206         delete m_pInstance;\r
207         m_pInstance = NULL;\r
208 }\r
209 \r
210 void CGitStatusCache::Stop()\r
211 {\r
212 //      m_svnHelp.Cancel(true);\r
213         watcher.Stop();\r
214         m_folderCrawler.Stop();\r
215         m_shellUpdater.Stop();\r
216 }\r
217 \r
218 void CGitStatusCache::Init()\r
219 {\r
220         m_folderCrawler.Initialise();\r
221         m_shellUpdater.Initialise();\r
222 }\r
223 \r
224 CGitStatusCache::CGitStatusCache(void)\r
225 {\r
226         TCHAR path[MAX_PATH];\r
227         SHGetFolderPath(NULL, CSIDL_COOKIES, NULL, 0, path);\r
228         m_NoWatchPaths.insert(CTGitPath(CString(path)));\r
229         SHGetFolderPath(NULL, CSIDL_HISTORY, NULL, 0, path);\r
230         m_NoWatchPaths.insert(CTGitPath(CString(path)));\r
231         SHGetFolderPath(NULL, CSIDL_INTERNET_CACHE, NULL, 0, path);\r
232         m_NoWatchPaths.insert(CTGitPath(CString(path)));\r
233         SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, path);\r
234         m_NoWatchPaths.insert(CTGitPath(CString(path)));\r
235         SHGetFolderPath(NULL, CSIDL_WINDOWS, NULL, 0, path);\r
236         m_NoWatchPaths.insert(CTGitPath(CString(path)));\r
237         m_bClearMemory = false;\r
238         m_mostRecentExpiresAt = 0;\r
239 }\r
240 \r
241 CGitStatusCache::~CGitStatusCache(void)\r
242 {\r
243         for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)\r
244         {\r
245                 delete I->second;\r
246                 I->second = NULL;\r
247         }\r
248 }\r
249 \r
250 void CGitStatusCache::Refresh()\r
251 {\r
252         m_shellCache.ForceRefresh();\r
253 //      m_pInstance->m_svnHelp.ReloadConfig();\r
254         if (m_pInstance->m_directoryCache.size())\r
255         {\r
256                 CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin();\r
257                 for (/* no init */; I != m_pInstance->m_directoryCache.end(); ++I)\r
258                 {\r
259                         if (m_shellCache.IsPathAllowed(I->first.GetWinPath()))\r
260                                 I->second->RefreshMostImportant();\r
261                         else\r
262                         {\r
263                                 CGitStatusCache::Instance().RemoveCacheForPath(I->first);\r
264                                 I = m_pInstance->m_directoryCache.begin();\r
265                                 if (I == m_pInstance->m_directoryCache.end())\r
266                                         break;\r
267                         }\r
268                 }\r
269         }\r
270 }\r
271 \r
272 bool CGitStatusCache::IsPathGood(const CTGitPath& path)\r
273 {\r
274         for (std::set<CTGitPath>::iterator it = m_NoWatchPaths.begin(); it != m_NoWatchPaths.end(); ++it)\r
275         {\r
276                 if (it->IsAncestorOf(path))\r
277                         return false;\r
278         }\r
279         return true;\r
280 }\r
281 \r
282 void CGitStatusCache::UpdateShell(const CTGitPath& path)\r
283 {\r
284         m_shellUpdater.AddPathForUpdate(path);\r
285 }\r
286 \r
287 void CGitStatusCache::ClearCache()\r
288 {\r
289         for (CCachedDirectory::CachedDirMap::iterator I = m_directoryCache.begin(); I != m_directoryCache.end(); ++I)\r
290         {\r
291                 delete I->second;\r
292                 I->second = NULL;\r
293         }\r
294         m_directoryCache.clear();\r
295 }\r
296 \r
297 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory * cdir)\r
298 {\r
299         if (cdir == NULL)\r
300                 return false;\r
301         AssertWriting();\r
302         typedef std::map<CTGitPath, git_wc_status_kind>  ChildDirStatus;\r
303         if (cdir->m_childDirectories.size())\r
304         {\r
305                 ChildDirStatus::iterator it = cdir->m_childDirectories.begin();\r
306                 for (; it != cdir->m_childDirectories.end(); )\r
307                 {\r
308                         CCachedDirectory * childdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it->first);\r
309                         if ((childdir)&&(!cdir->m_directoryPath.IsEquivalentTo(childdir->m_directoryPath)))\r
310                                 RemoveCacheForDirectory(childdir);\r
311                         cdir->m_childDirectories.erase(it->first);\r
312                         it = cdir->m_childDirectories.begin();\r
313                 }\r
314         }\r
315         cdir->m_childDirectories.clear();\r
316         m_directoryCache.erase(cdir->m_directoryPath);\r
317         ATLTRACE(_T("removed path %s from cache\n"), cdir->m_directoryPath);\r
318         delete cdir;\r
319         cdir = NULL;\r
320         return true;\r
321 }\r
322 \r
323 void CGitStatusCache::RemoveCacheForPath(const CTGitPath& path)\r
324 {\r
325         // Stop the crawler starting on a new folder\r
326         CCrawlInhibitor crawlInhibit(&m_folderCrawler);\r
327         CCachedDirectory::ItDir itMap;\r
328         CCachedDirectory * dirtoremove = NULL;\r
329 \r
330         AssertWriting();\r
331         itMap = m_directoryCache.find(path);\r
332         if ((itMap != m_directoryCache.end())&&(itMap->second))\r
333                 dirtoremove = itMap->second;\r
334         if (dirtoremove == NULL)\r
335                 return;\r
336         ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath));\r
337         RemoveCacheForDirectory(dirtoremove);\r
338 }\r
339 \r
340 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath& path)\r
341 {\r
342         ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));\r
343 \r
344 \r
345         CCachedDirectory::ItDir itMap;\r
346         itMap = m_directoryCache.find(path);\r
347         if ((itMap != m_directoryCache.end())&&(itMap->second))\r
348         {\r
349                 // We've found this directory in the cache \r
350                 return itMap->second;\r
351         }\r
352         else\r
353         {\r
354                 // if the CCachedDirectory is NULL but the path is in our cache,\r
355                 // that means that path got invalidated and needs to be treated\r
356                 // as if it never was in our cache. So we remove the last remains\r
357                 // from the cache and start from scratch.\r
358                 AssertLock();\r
359                 if (!IsWriter())\r
360                 {\r
361                         // upgrading our state to writer\r
362                         ATLTRACE("trying to upgrade the state to \"Writer\"\n");\r
363                         Done();\r
364                         ATLTRACE("Returned \"Reader\" state\n");\r
365                         WaitToWrite();\r
366                         ATLTRACE("Got \"Writer\" state now\n");\r
367                 }\r
368                 // Since above there's a small chance that before we can upgrade to\r
369                 // writer state some other thread gained writer state and changed\r
370                 // the data, we have to recreate the iterator here again.\r
371                 itMap = m_directoryCache.find(path);\r
372                 if (itMap!=m_directoryCache.end())\r
373                         m_directoryCache.erase(itMap);\r
374                 // We don't know anything about this directory yet - lets add it to our cache\r
375                 // but only if it exists!\r
376                 if (path.Exists() && m_shellCache.IsPathAllowed(path.GetWinPath()) && !g_GitAdminDir.IsAdminDirPath(path.GetWinPath()))\r
377                 {\r
378                         // some notifications are for files which got removed/moved around.\r
379                         // In such cases, the CTGitPath::IsDirectory() will return true (it assumes a directory if\r
380                         // the path doesn't exist). Which means we can get here with a path to a file\r
381                         // instead of a directory.\r
382                         // Since we're here most likely called from the crawler thread, the file could exist\r
383                         // again. If that's the case, just do nothing\r
384                         if (path.IsDirectory()||(!path.Exists()))\r
385                         {\r
386                                 ATLTRACE(_T("adding %s to our cache\n"), path.GetWinPath());\r
387                                 CCachedDirectory * newcdir = new CCachedDirectory(path);\r
388                                 if (newcdir)\r
389                                 {\r
390                                         CCachedDirectory * cdir = m_directoryCache.insert(m_directoryCache.lower_bound(path), std::make_pair(path, newcdir))->second;\r
391                                         if ((!path.IsEmpty())&&(path.HasAdminDir()))\r
392                                                 watcher.AddPath(path);\r
393                                         return cdir;            \r
394                                 }\r
395                                 m_bClearMemory = true;\r
396                         }\r
397                 }\r
398                 return NULL;\r
399         }\r
400 }\r
401 \r
402 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath& path)\r
403 {\r
404         ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));\r
405 \r
406         CCachedDirectory::ItDir itMap;\r
407         itMap = m_directoryCache.find(path);\r
408         if(itMap != m_directoryCache.end())\r
409         {\r
410                 // We've found this directory in the cache \r
411                 return itMap->second;\r
412         }\r
413         return NULL;\r
414 }\r
415 \r
416 CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags,  bool bFetch /* = true */)\r
417 {\r
418         bool bRecursive = !!(flags & TSVNCACHE_FLAGS_RECUSIVE_STATUS);\r
419 \r
420         // Check a very short-lived 'mini-cache' of the last thing we were asked for.\r
421         long now = (long)GetTickCount();\r
422         if(now-m_mostRecentExpiresAt < 0)\r
423         {\r
424                 if(path.IsEquivalentToWithoutCase(m_mostRecentPath))\r
425                 {\r
426                         return m_mostRecentStatus;\r
427                 }\r
428         }\r
429         m_mostRecentPath = path;\r
430         m_mostRecentExpiresAt = now+1000;\r
431 \r
432         if (IsPathGood(path))\r
433         {\r
434                 // Stop the crawler starting on a new folder while we're doing this much more important task...\r
435                 // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().\r
436                 CCrawlInhibitor crawlInhibit(&m_folderCrawler);\r
437 \r
438                 CTGitPath dirpath = path.GetContainingDirectory();\r
439                 if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath())))\r
440                         dirpath = path.GetDirectory();\r
441                 CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath);\r
442                 if (cachedDir != NULL)\r
443                 {\r
444                         m_mostRecentStatus = cachedDir->GetStatusForMember(path, bRecursive, bFetch);\r
445                         return m_mostRecentStatus;\r
446                 }\r
447         }\r
448         ATLTRACE(_T("ignored no good path %s\n"), path.GetWinPath());\r
449         m_mostRecentStatus = CStatusCacheEntry();\r
450         if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasSVNAdminDir(path.GetWinPath(), true))\r
451         {\r
452                 m_mostRecentStatus.ForceStatus(git_wc_status_normal);\r
453         }\r
454         return m_mostRecentStatus;\r
455 }\r
456 \r
457 void CGitStatusCache::AddFolderForCrawling(const CTGitPath& path)\r
458 {\r
459         m_folderCrawler.AddDirectoryForUpdate(path);\r
460 }\r
461 \r
462 void CGitStatusCache::CloseWatcherHandles(HDEVNOTIFY hdev)\r
463 {\r
464         CTGitPath path = watcher.CloseInfoMap(hdev);\r
465         m_folderCrawler.BlockPath(path);\r
466 }\r
467 \r
468 void CGitStatusCache::CloseWatcherHandles(const CTGitPath& path)\r
469 {\r
470         watcher.CloseHandlesForPath(path);\r
471         m_folderCrawler.BlockPath(path);\r
472 }\r