1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2006,2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "GitStatus.h"
22 #include "GitStatuscache.h"
23 #include "CacheInterface.h"
26 //////////////////////////////////////////////////////////////////////////
28 CGitStatusCache
* CGitStatusCache::m_pInstance
;
30 CGitStatusCache
& CGitStatusCache::Instance()
32 ATLASSERT(m_pInstance
!= NULL
);
36 void CGitStatusCache::Create()
38 ATLASSERT(m_pInstance
== NULL
);
39 m_pInstance
= new CGitStatusCache
;
41 m_pInstance
->watcher
.SetFolderCrawler(&m_pInstance
->m_folderCrawler
);
42 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto exit;
43 #define LOADVALUEFROMFILE2(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto error;
44 unsigned int value
= (unsigned int)-1;
46 // find the location of the cache
47 TCHAR path
[MAX_PATH
]; //MAX_PATH ok here.
48 TCHAR path2
[MAX_PATH
];
49 if (SHGetFolderPath(NULL
, CSIDL_LOCAL_APPDATA
, NULL
, SHGFP_TYPE_CURRENT
, path
)==S_OK
)
51 _tcscat_s(path
, MAX_PATH
, _T("\\TGitCache"));
52 if (!PathIsDirectory(path
))
54 if (CreateDirectory(path
, NULL
)==0)
57 _tcscat_s(path
, MAX_PATH
, _T("\\cache"));
58 // in case the cache file is corrupt, we could crash while
59 // reading it! To prevent crashing every time once that happens,
60 // we make a copy of the cache file and use that copy to read from.
61 // if that copy is corrupt, the original file won't exist anymore
62 // and the second time we start up and try to read the file,
63 // it's not there anymore and we start from scratch without a crash.
64 _tcscpy_s(path2
, MAX_PATH
, path
);
65 _tcscat_s(path2
, MAX_PATH
, _T("2"));
67 CopyFile(path
, path2
, FALSE
);
69 pFile
= _tfsopen(path2
, _T("rb"), _SH_DENYNO
);
74 LOADVALUEFROMFILE(value
);
80 LOADVALUEFROMFILE(mapsize
);
81 for (int i
=0; i
<mapsize
; ++i
)
83 LOADVALUEFROMFILE2(value
);
89 if (fread(sKey
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
91 sKey
.ReleaseBuffer(0);
94 sKey
.ReleaseBuffer(value
);
95 CCachedDirectory
* cacheddir
= new CCachedDirectory();
96 if (cacheddir
== NULL
)
98 if (!cacheddir
->LoadFromDisk(pFile
))
100 CTGitPath KeyPath
= CTGitPath(sKey
);
101 if (m_pInstance
->IsPathAllowed(KeyPath
))
103 m_pInstance
->m_directoryCache
[KeyPath
] = cacheddir
;
104 // only add the path to the watch list if it is versioned
105 if ((cacheddir
->GetCurrentFullStatus() != git_wc_status_unversioned
)&&(cacheddir
->GetCurrentFullStatus() != git_wc_status_none
))
106 m_pInstance
->watcher
.AddPath(KeyPath
);
107 // do *not* add the paths for crawling!
108 // because crawled paths will trigger a shell
109 // notification, which makes the desktop flash constantly
110 // until the whole first time crawling is over
111 // m_pInstance->AddFolderForCrawling(KeyPath);
116 catch (CAtlException
)
126 ATLTRACE("cache loaded from disk successfully!\n");
137 m_pInstance
= new CGitStatusCache
;
138 ATLTRACE("cache not loaded from disk\n");
141 bool CGitStatusCache::SaveCache()
143 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) goto error;
144 unsigned int value
= 0;
145 // save the cache to disk
147 // find a location to write the cache to
148 TCHAR path
[MAX_PATH
]; //MAX_PATH ok here.
149 if (SHGetFolderPath(NULL
, CSIDL_LOCAL_APPDATA
, NULL
, SHGFP_TYPE_CURRENT
, path
)==S_OK
)
151 _tcscat_s(path
, MAX_PATH
, _T("\\TGitCache"));
152 if (!PathIsDirectory(path
))
153 CreateDirectory(path
, NULL
);
154 _tcscat_s(path
, MAX_PATH
, _T("\\cache"));
155 _tfopen_s(&pFile
, path
, _T("wb"));
158 value
= 2; // 'version'
159 WRITEVALUETOFILE(value
);
160 value
= (int)m_pInstance
->m_directoryCache
.size();
161 WRITEVALUETOFILE(value
);
162 for (CCachedDirectory::CachedDirMap::iterator I
= m_pInstance
->m_directoryCache
.begin(); I
!= m_pInstance
->m_directoryCache
.end(); ++I
)
164 if (I
->second
== NULL
)
167 WRITEVALUETOFILE(value
);
170 const CString
& key
= I
->first
.GetWinPathString();
171 value
= key
.GetLength();
172 WRITEVALUETOFILE(value
);
175 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
177 if (!I
->second
->SaveToDisk(pFile
))
184 ATLTRACE(_T("cache saved to disk at %s\n"), path
);
199 void CGitStatusCache::Destroy()
210 void CGitStatusCache::Stop()
212 // m_svnHelp.Cancel(true);
214 m_folderCrawler
.Stop();
215 m_shellUpdater
.Stop();
218 void CGitStatusCache::Init()
220 m_folderCrawler
.Initialise();
221 m_shellUpdater
.Initialise();
224 CGitStatusCache::CGitStatusCache(void)
226 TCHAR path
[MAX_PATH
];
227 SHGetFolderPath(NULL
, CSIDL_COOKIES
, NULL
, 0, path
);
228 m_NoWatchPaths
.insert(CTGitPath(CString(path
)));
229 SHGetFolderPath(NULL
, CSIDL_HISTORY
, NULL
, 0, path
);
230 m_NoWatchPaths
.insert(CTGitPath(CString(path
)));
231 SHGetFolderPath(NULL
, CSIDL_INTERNET_CACHE
, NULL
, 0, path
);
232 m_NoWatchPaths
.insert(CTGitPath(CString(path
)));
233 SHGetFolderPath(NULL
, CSIDL_SYSTEM
, NULL
, 0, path
);
234 m_NoWatchPaths
.insert(CTGitPath(CString(path
)));
235 SHGetFolderPath(NULL
, CSIDL_WINDOWS
, NULL
, 0, path
);
236 m_NoWatchPaths
.insert(CTGitPath(CString(path
)));
237 m_bClearMemory
= false;
238 m_mostRecentExpiresAt
= 0;
241 CGitStatusCache::~CGitStatusCache(void)
243 for (CCachedDirectory::CachedDirMap::iterator I
= m_pInstance
->m_directoryCache
.begin(); I
!= m_pInstance
->m_directoryCache
.end(); ++I
)
250 void CGitStatusCache::Refresh()
252 m_shellCache
.ForceRefresh();
253 // m_pInstance->m_svnHelp.ReloadConfig();
254 if (m_pInstance
->m_directoryCache
.size())
256 CCachedDirectory::CachedDirMap::iterator I
= m_pInstance
->m_directoryCache
.begin();
257 for (/* no init */; I
!= m_pInstance
->m_directoryCache
.end(); ++I
)
259 if (m_shellCache
.IsPathAllowed(I
->first
.GetWinPath()))
260 I
->second
->RefreshMostImportant();
263 CGitStatusCache::Instance().RemoveCacheForPath(I
->first
);
264 I
= m_pInstance
->m_directoryCache
.begin();
265 if (I
== m_pInstance
->m_directoryCache
.end())
272 bool CGitStatusCache::IsPathGood(const CTGitPath
& path
)
274 for (std::set
<CTGitPath
>::iterator it
= m_NoWatchPaths
.begin(); it
!= m_NoWatchPaths
.end(); ++it
)
276 if (it
->IsAncestorOf(path
))
282 void CGitStatusCache::UpdateShell(const CTGitPath
& path
)
284 m_shellUpdater
.AddPathForUpdate(path
);
287 void CGitStatusCache::ClearCache()
289 for (CCachedDirectory::CachedDirMap::iterator I
= m_directoryCache
.begin(); I
!= m_directoryCache
.end(); ++I
)
294 m_directoryCache
.clear();
297 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory
* cdir
)
302 typedef std::map
<CTGitPath
, git_wc_status_kind
> ChildDirStatus
;
303 if (cdir
->m_childDirectories
.size())
305 ChildDirStatus::iterator it
= cdir
->m_childDirectories
.begin();
306 for (; it
!= cdir
->m_childDirectories
.end(); )
308 CCachedDirectory
* childdir
= CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it
->first
);
309 if ((childdir
)&&(!cdir
->m_directoryPath
.IsEquivalentTo(childdir
->m_directoryPath
)))
310 RemoveCacheForDirectory(childdir
);
311 cdir
->m_childDirectories
.erase(it
->first
);
312 it
= cdir
->m_childDirectories
.begin();
315 cdir
->m_childDirectories
.clear();
316 m_directoryCache
.erase(cdir
->m_directoryPath
);
317 ATLTRACE(_T("removed path %s from cache\n"), cdir
->m_directoryPath
);
323 void CGitStatusCache::RemoveCacheForPath(const CTGitPath
& path
)
325 // Stop the crawler starting on a new folder
326 CCrawlInhibitor
crawlInhibit(&m_folderCrawler
);
327 CCachedDirectory::ItDir itMap
;
328 CCachedDirectory
* dirtoremove
= NULL
;
331 itMap
= m_directoryCache
.find(path
);
332 if ((itMap
!= m_directoryCache
.end())&&(itMap
->second
))
333 dirtoremove
= itMap
->second
;
334 if (dirtoremove
== NULL
)
336 ATLASSERT(path
.IsEquivalentToWithoutCase(dirtoremove
->m_directoryPath
));
337 RemoveCacheForDirectory(dirtoremove
);
340 CCachedDirectory
* CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath
& path
)
342 ATLASSERT(path
.IsDirectory() || !PathFileExists(path
.GetWinPath()));
345 CCachedDirectory::ItDir itMap
;
346 itMap
= m_directoryCache
.find(path
);
347 if ((itMap
!= m_directoryCache
.end())&&(itMap
->second
))
349 // We've found this directory in the cache
350 return itMap
->second
;
354 // if the CCachedDirectory is NULL but the path is in our cache,
355 // that means that path got invalidated and needs to be treated
356 // as if it never was in our cache. So we remove the last remains
357 // from the cache and start from scratch.
361 // upgrading our state to writer
362 ATLTRACE("trying to upgrade the state to \"Writer\"\n");
364 ATLTRACE("Returned \"Reader\" state\n");
366 ATLTRACE("Got \"Writer\" state now\n");
368 // Since above there's a small chance that before we can upgrade to
369 // writer state some other thread gained writer state and changed
370 // the data, we have to recreate the iterator here again.
371 itMap
= m_directoryCache
.find(path
);
372 if (itMap
!=m_directoryCache
.end())
373 m_directoryCache
.erase(itMap
);
374 // We don't know anything about this directory yet - lets add it to our cache
375 // but only if it exists!
376 if (path
.Exists() && m_shellCache
.IsPathAllowed(path
.GetWinPath()) && !g_GitAdminDir
.IsAdminDirPath(path
.GetWinPath()))
378 // some notifications are for files which got removed/moved around.
379 // In such cases, the CTGitPath::IsDirectory() will return true (it assumes a directory if
380 // the path doesn't exist). Which means we can get here with a path to a file
381 // instead of a directory.
382 // Since we're here most likely called from the crawler thread, the file could exist
383 // again. If that's the case, just do nothing
384 if (path
.IsDirectory()||(!path
.Exists()))
386 ATLTRACE(_T("adding %s to our cache\n"), path
.GetWinPath());
387 CCachedDirectory
* newcdir
= new CCachedDirectory(path
);
390 CCachedDirectory
* cdir
= m_directoryCache
.insert(m_directoryCache
.lower_bound(path
), std::make_pair(path
, newcdir
))->second
;
391 if ((!path
.IsEmpty())&&(path
.HasAdminDir()))
392 watcher
.AddPath(path
);
395 m_bClearMemory
= true;
402 CCachedDirectory
* CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath
& path
)
404 ATLASSERT(path
.IsDirectory() || !PathFileExists(path
.GetWinPath()));
406 CCachedDirectory::ItDir itMap
;
407 itMap
= m_directoryCache
.find(path
);
408 if(itMap
!= m_directoryCache
.end())
410 // We've found this directory in the cache
411 return itMap
->second
;
416 CStatusCacheEntry
CGitStatusCache::GetStatusForPath(const CTGitPath
& path
, DWORD flags
, bool bFetch
/* = true */)
418 bool bRecursive
= !!(flags
& TSVNCACHE_FLAGS_RECUSIVE_STATUS
);
420 // Check a very short-lived 'mini-cache' of the last thing we were asked for.
421 long now
= (long)GetTickCount();
422 if(now
-m_mostRecentExpiresAt
< 0)
424 if(path
.IsEquivalentToWithoutCase(m_mostRecentPath
))
426 return m_mostRecentStatus
;
429 m_mostRecentPath
= path
;
430 m_mostRecentExpiresAt
= now
+1000;
432 if (IsPathGood(path
))
434 // Stop the crawler starting on a new folder while we're doing this much more important task...
435 // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().
436 CCrawlInhibitor
crawlInhibit(&m_folderCrawler
);
438 CTGitPath dirpath
= path
.GetContainingDirectory();
439 if ((dirpath
.IsEmpty()) || (!m_shellCache
.IsPathAllowed(dirpath
.GetWinPath())))
440 dirpath
= path
.GetDirectory();
441 CCachedDirectory
* cachedDir
= GetDirectoryCacheEntry(dirpath
);
442 if (cachedDir
!= NULL
)
444 m_mostRecentStatus
= cachedDir
->GetStatusForMember(path
, bRecursive
, bFetch
);
445 return m_mostRecentStatus
;
448 ATLTRACE(_T("ignored no good path %s\n"), path
.GetWinPath());
449 m_mostRecentStatus
= CStatusCacheEntry();
450 if (m_shellCache
.ShowExcludedAsNormal() && path
.IsDirectory() && m_shellCache
.HasSVNAdminDir(path
.GetWinPath(), true))
452 m_mostRecentStatus
.ForceStatus(git_wc_status_normal
);
454 return m_mostRecentStatus
;
457 void CGitStatusCache::AddFolderForCrawling(const CTGitPath
& path
)
459 m_folderCrawler
.AddDirectoryForUpdate(path
);
462 void CGitStatusCache::CloseWatcherHandles(HDEVNOTIFY hdev
)
464 CTGitPath path
= watcher
.CloseInfoMap(hdev
);
465 m_folderCrawler
.BlockPath(path
);
468 void CGitStatusCache::CloseWatcherHandles(const CTGitPath
& path
)
470 watcher
.CloseHandlesForPath(path
);
471 m_folderCrawler
.BlockPath(path
);