OSDN Git Service

Log Filter text work
[tortoisegit/TortoiseGitJp.git] / src / TGitCache / CachedDirectory.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // External Cache Copyright (C) 2005-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 #include "StdAfx.h"\r
20 #include ".\cacheddirectory.h"\r
21 #include "SVNHelpers.h"\r
22 #include "SVNStatusCache.h"\r
23 #include "SVNStatus.h"\r
24 #include <set>\r
25 \r
26 CCachedDirectory::CCachedDirectory(void)\r
27 {\r
28         m_entriesFileTime = 0;\r
29         m_propsFileTime = 0;\r
30         m_currentStatusFetchingPathTicks = 0;\r
31         m_bCurrentFullStatusValid = false;\r
32         m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
33         m_bRecursive = true;\r
34 }\r
35 \r
36 CCachedDirectory::~CCachedDirectory(void)\r
37 {\r
38 }\r
39 \r
40 CCachedDirectory::CCachedDirectory(const CTSVNPath& directoryPath)\r
41 {\r
42         ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));\r
43 \r
44         m_directoryPath = directoryPath;\r
45         m_entriesFileTime = 0;\r
46         m_propsFileTime = 0;\r
47         m_currentStatusFetchingPathTicks = 0;\r
48         m_bCurrentFullStatusValid = false;\r
49         m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
50         m_bRecursive = true;\r
51 }\r
52 \r
53 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)\r
54 {\r
55         AutoLocker lock(m_critSec);\r
56 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;\r
57 \r
58         unsigned int value = 1;\r
59         WRITEVALUETOFILE(value);        // 'version' of this save-format\r
60         value = (int)m_entryCache.size();\r
61         WRITEVALUETOFILE(value);        // size of the cache map\r
62         // now iterate through the maps and save every entry.\r
63         for (CacheEntryMap::iterator I = m_entryCache.begin(); I != m_entryCache.end(); ++I)\r
64         {\r
65                 const CString& key = I->first;\r
66                 value = key.GetLength();\r
67                 WRITEVALUETOFILE(value);\r
68                 if (value)\r
69                 {\r
70                         if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)\r
71                                 return false;\r
72                         if (!I->second.SaveToDisk(pFile))\r
73                                 return false;\r
74                 }\r
75         }\r
76         value = (int)m_childDirectories.size();\r
77         WRITEVALUETOFILE(value);\r
78         for (ChildDirStatus::iterator I = m_childDirectories.begin(); I != m_childDirectories.end(); ++I)\r
79         {\r
80                 const CString& path = I->first.GetWinPathString();\r
81                 value = path.GetLength();\r
82                 WRITEVALUETOFILE(value);\r
83                 if (value)\r
84                 {\r
85                         if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)\r
86                                 return false;\r
87                         svn_wc_status_kind status = I->second;\r
88                         WRITEVALUETOFILE(status);\r
89                 }\r
90         }\r
91         WRITEVALUETOFILE(m_entriesFileTime);\r
92         WRITEVALUETOFILE(m_propsFileTime);\r
93         value = m_directoryPath.GetWinPathString().GetLength();\r
94         WRITEVALUETOFILE(value);\r
95         if (value)\r
96         {\r
97                 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)\r
98                         return false;\r
99         }\r
100         if (!m_ownStatus.SaveToDisk(pFile))\r
101                 return false;\r
102         WRITEVALUETOFILE(m_currentFullStatus);\r
103         WRITEVALUETOFILE(m_mostImportantFileStatus);\r
104         return true;\r
105 }\r
106 \r
107 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)\r
108 {\r
109         AutoLocker lock(m_critSec);\r
110 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;\r
111         try\r
112         {\r
113                 unsigned int value = 0;\r
114                 LOADVALUEFROMFILE(value);\r
115                 if (value != 1)\r
116                         return false;           // not the correct version\r
117                 int mapsize = 0;\r
118                 LOADVALUEFROMFILE(mapsize);\r
119                 for (int i=0; i<mapsize; ++i)\r
120                 {\r
121                         LOADVALUEFROMFILE(value);\r
122                         if (value > MAX_PATH)\r
123                                 return false;\r
124                         if (value)\r
125                         {\r
126                                 CString sKey;\r
127                                 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)\r
128                                 {\r
129                                         sKey.ReleaseBuffer(0);\r
130                                         return false;\r
131                                 }\r
132                                 sKey.ReleaseBuffer(value);\r
133                                 CStatusCacheEntry entry;\r
134                                 if (!entry.LoadFromDisk(pFile))\r
135                                         return false;\r
136                                 m_entryCache[sKey] = entry;\r
137                         }\r
138                 }\r
139                 LOADVALUEFROMFILE(mapsize);\r
140                 for (int i=0; i<mapsize; ++i)\r
141                 {\r
142                         LOADVALUEFROMFILE(value);\r
143                         if (value > MAX_PATH)\r
144                                 return false;\r
145                         if (value)\r
146                         {\r
147                                 CString sPath;\r
148                                 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)\r
149                                 {\r
150                                         sPath.ReleaseBuffer(0);\r
151                                         return false;\r
152                                 }\r
153                                 sPath.ReleaseBuffer(value);\r
154                                 svn_wc_status_kind status;\r
155                                 LOADVALUEFROMFILE(status);\r
156                                 m_childDirectories[CTSVNPath(sPath)] = status;\r
157                         }\r
158                 }\r
159                 LOADVALUEFROMFILE(m_entriesFileTime);\r
160                 LOADVALUEFROMFILE(m_propsFileTime);\r
161                 LOADVALUEFROMFILE(value);\r
162                 if (value > MAX_PATH)\r
163                         return false;\r
164                 if (value)\r
165                 {\r
166                         CString sPath;\r
167                         if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)\r
168                         {\r
169                                 sPath.ReleaseBuffer(0);\r
170                                 return false;\r
171                         }\r
172                         sPath.ReleaseBuffer(value);\r
173                         m_directoryPath.SetFromWin(sPath);\r
174                 }\r
175                 if (!m_ownStatus.LoadFromDisk(pFile))\r
176                         return false;\r
177 \r
178                 LOADVALUEFROMFILE(m_currentFullStatus);\r
179                 LOADVALUEFROMFILE(m_mostImportantFileStatus);\r
180         }\r
181         catch ( CAtlException )\r
182         {\r
183                 return false;\r
184         }\r
185         return true;\r
186 \r
187 }\r
188 \r
189 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTSVNPath& path, bool bRecursive,  bool bFetch /* = true */)\r
190 {\r
191         CString strCacheKey;\r
192         bool bThisDirectoryIsUnversioned = false;\r
193         bool bRequestForSelf = false;\r
194         if(path.IsEquivalentToWithoutCase(m_directoryPath))\r
195         {\r
196                 bRequestForSelf = true;\r
197         }\r
198 \r
199         // In all most circumstances, we ask for the status of a member of this directory.\r
200         ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);\r
201 \r
202         // Check if the entries file has been changed\r
203         CTSVNPath entriesFilePath(m_directoryPath);\r
204         CTSVNPath propsDirPath(m_directoryPath);\r
205         if (g_SVNAdminDir.IsVSNETHackActive())\r
206         {\r
207                 entriesFilePath.AppendPathString(g_SVNAdminDir.GetVSNETAdminDirName() + _T("\\entries"));\r
208                 propsDirPath.AppendPathString(g_SVNAdminDir.GetVSNETAdminDirName() + _T("\\dir-props"));\r
209         }\r
210         else\r
211         {\r
212                 entriesFilePath.AppendPathString(g_SVNAdminDir.GetAdminDirName() + _T("\\entries"));\r
213                 propsDirPath.AppendPathString(g_SVNAdminDir.GetAdminDirName() + _T("\\dir-props"));\r
214         }\r
215         if ( (m_entriesFileTime == entriesFilePath.GetLastWriteTime()) && ((entriesFilePath.GetLastWriteTime() == 0) || (m_propsFileTime == propsDirPath.GetLastWriteTime())) )\r
216         {\r
217                 m_entriesFileTime = entriesFilePath.GetLastWriteTime();\r
218                 if (m_entriesFileTime)\r
219                         m_propsFileTime = propsDirPath.GetLastWriteTime();\r
220 \r
221                 if(m_entriesFileTime == 0)\r
222                 {\r
223                         // We are a folder which is not in a working copy\r
224                         bThisDirectoryIsUnversioned = true;\r
225                         m_ownStatus.SetStatus(NULL);\r
226 \r
227                         // If a user removes the .svn directory, we get here with m_entryCache\r
228                         // not being empty, but still us being unversioned\r
229                         if (!m_entryCache.empty())\r
230                         {\r
231                                 m_entryCache.clear();\r
232                         }\r
233                         ATLASSERT(m_entryCache.empty());\r
234                         \r
235                         // However, a member *DIRECTORY* might be the top of WC\r
236                         // so we need to ask them to get their own status\r
237                         if(!path.IsDirectory())\r
238                         {\r
239                                 if ((PathFileExists(path.GetWinPath()))||(bRequestForSelf))\r
240                                         return CStatusCacheEntry();\r
241                                 // the entry doesn't exist anymore! \r
242                                 // but we can't remove it from the cache here:\r
243                                 // the GetStatusForMember() method is called only with a read\r
244                                 // lock and not a write lock!\r
245                                 // So mark it for crawling, and let the crawler remove it\r
246                                 // later\r
247                                 CSVNStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());\r
248                                 return CStatusCacheEntry();\r
249                         }\r
250                         else\r
251                         {\r
252                                 // If we're in the special case of a directory being asked for its own status\r
253                                 // and this directory is unversioned, then we should just return that here\r
254                                 if(bRequestForSelf)\r
255                                 {\r
256                                         return CStatusCacheEntry();\r
257                                 }\r
258                         }\r
259                 }\r
260 \r
261                 if(path.IsDirectory())\r
262                 {\r
263                         // We don't have directory status in our cache\r
264                         // Ask the directory if it knows its own status\r
265                         CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);\r
266                         if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))\r
267                         {\r
268                                 // To keep recursive status up to date, we'll request that children are all crawled again\r
269                                 // This will be very quick if nothings changed, because it will all be cache hits\r
270                                 if (bRecursive)\r
271                                 {\r
272                                         AutoLocker lock(dirEntry->m_critSec);\r
273                                         ChildDirStatus::const_iterator it;\r
274                                         for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)\r
275                                         {\r
276                                                 CSVNStatusCache::Instance().AddFolderForCrawling(it->first);\r
277                                         }\r
278                                 }\r
279 \r
280                                 return dirEntry->GetOwnStatus(bRecursive);\r
281                         }\r
282                 }\r
283                 else\r
284                 {\r
285                         {\r
286                                 // if we currently are fetching the status of the directory\r
287                                 // we want the status for, we just return an empty entry here\r
288                                 // and don't wait for that fetching to finish.\r
289                                 // That's because fetching the status can take a *really* long\r
290                                 // time (e.g. if a commit is also in progress on that same\r
291                                 // directory), and we don't want to make the explorer appear\r
292                                 // to hang.\r
293                                 AutoLocker pathlock(m_critSecPath);\r
294                                 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))\r
295                                 {\r
296                                         if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))\r
297                                         {\r
298                                                 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());\r
299                                                 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
300                                                 return CStatusCacheEntry();\r
301                                         }\r
302                                 }\r
303                         }\r
304                         // Look up a file in our own cache\r
305                         AutoLocker lock(m_critSec);\r
306                         strCacheKey = GetCacheKey(path);\r
307                         CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);\r
308                         if(itMap != m_entryCache.end())\r
309                         {\r
310                                 // We've hit the cache - check for timeout\r
311                                 if(!itMap->second.HasExpired((long)GetTickCount()))\r
312                                 {\r
313                                         if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))\r
314                                         {\r
315                                                 if ((itMap->second.GetEffectiveStatus()!=svn_wc_status_missing)||(!PathFileExists(path.GetWinPath())))\r
316                                                 {\r
317                                                         // Note: the filetime matches after a modified has been committed too.\r
318                                                         // So in that case, we would return a wrong status (e.g. 'modified' instead\r
319                                                         // of 'normal') here.\r
320                                                         return itMap->second;\r
321                                                 }\r
322                                         }\r
323                                 }\r
324                         }\r
325                 }\r
326         }\r
327         else\r
328         {\r
329                 AutoLocker pathlock(m_critSecPath);\r
330                 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))\r
331                 {\r
332                         if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))\r
333                         {\r
334                                 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());\r
335                                 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
336                                 return CStatusCacheEntry();\r
337                         }\r
338                 }\r
339                 // if we're fetching the status for the explorer,\r
340                 // we don't refresh the status but use the one\r
341                 // we already have (to save time and make the explorer\r
342                 // more responsive in stress conditions).\r
343                 // We leave the refreshing to the crawler.\r
344                 if ((!bFetch)&&(m_entriesFileTime))\r
345                 {\r
346                         CSVNStatusCache::Instance().AddFolderForCrawling(path.GetDirectory());\r
347                         return CStatusCacheEntry();\r
348                 }\r
349                 AutoLocker lock(m_critSec);\r
350                 m_entriesFileTime = entriesFilePath.GetLastWriteTime();\r
351                 m_propsFileTime = propsDirPath.GetLastWriteTime();\r
352                 m_entryCache.clear();\r
353                 strCacheKey = GetCacheKey(path);\r
354         }\r
355 \r
356         svn_opt_revision_t revision;\r
357         revision.kind = svn_opt_revision_unspecified;\r
358 \r
359         // We've not got this item in the cache - let's add it\r
360         // We never bother asking SVN for the status of just one file, always for its containing directory\r
361 \r
362         if (g_SVNAdminDir.IsAdminDirPath(path.GetWinPathString()))\r
363         {\r
364                 // We're being asked for the status of an .SVN directory\r
365                 // It's not worth asking for this\r
366                 return CStatusCacheEntry();\r
367         }\r
368 \r
369         {\r
370                 {\r
371                         AutoLocker pathlock(m_critSecPath);\r
372                         if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))\r
373                         {\r
374                                 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))\r
375                                 {\r
376                                         ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());\r
377                                         m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
378                                         return CStatusCacheEntry();\r
379                                 }\r
380                         }\r
381                 }\r
382                 SVNPool subPool(CSVNStatusCache::Instance().m_svnHelp.Pool());\r
383                 {\r
384                         AutoLocker lock(m_critSec);\r
385                         m_mostImportantFileStatus = svn_wc_status_none;\r
386                         m_childDirectories.clear();\r
387                         m_entryCache.clear();\r
388                         m_ownStatus.SetStatus(NULL);\r
389                         m_bRecursive = bRecursive;\r
390                 }\r
391                 if(!bThisDirectoryIsUnversioned)\r
392                 {\r
393                         {\r
394                                 AutoLocker pathlock(m_critSecPath);\r
395                                 m_currentStatusFetchingPath = m_directoryPath;\r
396                                 m_currentStatusFetchingPathTicks = GetTickCount();\r
397                         }\r
398                         ATLTRACE(_T("svn_cli_stat for '%s' (req %s)\n"), m_directoryPath.GetWinPath(), path.GetWinPath());\r
399                         svn_error_t* pErr = svn_client_status4 (\r
400                                 NULL,\r
401                                 m_directoryPath.GetSVNApiPath(subPool),\r
402                                 &revision,\r
403                                 GetStatusCallback,\r
404                                 this,\r
405                                 svn_depth_immediates,\r
406                                 TRUE,                                                                   //getall\r
407                                 FALSE,\r
408                                 TRUE,                                                                   //noignore\r
409                                 FALSE,                                                                  //ignore externals\r
410                                 NULL,                                                                   //changelists\r
411                                 CSVNStatusCache::Instance().m_svnHelp.ClientContext(),\r
412                                 subPool\r
413                                 );\r
414                         {\r
415                                 AutoLocker pathlock(m_critSecPath);\r
416                                 m_currentStatusFetchingPath.Reset();\r
417                         }\r
418                         ATLTRACE(_T("svn_cli_stat finished for '%s'\n"), m_directoryPath.GetWinPath(), path.GetWinPath());\r
419                         if(pErr)\r
420                         {\r
421                                 // Handle an error\r
422                                 // The most likely error on a folder is that it's not part of a WC\r
423                                 // In most circumstances, this will have been caught earlier,\r
424                                 // but in some situations, we'll get this error.\r
425                                 // If we allow ourselves to fall on through, then folders will be asked\r
426                                 // for their own status, and will set themselves as unversioned, for the \r
427                                 // benefit of future requests\r
428                                 ATLTRACE("svn_cli_stat err: '%s'\n", pErr->message);\r
429                                 svn_error_clear(pErr);\r
430                                 // No assert here! Since we _can_ get here, an assertion is not an option!\r
431                                 // Reasons to get here: \r
432                                 // - renaming a folder with many sub folders --> results in "not a working copy" if the revert\r
433                                 //   happens between our checks and the svn_client_status() call.\r
434                                 // - reverting a move/copy --> results in "not a working copy" (as above)\r
435                                 if (!m_directoryPath.HasAdminDir())\r
436                                 {\r
437                                         m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
438                                         return CStatusCacheEntry();\r
439                                 }\r
440                                 else\r
441                                 {\r
442                                         ATLTRACE("svn_cli_stat error, assume none status\n");\r
443                                         // Since we only assume a none status here due to svn_client_status()\r
444                                         // returning an error, make sure that this status times out soon.\r
445                                         CSVNStatusCache::Instance().m_folderCrawler.BlockPath(m_directoryPath, 2000);\r
446                                         CSVNStatusCache::Instance().AddFolderForCrawling(m_directoryPath);\r
447                                         return CStatusCacheEntry();\r
448                                 }\r
449                         }\r
450                 }\r
451                 else\r
452                 {\r
453                         ATLTRACE("Skipped SVN status for unversioned folder\n");\r
454                 }\r
455         }\r
456         // Now that we've refreshed our SVN status, we can see if it's \r
457         // changed the 'most important' status value for this directory.\r
458         // If it has, then we should tell our parent\r
459         UpdateCurrentStatus();\r
460 \r
461         if (path.IsDirectory())\r
462         {\r
463                 CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);\r
464                 if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))\r
465                 {\r
466                         CSVNStatusCache::Instance().AddFolderForCrawling(path);\r
467                         return dirEntry->GetOwnStatus(bRecursive);\r
468                 }\r
469 \r
470                 // If the status *still* isn't valid here, it means that \r
471                 // the current directory is unversioned, and we shall need to ask its children for info about themselves\r
472                 if (dirEntry)\r
473                         return dirEntry->GetStatusForMember(path,bRecursive);\r
474                 CSVNStatusCache::Instance().AddFolderForCrawling(path);\r
475                 return CStatusCacheEntry();\r
476         }\r
477         else\r
478         {\r
479                 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);\r
480                 if(itMap != m_entryCache.end())\r
481                 {\r
482                         return itMap->second;\r
483                 }\r
484         }\r
485 \r
486         AddEntry(path, NULL);\r
487         return CStatusCacheEntry();\r
488 }\r
489 \r
490 void \r
491 CCachedDirectory::AddEntry(const CTSVNPath& path, const svn_wc_status2_t* pSVNStatus, DWORD validuntil /* = 0*/)\r
492 {\r
493         AutoLocker lock(m_critSec);\r
494         if(path.IsDirectory())\r
495         {\r
496                 CCachedDirectory * childDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);\r
497                 if (childDir)\r
498                 {\r
499                         if ((childDir->GetCurrentFullStatus() != svn_wc_status_ignored)||(pSVNStatus==NULL)||(pSVNStatus->text_status != svn_wc_status_unversioned))\r
500                                 childDir->m_ownStatus.SetStatus(pSVNStatus);\r
501                         childDir->m_ownStatus.SetKind(svn_node_dir);\r
502                 }\r
503         }\r
504         else\r
505         {\r
506                 CString cachekey = GetCacheKey(path);\r
507                 CacheEntryMap::iterator entry_it = m_entryCache.lower_bound(cachekey);\r
508                 if (entry_it != m_entryCache.end() && entry_it->first == cachekey)\r
509                 {\r
510                         if (pSVNStatus)\r
511                         {\r
512                                 if (entry_it->second.GetEffectiveStatus() > svn_wc_status_none &&\r
513                                         entry_it->second.GetEffectiveStatus() != SVNStatus::GetMoreImportant(pSVNStatus->prop_status, pSVNStatus->text_status))\r
514                                 {\r
515                                         CSVNStatusCache::Instance().UpdateShell(path);\r
516                                         ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());\r
517                                 }\r
518                         }\r
519                 }\r
520                 else\r
521                 {\r
522                         entry_it = m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));\r
523                 }\r
524                 entry_it->second = CStatusCacheEntry(pSVNStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);\r
525         }\r
526 }\r
527 \r
528 \r
529 CString \r
530 CCachedDirectory::GetCacheKey(const CTSVNPath& path)\r
531 {\r
532         // All we put into the cache as a key is just the end portion of the pathname\r
533         // There's no point storing the path of the containing directory for every item\r
534         return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength());\r
535 }\r
536 \r
537 CString \r
538 CCachedDirectory::GetFullPathString(const CString& cacheKey)\r
539 {\r
540         return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;\r
541 }\r
542 \r
543 svn_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, svn_wc_status2_t *status, apr_pool_t * /*pool*/)\r
544 {\r
545         CCachedDirectory* pThis = (CCachedDirectory*)baton;\r
546 \r
547         if (path == NULL)\r
548                 return SVN_NO_ERROR;\r
549                 \r
550         CTSVNPath svnPath;\r
551 \r
552         if(status->entry)\r
553         {\r
554                 if ((status->text_status != svn_wc_status_none)&&(status->text_status != svn_wc_status_ignored))\r
555                         svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));\r
556                 else\r
557                         svnPath.SetFromSVN(path);\r
558 \r
559                 if(svnPath.IsDirectory())\r
560                 {\r
561                         if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))\r
562                         {\r
563                                 if (pThis->m_bRecursive)\r
564                                 {\r
565                                         // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated\r
566                                         CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);\r
567                                 }\r
568 \r
569                                 // Make sure we know about this child directory\r
570                                 // This initial status value is likely to be overwritten from below at some point\r
571                                 svn_wc_status_kind s = SVNStatus::GetMoreImportant(status->text_status, status->prop_status);\r
572                                 CCachedDirectory * cdir = CSVNStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);\r
573                                 if (cdir)\r
574                                 {\r
575                                         // This child directory is already in our cache!\r
576                                         // So ask this dir about its recursive status\r
577                                         svn_wc_status_kind st = SVNStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());\r
578                                         AutoLocker lock(pThis->m_critSec);\r
579                                         pThis->m_childDirectories[svnPath] = st;\r
580                                 }\r
581                                 else\r
582                                 {\r
583                                         // the child directory is not in the cache. Create a new entry for it in the cache which is\r
584                                         // initially 'unversioned'. But we added that directory to the crawling list above, which\r
585                                         // means the cache will be updated soon.\r
586                                         CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);\r
587                                         AutoLocker lock(pThis->m_critSec);\r
588                                         pThis->m_childDirectories[svnPath] = s;\r
589                                 }\r
590                         }\r
591                 }\r
592                 else\r
593                 {\r
594                         // Keep track of the most important status of all the files in this directory\r
595                         // Don't include subdirectories in this figure, because they need to provide their \r
596                         // own 'most important' value\r
597                         pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);\r
598                         pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);\r
599                         if (((status->text_status == svn_wc_status_unversioned)||(status->text_status == svn_wc_status_none))\r
600                                 &&(CSVNStatusCache::Instance().IsUnversionedAsModified()))\r
601                         {\r
602                                 // treat unversioned files as modified\r
603                                 if (pThis->m_mostImportantFileStatus != svn_wc_status_added)\r
604                                         pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, svn_wc_status_modified);\r
605                         }\r
606                 }\r
607         }\r
608         else\r
609         {\r
610                 svnPath.SetFromSVN(path);\r
611                 // Subversion returns no 'entry' field for versioned folders if they're\r
612                 // part of another working copy (nested layouts).\r
613                 // So we have to make sure that such an 'unversioned' folder really\r
614                 // is unversioned.\r
615                 if (((status->text_status == svn_wc_status_unversioned)||(status->text_status == svn_wc_status_ignored))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))\r
616                 {\r
617                         if (svnPath.HasAdminDir())\r
618                         {\r
619                                 CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);\r
620                                 // Mark the directory as 'versioned' (status 'normal' for now).\r
621                                 // This initial value will be overwritten from below some time later\r
622                                 {\r
623                                         AutoLocker lock(pThis->m_critSec);\r
624                                         pThis->m_childDirectories[svnPath] = svn_wc_status_normal;\r
625                                 }\r
626                                 // Make sure the entry is also in the cache\r
627                                 CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);\r
628                                 // also mark the status in the status object as normal\r
629                                 status->text_status = svn_wc_status_normal;\r
630                         }\r
631                 }\r
632                 else if (status->text_status == svn_wc_status_external)\r
633                 {\r
634                         CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);\r
635                         // Mark the directory as 'versioned' (status 'normal' for now).\r
636                         // This initial value will be overwritten from below some time later\r
637                         {\r
638                                 AutoLocker lock(pThis->m_critSec);\r
639                                 pThis->m_childDirectories[svnPath] = svn_wc_status_normal;\r
640                         }\r
641                         // we have added a directory to the child-directory list of this\r
642                         // directory. We now must make sure that this directory also has\r
643                         // an entry in the cache.\r
644                         CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);\r
645                         // also mark the status in the status object as normal\r
646                         status->text_status = svn_wc_status_normal;\r
647                 }\r
648                 else\r
649                 {\r
650                         if (svnPath.IsDirectory())\r
651                         {\r
652                                 AutoLocker lock(pThis->m_critSec);\r
653                                 pThis->m_childDirectories[svnPath] = SVNStatus::GetMoreImportant(status->text_status, status->prop_status);\r
654                         }\r
655                         else if ((CSVNStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != svn_wc_status_ignored))\r
656                         {\r
657                                 // make this unversioned item change the most important status of this\r
658                                 // folder to modified if it doesn't already have another status\r
659                                 if (pThis->m_mostImportantFileStatus != svn_wc_status_added)\r
660                                         pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, svn_wc_status_modified);\r
661                         }\r
662                 }\r
663         }\r
664 \r
665         pThis->AddEntry(svnPath, status);\r
666 \r
667         return SVN_NO_ERROR;\r
668 }\r
669 \r
670 bool \r
671 CCachedDirectory::IsOwnStatusValid() const\r
672 {\r
673         return m_ownStatus.HasBeenSet() && \r
674                    !m_ownStatus.HasExpired(GetTickCount()) &&\r
675                    // 'external' isn't a valid status. That just\r
676                    // means the folder is not part of the current working\r
677                    // copy but it still has its own 'real' status\r
678                    m_ownStatus.GetEffectiveStatus()!=svn_wc_status_external &&\r
679                    m_ownStatus.IsKindKnown();\r
680 }\r
681 \r
682 void CCachedDirectory::Invalidate()\r
683 {\r
684         m_ownStatus.Invalidate();\r
685 }\r
686 \r
687 svn_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()\r
688 {\r
689         // Combine our OWN folder status with the most important of our *FILES'* status.\r
690         svn_wc_status_kind retVal = SVNStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());\r
691 \r
692         if ((retVal != svn_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))\r
693         {\r
694                 if ((retVal == svn_wc_status_added)||(retVal == svn_wc_status_deleted)||(retVal == svn_wc_status_missing))\r
695                         retVal = svn_wc_status_modified;\r
696         }\r
697 \r
698         // Now combine all our child-directorie's status\r
699         \r
700         AutoLocker lock(m_critSec);\r
701         ChildDirStatus::const_iterator it;\r
702         for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)\r
703         {\r
704                 retVal = SVNStatus::GetMoreImportant(retVal, it->second);\r
705                 if ((retVal != svn_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))\r
706                 {\r
707                         if ((retVal == svn_wc_status_added)||(retVal == svn_wc_status_deleted)||(retVal == svn_wc_status_missing))\r
708                                 retVal = svn_wc_status_modified;\r
709                 }\r
710         }\r
711         \r
712         return retVal;\r
713 }\r
714 \r
715 // Update our composite status and deal with things if it's changed\r
716 void CCachedDirectory::UpdateCurrentStatus()\r
717 {\r
718         svn_wc_status_kind newStatus = CalculateRecursiveStatus();\r
719 \r
720         if ((newStatus != m_currentFullStatus)&&(m_ownStatus.IsVersioned()))\r
721         {\r
722                 if ((m_currentFullStatus != svn_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != svn_wc_status_ignored))\r
723                 {\r
724                         // Our status has changed - tell the shell\r
725                         ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);           \r
726                         CSVNStatusCache::Instance().UpdateShell(m_directoryPath);\r
727                 }\r
728                 if (m_ownStatus.GetEffectiveStatus() != svn_wc_status_ignored)\r
729                         m_currentFullStatus = newStatus;\r
730                 else\r
731                         m_currentFullStatus = svn_wc_status_ignored;\r
732         }\r
733         // And tell our parent, if we've got one...\r
734         // we tell our parent *always* about our status, even if it hasn't\r
735         // changed. This is to make sure that the parent has really our current\r
736         // status - the parent can decide itself if our status has changed\r
737         // or not.\r
738         CTSVNPath parentPath = m_directoryPath.GetContainingDirectory();\r
739         if(!parentPath.IsEmpty())\r
740         {\r
741                 // We have a parent\r
742                 CCachedDirectory * cachedDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(parentPath);\r
743                 if (cachedDir)\r
744                         cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);\r
745         }\r
746 }\r
747 \r
748 \r
749 // Receive a notification from a child that its status has changed\r
750 void CCachedDirectory::UpdateChildDirectoryStatus(const CTSVNPath& childDir, svn_wc_status_kind childStatus)\r
751 {\r
752         svn_wc_status_kind currentStatus = svn_wc_status_none;\r
753         {\r
754                 AutoLocker lock(m_critSec);\r
755                 currentStatus = m_childDirectories[childDir];\r
756         }\r
757         if ((currentStatus != childStatus)||(!IsOwnStatusValid()))\r
758         {\r
759                 {\r
760                         AutoLocker lock(m_critSec);\r
761                         m_childDirectories[childDir] = childStatus;\r
762                 }\r
763                 UpdateCurrentStatus();\r
764         }\r
765 }\r
766 \r
767 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)\r
768 {\r
769         // Don't return recursive status if we're unversioned ourselves.\r
770         if(bRecursive && m_ownStatus.GetEffectiveStatus() > svn_wc_status_unversioned)\r
771         {\r
772                 CStatusCacheEntry recursiveStatus(m_ownStatus);\r
773                 UpdateCurrentStatus();\r
774                 recursiveStatus.ForceStatus(m_currentFullStatus);\r
775                 return recursiveStatus;                         \r
776         }\r
777         else\r
778         {\r
779                 return m_ownStatus;\r
780         }\r
781 }\r
782 \r
783 void CCachedDirectory::RefreshStatus(bool bRecursive)\r
784 {\r
785         // Make sure that our own status is up-to-date\r
786         GetStatusForMember(m_directoryPath,bRecursive);\r
787 \r
788         AutoLocker lock(m_critSec);\r
789         // We also need to check if all our file members have the right date on them\r
790         CacheEntryMap::iterator itMembers;\r
791         std::set<CTSVNPath> refreshedpaths;\r
792         DWORD now = GetTickCount();\r
793         if (m_entryCache.size() == 0)\r
794                 return;\r
795         for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)\r
796         {\r
797                 if (itMembers->first)\r
798                 {\r
799                         CTSVNPath filePath(m_directoryPath);\r
800                         filePath.AppendPathString(itMembers->first);\r
801                         std::set<CTSVNPath>::iterator refr_it;\r
802                         if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&\r
803                                 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))\r
804                         {\r
805                                 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))\r
806                                 {\r
807                                         lock.Unlock();\r
808                                         // We need to request this item as well\r
809                                         GetStatusForMember(filePath,bRecursive);\r
810                                         // GetStatusForMember now has recreated the m_entryCache map.\r
811                                         // So start the loop again, but add this path to the refreshed paths set\r
812                                         // to make sure we don't refresh this path again. This is to make sure\r
813                                         // that we don't end up in an endless loop.\r
814                                         lock.Lock();\r
815                                         refreshedpaths.insert(refr_it, filePath);\r
816                                         itMembers = m_entryCache.begin();\r
817                                         if (m_entryCache.size()==0)\r
818                                                 return;\r
819                                         continue;\r
820                                 }\r
821                                 else if ((bRecursive)&&(itMembers->second.IsDirectory()))\r
822                                 {\r
823                                         // crawl all sub folders too! Otherwise a change deep inside the\r
824                                         // tree which has changed won't get propagated up the tree.\r
825                                         CSVNStatusCache::Instance().AddFolderForCrawling(filePath);\r
826                                 }\r
827                         }\r
828                 }\r
829         }\r
830 }\r
831 \r
832 void CCachedDirectory::RefreshMostImportant()\r
833 {\r
834         CacheEntryMap::iterator itMembers;\r
835         svn_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();\r
836         for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)\r
837         {\r
838                 newStatus = SVNStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());\r
839                 if (((itMembers->second.GetEffectiveStatus() == svn_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == svn_wc_status_none))\r
840                         &&(CSVNStatusCache::Instance().IsUnversionedAsModified()))\r
841                 {\r
842                         // treat unversioned files as modified\r
843                         if (newStatus != svn_wc_status_added)\r
844                                 newStatus = SVNStatus::GetMoreImportant(newStatus, svn_wc_status_modified);\r
845                 }\r
846         }\r
847         if (newStatus != m_mostImportantFileStatus)\r
848         {\r
849                 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());\r
850                 CSVNStatusCache::Instance().UpdateShell(m_directoryPath);\r
851         }\r
852         m_mostImportantFileStatus = newStatus;\r
853 }\r