1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // External Cache Copyright (C) 2005-2006,2008 - TortoiseSVN
\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
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
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
21 #include "GitStatus.h"
\r
22 #include "GitStatuscache.h"
\r
23 #include "CacheInterface.h"
\r
26 //////////////////////////////////////////////////////////////////////////
\r
28 CGitStatusCache* CGitStatusCache::m_pInstance;
\r
30 CGitStatusCache& CGitStatusCache::Instance()
\r
32 ATLASSERT(m_pInstance != NULL);
\r
33 return *m_pInstance;
\r
36 void CGitStatusCache::Create()
\r
38 ATLASSERT(m_pInstance == NULL);
\r
39 m_pInstance = new CGitStatusCache;
\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
51 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));
\r
52 if (!PathIsDirectory(path))
\r
54 if (CreateDirectory(path, NULL)==0)
\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
67 CopyFile(path, path2, FALSE);
\r
69 pFile = _tfsopen(path2, _T("rb"), _SH_DENYNO);
\r
74 LOADVALUEFROMFILE(value);
\r
80 LOADVALUEFROMFILE(mapsize);
\r
81 for (int i=0; i<mapsize; ++i)
\r
83 LOADVALUEFROMFILE2(value);
\r
84 if (value > MAX_PATH)
\r
89 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
\r
91 sKey.ReleaseBuffer(0);
\r
94 sKey.ReleaseBuffer(value);
\r
95 CCachedDirectory * cacheddir = new CCachedDirectory();
\r
96 if (cacheddir == NULL)
\r
98 if (!cacheddir->LoadFromDisk(pFile))
\r
100 CTGitPath KeyPath = CTGitPath(sKey);
\r
101 if (m_pInstance->IsPathAllowed(KeyPath))
\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
116 catch (CAtlException)
\r
126 ATLTRACE("cache loaded from disk successfully!\n");
\r
133 m_pInstance->Stop();
\r
136 delete m_pInstance;
\r
137 m_pInstance = new CGitStatusCache;
\r
138 ATLTRACE("cache not loaded from disk\n");
\r
141 bool CGitStatusCache::SaveCache()
\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
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
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
164 if (I->second == NULL)
\r
167 WRITEVALUETOFILE(value);
\r
170 const CString& key = I->first.GetWinPathString();
\r
171 value = key.GetLength();
\r
172 WRITEVALUETOFILE(value);
\r
175 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
\r
177 if (!I->second->SaveToDisk(pFile))
\r
184 ATLTRACE(_T("cache saved to disk at %s\n"), path);
\r
190 m_pInstance->Stop();
\r
193 delete m_pInstance;
\r
194 m_pInstance = NULL;
\r
199 void CGitStatusCache::Destroy()
\r
203 m_pInstance->Stop();
\r
206 delete m_pInstance;
\r
207 m_pInstance = NULL;
\r
210 void CGitStatusCache::Stop()
\r
212 // m_svnHelp.Cancel(true);
\r
214 m_folderCrawler.Stop();
\r
215 m_shellUpdater.Stop();
\r
218 void CGitStatusCache::Init()
\r
220 m_folderCrawler.Initialise();
\r
221 m_shellUpdater.Initialise();
\r
224 CGitStatusCache::CGitStatusCache(void)
\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
241 CGitStatusCache::~CGitStatusCache(void)
\r
243 for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)
\r
250 void CGitStatusCache::Refresh()
\r
252 m_shellCache.ForceRefresh();
\r
253 // m_pInstance->m_svnHelp.ReloadConfig();
\r
254 if (m_pInstance->m_directoryCache.size())
\r
256 CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin();
\r
257 for (/* no init */; I != m_pInstance->m_directoryCache.end(); ++I)
\r
259 if (m_shellCache.IsPathAllowed(I->first.GetWinPath()))
\r
260 I->second->RefreshMostImportant();
\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
272 bool CGitStatusCache::IsPathGood(const CTGitPath& path)
\r
274 for (std::set<CTGitPath>::iterator it = m_NoWatchPaths.begin(); it != m_NoWatchPaths.end(); ++it)
\r
276 if (it->IsAncestorOf(path))
\r
282 void CGitStatusCache::UpdateShell(const CTGitPath& path)
\r
284 m_shellUpdater.AddPathForUpdate(path);
\r
287 void CGitStatusCache::ClearCache()
\r
289 for (CCachedDirectory::CachedDirMap::iterator I = m_directoryCache.begin(); I != m_directoryCache.end(); ++I)
\r
294 m_directoryCache.clear();
\r
297 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory * cdir)
\r
302 typedef std::map<CTGitPath, git_wc_status_kind> ChildDirStatus;
\r
303 if (cdir->m_childDirectories.size())
\r
305 ChildDirStatus::iterator it = cdir->m_childDirectories.begin();
\r
306 for (; it != cdir->m_childDirectories.end(); )
\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
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
323 void CGitStatusCache::RemoveCacheForPath(const CTGitPath& path)
\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
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
336 ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath));
\r
337 RemoveCacheForDirectory(dirtoremove);
\r
340 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath& path)
\r
342 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
\r
345 CCachedDirectory::ItDir itMap;
\r
346 itMap = m_directoryCache.find(path);
\r
347 if ((itMap != m_directoryCache.end())&&(itMap->second))
\r
349 // We've found this directory in the cache
\r
350 return itMap->second;
\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
361 // upgrading our state to writer
\r
362 ATLTRACE("trying to upgrade the state to \"Writer\"\n");
\r
364 ATLTRACE("Returned \"Reader\" state\n");
\r
366 ATLTRACE("Got \"Writer\" state now\n");
\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
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
386 ATLTRACE(_T("adding %s to our cache\n"), path.GetWinPath());
\r
387 CCachedDirectory * newcdir = new CCachedDirectory(path);
\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
395 m_bClearMemory = true;
\r
402 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath& path)
\r
404 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
\r
406 CCachedDirectory::ItDir itMap;
\r
407 itMap = m_directoryCache.find(path);
\r
408 if(itMap != m_directoryCache.end())
\r
410 // We've found this directory in the cache
\r
411 return itMap->second;
\r
416 CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags, bool bFetch /* = true */)
\r
418 bool bRecursive = !!(flags & TSVNCACHE_FLAGS_RECUSIVE_STATUS);
\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
424 if(path.IsEquivalentToWithoutCase(m_mostRecentPath))
\r
426 return m_mostRecentStatus;
\r
429 m_mostRecentPath = path;
\r
430 m_mostRecentExpiresAt = now+1000;
\r
432 if (IsPathGood(path))
\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
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
444 m_mostRecentStatus = cachedDir->GetStatusForMember(path, bRecursive, bFetch);
\r
445 return m_mostRecentStatus;
\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
452 m_mostRecentStatus.ForceStatus(git_wc_status_normal);
\r
454 return m_mostRecentStatus;
\r
457 void CGitStatusCache::AddFolderForCrawling(const CTGitPath& path)
\r
459 m_folderCrawler.AddDirectoryForUpdate(path);
\r
462 void CGitStatusCache::CloseWatcherHandles(HDEVNOTIFY hdev)
\r
464 CTGitPath path = watcher.CloseInfoMap(hdev);
\r
465 m_folderCrawler.BlockPath(path);
\r
468 void CGitStatusCache::CloseWatcherHandles(const CTGitPath& path)
\r
470 watcher.CloseHandlesForPath(path);
\r
471 m_folderCrawler.BlockPath(path);
\r