Sync CrashReport.h from TortoiseSVN
[TortoiseGit.git] / src / TGitCache / GITStatusCache.cpp
blob60bbb9c9647f5e5a97a156b01c9db491714ce2a1
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2006,2008,2010,2014 - TortoiseSVN
4 // Copyright (C) 2008-2014 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "stdafx.h"
22 #include "GitStatus.h"
23 #include "GitStatusCache.h"
24 #include "CacheInterface.h"
25 #include <ShlObj.h>
27 //////////////////////////////////////////////////////////////////////////
29 #define BLOCK_PATH_DEFAULT_TIMEOUT 600 // 10 minutes
30 #define BLOCK_PATH_MAX_TIMEOUT 1200 // 20 minutes
32 CGitStatusCache* CGitStatusCache::m_pInstance;
34 CGitStatusCache& CGitStatusCache::Instance()
36 ATLASSERT(m_pInstance != NULL);
37 return *m_pInstance;
40 void CGitStatusCache::Create()
42 ATLASSERT(m_pInstance == NULL);
43 m_pInstance = new CGitStatusCache;
45 m_pInstance->watcher.SetFolderCrawler(&m_pInstance->m_folderCrawler);
47 if (!CRegStdDWORD(_T("Software\\TortoiseGit\\CacheSave"), TRUE))
48 return;
50 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto exit;
51 #define LOADVALUEFROMFILE2(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto error;
52 unsigned int value = (unsigned int)-1;
53 FILE * pFile = NULL;
54 // find the location of the cache
55 TCHAR path[MAX_PATH] = { 0 }; //MAX_PATH ok here.
56 TCHAR path2[MAX_PATH] = { 0 };
57 if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)
59 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));
60 if (!PathIsDirectory(path))
62 if (CreateDirectory(path, NULL)==0)
63 goto error;
65 _tcscat_s(path, MAX_PATH, _T("\\cache"));
66 // in case the cache file is corrupt, we could crash while
67 // reading it! To prevent crashing every time once that happens,
68 // we make a copy of the cache file and use that copy to read from.
69 // if that copy is corrupt, the original file won't exist anymore
70 // and the second time we start up and try to read the file,
71 // it's not there anymore and we start from scratch without a crash.
72 _tcscpy_s(path2, MAX_PATH, path);
73 _tcscat_s(path2, MAX_PATH, _T("2"));
74 DeleteFile(path2);
75 CopyFile(path, path2, FALSE);
76 DeleteFile(path);
77 pFile = _tfsopen(path2, _T("rb"), _SH_DENYNO);
78 if (pFile)
80 try
82 LOADVALUEFROMFILE(value);
83 if (value != 2)
85 goto error;
87 int mapsize = 0;
88 LOADVALUEFROMFILE(mapsize);
89 for (int i=0; i<mapsize; ++i)
91 LOADVALUEFROMFILE2(value);
92 if (value > MAX_PATH)
93 goto error;
94 if (value)
96 CString sKey;
97 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
99 sKey.ReleaseBuffer(0);
100 goto error;
102 sKey.ReleaseBuffer(value);
103 std::unique_ptr<CCachedDirectory> cacheddir (new CCachedDirectory());
104 if (!cacheddir.get() || !cacheddir->LoadFromDisk(pFile))
106 cacheddir.reset();
107 goto error;
109 CTGitPath KeyPath = CTGitPath(sKey);
110 if (m_pInstance->IsPathAllowed(KeyPath))
112 // only add the path to the watch list if it is versioned
113 if ((cacheddir->GetCurrentFullStatus() != git_wc_status_unversioned)&&(cacheddir->GetCurrentFullStatus() != git_wc_status_none))
114 m_pInstance->watcher.AddPath(KeyPath, false);
116 m_pInstance->m_directoryCache[KeyPath] = cacheddir.release();
118 // do *not* add the paths for crawling!
119 // because crawled paths will trigger a shell
120 // notification, which makes the desktop flash constantly
121 // until the whole first time crawling is over
122 // m_pInstance->AddFolderForCrawling(KeyPath);
127 catch (CAtlException)
129 goto error;
133 exit:
134 if (pFile)
135 fclose(pFile);
136 DeleteFile(path2);
137 m_pInstance->watcher.ClearInfoMap();
138 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": cache loaded from disk successfully!\n");
139 return;
140 error:
141 if (pFile)
142 fclose(pFile);
143 DeleteFile(path2);
144 m_pInstance->watcher.ClearInfoMap();
145 Destroy();
146 m_pInstance = new CGitStatusCache;
147 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": cache not loaded from disk\n");
150 bool CGitStatusCache::SaveCache()
152 if (!CRegStdDWORD(_T("Software\\TortoiseGit\\CacheSave"), TRUE))
153 return false;
155 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) goto error;
156 unsigned int value = 0;
157 // save the cache to disk
158 FILE * pFile = NULL;
159 // find a location to write the cache to
160 TCHAR path[MAX_PATH] = { 0 }; //MAX_PATH ok here.
161 if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)
163 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));
164 if (!PathIsDirectory(path))
165 CreateDirectory(path, NULL);
166 _tcscat_s(path, MAX_PATH, _T("\\cache"));
167 _tfopen_s(&pFile, path, _T("wb"));
168 if (pFile)
170 value = 2; // 'version'
171 WRITEVALUETOFILE(value);
172 value = (int)m_pInstance->m_directoryCache.size();
173 WRITEVALUETOFILE(value);
174 for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)
176 if (I->second == NULL)
178 value = 0;
179 WRITEVALUETOFILE(value);
180 continue;
182 const CString& key = I->first.GetWinPathString();
183 value = key.GetLength();
184 WRITEVALUETOFILE(value);
185 if (value)
187 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
188 goto error;
189 if (!I->second->SaveToDisk(pFile))
190 goto error;
193 fclose(pFile);
196 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": cache saved to disk at %s\n"), path);
197 return true;
198 error:
199 fclose(pFile);
200 Destroy();
201 DeleteFile(path);
202 return false;
205 void CGitStatusCache::Destroy()
207 if (m_pInstance)
209 m_pInstance->Stop();
210 Sleep(100);
211 delete m_pInstance;
212 m_pInstance = NULL;
216 void CGitStatusCache::Stop()
218 // m_svnHelp.Cancel(true);
219 watcher.Stop();
220 m_folderCrawler.Stop();
221 m_shellUpdater.Stop();
224 void CGitStatusCache::Init()
226 m_folderCrawler.Initialise();
227 m_shellUpdater.Initialise();
230 CGitStatusCache::CGitStatusCache(void)
232 #define forever DWORD(-1)
233 AutoLocker lock(m_NoWatchPathCritSec);
234 TCHAR path[MAX_PATH] = { 0 };
235 SHGetFolderPath(NULL, CSIDL_COOKIES, NULL, 0, path);
236 m_NoWatchPaths[CTGitPath(CString(path))] = forever;
237 SecureZeroMemory(path, sizeof(path));
238 SHGetFolderPath(NULL, CSIDL_HISTORY, NULL, 0, path);
239 m_NoWatchPaths[CTGitPath(CString(path))] = forever;
240 SecureZeroMemory(path, sizeof(path));
241 SHGetFolderPath(NULL, CSIDL_INTERNET_CACHE, NULL, 0, path);
242 m_NoWatchPaths[CTGitPath(CString(path))] = forever;
243 SecureZeroMemory(path, sizeof(path));
244 SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, path);
245 m_NoWatchPaths[CTGitPath(CString(path))] = forever;
246 SecureZeroMemory(path, sizeof(path));
247 SHGetFolderPath(NULL, CSIDL_WINDOWS, NULL, 0, path);
248 m_NoWatchPaths[CTGitPath(CString(path))] = forever;
249 m_bClearMemory = false;
250 m_mostRecentExpiresAt = 0;
253 CGitStatusCache::~CGitStatusCache(void)
255 CAutoWriteLock writeLock(m_guard);
256 ClearCache();
259 void CGitStatusCache::Refresh()
261 m_shellCache.ForceRefresh();
262 // m_pInstance->m_svnHelp.ReloadConfig();
263 if (!m_pInstance->m_directoryCache.empty())
265 CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin();
266 for (/* no init */; I != m_pInstance->m_directoryCache.end(); ++I)
268 if (m_shellCache.IsPathAllowed(I->first.GetWinPath()))
269 I->second->RefreshMostImportant();
270 else
272 CGitStatusCache::Instance().RemoveCacheForPath(I->first);
273 I = m_pInstance->m_directoryCache.begin();
274 if (I == m_pInstance->m_directoryCache.end())
275 break;
281 bool CGitStatusCache::IsPathGood(const CTGitPath& path)
283 AutoLocker lock(m_NoWatchPathCritSec);
284 for (std::map<CTGitPath, DWORD>::const_iterator it = m_NoWatchPaths.begin(); it != m_NoWatchPaths.end(); ++it)
286 // the ticks check is necessary here, because RemoveTimedoutBlocks is only called within the FolderCrawler loop
287 // and we might miss update calls
288 // TODO: maybe we also need to check if index.lock and HEAD.lock still exists after a specific timeout (if we miss update notifications for these files too often)
289 if (GetTickCount() < it->second && it->first.IsAncestorOf(path))
291 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": path not good: %s\n"), it->first.GetWinPath());
292 return false;
295 return true;
298 bool CGitStatusCache::BlockPath(const CTGitPath& path, DWORD timeout /* = 0 */)
300 if (timeout == 0)
301 timeout = BLOCK_PATH_DEFAULT_TIMEOUT;
303 if (timeout > BLOCK_PATH_MAX_TIMEOUT)
304 timeout = BLOCK_PATH_MAX_TIMEOUT;
306 timeout = GetTickCount() + (timeout * 1000); // timeout is in seconds, but we need the milliseconds
308 AutoLocker lock(m_NoWatchPathCritSec);
309 m_NoWatchPaths[path.GetDirectory()] = timeout;
311 return true;
314 bool CGitStatusCache::UnBlockPath(const CTGitPath& path)
316 bool ret = false;
317 AutoLocker lock(m_NoWatchPathCritSec);
318 std::map<CTGitPath, DWORD>::iterator it = m_NoWatchPaths.find(path.GetDirectory());
319 if (it != m_NoWatchPaths.end())
321 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": path removed from no good: %s\n"), it->first.GetWinPath());
322 m_NoWatchPaths.erase(it);
323 ret = true;
325 AddFolderForCrawling(path.GetDirectory());
327 return ret;
330 bool CGitStatusCache::RemoveTimedoutBlocks()
332 bool ret = false;
333 DWORD currentTicks = GetTickCount();
334 AutoLocker lock(m_NoWatchPathCritSec);
335 std::vector<CTGitPath> toRemove;
336 for (std::map<CTGitPath, DWORD>::const_iterator it = m_NoWatchPaths.begin(); it != m_NoWatchPaths.end(); ++it)
338 if (currentTicks > it->second)
340 toRemove.push_back(it->first);
343 if (!toRemove.empty())
345 for (std::vector<CTGitPath>::const_iterator it = toRemove.begin(); it != toRemove.end(); ++it)
347 ret = ret || UnBlockPath(*it);
351 return ret;
354 void CGitStatusCache::UpdateShell(const CTGitPath& path)
356 m_shellUpdater.AddPathForUpdate(path);
359 void CGitStatusCache::ClearCache()
361 CAutoWriteLock writeLock(m_guard);
362 for (CCachedDirectory::CachedDirMap::iterator I = m_directoryCache.begin(); I != m_directoryCache.end(); ++I)
364 delete I->second;
365 I->second = NULL;
367 m_directoryCache.clear();
370 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory * cdir)
372 if (cdir == NULL)
373 return false;
375 typedef std::map<CTGitPath, git_wc_status_kind> ChildDirStatus;
376 CAutoWriteLock writeLock(m_guard);
377 if (!cdir->m_childDirectories.empty())
379 ChildDirStatus::iterator it = cdir->m_childDirectories.begin();
380 for (; it != cdir->m_childDirectories.end(); )
382 CCachedDirectory * childdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it->first);
383 if ((childdir) && (!cdir->m_directoryPath.IsEquivalentTo(childdir->m_directoryPath)) && (cdir->m_directoryPath.GetFileOrDirectoryName() != L".."))
384 RemoveCacheForDirectory(childdir);
385 cdir->m_childDirectories.erase(it->first);
386 it = cdir->m_childDirectories.begin();
389 cdir->m_childDirectories.clear();
390 m_directoryCache.erase(cdir->m_directoryPath);
392 // we could have entries versioned and/or stored in our cache which are
393 // children of the specified directory, but not in the m_childDirectories
394 // member
395 CCachedDirectory::ItDir itMap = m_directoryCache.lower_bound(cdir->m_directoryPath);
398 if (itMap != m_directoryCache.end())
400 if (cdir->m_directoryPath.IsAncestorOf(itMap->first))
402 // just in case (see TortoiseSVN issue #255)
403 if (itMap->second == cdir)
405 m_directoryCache.erase(itMap);
407 else
408 RemoveCacheForDirectory(itMap->second);
411 itMap = m_directoryCache.lower_bound(cdir->m_directoryPath);
412 } while (itMap != m_directoryCache.end() && cdir->m_directoryPath.IsAncestorOf(itMap->first));
414 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": removed from cache %s\n"), cdir->m_directoryPath.GetWinPathString());
415 delete cdir;
416 return true;
419 void CGitStatusCache::RemoveCacheForPath(const CTGitPath& path)
421 // Stop the crawler starting on a new folder
422 CCrawlInhibitor crawlInhibit(&m_folderCrawler);
423 CCachedDirectory::ItDir itMap;
424 CCachedDirectory * dirtoremove = NULL;
426 itMap = m_directoryCache.find(path);
427 if ((itMap != m_directoryCache.end())&&(itMap->second))
428 dirtoremove = itMap->second;
429 if (dirtoremove == NULL)
430 return;
431 ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath));
432 RemoveCacheForDirectory(dirtoremove);
435 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath& path, bool isAddToWatch)
437 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
440 CCachedDirectory::ItDir itMap;
441 itMap = m_directoryCache.find(path);
442 if ((itMap != m_directoryCache.end())&&(itMap->second))
444 // We've found this directory in the cache
445 return itMap->second;
447 else
449 // if the CCachedDirectory is NULL but the path is in our cache,
450 // that means that path got invalidated and needs to be treated
451 // as if it never was in our cache. So we remove the last remains
452 // from the cache and start from scratch.
454 CAutoWriteLock writeLock(m_guard);
455 // Since above there's a small chance that before we can upgrade to
456 // writer state some other thread gained writer state and changed
457 // the data, we have to recreate the iterator here again.
458 itMap = m_directoryCache.find(path);
459 if (itMap!=m_directoryCache.end())
461 delete itMap->second;
462 m_directoryCache.erase(itMap);
464 // We don't know anything about this directory yet - lets add it to our cache
465 // but only if it exists!
466 if (path.Exists() && m_shellCache.IsPathAllowed(path.GetWinPath()) && !g_GitAdminDir.IsAdminDirPath(path.GetWinPath()))
468 // some notifications are for files which got removed/moved around.
469 // In such cases, the CTGitPath::IsDirectory() will return true (it assumes a directory if
470 // the path doesn't exist). Which means we can get here with a path to a file
471 // instead of a directory.
472 // Since we're here most likely called from the crawler thread, the file could exist
473 // again. If that's the case, just do nothing
474 if (path.IsDirectory()||(!path.Exists()))
476 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": adding %s to our cache\n"), path.GetWinPath());
477 CCachedDirectory * newcdir = new CCachedDirectory(path);
478 if (newcdir)
480 CCachedDirectory * cdir = m_directoryCache.insert(m_directoryCache.lower_bound(path), std::make_pair(path, newcdir))->second;
481 CString gitdir;
482 if ((!path.IsEmpty())&&(path.HasAdminDir(&gitdir))&&isAddToWatch)
484 /* Just watch version path */
485 watcher.AddPath(gitdir);
486 watcher.AddPath(path);
488 return cdir;
490 m_bClearMemory = true;
493 return NULL;
497 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath& path)
499 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
501 CCachedDirectory::ItDir itMap;
502 itMap = m_directoryCache.find(path);
503 if(itMap != m_directoryCache.end())
505 // We've found this directory in the cache
506 return itMap->second;
508 return NULL;
511 /* Fetch is true, means fetch status from */
512 /* Fetch is false, means fetch status from cache */
513 CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags, bool bFetch /* = true */)
515 bool bRecursive = !!(flags & TGITCACHE_FLAGS_RECUSIVE_STATUS);
517 // Check a very short-lived 'mini-cache' of the last thing we were asked for.
518 long now = (long)GetTickCount();
519 if(now-m_mostRecentExpiresAt < 0)
521 if(path.IsEquivalentToWithoutCase(m_mostRecentPath))
523 return m_mostRecentStatus;
527 AutoLocker lock(m_critSec);
528 m_mostRecentPath = path;
529 m_mostRecentExpiresAt = now + 1000;
532 if (IsPathGood(path))
534 // Stop the crawler starting on a new folder while we're doing this much more important task...
535 // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().
536 CCrawlInhibitor crawlInhibit(&m_folderCrawler);
538 CTGitPath dirpath = path.GetContainingDirectory();
539 if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath())))
540 dirpath = path.GetDirectory();
541 CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath);
542 if (cachedDir != NULL)
544 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": GetStatusForMember %d\n"), bFetch);
545 CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch);
547 AutoLocker lock(m_critSec);
548 m_mostRecentStatus = entry;
549 return m_mostRecentStatus;
553 else
555 // path is blocked for some reason: return the cached status if we have one
556 // we do here only a cache search, absolutely no disk access is allowed!
557 CCachedDirectory::ItDir itMap = m_directoryCache.find(path.GetDirectory());
558 if ((itMap != m_directoryCache.end())&&(itMap->second))
560 if (path.IsDirectory())
562 CStatusCacheEntry entry = itMap->second->GetOwnStatus(false);
563 AutoLocker lock(m_critSec);
564 m_mostRecentStatus = entry;
565 return m_mostRecentStatus;
567 else
569 // We've found this directory in the cache
570 CCachedDirectory * cachedDir = itMap->second;
571 CStatusCacheEntry entry = cachedDir->GetCacheStatusForMember(path);
573 AutoLocker lock(m_critSec);
574 m_mostRecentStatus = entry;
575 return m_mostRecentStatus;
580 AutoLocker lock(m_critSec);
581 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": ignored no good path %s\n"), path.GetWinPath());
582 m_mostRecentStatus = CStatusCacheEntry();
583 if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasGITAdminDir(path.GetWinPath(), true))
585 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": force status %s\n"), path.GetWinPath());
586 m_mostRecentStatus.ForceStatus(git_wc_status_normal);
588 return m_mostRecentStatus;
591 void CGitStatusCache::AddFolderForCrawling(const CTGitPath& path)
593 m_folderCrawler.AddDirectoryForUpdate(path);
596 void CGitStatusCache::CloseWatcherHandles(HANDLE hFile)
598 CTGitPath path = watcher.CloseInfoMap(hFile);
599 if (!path.IsEmpty())
600 m_folderCrawler.BlockPath(path);
601 CGitStatusCache::Instance().m_GitStatus.ReleasePathsRecursively(path.GetWinPathString());
604 void CGitStatusCache::CloseWatcherHandles(const CTGitPath& path)
606 watcher.CloseHandlesForPath(path);
607 m_folderCrawler.ReleasePathForUpdate(path);
608 CGitStatusCache::Instance().m_GitStatus.ReleasePathsRecursively(path.GetWinPathString());