OSDN Git Service

Add TGitCache Code
authorFrank Li <lznuaa@gmail.com>
Sun, 14 Dec 2008 13:04:54 +0000 (21:04 +0800)
committerFrank Li <lznuaa@gmail.com>
Sun, 14 Dec 2008 13:04:54 +0000 (21:04 +0800)
22 files changed:
src/TGitCache/CacheInterface.cpp [new file with mode: 0644]
src/TGitCache/CacheInterface.h [new file with mode: 0644]
src/TGitCache/CachedDirectory.cpp [new file with mode: 0644]
src/TGitCache/CachedDirectory.h [new file with mode: 0644]
src/TGitCache/DirectoryWatcher.cpp [new file with mode: 0644]
src/TGitCache/DirectoryWatcher.h [new file with mode: 0644]
src/TGitCache/FolderCrawler.cpp [new file with mode: 0644]
src/TGitCache/FolderCrawler.h [new file with mode: 0644]
src/TGitCache/SVNStatusCache.cpp [new file with mode: 0644]
src/TGitCache/SVNStatusCache.h [new file with mode: 0644]
src/TGitCache/ShellUpdater.cpp [new file with mode: 0644]
src/TGitCache/ShellUpdater.h [new file with mode: 0644]
src/TGitCache/StatusCacheEntry.cpp [new file with mode: 0644]
src/TGitCache/StatusCacheEntry.h [new file with mode: 0644]
src/TGitCache/TSVNCache.cpp [new file with mode: 0644]
src/TGitCache/TSVNCache.h [new file with mode: 0644]
src/TGitCache/TSVNCache.rc [new file with mode: 0644]
src/TGitCache/TSVNCache.rc2 [new file with mode: 0644]
src/TGitCache/TSVNCache.vcproj [new file with mode: 0644]
src/TGitCache/resource.h [new file with mode: 0644]
src/TGitCache/stdafx.cpp [new file with mode: 0644]
src/TGitCache/stdafx.h [new file with mode: 0644]

diff --git a/src/TGitCache/CacheInterface.cpp b/src/TGitCache/CacheInterface.cpp
new file mode 100644 (file)
index 0000000..f7f46c2
--- /dev/null
@@ -0,0 +1,54 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2007 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#include "stdafx.h"\r
+#include "CacheInterface.h"\r
+\r
+CString GetCachePipeName()\r
+{\r
+       return TSVN_CACHE_PIPE_NAME + GetCacheID();\r
+}\r
+\r
+CString GetCacheCommandPipeName()\r
+{\r
+       return TSVN_CACHE_COMMANDPIPE_NAME + GetCacheID();\r
+}\r
+\r
+CString GetCacheMutexName()\r
+{\r
+       return TSVN_CACHE_MUTEX_NAME + GetCacheID();\r
+}\r
+CString GetCacheID()\r
+{\r
+       HANDLE token;\r
+       DWORD len;\r
+       BOOL result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token);\r
+       if(result)\r
+       {\r
+               GetTokenInformation(token, TokenStatistics, NULL, 0, &len);\r
+               LPBYTE data = new BYTE[len];\r
+               GetTokenInformation(token, TokenStatistics, data, len, &len);\r
+               LUID uid = ((PTOKEN_STATISTICS)data)->AuthenticationId;\r
+               delete [ ] data;\r
+               CString t;\r
+               t.Format(_T("-%08x%08x"), uid.HighPart, uid.LowPart);\r
+               CloseHandle(token);\r
+               return t;\r
+       }\r
+       return _T("");\r
+}
\ No newline at end of file
diff --git a/src/TGitCache/CacheInterface.h b/src/TGitCache/CacheInterface.h
new file mode 100644 (file)
index 0000000..2c20220
--- /dev/null
@@ -0,0 +1,97 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2006,2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+\r
+#pragma once\r
+#include "wininet.h"\r
+\r
+// The name of the named-pipe for the cache\r
+#ifdef WIN64\r
+#define TSVN_CACHE_PIPE_NAME _T("\\\\.\\pipe\\TSVNCache64")\r
+#define TSVN_CACHE_COMMANDPIPE_NAME _T("\\\\.\\pipe\\TSVNCacheCommand64")\r
+#define TSVN_CACHE_WINDOW_NAME _T("TSVNCacheWindow64")\r
+#define TSVN_CACHE_MUTEX_NAME _T("TSVNCacheMutex64")\r
+#else\r
+#define TSVN_CACHE_PIPE_NAME _T("\\\\.\\pipe\\TSVNCache")\r
+#define TSVN_CACHE_COMMANDPIPE_NAME _T("\\\\.\\pipe\\TSVNCacheCommand")\r
+#define TSVN_CACHE_WINDOW_NAME _T("TSVNCacheWindow")\r
+#define TSVN_CACHE_MUTEX_NAME _T("TSVNCacheMutex")\r
+#endif\r
+\r
+CString GetCachePipeName();\r
+CString GetCacheCommandPipeName();\r
+CString GetCacheMutexName();\r
+\r
+CString GetCacheID();\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * A structure passed as a request from the shell (or other client) to the external cache\r
+ */ \r
+struct TSVNCacheRequest\r
+{\r
+       DWORD flags;\r
+       WCHAR path[MAX_PATH+1];\r
+};\r
+\r
+// CustomActions will use this header but does not need nor understand the SVN types ...\r
+\r
+#ifdef SVN_WC_H\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * The structure returned as a response\r
+ */\r
+struct TSVNCacheResponse\r
+{\r
+       svn_wc_status2_t m_status;\r
+       svn_wc_entry_t m_entry;\r
+       svn_node_kind_t m_kind;\r
+       char m_url[INTERNET_MAX_URL_LENGTH+1];\r
+       char m_owner[255];              ///< owner of the lock\r
+       char m_author[255];\r
+       bool m_readonly;                ///< whether the file is write protected or not\r
+       bool m_needslock;               ///< whether the file has the svn:needs-lock property set or not (only works with the new working copy version)\r
+};\r
+\r
+#endif // SVN_WC_H\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * a cache command\r
+ */\r
+struct TSVNCacheCommand\r
+{\r
+       BYTE command;                           ///< the command to execute\r
+       WCHAR path[MAX_PATH+1];         ///< path to do the command for\r
+};\r
+\r
+#define                TSVNCACHECOMMAND_END            0               ///< ends the thread handling the pipe communication\r
+#define                TSVNCACHECOMMAND_CRAWL          1               ///< start crawling the specified path for changes\r
+#define                TSVNCACHECOMMAND_REFRESHALL     2               ///< Refreshes the whole cache, usually necessary after the "treat unversioned files as modified" option changed.\r
+#define                TSVNCACHECOMMAND_RELEASE        3               ///< Releases all open handles for the specified path and all paths below\r
+\r
+\r
+/// Set this flag if you already know whether or not the item is a folder\r
+#define TSVNCACHE_FLAGS_FOLDERISKNOWN          0x01\r
+/// Set this flag if the item is a folder\r
+#define TSVNCACHE_FLAGS_ISFOLDER                       0x02\r
+/// Set this flag if you want recursive folder status (safely ignored for file paths)\r
+#define TSVNCACHE_FLAGS_RECUSIVE_STATUS                0x04\r
+/// Set this flag if notifications to the shell are not allowed\r
+#define TSVNCACHE_FLAGS_NONOTIFICATIONS                0x08\r
diff --git a/src/TGitCache/CachedDirectory.cpp b/src/TGitCache/CachedDirectory.cpp
new file mode 100644 (file)
index 0000000..5db9d28
--- /dev/null
@@ -0,0 +1,853 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#include "StdAfx.h"\r
+#include ".\cacheddirectory.h"\r
+#include "SVNHelpers.h"\r
+#include "SVNStatusCache.h"\r
+#include "SVNStatus.h"\r
+#include <set>\r
+\r
+CCachedDirectory::CCachedDirectory(void)\r
+{\r
+       m_entriesFileTime = 0;\r
+       m_propsFileTime = 0;\r
+       m_currentStatusFetchingPathTicks = 0;\r
+       m_bCurrentFullStatusValid = false;\r
+       m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
+       m_bRecursive = true;\r
+}\r
+\r
+CCachedDirectory::~CCachedDirectory(void)\r
+{\r
+}\r
+\r
+CCachedDirectory::CCachedDirectory(const CTSVNPath& directoryPath)\r
+{\r
+       ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));\r
+\r
+       m_directoryPath = directoryPath;\r
+       m_entriesFileTime = 0;\r
+       m_propsFileTime = 0;\r
+       m_currentStatusFetchingPathTicks = 0;\r
+       m_bCurrentFullStatusValid = false;\r
+       m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
+       m_bRecursive = true;\r
+}\r
+\r
+BOOL CCachedDirectory::SaveToDisk(FILE * pFile)\r
+{\r
+       AutoLocker lock(m_critSec);\r
+#define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;\r
+\r
+       unsigned int value = 1;\r
+       WRITEVALUETOFILE(value);        // 'version' of this save-format\r
+       value = (int)m_entryCache.size();\r
+       WRITEVALUETOFILE(value);        // size of the cache map\r
+       // now iterate through the maps and save every entry.\r
+       for (CacheEntryMap::iterator I = m_entryCache.begin(); I != m_entryCache.end(); ++I)\r
+       {\r
+               const CString& key = I->first;\r
+               value = key.GetLength();\r
+               WRITEVALUETOFILE(value);\r
+               if (value)\r
+               {\r
+                       if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)\r
+                               return false;\r
+                       if (!I->second.SaveToDisk(pFile))\r
+                               return false;\r
+               }\r
+       }\r
+       value = (int)m_childDirectories.size();\r
+       WRITEVALUETOFILE(value);\r
+       for (ChildDirStatus::iterator I = m_childDirectories.begin(); I != m_childDirectories.end(); ++I)\r
+       {\r
+               const CString& path = I->first.GetWinPathString();\r
+               value = path.GetLength();\r
+               WRITEVALUETOFILE(value);\r
+               if (value)\r
+               {\r
+                       if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)\r
+                               return false;\r
+                       svn_wc_status_kind status = I->second;\r
+                       WRITEVALUETOFILE(status);\r
+               }\r
+       }\r
+       WRITEVALUETOFILE(m_entriesFileTime);\r
+       WRITEVALUETOFILE(m_propsFileTime);\r
+       value = m_directoryPath.GetWinPathString().GetLength();\r
+       WRITEVALUETOFILE(value);\r
+       if (value)\r
+       {\r
+               if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)\r
+                       return false;\r
+       }\r
+       if (!m_ownStatus.SaveToDisk(pFile))\r
+               return false;\r
+       WRITEVALUETOFILE(m_currentFullStatus);\r
+       WRITEVALUETOFILE(m_mostImportantFileStatus);\r
+       return true;\r
+}\r
+\r
+BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)\r
+{\r
+       AutoLocker lock(m_critSec);\r
+#define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;\r
+       try\r
+       {\r
+               unsigned int value = 0;\r
+               LOADVALUEFROMFILE(value);\r
+               if (value != 1)\r
+                       return false;           // not the correct version\r
+               int mapsize = 0;\r
+               LOADVALUEFROMFILE(mapsize);\r
+               for (int i=0; i<mapsize; ++i)\r
+               {\r
+                       LOADVALUEFROMFILE(value);\r
+                       if (value > MAX_PATH)\r
+                               return false;\r
+                       if (value)\r
+                       {\r
+                               CString sKey;\r
+                               if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)\r
+                               {\r
+                                       sKey.ReleaseBuffer(0);\r
+                                       return false;\r
+                               }\r
+                               sKey.ReleaseBuffer(value);\r
+                               CStatusCacheEntry entry;\r
+                               if (!entry.LoadFromDisk(pFile))\r
+                                       return false;\r
+                               m_entryCache[sKey] = entry;\r
+                       }\r
+               }\r
+               LOADVALUEFROMFILE(mapsize);\r
+               for (int i=0; i<mapsize; ++i)\r
+               {\r
+                       LOADVALUEFROMFILE(value);\r
+                       if (value > MAX_PATH)\r
+                               return false;\r
+                       if (value)\r
+                       {\r
+                               CString sPath;\r
+                               if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)\r
+                               {\r
+                                       sPath.ReleaseBuffer(0);\r
+                                       return false;\r
+                               }\r
+                               sPath.ReleaseBuffer(value);\r
+                               svn_wc_status_kind status;\r
+                               LOADVALUEFROMFILE(status);\r
+                               m_childDirectories[CTSVNPath(sPath)] = status;\r
+                       }\r
+               }\r
+               LOADVALUEFROMFILE(m_entriesFileTime);\r
+               LOADVALUEFROMFILE(m_propsFileTime);\r
+               LOADVALUEFROMFILE(value);\r
+               if (value > MAX_PATH)\r
+                       return false;\r
+               if (value)\r
+               {\r
+                       CString sPath;\r
+                       if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)\r
+                       {\r
+                               sPath.ReleaseBuffer(0);\r
+                               return false;\r
+                       }\r
+                       sPath.ReleaseBuffer(value);\r
+                       m_directoryPath.SetFromWin(sPath);\r
+               }\r
+               if (!m_ownStatus.LoadFromDisk(pFile))\r
+                       return false;\r
+\r
+               LOADVALUEFROMFILE(m_currentFullStatus);\r
+               LOADVALUEFROMFILE(m_mostImportantFileStatus);\r
+       }\r
+       catch ( CAtlException )\r
+       {\r
+               return false;\r
+       }\r
+       return true;\r
+\r
+}\r
+\r
+CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTSVNPath& path, bool bRecursive,  bool bFetch /* = true */)\r
+{\r
+       CString strCacheKey;\r
+       bool bThisDirectoryIsUnversioned = false;\r
+       bool bRequestForSelf = false;\r
+       if(path.IsEquivalentToWithoutCase(m_directoryPath))\r
+       {\r
+               bRequestForSelf = true;\r
+       }\r
+\r
+       // In all most circumstances, we ask for the status of a member of this directory.\r
+       ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);\r
+\r
+       // Check if the entries file has been changed\r
+       CTSVNPath entriesFilePath(m_directoryPath);\r
+       CTSVNPath propsDirPath(m_directoryPath);\r
+       if (g_SVNAdminDir.IsVSNETHackActive())\r
+       {\r
+               entriesFilePath.AppendPathString(g_SVNAdminDir.GetVSNETAdminDirName() + _T("\\entries"));\r
+               propsDirPath.AppendPathString(g_SVNAdminDir.GetVSNETAdminDirName() + _T("\\dir-props"));\r
+       }\r
+       else\r
+       {\r
+               entriesFilePath.AppendPathString(g_SVNAdminDir.GetAdminDirName() + _T("\\entries"));\r
+               propsDirPath.AppendPathString(g_SVNAdminDir.GetAdminDirName() + _T("\\dir-props"));\r
+       }\r
+       if ( (m_entriesFileTime == entriesFilePath.GetLastWriteTime()) && ((entriesFilePath.GetLastWriteTime() == 0) || (m_propsFileTime == propsDirPath.GetLastWriteTime())) )\r
+       {\r
+               m_entriesFileTime = entriesFilePath.GetLastWriteTime();\r
+               if (m_entriesFileTime)\r
+                       m_propsFileTime = propsDirPath.GetLastWriteTime();\r
+\r
+               if(m_entriesFileTime == 0)\r
+               {\r
+                       // We are a folder which is not in a working copy\r
+                       bThisDirectoryIsUnversioned = true;\r
+                       m_ownStatus.SetStatus(NULL);\r
+\r
+                       // If a user removes the .svn directory, we get here with m_entryCache\r
+                       // not being empty, but still us being unversioned\r
+                       if (!m_entryCache.empty())\r
+                       {\r
+                               m_entryCache.clear();\r
+                       }\r
+                       ATLASSERT(m_entryCache.empty());\r
+                       \r
+                       // However, a member *DIRECTORY* might be the top of WC\r
+                       // so we need to ask them to get their own status\r
+                       if(!path.IsDirectory())\r
+                       {\r
+                               if ((PathFileExists(path.GetWinPath()))||(bRequestForSelf))\r
+                                       return CStatusCacheEntry();\r
+                               // the entry doesn't exist anymore! \r
+                               // but we can't remove it from the cache here:\r
+                               // the GetStatusForMember() method is called only with a read\r
+                               // lock and not a write lock!\r
+                               // So mark it for crawling, and let the crawler remove it\r
+                               // later\r
+                               CSVNStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());\r
+                               return CStatusCacheEntry();\r
+                       }\r
+                       else\r
+                       {\r
+                               // If we're in the special case of a directory being asked for its own status\r
+                               // and this directory is unversioned, then we should just return that here\r
+                               if(bRequestForSelf)\r
+                               {\r
+                                       return CStatusCacheEntry();\r
+                               }\r
+                       }\r
+               }\r
+\r
+               if(path.IsDirectory())\r
+               {\r
+                       // We don't have directory status in our cache\r
+                       // Ask the directory if it knows its own status\r
+                       CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);\r
+                       if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))\r
+                       {\r
+                               // To keep recursive status up to date, we'll request that children are all crawled again\r
+                               // This will be very quick if nothings changed, because it will all be cache hits\r
+                               if (bRecursive)\r
+                               {\r
+                                       AutoLocker lock(dirEntry->m_critSec);\r
+                                       ChildDirStatus::const_iterator it;\r
+                                       for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)\r
+                                       {\r
+                                               CSVNStatusCache::Instance().AddFolderForCrawling(it->first);\r
+                                       }\r
+                               }\r
+\r
+                               return dirEntry->GetOwnStatus(bRecursive);\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       {\r
+                               // if we currently are fetching the status of the directory\r
+                               // we want the status for, we just return an empty entry here\r
+                               // and don't wait for that fetching to finish.\r
+                               // That's because fetching the status can take a *really* long\r
+                               // time (e.g. if a commit is also in progress on that same\r
+                               // directory), and we don't want to make the explorer appear\r
+                               // to hang.\r
+                               AutoLocker pathlock(m_critSecPath);\r
+                               if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))\r
+                               {\r
+                                       if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))\r
+                                       {\r
+                                               ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());\r
+                                               m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
+                                               return CStatusCacheEntry();\r
+                                       }\r
+                               }\r
+                       }\r
+                       // Look up a file in our own cache\r
+                       AutoLocker lock(m_critSec);\r
+                       strCacheKey = GetCacheKey(path);\r
+                       CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);\r
+                       if(itMap != m_entryCache.end())\r
+                       {\r
+                               // We've hit the cache - check for timeout\r
+                               if(!itMap->second.HasExpired((long)GetTickCount()))\r
+                               {\r
+                                       if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))\r
+                                       {\r
+                                               if ((itMap->second.GetEffectiveStatus()!=svn_wc_status_missing)||(!PathFileExists(path.GetWinPath())))\r
+                                               {\r
+                                                       // Note: the filetime matches after a modified has been committed too.\r
+                                                       // So in that case, we would return a wrong status (e.g. 'modified' instead\r
+                                                       // of 'normal') here.\r
+                                                       return itMap->second;\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+       else\r
+       {\r
+               AutoLocker pathlock(m_critSecPath);\r
+               if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))\r
+               {\r
+                       if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))\r
+                       {\r
+                               ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());\r
+                               m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
+                               return CStatusCacheEntry();\r
+                       }\r
+               }\r
+               // if we're fetching the status for the explorer,\r
+               // we don't refresh the status but use the one\r
+               // we already have (to save time and make the explorer\r
+               // more responsive in stress conditions).\r
+               // We leave the refreshing to the crawler.\r
+               if ((!bFetch)&&(m_entriesFileTime))\r
+               {\r
+                       CSVNStatusCache::Instance().AddFolderForCrawling(path.GetDirectory());\r
+                       return CStatusCacheEntry();\r
+               }\r
+               AutoLocker lock(m_critSec);\r
+               m_entriesFileTime = entriesFilePath.GetLastWriteTime();\r
+               m_propsFileTime = propsDirPath.GetLastWriteTime();\r
+               m_entryCache.clear();\r
+               strCacheKey = GetCacheKey(path);\r
+       }\r
+\r
+       svn_opt_revision_t revision;\r
+       revision.kind = svn_opt_revision_unspecified;\r
+\r
+       // We've not got this item in the cache - let's add it\r
+       // We never bother asking SVN for the status of just one file, always for its containing directory\r
+\r
+       if (g_SVNAdminDir.IsAdminDirPath(path.GetWinPathString()))\r
+       {\r
+               // We're being asked for the status of an .SVN directory\r
+               // It's not worth asking for this\r
+               return CStatusCacheEntry();\r
+       }\r
+\r
+       {\r
+               {\r
+                       AutoLocker pathlock(m_critSecPath);\r
+                       if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))\r
+                       {\r
+                               if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))\r
+                               {\r
+                                       ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());\r
+                                       m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
+                                       return CStatusCacheEntry();\r
+                               }\r
+                       }\r
+               }\r
+               SVNPool subPool(CSVNStatusCache::Instance().m_svnHelp.Pool());\r
+               {\r
+                       AutoLocker lock(m_critSec);\r
+                       m_mostImportantFileStatus = svn_wc_status_none;\r
+                       m_childDirectories.clear();\r
+                       m_entryCache.clear();\r
+                       m_ownStatus.SetStatus(NULL);\r
+                       m_bRecursive = bRecursive;\r
+               }\r
+               if(!bThisDirectoryIsUnversioned)\r
+               {\r
+                       {\r
+                               AutoLocker pathlock(m_critSecPath);\r
+                               m_currentStatusFetchingPath = m_directoryPath;\r
+                               m_currentStatusFetchingPathTicks = GetTickCount();\r
+                       }\r
+                       ATLTRACE(_T("svn_cli_stat for '%s' (req %s)\n"), m_directoryPath.GetWinPath(), path.GetWinPath());\r
+                       svn_error_t* pErr = svn_client_status4 (\r
+                               NULL,\r
+                               m_directoryPath.GetSVNApiPath(subPool),\r
+                               &revision,\r
+                               GetStatusCallback,\r
+                               this,\r
+                               svn_depth_immediates,\r
+                               TRUE,                                                                   //getall\r
+                               FALSE,\r
+                               TRUE,                                                                   //noignore\r
+                               FALSE,                                                                  //ignore externals\r
+                               NULL,                                                                   //changelists\r
+                               CSVNStatusCache::Instance().m_svnHelp.ClientContext(),\r
+                               subPool\r
+                               );\r
+                       {\r
+                               AutoLocker pathlock(m_critSecPath);\r
+                               m_currentStatusFetchingPath.Reset();\r
+                       }\r
+                       ATLTRACE(_T("svn_cli_stat finished for '%s'\n"), m_directoryPath.GetWinPath(), path.GetWinPath());\r
+                       if(pErr)\r
+                       {\r
+                               // Handle an error\r
+                               // The most likely error on a folder is that it's not part of a WC\r
+                               // In most circumstances, this will have been caught earlier,\r
+                               // but in some situations, we'll get this error.\r
+                               // If we allow ourselves to fall on through, then folders will be asked\r
+                               // for their own status, and will set themselves as unversioned, for the \r
+                               // benefit of future requests\r
+                               ATLTRACE("svn_cli_stat err: '%s'\n", pErr->message);\r
+                               svn_error_clear(pErr);\r
+                               // No assert here! Since we _can_ get here, an assertion is not an option!\r
+                               // Reasons to get here: \r
+                               // - renaming a folder with many sub folders --> results in "not a working copy" if the revert\r
+                               //   happens between our checks and the svn_client_status() call.\r
+                               // - reverting a move/copy --> results in "not a working copy" (as above)\r
+                               if (!m_directoryPath.HasAdminDir())\r
+                               {\r
+                                       m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;\r
+                                       return CStatusCacheEntry();\r
+                               }\r
+                               else\r
+                               {\r
+                                       ATLTRACE("svn_cli_stat error, assume none status\n");\r
+                                       // Since we only assume a none status here due to svn_client_status()\r
+                                       // returning an error, make sure that this status times out soon.\r
+                                       CSVNStatusCache::Instance().m_folderCrawler.BlockPath(m_directoryPath, 2000);\r
+                                       CSVNStatusCache::Instance().AddFolderForCrawling(m_directoryPath);\r
+                                       return CStatusCacheEntry();\r
+                               }\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       ATLTRACE("Skipped SVN status for unversioned folder\n");\r
+               }\r
+       }\r
+       // Now that we've refreshed our SVN status, we can see if it's \r
+       // changed the 'most important' status value for this directory.\r
+       // If it has, then we should tell our parent\r
+       UpdateCurrentStatus();\r
+\r
+       if (path.IsDirectory())\r
+       {\r
+               CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);\r
+               if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))\r
+               {\r
+                       CSVNStatusCache::Instance().AddFolderForCrawling(path);\r
+                       return dirEntry->GetOwnStatus(bRecursive);\r
+               }\r
+\r
+               // If the status *still* isn't valid here, it means that \r
+               // the current directory is unversioned, and we shall need to ask its children for info about themselves\r
+               if (dirEntry)\r
+                       return dirEntry->GetStatusForMember(path,bRecursive);\r
+               CSVNStatusCache::Instance().AddFolderForCrawling(path);\r
+               return CStatusCacheEntry();\r
+       }\r
+       else\r
+       {\r
+               CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);\r
+               if(itMap != m_entryCache.end())\r
+               {\r
+                       return itMap->second;\r
+               }\r
+       }\r
+\r
+       AddEntry(path, NULL);\r
+       return CStatusCacheEntry();\r
+}\r
+\r
+void \r
+CCachedDirectory::AddEntry(const CTSVNPath& path, const svn_wc_status2_t* pSVNStatus, DWORD validuntil /* = 0*/)\r
+{\r
+       AutoLocker lock(m_critSec);\r
+       if(path.IsDirectory())\r
+       {\r
+               CCachedDirectory * childDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);\r
+               if (childDir)\r
+               {\r
+                       if ((childDir->GetCurrentFullStatus() != svn_wc_status_ignored)||(pSVNStatus==NULL)||(pSVNStatus->text_status != svn_wc_status_unversioned))\r
+                               childDir->m_ownStatus.SetStatus(pSVNStatus);\r
+                       childDir->m_ownStatus.SetKind(svn_node_dir);\r
+               }\r
+       }\r
+       else\r
+       {\r
+               CString cachekey = GetCacheKey(path);\r
+               CacheEntryMap::iterator entry_it = m_entryCache.lower_bound(cachekey);\r
+               if (entry_it != m_entryCache.end() && entry_it->first == cachekey)\r
+               {\r
+                       if (pSVNStatus)\r
+                       {\r
+                               if (entry_it->second.GetEffectiveStatus() > svn_wc_status_none &&\r
+                                       entry_it->second.GetEffectiveStatus() != SVNStatus::GetMoreImportant(pSVNStatus->prop_status, pSVNStatus->text_status))\r
+                               {\r
+                                       CSVNStatusCache::Instance().UpdateShell(path);\r
+                                       ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());\r
+                               }\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       entry_it = m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));\r
+               }\r
+               entry_it->second = CStatusCacheEntry(pSVNStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);\r
+       }\r
+}\r
+\r
+\r
+CString \r
+CCachedDirectory::GetCacheKey(const CTSVNPath& path)\r
+{\r
+       // All we put into the cache as a key is just the end portion of the pathname\r
+       // There's no point storing the path of the containing directory for every item\r
+       return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength());\r
+}\r
+\r
+CString \r
+CCachedDirectory::GetFullPathString(const CString& cacheKey)\r
+{\r
+       return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;\r
+}\r
+\r
+svn_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, svn_wc_status2_t *status, apr_pool_t * /*pool*/)\r
+{\r
+       CCachedDirectory* pThis = (CCachedDirectory*)baton;\r
+\r
+       if (path == NULL)\r
+               return SVN_NO_ERROR;\r
+               \r
+       CTSVNPath svnPath;\r
+\r
+       if(status->entry)\r
+       {\r
+               if ((status->text_status != svn_wc_status_none)&&(status->text_status != svn_wc_status_ignored))\r
+                       svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));\r
+               else\r
+                       svnPath.SetFromSVN(path);\r
+\r
+               if(svnPath.IsDirectory())\r
+               {\r
+                       if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))\r
+                       {\r
+                               if (pThis->m_bRecursive)\r
+                               {\r
+                                       // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated\r
+                                       CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);\r
+                               }\r
+\r
+                               // Make sure we know about this child directory\r
+                               // This initial status value is likely to be overwritten from below at some point\r
+                               svn_wc_status_kind s = SVNStatus::GetMoreImportant(status->text_status, status->prop_status);\r
+                               CCachedDirectory * cdir = CSVNStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);\r
+                               if (cdir)\r
+                               {\r
+                                       // This child directory is already in our cache!\r
+                                       // So ask this dir about its recursive status\r
+                                       svn_wc_status_kind st = SVNStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());\r
+                                       AutoLocker lock(pThis->m_critSec);\r
+                                       pThis->m_childDirectories[svnPath] = st;\r
+                               }\r
+                               else\r
+                               {\r
+                                       // the child directory is not in the cache. Create a new entry for it in the cache which is\r
+                                       // initially 'unversioned'. But we added that directory to the crawling list above, which\r
+                                       // means the cache will be updated soon.\r
+                                       CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);\r
+                                       AutoLocker lock(pThis->m_critSec);\r
+                                       pThis->m_childDirectories[svnPath] = s;\r
+                               }\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       // Keep track of the most important status of all the files in this directory\r
+                       // Don't include subdirectories in this figure, because they need to provide their \r
+                       // own 'most important' value\r
+                       pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);\r
+                       pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);\r
+                       if (((status->text_status == svn_wc_status_unversioned)||(status->text_status == svn_wc_status_none))\r
+                               &&(CSVNStatusCache::Instance().IsUnversionedAsModified()))\r
+                       {\r
+                               // treat unversioned files as modified\r
+                               if (pThis->m_mostImportantFileStatus != svn_wc_status_added)\r
+                                       pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, svn_wc_status_modified);\r
+                       }\r
+               }\r
+       }\r
+       else\r
+       {\r
+               svnPath.SetFromSVN(path);\r
+               // Subversion returns no 'entry' field for versioned folders if they're\r
+               // part of another working copy (nested layouts).\r
+               // So we have to make sure that such an 'unversioned' folder really\r
+               // is unversioned.\r
+               if (((status->text_status == svn_wc_status_unversioned)||(status->text_status == svn_wc_status_ignored))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))\r
+               {\r
+                       if (svnPath.HasAdminDir())\r
+                       {\r
+                               CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);\r
+                               // Mark the directory as 'versioned' (status 'normal' for now).\r
+                               // This initial value will be overwritten from below some time later\r
+                               {\r
+                                       AutoLocker lock(pThis->m_critSec);\r
+                                       pThis->m_childDirectories[svnPath] = svn_wc_status_normal;\r
+                               }\r
+                               // Make sure the entry is also in the cache\r
+                               CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);\r
+                               // also mark the status in the status object as normal\r
+                               status->text_status = svn_wc_status_normal;\r
+                       }\r
+               }\r
+               else if (status->text_status == svn_wc_status_external)\r
+               {\r
+                       CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);\r
+                       // Mark the directory as 'versioned' (status 'normal' for now).\r
+                       // This initial value will be overwritten from below some time later\r
+                       {\r
+                               AutoLocker lock(pThis->m_critSec);\r
+                               pThis->m_childDirectories[svnPath] = svn_wc_status_normal;\r
+                       }\r
+                       // we have added a directory to the child-directory list of this\r
+                       // directory. We now must make sure that this directory also has\r
+                       // an entry in the cache.\r
+                       CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);\r
+                       // also mark the status in the status object as normal\r
+                       status->text_status = svn_wc_status_normal;\r
+               }\r
+               else\r
+               {\r
+                       if (svnPath.IsDirectory())\r
+                       {\r
+                               AutoLocker lock(pThis->m_critSec);\r
+                               pThis->m_childDirectories[svnPath] = SVNStatus::GetMoreImportant(status->text_status, status->prop_status);\r
+                       }\r
+                       else if ((CSVNStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != svn_wc_status_ignored))\r
+                       {\r
+                               // make this unversioned item change the most important status of this\r
+                               // folder to modified if it doesn't already have another status\r
+                               if (pThis->m_mostImportantFileStatus != svn_wc_status_added)\r
+                                       pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, svn_wc_status_modified);\r
+                       }\r
+               }\r
+       }\r
+\r
+       pThis->AddEntry(svnPath, status);\r
+\r
+       return SVN_NO_ERROR;\r
+}\r
+\r
+bool \r
+CCachedDirectory::IsOwnStatusValid() const\r
+{\r
+       return m_ownStatus.HasBeenSet() && \r
+                  !m_ownStatus.HasExpired(GetTickCount()) &&\r
+                  // 'external' isn't a valid status. That just\r
+                  // means the folder is not part of the current working\r
+                  // copy but it still has its own 'real' status\r
+                  m_ownStatus.GetEffectiveStatus()!=svn_wc_status_external &&\r
+                  m_ownStatus.IsKindKnown();\r
+}\r
+\r
+void CCachedDirectory::Invalidate()\r
+{\r
+       m_ownStatus.Invalidate();\r
+}\r
+\r
+svn_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()\r
+{\r
+       // Combine our OWN folder status with the most important of our *FILES'* status.\r
+       svn_wc_status_kind retVal = SVNStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());\r
+\r
+       if ((retVal != svn_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))\r
+       {\r
+               if ((retVal == svn_wc_status_added)||(retVal == svn_wc_status_deleted)||(retVal == svn_wc_status_missing))\r
+                       retVal = svn_wc_status_modified;\r
+       }\r
+\r
+       // Now combine all our child-directorie's status\r
+       \r
+       AutoLocker lock(m_critSec);\r
+       ChildDirStatus::const_iterator it;\r
+       for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)\r
+       {\r
+               retVal = SVNStatus::GetMoreImportant(retVal, it->second);\r
+               if ((retVal != svn_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))\r
+               {\r
+                       if ((retVal == svn_wc_status_added)||(retVal == svn_wc_status_deleted)||(retVal == svn_wc_status_missing))\r
+                               retVal = svn_wc_status_modified;\r
+               }\r
+       }\r
+       \r
+       return retVal;\r
+}\r
+\r
+// Update our composite status and deal with things if it's changed\r
+void CCachedDirectory::UpdateCurrentStatus()\r
+{\r
+       svn_wc_status_kind newStatus = CalculateRecursiveStatus();\r
+\r
+       if ((newStatus != m_currentFullStatus)&&(m_ownStatus.IsVersioned()))\r
+       {\r
+               if ((m_currentFullStatus != svn_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != svn_wc_status_ignored))\r
+               {\r
+                       // Our status has changed - tell the shell\r
+                       ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);           \r
+                       CSVNStatusCache::Instance().UpdateShell(m_directoryPath);\r
+               }\r
+               if (m_ownStatus.GetEffectiveStatus() != svn_wc_status_ignored)\r
+                       m_currentFullStatus = newStatus;\r
+               else\r
+                       m_currentFullStatus = svn_wc_status_ignored;\r
+       }\r
+       // And tell our parent, if we've got one...\r
+       // we tell our parent *always* about our status, even if it hasn't\r
+       // changed. This is to make sure that the parent has really our current\r
+       // status - the parent can decide itself if our status has changed\r
+       // or not.\r
+       CTSVNPath parentPath = m_directoryPath.GetContainingDirectory();\r
+       if(!parentPath.IsEmpty())\r
+       {\r
+               // We have a parent\r
+               CCachedDirectory * cachedDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(parentPath);\r
+               if (cachedDir)\r
+                       cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);\r
+       }\r
+}\r
+\r
+\r
+// Receive a notification from a child that its status has changed\r
+void CCachedDirectory::UpdateChildDirectoryStatus(const CTSVNPath& childDir, svn_wc_status_kind childStatus)\r
+{\r
+       svn_wc_status_kind currentStatus = svn_wc_status_none;\r
+       {\r
+               AutoLocker lock(m_critSec);\r
+               currentStatus = m_childDirectories[childDir];\r
+       }\r
+       if ((currentStatus != childStatus)||(!IsOwnStatusValid()))\r
+       {\r
+               {\r
+                       AutoLocker lock(m_critSec);\r
+                       m_childDirectories[childDir] = childStatus;\r
+               }\r
+               UpdateCurrentStatus();\r
+       }\r
+}\r
+\r
+CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)\r
+{\r
+       // Don't return recursive status if we're unversioned ourselves.\r
+       if(bRecursive && m_ownStatus.GetEffectiveStatus() > svn_wc_status_unversioned)\r
+       {\r
+               CStatusCacheEntry recursiveStatus(m_ownStatus);\r
+               UpdateCurrentStatus();\r
+               recursiveStatus.ForceStatus(m_currentFullStatus);\r
+               return recursiveStatus;                         \r
+       }\r
+       else\r
+       {\r
+               return m_ownStatus;\r
+       }\r
+}\r
+\r
+void CCachedDirectory::RefreshStatus(bool bRecursive)\r
+{\r
+       // Make sure that our own status is up-to-date\r
+       GetStatusForMember(m_directoryPath,bRecursive);\r
+\r
+       AutoLocker lock(m_critSec);\r
+       // We also need to check if all our file members have the right date on them\r
+       CacheEntryMap::iterator itMembers;\r
+       std::set<CTSVNPath> refreshedpaths;\r
+       DWORD now = GetTickCount();\r
+       if (m_entryCache.size() == 0)\r
+               return;\r
+       for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)\r
+       {\r
+               if (itMembers->first)\r
+               {\r
+                       CTSVNPath filePath(m_directoryPath);\r
+                       filePath.AppendPathString(itMembers->first);\r
+                       std::set<CTSVNPath>::iterator refr_it;\r
+                       if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&\r
+                               (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))\r
+                       {\r
+                               if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))\r
+                               {\r
+                                       lock.Unlock();\r
+                                       // We need to request this item as well\r
+                                       GetStatusForMember(filePath,bRecursive);\r
+                                       // GetStatusForMember now has recreated the m_entryCache map.\r
+                                       // So start the loop again, but add this path to the refreshed paths set\r
+                                       // to make sure we don't refresh this path again. This is to make sure\r
+                                       // that we don't end up in an endless loop.\r
+                                       lock.Lock();\r
+                                       refreshedpaths.insert(refr_it, filePath);\r
+                                       itMembers = m_entryCache.begin();\r
+                                       if (m_entryCache.size()==0)\r
+                                               return;\r
+                                       continue;\r
+                               }\r
+                               else if ((bRecursive)&&(itMembers->second.IsDirectory()))\r
+                               {\r
+                                       // crawl all sub folders too! Otherwise a change deep inside the\r
+                                       // tree which has changed won't get propagated up the tree.\r
+                                       CSVNStatusCache::Instance().AddFolderForCrawling(filePath);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+void CCachedDirectory::RefreshMostImportant()\r
+{\r
+       CacheEntryMap::iterator itMembers;\r
+       svn_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();\r
+       for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)\r
+       {\r
+               newStatus = SVNStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());\r
+               if (((itMembers->second.GetEffectiveStatus() == svn_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == svn_wc_status_none))\r
+                       &&(CSVNStatusCache::Instance().IsUnversionedAsModified()))\r
+               {\r
+                       // treat unversioned files as modified\r
+                       if (newStatus != svn_wc_status_added)\r
+                               newStatus = SVNStatus::GetMoreImportant(newStatus, svn_wc_status_modified);\r
+               }\r
+       }\r
+       if (newStatus != m_mostImportantFileStatus)\r
+       {\r
+               ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());\r
+               CSVNStatusCache::Instance().UpdateShell(m_directoryPath);\r
+       }\r
+       m_mostImportantFileStatus = newStatus;\r
+}\r
diff --git a/src/TGitCache/CachedDirectory.h b/src/TGitCache/CachedDirectory.h
new file mode 100644 (file)
index 0000000..71e2da6
--- /dev/null
@@ -0,0 +1,100 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006, 2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+\r
+#include "StatusCacheEntry.h"\r
+#include "TSVNPath.h"\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * Holds the status for a folder and all files and folders directly inside\r
+ * that folder.\r
+ */\r
+class CCachedDirectory\r
+{\r
+public:\r
+       typedef std::map<CTSVNPath, CCachedDirectory *> CachedDirMap; \r
+       typedef CachedDirMap::iterator ItDir;\r
+\r
+public:\r
+\r
+       CCachedDirectory();\r
+       CCachedDirectory(const CTSVNPath& directoryPath);\r
+       ~CCachedDirectory(void);\r
+       CStatusCacheEntry GetStatusForMember(const CTSVNPath& path, bool bRecursive, bool bFetch = true);\r
+       CStatusCacheEntry GetOwnStatus(bool bRecursive);\r
+       bool IsOwnStatusValid() const;\r
+       void Invalidate();\r
+       void RefreshStatus(bool bRecursive);\r
+       void RefreshMostImportant();\r
+       BOOL SaveToDisk(FILE * pFile);\r
+       BOOL LoadFromDisk(FILE * pFile);\r
+       /// Get the current full status of this folder\r
+       svn_wc_status_kind GetCurrentFullStatus() {return m_currentFullStatus;}\r
+private:\r
+       static svn_error_t* GetStatusCallback(void *baton, const char *path, svn_wc_status2_t *status, apr_pool_t *pool);\r
+       void AddEntry(const CTSVNPath& path, const svn_wc_status2_t* pSVNStatus, DWORD validuntil = 0);\r
+       CString GetCacheKey(const CTSVNPath& path);\r
+       CString GetFullPathString(const CString& cacheKey);\r
+       CStatusCacheEntry LookForItemInCache(const CTSVNPath& path, bool &bFound);\r
+       void UpdateChildDirectoryStatus(const CTSVNPath& childDir, svn_wc_status_kind childStatus);\r
+\r
+       // Calculate the complete, composite status from ourselves, our files, and our descendants\r
+       svn_wc_status_kind CalculateRecursiveStatus();\r
+\r
+       // Update our composite status and deal with things if it's changed\r
+       void UpdateCurrentStatus();\r
+\r
+\r
+private:\r
+       CComAutoCriticalSection m_critSec;\r
+       CComAutoCriticalSection m_critSecPath;\r
+\r
+       CTSVNPath       m_currentStatusFetchingPath;\r
+       DWORD           m_currentStatusFetchingPathTicks;\r
+       // The cache of files and directories within this directory\r
+       typedef std::map<CString, CStatusCacheEntry> CacheEntryMap; \r
+       CacheEntryMap m_entryCache; \r
+\r
+       /// A vector if iterators to child directories - used to put-together recursive status\r
+       typedef std::map<CTSVNPath, svn_wc_status_kind>  ChildDirStatus;\r
+       ChildDirStatus m_childDirectories;\r
+\r
+       // The timestamp of the .SVN\entries file.  For an unversioned directory, this will be zero\r
+       __int64 m_entriesFileTime;\r
+       // The timestamp of the .SVN\props dir.  For an unversioned directory, this will be zero\r
+       __int64 m_propsFileTime;\r
+       \r
+       // The path of the directory with this object looks after\r
+       CTSVNPath       m_directoryPath;\r
+\r
+       // The status of THIS directory (not a composite of children or members)\r
+       CStatusCacheEntry m_ownStatus;\r
+\r
+       // Our current fully recursive status\r
+       svn_wc_status_kind  m_currentFullStatus;\r
+       bool m_bCurrentFullStatusValid;\r
+\r
+       // The most important status from all our file entries\r
+       svn_wc_status_kind m_mostImportantFileStatus;\r
+\r
+       bool m_bRecursive;              // used in the status callback\r
+       friend class CSVNStatusCache;           \r
+};\r
+\r
diff --git a/src/TGitCache/DirectoryWatcher.cpp b/src/TGitCache/DirectoryWatcher.cpp
new file mode 100644 (file)
index 0000000..d15094d
--- /dev/null
@@ -0,0 +1,532 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2007 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#include "StdAfx.h"\r
+#include "Dbt.h"\r
+#include "SVNStatusCache.h"\r
+#include ".\directorywatcher.h"\r
+\r
+extern HWND hWnd;\r
+\r
+CDirectoryWatcher::CDirectoryWatcher(void) : m_hCompPort(NULL)\r
+       , m_bRunning(TRUE)\r
+       , m_FolderCrawler(NULL)\r
+       , blockTickCount(0)\r
+{\r
+       // enable the required privileges for this process\r
+       \r
+       LPCTSTR arPrivelegeNames[] = {  SE_BACKUP_NAME,\r
+                                                                       SE_RESTORE_NAME,\r
+                                                                       SE_CHANGE_NOTIFY_NAME\r
+                                                                };\r
+\r
+       for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)\r
+       {\r
+               HANDLE hToken;    \r
+               if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))    \r
+               {        \r
+                       TOKEN_PRIVILEGES tp = { 1 };        \r
+\r
+                       if (LookupPrivilegeValue(NULL, arPrivelegeNames[i],  &tp.Privileges[0].Luid))\r
+                       {\r
+                               tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\r
+\r
+                               AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);\r
+                       }\r
+                       CloseHandle(hToken);    \r
+               }       \r
+       }\r
+\r
+       unsigned int threadId;\r
+       m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);\r
+}\r
+\r
+CDirectoryWatcher::~CDirectoryWatcher(void)\r
+{\r
+       InterlockedExchange(&m_bRunning, FALSE);\r
+       if (m_hThread != INVALID_HANDLE_VALUE)\r
+       {\r
+               CloseHandle(m_hThread);\r
+               m_hThread = INVALID_HANDLE_VALUE;\r
+       }\r
+       AutoLocker lock(m_critSec);\r
+       ClearInfoMap();\r
+}\r
+\r
+void CDirectoryWatcher::Stop()\r
+{\r
+       InterlockedExchange(&m_bRunning, FALSE);\r
+       if (m_hThread != INVALID_HANDLE_VALUE)\r
+               CloseHandle(m_hThread);\r
+       m_hThread = INVALID_HANDLE_VALUE;\r
+       if (m_hCompPort != INVALID_HANDLE_VALUE)\r
+               CloseHandle(m_hCompPort);\r
+       m_hCompPort = INVALID_HANDLE_VALUE;\r
+}\r
+\r
+void CDirectoryWatcher::SetFolderCrawler(CFolderCrawler * crawler)\r
+{\r
+       m_FolderCrawler = crawler;\r
+}\r
+\r
+bool CDirectoryWatcher::RemovePathAndChildren(const CTSVNPath& path)\r
+{\r
+       bool bRemoved = false;\r
+       AutoLocker lock(m_critSec);\r
+repeat:\r
+       for (int i=0; i<watchedPaths.GetCount(); ++i)\r
+       {\r
+               if (path.IsAncestorOf(watchedPaths[i]))\r
+               {\r
+                       watchedPaths.RemovePath(watchedPaths[i]);\r
+                       bRemoved = true;\r
+                       goto repeat;\r
+               }\r
+       }\r
+       return bRemoved;\r
+}\r
+\r
+void CDirectoryWatcher::BlockPath(const CTSVNPath& path)\r
+{\r
+       blockedPath = path;\r
+       // block the path from being watched for 4 seconds\r
+       blockTickCount = GetTickCount()+4000;\r
+       ATLTRACE(_T("Blocking path: %s\n"), path.GetWinPath());\r
+}\r
+\r
+bool CDirectoryWatcher::AddPath(const CTSVNPath& path)\r
+{\r
+       if (!CSVNStatusCache::Instance().IsPathAllowed(path))\r
+               return false;\r
+       if ((!blockedPath.IsEmpty())&&(blockedPath.IsAncestorOf(path)))\r
+       {\r
+               if (GetTickCount() < blockTickCount)\r
+               {\r
+                       ATLTRACE(_T("Path %s prevented from being watched\n"), path.GetWinPath());\r
+                       return false;\r
+               }\r
+       }\r
+       AutoLocker lock(m_critSec);\r
+       for (int i=0; i<watchedPaths.GetCount(); ++i)\r
+       {\r
+               if (watchedPaths[i].IsAncestorOf(path))\r
+                       return false;           // already watched (recursively)\r
+       }\r
+       \r
+       // now check if with the new path we might have a new root\r
+       CTSVNPath newroot;\r
+       for (int i=0; i<watchedPaths.GetCount(); ++i)\r
+       {\r
+               const CString& watched = watchedPaths[i].GetWinPathString();\r
+               const CString& sPath = path.GetWinPathString();\r
+               int minlen = min(sPath.GetLength(), watched.GetLength());\r
+               int len = 0;\r
+               for (len = 0; len < minlen; ++len)\r
+               {\r
+                       if (watched.GetAt(len) != sPath.GetAt(len))\r
+                       {\r
+                               if ((len > 1)&&(len < minlen))\r
+                               {\r
+                                       if (sPath.GetAt(len)=='\\')\r
+                                       {\r
+                                               newroot = CTSVNPath(sPath.Left(len));\r
+                                       }\r
+                                       else if (watched.GetAt(len)=='\\')\r
+                                       {\r
+                                               newroot = CTSVNPath(watched.Left(len));\r
+                                       }\r
+                               }\r
+                               break;\r
+                       }\r
+               }\r
+               if (len == minlen)\r
+               {\r
+                       if (sPath.GetLength() == minlen)\r
+                       {\r
+                               if (watched.GetLength() > minlen)\r
+                               {\r
+                                       if (watched.GetAt(len)=='\\')\r
+                                       {\r
+                                               newroot = path;\r
+                                       }\r
+                                       else if (sPath.GetLength() == 3 && sPath[1] == ':')\r
+                                       {\r
+                                               newroot = path;\r
+                                       }\r
+                               }\r
+                       }\r
+                       else\r
+                       {\r
+                               if (sPath.GetLength() > minlen)\r
+                               {\r
+                                       if (sPath.GetAt(len)=='\\')\r
+                                       {\r
+                                               newroot = CTSVNPath(watched);\r
+                                       }\r
+                                       else if (watched.GetLength() == 3 && watched[1] == ':')\r
+                                       {\r
+                                               newroot = CTSVNPath(watched);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+       if (!newroot.IsEmpty())\r
+       {\r
+               ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());\r
+               watchedPaths.AddPath(newroot);\r
+               watchedPaths.RemoveChildren();\r
+               CloseInfoMap();\r
+               m_hCompPort = INVALID_HANDLE_VALUE;\r
+               return true;\r
+       }\r
+       ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());\r
+       watchedPaths.AddPath(path);\r
+       CloseInfoMap();\r
+       m_hCompPort = INVALID_HANDLE_VALUE;\r
+       return true;\r
+}\r
+\r
+bool CDirectoryWatcher::IsPathWatched(const CTSVNPath& path)\r
+{\r
+       for (int i=0; i<watchedPaths.GetCount(); ++i)\r
+       {\r
+               if (watchedPaths[i].IsAncestorOf(path))\r
+                       return true;\r
+       }\r
+       return false;\r
+}\r
+\r
+unsigned int CDirectoryWatcher::ThreadEntry(void* pContext)\r
+{\r
+       ((CDirectoryWatcher*)pContext)->WorkerThread();\r
+       return 0;\r
+}\r
+\r
+void CDirectoryWatcher::WorkerThread()\r
+{\r
+       DWORD numBytes;\r
+       CDirWatchInfo * pdi = NULL;\r
+       LPOVERLAPPED lpOverlapped;\r
+       WCHAR buf[READ_DIR_CHANGE_BUFFER_SIZE] = {0};\r
+       WCHAR * pFound = NULL;\r
+       while (m_bRunning)\r
+       {\r
+               if (watchedPaths.GetCount())\r
+               {\r
+                       if (!GetQueuedCompletionStatus(m_hCompPort,\r
+                                                                                       &numBytes,\r
+                                                                                       (PULONG_PTR) &pdi,\r
+                                                                                       &lpOverlapped,\r
+                                                                                       INFINITE))\r
+                       {\r
+                               // Error retrieving changes\r
+                               // Clear the list of watched objects and recreate that list\r
+                               if (!m_bRunning)\r
+                                       return;\r
+                               {\r
+                                       AutoLocker lock(m_critSec);\r
+                                       ClearInfoMap();\r
+                               }\r
+                               DWORD lasterr = GetLastError();\r
+                               if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))\r
+                               {\r
+                                       CloseHandle(m_hCompPort);\r
+                                       m_hCompPort = INVALID_HANDLE_VALUE;\r
+                               }\r
+                               // Since we pass m_hCompPort to CreateIoCompletionPort, we\r
+                               // have to set this to NULL to have that API create a new\r
+                               // handle.\r
+                               m_hCompPort = NULL;\r
+                               for (int i=0; i<watchedPaths.GetCount(); ++i)\r
+                               {\r
+                                       CTSVNPath watchedPath = watchedPaths[i];\r
+\r
+                                       HANDLE hDir = CreateFile(watchedPath.GetWinPath(), \r
+                                                                                       FILE_LIST_DIRECTORY, \r
+                                                                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\r
+                                                                                       NULL, //security attributes\r
+                                                                                       OPEN_EXISTING,\r
+                                                                                       FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.\r
+                                                                                       FILE_FLAG_OVERLAPPED,\r
+                                                                                       NULL);\r
+                                       if (hDir == INVALID_HANDLE_VALUE)\r
+                                       {\r
+                                               // this could happen if a watched folder has been removed/renamed\r
+                                               ATLTRACE(_T("CDirectoryWatcher: CreateFile failed. Can't watch directory %s\n"), watchedPaths[i].GetWinPath());\r
+                                               CloseHandle(m_hCompPort);\r
+                                               m_hCompPort = INVALID_HANDLE_VALUE;\r
+                                               AutoLocker lock(m_critSec);\r
+                                               watchedPaths.RemovePath(watchedPath);\r
+                                               i--; if (i<0) i=0;\r
+                                               break;\r
+                                       }\r
+                                       \r
+                                       DEV_BROADCAST_HANDLE NotificationFilter;\r
+                                       SecureZeroMemory(&NotificationFilter, sizeof(NotificationFilter));\r
+                                       NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);\r
+                                       NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;\r
+                                       NotificationFilter.dbch_handle = hDir;\r
+                                       NotificationFilter.dbch_hdevnotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);\r
+\r
+                                       CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPath);\r
+                                       pDirInfo->m_hDevNotify = NotificationFilter.dbch_hdevnotify;\r
+                                       m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);\r
+                                       if (m_hCompPort == NULL)\r
+                                       {\r
+                                               ATLTRACE(_T("CDirectoryWatcher: CreateIoCompletionPort failed. Can't watch directory %s\n"), watchedPath.GetWinPath());\r
+                                               AutoLocker lock(m_critSec);\r
+                                               ClearInfoMap();\r
+                                               delete pDirInfo;\r
+                                               pDirInfo = NULL;\r
+                                               watchedPaths.RemovePath(watchedPath);\r
+                                               i--; if (i<0) i=0;\r
+                                               break;\r
+                                       }\r
+                                       if (!ReadDirectoryChangesW(pDirInfo->m_hDir,\r
+                                                                                               pDirInfo->m_Buffer,\r
+                                                                                               READ_DIR_CHANGE_BUFFER_SIZE,\r
+                                                                                               TRUE,\r
+                                                                                               FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,\r
+                                                                                               &numBytes,// not used\r
+                                                                                               &pDirInfo->m_Overlapped,\r
+                                                                                               NULL))  //no completion routine!\r
+                                       {\r
+                                               ATLTRACE(_T("CDirectoryWatcher: ReadDirectoryChangesW failed. Can't watch directory %s\n"), watchedPath.GetWinPath());\r
+                                               AutoLocker lock(m_critSec);\r
+                                               ClearInfoMap();\r
+                                               delete pDirInfo;\r
+                                               pDirInfo = NULL;\r
+                                               watchedPaths.RemovePath(watchedPath);\r
+                                               i--; if (i<0) i=0;\r
+                                               break;\r
+                                       }\r
+                                       AutoLocker lock(m_critSec);\r
+                                       watchInfoMap[pDirInfo->m_hDir] = pDirInfo;\r
+                                       ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());\r
+                               }\r
+                       }\r
+                       else\r
+                       {\r
+                               if (!m_bRunning)\r
+                                       return;\r
+                               // NOTE: the longer this code takes to execute until ReadDirectoryChangesW\r
+                               // is called again, the higher the chance that we miss some\r
+                               // changes in the file system! \r
+                               if (pdi)\r
+                               {\r
+                                       if (numBytes == 0)\r
+                                       {\r
+                                               goto continuewatching;\r
+                                       }\r
+                                       PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;\r
+                                       if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
+                                               goto continuewatching;\r
+                                       DWORD nOffset = pnotify->NextEntryOffset;\r
+                                       do \r
+                                       {\r
+                                               nOffset = pnotify->NextEntryOffset;\r
+                                               if (pnotify->FileNameLength >= (READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR)))\r
+                                                       continue;\r
+                                               SecureZeroMemory(buf, READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR));\r
+                                               _tcsncpy_s(buf, READ_DIR_CHANGE_BUFFER_SIZE, pdi->m_DirPath, READ_DIR_CHANGE_BUFFER_SIZE);\r
+                                               errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), READ_DIR_CHANGE_BUFFER_SIZE-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE);\r
+                                               if (err == STRUNCATE)\r
+                                               {\r
+                                                       pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);\r
+                                                       continue;\r
+                                               }\r
+                                               buf[(pnotify->FileNameLength/sizeof(TCHAR))+pdi->m_DirPath.GetLength()] = 0;\r
+                                               pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);\r
+                                               if (m_FolderCrawler)\r
+                                               {\r
+                                                       if ((pFound = wcsstr(buf, L"\\tmp"))!=NULL)\r
+                                                       {\r
+                                                               pFound += 4;\r
+                                                               if (((*pFound)=='\\')||((*pFound)=='\0'))\r
+                                                               {\r
+                                                                       if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
+                                                                               break;\r
+                                                                       continue;\r
+                                                               }\r
+                                                       }\r
+                                                       if ((pFound = wcsstr(buf, L":\\RECYCLER\\"))!=NULL)\r
+                                                       {\r
+                                                               if ((pFound-buf) < 5)\r
+                                                               {\r
+                                                                       // a notification for the recycle bin - ignore it\r
+                                                                       if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
+                                                                               break;\r
+                                                                       continue;\r
+                                                               }\r
+                                                       }\r
+                                                       if ((pFound = wcsstr(buf, L":\\$Recycle.Bin\\"))!=NULL)\r
+                                                       {\r
+                                                               if ((pFound-buf) < 5)\r
+                                                               {\r
+                                                                       // a notification for the recycle bin - ignore it\r
+                                                                       if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
+                                                                               break;\r
+                                                                       continue;\r
+                                                               }\r
+                                                       }\r
+                                                       if ((pFound = wcsstr(buf, L".tmp"))!=NULL)\r
+                                                       {\r
+                                                               // assume files with a .tmp extension are not versioned and interesting,\r
+                                                               // so ignore them.\r
+                                                               if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
+                                                                       break;\r
+                                                               continue;\r
+                                                       }\r
+                                                       ATLTRACE(_T("change notification: %s\n"), buf);\r
+                                                       m_FolderCrawler->AddPathForUpdate(CTSVNPath(buf));\r
+                                               }\r
+                                               if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
+                                                       break;\r
+                                       } while (nOffset);\r
+continuewatching:\r
+                                       SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer));\r
+                                       SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED));\r
+                                       if (!ReadDirectoryChangesW(pdi->m_hDir,\r
+                                                                                               pdi->m_Buffer,\r
+                                                                                               READ_DIR_CHANGE_BUFFER_SIZE,\r
+                                                                                               TRUE,\r
+                                                                                               FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,\r
+                                                                                               &numBytes,// not used\r
+                                                                                               &pdi->m_Overlapped,\r
+                                                                                               NULL))  //no completion routine!\r
+                                       {\r
+                                               // Since the call to ReadDirectoryChangesW failed, just\r
+                                               // wait a while. We don't want to have this thread\r
+                                               // running using 100% CPU if something goes completely\r
+                                               // wrong.\r
+                                               Sleep(200);\r
+                                       }\r
+                               }\r
+                       }\r
+               }// if (watchedPaths.GetCount())\r
+               else\r
+                       Sleep(200);\r
+       }// while (m_bRunning)\r
+}\r
+\r
+void CDirectoryWatcher::ClearInfoMap()\r
+{\r
+       if (watchInfoMap.size()!=0)\r
+       {\r
+               AutoLocker lock(m_critSec);\r
+               for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)\r
+               {\r
+                       CDirectoryWatcher::CDirWatchInfo * info = I->second;\r
+                       delete info;\r
+                       info = NULL;\r
+               }\r
+       }\r
+       watchInfoMap.clear();\r
+       if (m_hCompPort != INVALID_HANDLE_VALUE)\r
+               CloseHandle(m_hCompPort);\r
+       m_hCompPort = INVALID_HANDLE_VALUE;\r
+}\r
+\r
+CTSVNPath CDirectoryWatcher::CloseInfoMap(HDEVNOTIFY hdev)\r
+{\r
+       CTSVNPath path;\r
+       if (watchInfoMap.size() == 0)\r
+               return path;\r
+       AutoLocker lock(m_critSec);\r
+       for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)\r
+       {\r
+               CDirectoryWatcher::CDirWatchInfo * info = I->second;\r
+               if (info->m_hDevNotify == hdev)\r
+               {\r
+                       path = info->m_DirName;\r
+                       RemovePathAndChildren(path);\r
+                       BlockPath(path);\r
+               }\r
+               info->CloseDirectoryHandle();\r
+       }\r
+       watchInfoMap.clear();\r
+       if (m_hCompPort != INVALID_HANDLE_VALUE)\r
+               CloseHandle(m_hCompPort);\r
+       m_hCompPort = INVALID_HANDLE_VALUE;\r
+       return path;\r
+}\r
+\r
+bool CDirectoryWatcher::CloseHandlesForPath(const CTSVNPath& path)\r
+{\r
+       if (watchInfoMap.size() == 0)\r
+               return false;\r
+       AutoLocker lock(m_critSec);\r
+       for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)\r
+       {\r
+               CDirectoryWatcher::CDirWatchInfo * info = I->second;\r
+               CTSVNPath p = CTSVNPath(info->m_DirPath);\r
+               if (path.IsAncestorOf(p))\r
+               {\r
+                       RemovePathAndChildren(p);\r
+                       BlockPath(p);\r
+               }\r
+               info->CloseDirectoryHandle();\r
+       }\r
+       watchInfoMap.clear();\r
+       if (m_hCompPort != INVALID_HANDLE_VALUE)\r
+               CloseHandle(m_hCompPort);\r
+       m_hCompPort = INVALID_HANDLE_VALUE;\r
+       return true;\r
+}\r
+\r
+CDirectoryWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTSVNPath& DirectoryName) :\r
+       m_hDir(hDir),\r
+       m_DirName(DirectoryName)\r
+{\r
+       ATLASSERT( hDir != INVALID_HANDLE_VALUE \r
+               && !DirectoryName.IsEmpty());\r
+       memset(&m_Overlapped, 0, sizeof(m_Overlapped));\r
+       m_DirPath = m_DirName.GetWinPathString();\r
+       if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')\r
+               m_DirPath += _T("\\");\r
+       m_hDevNotify = INVALID_HANDLE_VALUE;\r
+}\r
+\r
+CDirectoryWatcher::CDirWatchInfo::~CDirWatchInfo()\r
+{\r
+       CloseDirectoryHandle();\r
+}\r
+\r
+bool CDirectoryWatcher::CDirWatchInfo::CloseDirectoryHandle()\r
+{\r
+       bool b = TRUE;\r
+       if( m_hDir != INVALID_HANDLE_VALUE )\r
+       {\r
+               b = !!CloseHandle(m_hDir);\r
+               m_hDir = INVALID_HANDLE_VALUE;\r
+       }\r
+       if (m_hDevNotify != INVALID_HANDLE_VALUE)\r
+       {\r
+               UnregisterDeviceNotification(m_hDevNotify);\r
+       }\r
+       return b;\r
+}\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
diff --git a/src/TGitCache/DirectoryWatcher.h b/src/TGitCache/DirectoryWatcher.h
new file mode 100644 (file)
index 0000000..08ca63f
--- /dev/null
@@ -0,0 +1,133 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+#include "TSVNPath.h"\r
+#include "FolderCrawler.h"\r
+#include "ShellCache.h"\r
+\r
+#define READ_DIR_CHANGE_BUFFER_SIZE 4096\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * Watches the file system for changes.\r
+ * When changes are detected, those changes are reported back to the CFolderCrawler\r
+ * which then can update the status cache.\r
+ *\r
+ * When a CDirectoryWatcher object is created, a new thread is started which\r
+ * waits for file system change notifications.\r
+ * To add folders to the list of watched folders, call \c AddPath().\r
+ *\r
+ * The folders are watched recursively. To prevent having too many folders watched,\r
+ * children of already watched folders are automatically removed from watching.\r
+ * This leads to having only the roots of file systems watched (e.g. C:\, D:\,...)\r
+ * after a few paths have been added to the watched list (at least, when the\r
+ * CSVNStatusCache adds those paths).\r
+ */\r
+class CDirectoryWatcher\r
+{\r
+public:\r
+       CDirectoryWatcher(void);\r
+       ~CDirectoryWatcher(void);\r
+       \r
+       /**\r
+        * Adds a new path to be watched. The path \b must point to a directory.\r
+        * If the path is already watched because a parent of that path is already\r
+        * watched recursively, then the new path is just ignored and the method\r
+        * returns false.\r
+        */\r
+       bool AddPath(const CTSVNPath& path);\r
+       /**\r
+        * Removes a path and all its children from the watched list.\r
+        */\r
+       bool RemovePathAndChildren(const CTSVNPath& path);\r
+       /**\r
+        * Checks if a path is watched\r
+        */\r
+       bool IsPathWatched(const CTSVNPath& path);\r
+       \r
+       /**\r
+        * Returns the number of recursively watched paths.\r
+        */\r
+       int GetNumberOfWatchedPaths() {return watchedPaths.GetCount();}\r
+       \r
+       /**\r
+        * Sets the CFolderCrawler object which the change notifications are sent to.\r
+        */\r
+       void SetFolderCrawler(CFolderCrawler * crawler);\r
+       \r
+       /**\r
+        * Stops the watching thread.\r
+        */\r
+       void Stop();\r
+\r
+       CTSVNPath CloseInfoMap(HDEVNOTIFY hdev = INVALID_HANDLE_VALUE);\r
+       bool CloseHandlesForPath(const CTSVNPath& path);\r
+\r
+private:\r
+       static unsigned int __stdcall ThreadEntry(void* pContext);\r
+       void WorkerThread();\r
+\r
+       void ClearInfoMap();\r
+\r
+       void BlockPath(const CTSVNPath& path);\r
+\r
+private:\r
+       CComAutoCriticalSection m_critSec;\r
+       HANDLE                                  m_hThread;\r
+       HANDLE                                  m_hCompPort;\r
+       volatile LONG                   m_bRunning;\r
+\r
+       CFolderCrawler *                m_FolderCrawler;        ///< where the change reports go to\r
+       \r
+       CTSVNPathList                   watchedPaths;   ///< list of watched paths.\r
+\r
+       CTSVNPath                               blockedPath;\r
+       DWORD                                   blockTickCount;\r
+\r
+       /**\r
+        * \ingroup TSVNCache\r
+        * Helper class: provides information about watched directories.\r
+        */\r
+       class CDirWatchInfo \r
+       {\r
+       private:\r
+               CDirWatchInfo();        // private & not implemented\r
+               CDirWatchInfo & operator=(const CDirWatchInfo & rhs);//so that they're aren't accidentally used. -- you'll get a linker error\r
+       public:\r
+               CDirWatchInfo(HANDLE hDir, const CTSVNPath& DirectoryName);\r
+               ~CDirWatchInfo();\r
+\r
+       protected:\r
+       public:\r
+               bool    CloseDirectoryHandle();\r
+\r
+               HANDLE          m_hDir;                 ///< handle to the directory that we're watching\r
+               CTSVNPath       m_DirName;              ///< the directory that we're watching\r
+               CHAR            m_Buffer[READ_DIR_CHANGE_BUFFER_SIZE]; ///< buffer for ReadDirectoryChangesW\r
+               DWORD           m_dwBufLength;  ///< length or returned data from ReadDirectoryChangesW -- ignored?...\r
+               OVERLAPPED  m_Overlapped;\r
+               CString         m_DirPath;              ///< the directory name we're watching with a backslash at the end\r
+               HDEVNOTIFY      m_hDevNotify;   ///< Notification handle\r
+       };\r
+\r
+       std::map<HANDLE, CDirWatchInfo *> watchInfoMap;\r
+       \r
+       HDEVNOTIFY              m_hdev;\r
+\r
+};\r
diff --git a/src/TGitCache/FolderCrawler.cpp b/src/TGitCache/FolderCrawler.cpp
new file mode 100644 (file)
index 0000000..f1033be
--- /dev/null
@@ -0,0 +1,495 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+\r
+#include "StdAfx.h"\r
+#include ".\foldercrawler.h"\r
+#include "SVNStatusCache.h"\r
+#include "registry.h"\r
+#include "TSVNCache.h"\r
+#include "shlobj.h"\r
+\r
+\r
+CFolderCrawler::CFolderCrawler(void)\r
+{\r
+       m_hWakeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);\r
+       m_hTerminationEvent = CreateEvent(NULL,TRUE,FALSE,NULL);\r
+       m_hThread = INVALID_HANDLE_VALUE;\r
+       m_lCrawlInhibitSet = 0;\r
+       m_crawlHoldoffReleasesAt = (long)GetTickCount();\r
+       m_bRun = false;\r
+       m_bPathsAddedSinceLastCrawl = false;\r
+       m_bItemsAddedSinceLastCrawl = false;\r
+}\r
+\r
+CFolderCrawler::~CFolderCrawler(void)\r
+{\r
+       Stop();\r
+}\r
+\r
+void CFolderCrawler::Stop()\r
+{\r
+       m_bRun = false;\r
+       if (m_hTerminationEvent != INVALID_HANDLE_VALUE)\r
+       {\r
+               SetEvent(m_hTerminationEvent);\r
+               if(WaitForSingleObject(m_hThread, 4000) != WAIT_OBJECT_0)\r
+               {\r
+                       ATLTRACE("Error terminating crawler thread\n");\r
+               }\r
+               CloseHandle(m_hThread);\r
+               m_hThread = INVALID_HANDLE_VALUE;\r
+               CloseHandle(m_hTerminationEvent);\r
+               m_hTerminationEvent = INVALID_HANDLE_VALUE;\r
+               CloseHandle(m_hWakeEvent);\r
+               m_hWakeEvent = INVALID_HANDLE_VALUE;\r
+       }\r
+}\r
+\r
+void CFolderCrawler::Initialise()\r
+{\r
+       // Don't call Initialize more than once\r
+       ATLASSERT(m_hThread == INVALID_HANDLE_VALUE);\r
+\r
+       // Just start the worker thread. \r
+       // It will wait for event being signaled.\r
+       // If m_hWakeEvent is already signaled the worker thread \r
+       // will behave properly (with normal priority at worst).\r
+\r
+       m_bRun = true;\r
+       unsigned int threadId;\r
+       m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);\r
+       SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);\r
+}\r
+\r
+void CFolderCrawler::AddDirectoryForUpdate(const CTSVNPath& path)\r
+{\r
+       if (!CSVNStatusCache::Instance().IsPathGood(path))\r
+               return;\r
+       {\r
+               AutoLocker lock(m_critSec);\r
+               m_foldersToUpdate.push_back(path);\r
+               m_foldersToUpdate.back().SetCustomData(GetTickCount()+10);\r
+               ATLASSERT(path.IsDirectory() || !path.Exists());\r
+               // set this flag while we are sync'ed \r
+               // with the worker thread\r
+               m_bItemsAddedSinceLastCrawl = true;\r
+       }\r
+       if (SetHoldoff())\r
+               SetEvent(m_hWakeEvent);\r
+}\r
+\r
+void CFolderCrawler::AddPathForUpdate(const CTSVNPath& path)\r
+{\r
+       if (!CSVNStatusCache::Instance().IsPathGood(path))\r
+               return;\r
+       {\r
+               AutoLocker lock(m_critSec);\r
+               m_pathsToUpdate.push_back(path);\r
+               m_pathsToUpdate.back().SetCustomData(GetTickCount()+1000);\r
+               m_bPathsAddedSinceLastCrawl = true;\r
+       }\r
+       if (SetHoldoff())\r
+               SetEvent(m_hWakeEvent);\r
+}\r
+\r
+unsigned int CFolderCrawler::ThreadEntry(void* pContext)\r
+{\r
+       ((CFolderCrawler*)pContext)->WorkerThread();\r
+       return 0;\r
+}\r
+\r
+void CFolderCrawler::WorkerThread()\r
+{\r
+       HANDLE hWaitHandles[2];\r
+       hWaitHandles[0] = m_hTerminationEvent;  \r
+       hWaitHandles[1] = m_hWakeEvent;\r
+       CTSVNPath workingPath;\r
+       bool bFirstRunAfterWakeup = false;\r
+       DWORD currentTicks = 0;\r
+\r
+       // Quick check if we're on Vista\r
+       OSVERSIONINFOEX inf;\r
+       SecureZeroMemory(&inf, sizeof(OSVERSIONINFOEX));\r
+       inf.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);\r
+       GetVersionEx((OSVERSIONINFO *)&inf);\r
+       WORD fullver = MAKEWORD(inf.dwMinorVersion, inf.dwMajorVersion);\r
+\r
+       for(;;)\r
+       {\r
+               bool bRecursive = !!(DWORD)CRegStdWORD(_T("Software\\TortoiseSVN\\RecursiveOverlay"), TRUE);\r
+\r
+               if (fullver >= 0x0600)\r
+               {\r
+                       SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END);\r
+               }\r
+               DWORD waitResult = WaitForMultipleObjects(sizeof(hWaitHandles)/sizeof(hWaitHandles[0]), hWaitHandles, FALSE, INFINITE);\r
+               \r
+               // exit event/working loop if the first event (m_hTerminationEvent)\r
+               // has been signaled or if one of the events has been abandoned\r
+               // (i.e. ~CFolderCrawler() is being executed)\r
+               if(m_bRun == false || waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1)\r
+               {\r
+                       // Termination event\r
+                       break;\r
+               }\r
+\r
+               if (fullver >= 0x0600)\r
+               {\r
+                       SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN);\r
+               }\r
+\r
+               // If we get here, we've been woken up by something being added to the queue.\r
+               // However, it's important that we don't do our crawling while\r
+               // the shell is still asking for items\r
+               // \r
+               bFirstRunAfterWakeup = true;\r
+               for(;;)\r
+               {\r
+                       if (!m_bRun)\r
+                               break;\r
+                       // Any locks today?\r
+                       if (CSVNStatusCache::Instance().m_bClearMemory)\r
+                       {\r
+                               CSVNStatusCache::Instance().WaitToWrite();\r
+                               CSVNStatusCache::Instance().ClearCache();\r
+                               CSVNStatusCache::Instance().Done();\r
+                               CSVNStatusCache::Instance().m_bClearMemory = false;\r
+                       }\r
+                       if(m_lCrawlInhibitSet > 0)\r
+                       {\r
+                               // We're in crawl hold-off \r
+                               ATLTRACE("Crawl hold-off\n");\r
+                               Sleep(50);\r
+                               continue;\r
+                       }\r
+                       if (bFirstRunAfterWakeup)\r
+                       {\r
+                               Sleep(2000);\r
+                               bFirstRunAfterWakeup = false;\r
+                               continue;\r
+                       }\r
+                       if ((m_blockReleasesAt < GetTickCount())&&(!m_blockedPath.IsEmpty()))\r
+                       {\r
+                               ATLTRACE(_T("stop blocking path %s\n"), m_blockedPath.GetWinPath());\r
+                               m_blockedPath.Reset();\r
+                       }\r
+       \r
+                       if ((m_foldersToUpdate.empty())&&(m_pathsToUpdate.empty()))\r
+                       {\r
+                               // Nothing left to do \r
+                               break;\r
+                       }\r
+                       currentTicks = GetTickCount();\r
+                       if (!m_pathsToUpdate.empty())\r
+                       {\r
+                               {\r
+                                       AutoLocker lock(m_critSec);\r
+\r
+                                       if (m_bPathsAddedSinceLastCrawl)\r
+                                       {\r
+                                               // The queue has changed - it's worth sorting and de-duping\r
+                                               std::sort(m_pathsToUpdate.begin(), m_pathsToUpdate.end());\r
+                                               m_pathsToUpdate.erase(std::unique(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), &CTSVNPath::PredLeftSameWCPathAsRight), m_pathsToUpdate.end());\r
+                                               m_bPathsAddedSinceLastCrawl = false;\r
+                                       }\r
+                                       workingPath = m_pathsToUpdate.front();\r
+                                       if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
+                                               m_pathsToUpdate.pop_front();\r
+                                       else\r
+                                       {\r
+                                               // since we always sort the whole list, we risk adding tons of new paths to m_pathsToUpdate\r
+                                               // until the last one in the sorted list finally times out.\r
+                                               // to avoid that, we go through the list again and crawl the first one which is valid\r
+                                               // to crawl. That way, we still reduce the size of the list.\r
+                                               if (m_pathsToUpdate.size() > 10)\r
+                                                       ATLTRACE("attention: the list of paths to update is now %ld big!\n", m_pathsToUpdate.size());\r
+                                               for (std::deque<CTSVNPath>::iterator it = m_pathsToUpdate.begin(); it != m_pathsToUpdate.end(); ++it)\r
+                                               {\r
+                                                       workingPath = *it;\r
+                                                       if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
+                                                       {\r
+                                                               m_pathsToUpdate.erase(it);\r
+                                                               break;\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                               }\r
+                               if (DWORD(workingPath.GetCustomData()) >= currentTicks)\r
+                               {\r
+                                       Sleep(50);\r
+                                       continue;\r
+                               }\r
+                               if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath)))\r
+                                       continue;\r
+                               // don't crawl paths that are excluded\r
+                               if (!CSVNStatusCache::Instance().IsPathAllowed(workingPath))\r
+                                       continue;\r
+                               // check if the changed path is inside an .svn folder\r
+                               if ((workingPath.HasAdminDir()&&workingPath.IsDirectory())||workingPath.IsAdminDir())\r
+                               {\r
+                                       // we don't crawl for paths changed in a tmp folder inside an .svn folder.\r
+                                       // Because we also get notifications for those even if we just ask for the status!\r
+                                       // And changes there don't affect the file status at all, so it's safe\r
+                                       // to ignore notifications on those paths.\r
+                                       if (workingPath.IsAdminDir())\r
+                                       {\r
+                                               CString lowerpath = workingPath.GetWinPathString();\r
+                                               lowerpath.MakeLower();\r
+                                               if (lowerpath.Find(_T("\\tmp\\"))>0)\r
+                                                       continue;\r
+                                               if (lowerpath.Find(_T("\\tmp")) == (lowerpath.GetLength()-4))\r
+                                                       continue;\r
+                                               if (lowerpath.Find(_T("\\log"))>0)\r
+                                                       continue;\r
+                                               // Here's a little problem:\r
+                                               // the lock file is also created for fetching the status\r
+                                               // and not just when committing.\r
+                                               // If we could find out why the lock file was changed\r
+                                               // we could decide to crawl the folder again or not.\r
+                                               // But for now, we have to crawl the parent folder\r
+                                               // no matter what.\r
+\r
+                                               //if (lowerpath.Find(_T("\\lock"))>0)\r
+                                               //      continue;\r
+                                       }\r
+                                       else if (!workingPath.Exists())\r
+                                       {\r
+                                               CSVNStatusCache::Instance().WaitToWrite();\r
+                                               CSVNStatusCache::Instance().RemoveCacheForPath(workingPath);\r
+                                               CSVNStatusCache::Instance().Done();\r
+                                               continue;\r
+                                       }\r
+\r
+                                       do \r
+                                       {\r
+                                               workingPath = workingPath.GetContainingDirectory();     \r
+                                       } while(workingPath.IsAdminDir());\r
+\r
+                                       ATLTRACE(_T("Invalidating and refreshing folder: %s\n"), workingPath.GetWinPath());\r
+                                       {\r
+                                               AutoLocker print(critSec);\r
+                                               _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Invalidating and refreshing folder: %s"), workingPath.GetWinPath());\r
+                                               nCurrentCrawledpathIndex++;\r
+                                               if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS)\r
+                                                       nCurrentCrawledpathIndex = 0;\r
+                                       }\r
+                                       InvalidateRect(hWnd, NULL, FALSE);\r
+                                       CSVNStatusCache::Instance().WaitToRead();\r
+                                       // Invalidate the cache of this folder, to make sure its status is fetched again.\r
+                                       CCachedDirectory * pCachedDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(workingPath);\r
+                                       if (pCachedDir)\r
+                                       {\r
+                                               svn_wc_status_kind status = pCachedDir->GetCurrentFullStatus();\r
+                                               pCachedDir->Invalidate();\r
+                                               if (workingPath.Exists())\r
+                                               {\r
+                                                       pCachedDir->RefreshStatus(bRecursive);\r
+                                                       // if the previous status wasn't normal and now it is, then\r
+                                                       // send a notification too.\r
+                                                       // We do this here because GetCurrentFullStatus() doesn't send\r
+                                                       // notifications for 'normal' status - if it would, we'd get tons\r
+                                                       // of notifications when crawling a working copy not yet in the cache.\r
+                                                       if ((status != svn_wc_status_normal)&&(pCachedDir->GetCurrentFullStatus() != status))\r
+                                                       {\r
+                                                               CSVNStatusCache::Instance().UpdateShell(workingPath);\r
+                                                               ATLTRACE(_T("shell update in crawler for %s\n"), workingPath.GetWinPath());\r
+                                                       }\r
+                                               }\r
+                                               else\r
+                                               {\r
+                                                       CSVNStatusCache::Instance().Done();\r
+                                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                                       CSVNStatusCache::Instance().RemoveCacheForPath(workingPath);\r
+                                               }\r
+                                       }\r
+                                       CSVNStatusCache::Instance().Done();\r
+                                       //In case that svn_client_stat() modified a file and we got\r
+                                       //a notification about that in the directory watcher,\r
+                                       //remove that here again - this is to prevent an endless loop\r
+                                       AutoLocker lock(m_critSec);\r
+                                       m_pathsToUpdate.erase(std::remove(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), workingPath), m_pathsToUpdate.end());\r
+                               }\r
+                               else if (workingPath.HasAdminDir())\r
+                               {\r
+                                       if (!workingPath.Exists())\r
+                                       {\r
+                                               CSVNStatusCache::Instance().WaitToWrite();\r
+                                               CSVNStatusCache::Instance().RemoveCacheForPath(workingPath);\r
+                                               CSVNStatusCache::Instance().Done();\r
+                                               continue;\r
+                                       }\r
+                                       if (!workingPath.Exists())\r
+                                               continue;\r
+                                       ATLTRACE(_T("Updating path: %s\n"), workingPath.GetWinPath());\r
+                                       {\r
+                                               AutoLocker print(critSec);\r
+                                               _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Updating path: %s"), workingPath.GetWinPath());\r
+                                               nCurrentCrawledpathIndex++;\r
+                                               if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS)\r
+                                                       nCurrentCrawledpathIndex = 0;\r
+                                       }\r
+                                       InvalidateRect(hWnd, NULL, FALSE);\r
+                                       // HasAdminDir() already checks if the path points to a dir\r
+                                       DWORD flags = TSVNCACHE_FLAGS_FOLDERISKNOWN;\r
+                                       flags |= (workingPath.IsDirectory() ? TSVNCACHE_FLAGS_ISFOLDER : 0);\r
+                                       flags |= (bRecursive ? TSVNCACHE_FLAGS_RECUSIVE_STATUS : 0);\r
+                                       CSVNStatusCache::Instance().WaitToRead();\r
+                                       // Invalidate the cache of folders manually. The cache of files is invalidated\r
+                                       // automatically if the status is asked for it and the file times don't match\r
+                                       // anymore, so we don't need to manually invalidate those.\r
+                                       if (workingPath.IsDirectory())\r
+                                       {\r
+                                               CCachedDirectory * cachedDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(workingPath);\r
+                                               if (cachedDir)\r
+                                                       cachedDir->Invalidate();\r
+                                       }\r
+                                       CStatusCacheEntry ce = CSVNStatusCache::Instance().GetStatusForPath(workingPath, flags);\r
+                                       if (ce.GetEffectiveStatus() > svn_wc_status_unversioned)\r
+                                       {\r
+                                               CSVNStatusCache::Instance().UpdateShell(workingPath);\r
+                                               ATLTRACE(_T("shell update in folder crawler for %s\n"), workingPath.GetWinPath());\r
+                                       }\r
+                                       CSVNStatusCache::Instance().Done();\r
+                                       AutoLocker lock(m_critSec);\r
+                                       m_pathsToUpdate.erase(std::remove(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), workingPath), m_pathsToUpdate.end());\r
+                               }\r
+                               else\r
+                               {\r
+                                       if (!workingPath.Exists())\r
+                                       {\r
+                                               CSVNStatusCache::Instance().WaitToWrite();\r
+                                               CSVNStatusCache::Instance().RemoveCacheForPath(workingPath);\r
+                                               CSVNStatusCache::Instance().Done();\r
+                                       }\r
+                               }\r
+                       }\r
+                       else if (!m_foldersToUpdate.empty())\r
+                       {\r
+                               {\r
+                                       AutoLocker lock(m_critSec);\r
+\r
+                                       if (m_bItemsAddedSinceLastCrawl)\r
+                                       {\r
+                                               // The queue has changed - it's worth sorting and de-duping\r
+                                               std::sort(m_foldersToUpdate.begin(), m_foldersToUpdate.end());\r
+                                               m_foldersToUpdate.erase(std::unique(m_foldersToUpdate.begin(), m_foldersToUpdate.end(), &CTSVNPath::PredLeftEquivalentToRight), m_foldersToUpdate.end());\r
+                                               m_bItemsAddedSinceLastCrawl = false;\r
+                                       }\r
+                                       // create a new CTSVNPath object to make sure the cached flags are requested again.\r
+                                       // without this, a missing file/folder is still treated as missing even if it is available\r
+                                       // now when crawling.\r
+                                       workingPath = CTSVNPath(m_foldersToUpdate.front().GetWinPath());\r
+                                       workingPath.SetCustomData(m_foldersToUpdate.front().GetCustomData());\r
+                                       if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
+                                               m_foldersToUpdate.pop_front();\r
+                                       else\r
+                                       {\r
+                                               // since we always sort the whole list, we risk adding tons of new paths to m_pathsToUpdate\r
+                                               // until the last one in the sorted list finally times out.\r
+                                               // to avoid that, we go through the list again and crawl the first one which is valid\r
+                                               // to crawl. That way, we still reduce the size of the list.\r
+                                               if (m_foldersToUpdate.size() > 10)\r
+                                                       ATLTRACE("attention: the list of folders to update is now %ld big!\n", m_foldersToUpdate.size());\r
+                                               for (std::deque<CTSVNPath>::iterator it = m_foldersToUpdate.begin(); it != m_foldersToUpdate.end(); ++it)\r
+                                               {\r
+                                                       workingPath = *it;\r
+                                                       if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
+                                                       {\r
+                                                               m_foldersToUpdate.erase(it);\r
+                                                               break;\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                               }\r
+                               if (DWORD(workingPath.GetCustomData()) >= currentTicks)\r
+                               {\r
+                                       Sleep(50);\r
+                                       continue;\r
+                               }\r
+                               if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath)))\r
+                                       continue;\r
+                               if (!CSVNStatusCache::Instance().IsPathAllowed(workingPath))\r
+                                       continue;\r
+\r
+                               ATLTRACE(_T("Crawling folder: %s\n"), workingPath.GetWinPath());\r
+                               {\r
+                                       AutoLocker print(critSec);\r
+                                       _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Crawling folder: %s"), workingPath.GetWinPath());\r
+                                       nCurrentCrawledpathIndex++;\r
+                                       if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS)\r
+                                               nCurrentCrawledpathIndex = 0;\r
+                               }\r
+                               InvalidateRect(hWnd, NULL, FALSE);\r
+                               CSVNStatusCache::Instance().WaitToRead();\r
+                               // Now, we need to visit this folder, to make sure that we know its 'most important' status\r
+                               CCachedDirectory * cachedDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(workingPath.GetDirectory());\r
+                               // check if the path is monitored by the watcher. If it isn't, then we have to invalidate the cache\r
+                               // for that path and add it to the watcher.\r
+                               if (!CSVNStatusCache::Instance().IsPathWatched(workingPath))\r
+                               {\r
+                                       if (workingPath.HasAdminDir())\r
+                                               CSVNStatusCache::Instance().AddPathToWatch(workingPath);\r
+                                       if (cachedDir)\r
+                                               cachedDir->Invalidate();\r
+                                       else\r
+                                       {\r
+                                               CSVNStatusCache::Instance().Done();\r
+                                               CSVNStatusCache::Instance().WaitToWrite();\r
+                                               CSVNStatusCache::Instance().RemoveCacheForPath(workingPath);\r
+                                       }\r
+                               }\r
+                               if (cachedDir)\r
+                                       cachedDir->RefreshStatus(bRecursive);\r
+\r
+                               // While refreshing the status, we could get another crawl request for the same folder.\r
+                               // This can happen if the crawled folder has a lower status than one of the child folders\r
+                               // (recursively). To avoid double crawlings, remove such a crawl request here\r
+                               AutoLocker lock(m_critSec);\r
+                               if (m_bItemsAddedSinceLastCrawl)\r
+                               {\r
+                                       if (m_foldersToUpdate.back().IsEquivalentToWithoutCase(workingPath))\r
+                                       {\r
+                                               m_foldersToUpdate.pop_back();\r
+                                               m_bItemsAddedSinceLastCrawl = false;\r
+                                       }\r
+                               }\r
+                               CSVNStatusCache::Instance().Done();\r
+                       }\r
+               }\r
+       }\r
+       _endthread();\r
+}\r
+\r
+bool CFolderCrawler::SetHoldoff(DWORD milliseconds /* = 100*/)\r
+{\r
+       long tick = (long)GetTickCount();\r
+       bool ret = ((tick - m_crawlHoldoffReleasesAt) > 0);\r
+       m_crawlHoldoffReleasesAt = tick + milliseconds;\r
+       return ret;\r
+}\r
+\r
+void CFolderCrawler::BlockPath(const CTSVNPath& path, DWORD ticks)\r
+{\r
+       ATLTRACE(_T("block path %s from being crawled\n"), path.GetWinPath());\r
+       m_blockedPath = path;\r
+       if (ticks == 0)\r
+               m_blockReleasesAt = GetTickCount()+10000;\r
+       else\r
+               m_blockReleasesAt = GetTickCount()+ticks;\r
+}\r
diff --git a/src/TGitCache/FolderCrawler.h b/src/TGitCache/FolderCrawler.h
new file mode 100644 (file)
index 0000000..22f9d0b
--- /dev/null
@@ -0,0 +1,107 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006 - Will Dean, Stefan Kueng\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+\r
+#include "TSVNPath.h"\r
+#include "CacheInterface.h"\r
+#include <set>\r
+//////////////////////////////////////////////////////////////////////////\r
+\r
+\r
+\r
+#pragma pack(push, r1, 16)\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * Helper class to crawl folders in the background (in a separate thread)\r
+ * so that the main cache isn't blocked until all the status are fetched.\r
+ */\r
+class CFolderCrawler\r
+{\r
+public:\r
+       CFolderCrawler(void);\r
+       ~CFolderCrawler(void);\r
+\r
+public:\r
+       void Initialise();\r
+       void AddDirectoryForUpdate(const CTSVNPath& path);\r
+       void AddPathForUpdate(const CTSVNPath& path);\r
+       void Stop();\r
+       bool SetHoldoff(DWORD milliseconds = 100);\r
+       void BlockPath(const CTSVNPath& path, DWORD ticks = 0);\r
+private:\r
+       static unsigned int __stdcall ThreadEntry(void* pContext);\r
+       void WorkerThread();\r
+\r
+private:\r
+       CComAutoCriticalSection m_critSec;\r
+       HANDLE m_hThread;\r
+       std::deque<CTSVNPath> m_foldersToUpdate;\r
+       std::deque<CTSVNPath> m_pathsToUpdate;\r
+       HANDLE m_hTerminationEvent;\r
+       HANDLE m_hWakeEvent;\r
+       \r
+       // This will be *asynchronously* modified by CCrawlInhibitor.\r
+       // So, we have to mark it volatile, preparing compiler and\r
+       // optimizer for the "worst".\r
+       volatile LONG m_lCrawlInhibitSet;\r
+       \r
+       // While the shell is still asking for items, we don't\r
+       // want to start crawling.  This timer is pushed-out for \r
+       // every shell request, and stops us crawling until\r
+       // a bit of quiet time has elapsed\r
+       long m_crawlHoldoffReleasesAt;\r
+       bool m_bItemsAddedSinceLastCrawl;\r
+       bool m_bPathsAddedSinceLastCrawl;\r
+       \r
+       CTSVNPath m_blockedPath;\r
+       DWORD m_blockReleasesAt;\r
+       bool m_bRun;\r
+\r
+\r
+       friend class CCrawlInhibitor;\r
+};\r
+\r
+\r
+//////////////////////////////////////////////////////////////////////////\r
+\r
+\r
+class CCrawlInhibitor\r
+{\r
+private:\r
+       CCrawlInhibitor(); // Not defined\r
+public:\r
+       explicit CCrawlInhibitor(CFolderCrawler* pCrawler)\r
+       {\r
+               m_pCrawler = pCrawler;\r
+               \r
+               // Count locks instead of a mere flag toggle (exchange)\r
+               // to allow for multiple, concurrent locks.\r
+               ::InterlockedIncrement(&m_pCrawler->m_lCrawlInhibitSet);\r
+       }\r
+       ~CCrawlInhibitor()\r
+       {\r
+               ::InterlockedDecrement(&m_pCrawler->m_lCrawlInhibitSet);\r
+               m_pCrawler->SetHoldoff();\r
+       }\r
+private:\r
+       CFolderCrawler* m_pCrawler;\r
+};\r
+\r
+#pragma pack(pop, r1)\r
diff --git a/src/TGitCache/SVNStatusCache.cpp b/src/TGitCache/SVNStatusCache.cpp
new file mode 100644 (file)
index 0000000..ececade
--- /dev/null
@@ -0,0 +1,472 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2006,2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+\r
+#include "StdAfx.h"\r
+#include "SVNStatus.h"\r
+#include "Svnstatuscache.h"\r
+#include "CacheInterface.h"\r
+#include "shlobj.h"\r
+\r
+//////////////////////////////////////////////////////////////////////////\r
+\r
+CSVNStatusCache* CSVNStatusCache::m_pInstance;\r
+\r
+CSVNStatusCache& CSVNStatusCache::Instance()\r
+{\r
+       ATLASSERT(m_pInstance != NULL);\r
+       return *m_pInstance;\r
+}\r
+\r
+void CSVNStatusCache::Create()\r
+{\r
+       ATLASSERT(m_pInstance == NULL);\r
+       m_pInstance = new CSVNStatusCache;\r
+\r
+       m_pInstance->watcher.SetFolderCrawler(&m_pInstance->m_folderCrawler);\r
+#define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto exit;\r
+#define LOADVALUEFROMFILE2(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto error;\r
+       unsigned int value = (unsigned int)-1;\r
+       FILE * pFile = NULL;\r
+       // find the location of the cache\r
+       TCHAR path[MAX_PATH];           //MAX_PATH ok here.\r
+       TCHAR path2[MAX_PATH];\r
+       if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)\r
+       {\r
+               _tcscat_s(path, MAX_PATH, _T("\\TSVNCache"));\r
+               if (!PathIsDirectory(path))\r
+               {\r
+                       if (CreateDirectory(path, NULL)==0)\r
+                               goto error;\r
+               }\r
+               _tcscat_s(path, MAX_PATH, _T("\\cache"));\r
+               // in case the cache file is corrupt, we could crash while\r
+               // reading it! To prevent crashing every time once that happens,\r
+               // we make a copy of the cache file and use that copy to read from.\r
+               // if that copy is corrupt, the original file won't exist anymore\r
+               // and the second time we start up and try to read the file,\r
+               // it's not there anymore and we start from scratch without a crash.\r
+               _tcscpy_s(path2, MAX_PATH, path);\r
+               _tcscat_s(path2, MAX_PATH, _T("2"));\r
+               DeleteFile(path2);\r
+               CopyFile(path, path2, FALSE);\r
+               DeleteFile(path);\r
+               pFile = _tfsopen(path2, _T("rb"), _SH_DENYNO);\r
+               if (pFile)\r
+               {\r
+                       try\r
+                       {\r
+                               LOADVALUEFROMFILE(value);\r
+                               if (value != 2)\r
+                               {\r
+                                       goto error;\r
+                               }\r
+                               int mapsize = 0;\r
+                               LOADVALUEFROMFILE(mapsize);\r
+                               for (int i=0; i<mapsize; ++i)\r
+                               {\r
+                                       LOADVALUEFROMFILE2(value);      \r
+                                       if (value > MAX_PATH)\r
+                                               goto error;\r
+                                       if (value)\r
+                                       {\r
+                                               CString sKey;\r
+                                               if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)\r
+                                               {\r
+                                                       sKey.ReleaseBuffer(0);\r
+                                                       goto error;\r
+                                               }\r
+                                               sKey.ReleaseBuffer(value);\r
+                                               CCachedDirectory * cacheddir = new CCachedDirectory();\r
+                                               if (cacheddir == NULL)\r
+                                                       goto error;\r
+                                               if (!cacheddir->LoadFromDisk(pFile))\r
+                                                       goto error;\r
+                                               CTSVNPath KeyPath = CTSVNPath(sKey);\r
+                                               if (m_pInstance->IsPathAllowed(KeyPath))\r
+                                               {\r
+                                                       m_pInstance->m_directoryCache[KeyPath] = cacheddir;\r
+                                                       // only add the path to the watch list if it is versioned\r
+                                                       if ((cacheddir->GetCurrentFullStatus() != svn_wc_status_unversioned)&&(cacheddir->GetCurrentFullStatus() != svn_wc_status_none))\r
+                                                               m_pInstance->watcher.AddPath(KeyPath);\r
+                                                       // do *not* add the paths for crawling!\r
+                                                       // because crawled paths will trigger a shell\r
+                                                       // notification, which makes the desktop flash constantly\r
+                                                       // until the whole first time crawling is over\r
+                                                       // m_pInstance->AddFolderForCrawling(KeyPath);\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+                       catch (CAtlException)\r
+                       {\r
+                               goto error;\r
+                       }\r
+               }\r
+       }\r
+exit:\r
+       if (pFile)\r
+               fclose(pFile);\r
+       DeleteFile(path2);\r
+       ATLTRACE("cache loaded from disk successfully!\n");\r
+       return;\r
+error:\r
+       fclose(pFile);\r
+       DeleteFile(path2);\r
+       if (m_pInstance)\r
+       {\r
+               m_pInstance->Stop();\r
+               Sleep(100);\r
+       }\r
+       delete m_pInstance;\r
+       m_pInstance = new CSVNStatusCache;\r
+       ATLTRACE("cache not loaded from disk\n");\r
+}\r
+\r
+bool CSVNStatusCache::SaveCache()\r
+{\r
+#define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) goto error;\r
+       unsigned int value = 0;\r
+       // save the cache to disk\r
+       FILE * pFile = NULL;\r
+       // find a location to write the cache to\r
+       TCHAR path[MAX_PATH];           //MAX_PATH ok here.\r
+       if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)\r
+       {\r
+               _tcscat_s(path, MAX_PATH, _T("\\TSVNCache"));\r
+               if (!PathIsDirectory(path))\r
+                       CreateDirectory(path, NULL);\r
+               _tcscat_s(path, MAX_PATH, _T("\\cache"));\r
+               _tfopen_s(&pFile, path, _T("wb"));\r
+               if (pFile)\r
+               {\r
+                       value = 2;              // 'version'\r
+                       WRITEVALUETOFILE(value);\r
+                       value = (int)m_pInstance->m_directoryCache.size();\r
+                       WRITEVALUETOFILE(value);\r
+                       for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)\r
+                       {\r
+                               if (I->second == NULL)\r
+                               {\r
+                                       value = 0;\r
+                                       WRITEVALUETOFILE(value);\r
+                                       continue;\r
+                               }\r
+                               const CString& key = I->first.GetWinPathString();\r
+                               value = key.GetLength();\r
+                               WRITEVALUETOFILE(value);\r
+                               if (value)\r
+                               {\r
+                                       if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)\r
+                                               goto error;\r
+                                       if (!I->second->SaveToDisk(pFile))\r
+                                               goto error;\r
+                               }\r
+                       }\r
+                       fclose(pFile);\r
+               }\r
+       }\r
+       ATLTRACE(_T("cache saved to disk at %s\n"), path);\r
+       return true;\r
+error:\r
+       fclose(pFile);\r
+       if (m_pInstance)\r
+       {\r
+               m_pInstance->Stop();\r
+               Sleep(100);\r
+       }\r
+       delete m_pInstance;\r
+       m_pInstance = NULL;\r
+       DeleteFile(path);\r
+       return false;\r
+}\r
+\r
+void CSVNStatusCache::Destroy()\r
+{\r
+       if (m_pInstance)\r
+       {\r
+               m_pInstance->Stop();\r
+               Sleep(100);\r
+       }\r
+       delete m_pInstance;\r
+       m_pInstance = NULL;\r
+}\r
+\r
+void CSVNStatusCache::Stop()\r
+{\r
+       m_svnHelp.Cancel(true);\r
+       watcher.Stop();\r
+       m_folderCrawler.Stop();\r
+       m_shellUpdater.Stop();\r
+}\r
+\r
+void CSVNStatusCache::Init()\r
+{\r
+       m_folderCrawler.Initialise();\r
+       m_shellUpdater.Initialise();\r
+}\r
+\r
+CSVNStatusCache::CSVNStatusCache(void)\r
+{\r
+       TCHAR path[MAX_PATH];\r
+       SHGetFolderPath(NULL, CSIDL_COOKIES, NULL, 0, path);\r
+       m_NoWatchPaths.insert(CTSVNPath(CString(path)));\r
+       SHGetFolderPath(NULL, CSIDL_HISTORY, NULL, 0, path);\r
+       m_NoWatchPaths.insert(CTSVNPath(CString(path)));\r
+       SHGetFolderPath(NULL, CSIDL_INTERNET_CACHE, NULL, 0, path);\r
+       m_NoWatchPaths.insert(CTSVNPath(CString(path)));\r
+       SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, path);\r
+       m_NoWatchPaths.insert(CTSVNPath(CString(path)));\r
+       SHGetFolderPath(NULL, CSIDL_WINDOWS, NULL, 0, path);\r
+       m_NoWatchPaths.insert(CTSVNPath(CString(path)));\r
+       m_bClearMemory = false;\r
+       m_mostRecentExpiresAt = 0;\r
+}\r
+\r
+CSVNStatusCache::~CSVNStatusCache(void)\r
+{\r
+       for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)\r
+       {\r
+               delete I->second;\r
+               I->second = NULL;\r
+       }\r
+}\r
+\r
+void CSVNStatusCache::Refresh()\r
+{\r
+       m_shellCache.ForceRefresh();\r
+       m_pInstance->m_svnHelp.ReloadConfig();\r
+       if (m_pInstance->m_directoryCache.size())\r
+       {\r
+               CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin();\r
+               for (/* no init */; I != m_pInstance->m_directoryCache.end(); ++I)\r
+               {\r
+                       if (m_shellCache.IsPathAllowed(I->first.GetWinPath()))\r
+                               I->second->RefreshMostImportant();\r
+                       else\r
+                       {\r
+                               CSVNStatusCache::Instance().RemoveCacheForPath(I->first);\r
+                               I = m_pInstance->m_directoryCache.begin();\r
+                               if (I == m_pInstance->m_directoryCache.end())\r
+                                       break;\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+bool CSVNStatusCache::IsPathGood(const CTSVNPath& path)\r
+{\r
+       for (std::set<CTSVNPath>::iterator it = m_NoWatchPaths.begin(); it != m_NoWatchPaths.end(); ++it)\r
+       {\r
+               if (it->IsAncestorOf(path))\r
+                       return false;\r
+       }\r
+       return true;\r
+}\r
+\r
+void CSVNStatusCache::UpdateShell(const CTSVNPath& path)\r
+{\r
+       m_shellUpdater.AddPathForUpdate(path);\r
+}\r
+\r
+void CSVNStatusCache::ClearCache()\r
+{\r
+       for (CCachedDirectory::CachedDirMap::iterator I = m_directoryCache.begin(); I != m_directoryCache.end(); ++I)\r
+       {\r
+               delete I->second;\r
+               I->second = NULL;\r
+       }\r
+       m_directoryCache.clear();\r
+}\r
+\r
+bool CSVNStatusCache::RemoveCacheForDirectory(CCachedDirectory * cdir)\r
+{\r
+       if (cdir == NULL)\r
+               return false;\r
+       AssertWriting();\r
+       typedef std::map<CTSVNPath, svn_wc_status_kind>  ChildDirStatus;\r
+       if (cdir->m_childDirectories.size())\r
+       {\r
+               ChildDirStatus::iterator it = cdir->m_childDirectories.begin();\r
+               for (; it != cdir->m_childDirectories.end(); )\r
+               {\r
+                       CCachedDirectory * childdir = CSVNStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it->first);\r
+                       if ((childdir)&&(!cdir->m_directoryPath.IsEquivalentTo(childdir->m_directoryPath)))\r
+                               RemoveCacheForDirectory(childdir);\r
+                       cdir->m_childDirectories.erase(it->first);\r
+                       it = cdir->m_childDirectories.begin();\r
+               }\r
+       }\r
+       cdir->m_childDirectories.clear();\r
+       m_directoryCache.erase(cdir->m_directoryPath);\r
+       ATLTRACE(_T("removed path %s from cache\n"), cdir->m_directoryPath);\r
+       delete cdir;\r
+       cdir = NULL;\r
+       return true;\r
+}\r
+\r
+void CSVNStatusCache::RemoveCacheForPath(const CTSVNPath& path)\r
+{\r
+       // Stop the crawler starting on a new folder\r
+       CCrawlInhibitor crawlInhibit(&m_folderCrawler);\r
+       CCachedDirectory::ItDir itMap;\r
+       CCachedDirectory * dirtoremove = NULL;\r
+\r
+       AssertWriting();\r
+       itMap = m_directoryCache.find(path);\r
+       if ((itMap != m_directoryCache.end())&&(itMap->second))\r
+               dirtoremove = itMap->second;\r
+       if (dirtoremove == NULL)\r
+               return;\r
+       ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath));\r
+       RemoveCacheForDirectory(dirtoremove);\r
+}\r
+\r
+CCachedDirectory * CSVNStatusCache::GetDirectoryCacheEntry(const CTSVNPath& path)\r
+{\r
+       ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));\r
+\r
+\r
+       CCachedDirectory::ItDir itMap;\r
+       itMap = m_directoryCache.find(path);\r
+       if ((itMap != m_directoryCache.end())&&(itMap->second))\r
+       {\r
+               // We've found this directory in the cache \r
+               return itMap->second;\r
+       }\r
+       else\r
+       {\r
+               // if the CCachedDirectory is NULL but the path is in our cache,\r
+               // that means that path got invalidated and needs to be treated\r
+               // as if it never was in our cache. So we remove the last remains\r
+               // from the cache and start from scratch.\r
+               AssertLock();\r
+               if (!IsWriter())\r
+               {\r
+                       // upgrading our state to writer\r
+                       ATLTRACE("trying to upgrade the state to \"Writer\"\n");\r
+                       Done();\r
+                       ATLTRACE("Returned \"Reader\" state\n");\r
+                       WaitToWrite();\r
+                       ATLTRACE("Got \"Writer\" state now\n");\r
+               }\r
+               // Since above there's a small chance that before we can upgrade to\r
+               // writer state some other thread gained writer state and changed\r
+               // the data, we have to recreate the iterator here again.\r
+               itMap = m_directoryCache.find(path);\r
+               if (itMap!=m_directoryCache.end())\r
+                       m_directoryCache.erase(itMap);\r
+               // We don't know anything about this directory yet - lets add it to our cache\r
+               // but only if it exists!\r
+               if (path.Exists() && m_shellCache.IsPathAllowed(path.GetWinPath()) && !g_SVNAdminDir.IsAdminDirPath(path.GetWinPath()))\r
+               {\r
+                       // some notifications are for files which got removed/moved around.\r
+                       // In such cases, the CTSVNPath::IsDirectory() will return true (it assumes a directory if\r
+                       // the path doesn't exist). Which means we can get here with a path to a file\r
+                       // instead of a directory.\r
+                       // Since we're here most likely called from the crawler thread, the file could exist\r
+                       // again. If that's the case, just do nothing\r
+                       if (path.IsDirectory()||(!path.Exists()))\r
+                       {\r
+                               ATLTRACE(_T("adding %s to our cache\n"), path.GetWinPath());\r
+                               CCachedDirectory * newcdir = new CCachedDirectory(path);\r
+                               if (newcdir)\r
+                               {\r
+                                       CCachedDirectory * cdir = m_directoryCache.insert(m_directoryCache.lower_bound(path), std::make_pair(path, newcdir))->second;\r
+                                       if ((!path.IsEmpty())&&(path.HasAdminDir()))\r
+                                               watcher.AddPath(path);\r
+                                       return cdir;            \r
+                               }\r
+                               m_bClearMemory = true;\r
+                       }\r
+               }\r
+               return NULL;\r
+       }\r
+}\r
+\r
+CCachedDirectory * CSVNStatusCache::GetDirectoryCacheEntryNoCreate(const CTSVNPath& path)\r
+{\r
+       ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));\r
+\r
+       CCachedDirectory::ItDir itMap;\r
+       itMap = m_directoryCache.find(path);\r
+       if(itMap != m_directoryCache.end())\r
+       {\r
+               // We've found this directory in the cache \r
+               return itMap->second;\r
+       }\r
+       return NULL;\r
+}\r
+\r
+CStatusCacheEntry CSVNStatusCache::GetStatusForPath(const CTSVNPath& path, DWORD flags,  bool bFetch /* = true */)\r
+{\r
+       bool bRecursive = !!(flags & TSVNCACHE_FLAGS_RECUSIVE_STATUS);\r
+\r
+       // Check a very short-lived 'mini-cache' of the last thing we were asked for.\r
+       long now = (long)GetTickCount();\r
+       if(now-m_mostRecentExpiresAt < 0)\r
+       {\r
+               if(path.IsEquivalentToWithoutCase(m_mostRecentPath))\r
+               {\r
+                       return m_mostRecentStatus;\r
+               }\r
+       }\r
+       m_mostRecentPath = path;\r
+       m_mostRecentExpiresAt = now+1000;\r
+\r
+       if (IsPathGood(path))\r
+       {\r
+               // Stop the crawler starting on a new folder while we're doing this much more important task...\r
+               // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().\r
+               CCrawlInhibitor crawlInhibit(&m_folderCrawler);\r
+\r
+               CTSVNPath dirpath = path.GetContainingDirectory();\r
+               if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath())))\r
+                       dirpath = path.GetDirectory();\r
+               CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath);\r
+               if (cachedDir != NULL)\r
+               {\r
+                       m_mostRecentStatus = cachedDir->GetStatusForMember(path, bRecursive, bFetch);\r
+                       return m_mostRecentStatus;\r
+               }\r
+       }\r
+       ATLTRACE(_T("ignored no good path %s\n"), path.GetWinPath());\r
+       m_mostRecentStatus = CStatusCacheEntry();\r
+       if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasSVNAdminDir(path.GetWinPath(), true))\r
+       {\r
+               m_mostRecentStatus.ForceStatus(svn_wc_status_normal);\r
+       }\r
+       return m_mostRecentStatus;\r
+}\r
+\r
+void CSVNStatusCache::AddFolderForCrawling(const CTSVNPath& path)\r
+{\r
+       m_folderCrawler.AddDirectoryForUpdate(path);\r
+}\r
+\r
+void CSVNStatusCache::CloseWatcherHandles(HDEVNOTIFY hdev)\r
+{\r
+       CTSVNPath path = watcher.CloseInfoMap(hdev);\r
+       m_folderCrawler.BlockPath(path);\r
+}\r
+\r
+void CSVNStatusCache::CloseWatcherHandles(const CTSVNPath& path)\r
+{\r
+       watcher.CloseHandlesForPath(path);\r
+       m_folderCrawler.BlockPath(path);\r
+}\r
diff --git a/src/TGitCache/SVNStatusCache.h b/src/TGitCache/SVNStatusCache.h
new file mode 100644 (file)
index 0000000..1c26454
--- /dev/null
@@ -0,0 +1,129 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006 - Will Dean, Stefan Kueng\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+\r
+#include "TSVNPath.h"\r
+#include "SVNHelpers.h"\r
+#include "StatusCacheEntry.h"\r
+#include "CachedDirectory.h"\r
+#include "FolderCrawler.h"\r
+#include "DirectoryWatcher.h"\r
+#include "ShellUpdater.h"\r
+#include "RWSection.h"\r
+#include "atlcoll.h"\r
+\r
+//////////////////////////////////////////////////////////////////////////\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * The main class handling the status cache.\r
+ * Provides access to a global object of itself which handles all\r
+ * the requests for status.\r
+ */\r
+class CSVNStatusCache\r
+{\r
+private:\r
+       CSVNStatusCache(void);\r
+       ~CSVNStatusCache(void);\r
+\r
+public:\r
+       static CSVNStatusCache& Instance();\r
+       static void Create();\r
+       static void Destroy();\r
+       static bool SaveCache();\r
+\r
+public:\r
+       /// Refreshes the cache.\r
+       void Refresh();\r
+\r
+       /// Get the status for a single path (main entry point, called from named-pipe code\r
+       CStatusCacheEntry GetStatusForPath(const CTSVNPath& path, DWORD flags,  bool bFetch = true);\r
+\r
+       /// Find a directory in the cache (a new entry will be created if there isn't an existing entry)\r
+       CCachedDirectory * GetDirectoryCacheEntry(const CTSVNPath& path);\r
+       CCachedDirectory * GetDirectoryCacheEntryNoCreate(const CTSVNPath& path);\r
+\r
+       /// Add a folder to the background crawler's work list\r
+       void AddFolderForCrawling(const CTSVNPath& path);\r
+\r
+       /// Removes the cache for a specific path, e.g. if a folder got deleted/renamed\r
+       void RemoveCacheForPath(const CTSVNPath& path);\r
+\r
+       /// Removes all items from the cache\r
+       void ClearCache();\r
+       \r
+       /// Call this method before getting the status for a shell request\r
+       void StartRequest(const CTSVNPath& path);\r
+       /// Call this method after the data for the shell request has been gathered\r
+       void EndRequest(const CTSVNPath& path);\r
+       \r
+       /// Notifies the shell about file/folder status changes.\r
+       /// A notification is only sent for paths which aren't currently\r
+       /// in the list of handled shell requests to avoid deadlocks.\r
+       void UpdateShell(const CTSVNPath& path);\r
+\r
+       size_t GetCacheSize() {return m_directoryCache.size();}\r
+       int GetNumberOfWatchedPaths() {return watcher.GetNumberOfWatchedPaths();}\r
+\r
+       void Init();\r
+       void Stop();\r
+\r
+       void CloseWatcherHandles(HDEVNOTIFY hdev);\r
+       void CSVNStatusCache::CloseWatcherHandles(const CTSVNPath& path);\r
+\r
+       bool WaitToRead(DWORD waitTime = INFINITE) {return m_rwSection.WaitToRead(waitTime);}\r
+       bool WaitToWrite(DWORD waitTime = INFINITE) {return m_rwSection.WaitToWrite(waitTime);}\r
+       void Done() {m_rwSection.Done();}\r
+       bool IsWriter() {return m_rwSection.IsWriter();}\r
+#if defined (DEBUG) || defined (_DEBUG)\r
+       void AssertLock() {m_rwSection.AssertLock();}\r
+       void AssertWriting() {m_rwSection.AssertWriting();}\r
+#else\r
+       void AssertLock() {;}\r
+       void AssertWriting() {;}\r
+#endif\r
+       bool IsPathAllowed(const CTSVNPath& path) {return !!m_shellCache.IsPathAllowed(path.GetWinPath());}\r
+       bool IsUnversionedAsModified() {return !!m_shellCache.IsUnversionedAsModified();}\r
+       bool IsPathGood(const CTSVNPath& path);\r
+       bool IsPathWatched(const CTSVNPath& path) {return watcher.IsPathWatched(path);}\r
+       bool AddPathToWatch(const CTSVNPath& path) {return watcher.AddPath(path);}\r
+\r
+       bool m_bClearMemory;\r
+private:\r
+       bool RemoveCacheForDirectory(CCachedDirectory * cdir);\r
+       CRWSection m_rwSection;\r
+       CAtlList<CString> m_askedList;\r
+       CCachedDirectory::CachedDirMap m_directoryCache;\r
+       std::set<CTSVNPath> m_NoWatchPaths;\r
+       SVNHelper m_svnHelp;\r
+       ShellCache      m_shellCache;\r
+\r
+       static CSVNStatusCache* m_pInstance;\r
+\r
+       CFolderCrawler m_folderCrawler;\r
+       CShellUpdater m_shellUpdater;\r
+\r
+       CTSVNPath m_mostRecentPath;\r
+       CStatusCacheEntry m_mostRecentStatus;\r
+       long m_mostRecentExpiresAt;\r
+\r
+       CDirectoryWatcher watcher;\r
+\r
+       friend class CCachedDirectory;  // Needed for access to the SVN helpers\r
+};\r
diff --git a/src/TGitCache/ShellUpdater.cpp b/src/TGitCache/ShellUpdater.cpp
new file mode 100644 (file)
index 0000000..593f64e
--- /dev/null
@@ -0,0 +1,167 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+\r
+#include "StdAfx.h"\r
+#include "shlobj.h"\r
+#include "SVNStatusCache.h"\r
+\r
+CShellUpdater::CShellUpdater(void)\r
+{\r
+       m_hWakeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);\r
+       m_hTerminationEvent = CreateEvent(NULL,TRUE,FALSE,NULL);\r
+       m_hThread = INVALID_HANDLE_VALUE;\r
+       m_bRunning = FALSE;\r
+       m_bItemsAddedSinceLastUpdate = false;\r
+}\r
+\r
+CShellUpdater::~CShellUpdater(void)\r
+{\r
+       Stop();\r
+}\r
+\r
+void CShellUpdater::Stop()\r
+{\r
+       InterlockedExchange(&m_bRunning, FALSE);\r
+       if (m_hTerminationEvent != INVALID_HANDLE_VALUE)\r
+       {\r
+               SetEvent(m_hTerminationEvent);\r
+               if(WaitForSingleObject(m_hThread, 200) != WAIT_OBJECT_0)\r
+               {\r
+                       ATLTRACE("Error terminating shell updater thread\n");\r
+               }\r
+               CloseHandle(m_hThread);\r
+               m_hThread = INVALID_HANDLE_VALUE;\r
+               CloseHandle(m_hTerminationEvent);\r
+               m_hTerminationEvent = INVALID_HANDLE_VALUE;\r
+               CloseHandle(m_hWakeEvent);\r
+               m_hWakeEvent = INVALID_HANDLE_VALUE;\r
+       }\r
+}\r
+\r
+void CShellUpdater::Initialise()\r
+{\r
+       // Don't call Initialize more than once\r
+       ATLASSERT(m_hThread == INVALID_HANDLE_VALUE);\r
+\r
+       // Just start the worker thread. \r
+       // It will wait for event being signaled.\r
+       // If m_hWakeEvent is already signaled the worker thread \r
+       // will behave properly (with normal priority at worst).\r
+\r
+       InterlockedExchange(&m_bRunning, TRUE);\r
+       unsigned int threadId;\r
+       m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);\r
+       SetThreadPriority(m_hThread, THREAD_PRIORITY_LOWEST);\r
+}\r
+\r
+void CShellUpdater::AddPathForUpdate(const CTSVNPath& path)\r
+{\r
+       {\r
+               AutoLocker lock(m_critSec);\r
+               m_pathsToUpdate.push_back(path);\r
+               \r
+               // set this flag while we are synced \r
+               // with the worker thread\r
+               m_bItemsAddedSinceLastUpdate = true;\r
+       }\r
+\r
+       SetEvent(m_hWakeEvent);\r
+}\r
+\r
+\r
+unsigned int CShellUpdater::ThreadEntry(void* pContext)\r
+{\r
+       ((CShellUpdater*)pContext)->WorkerThread();\r
+       return 0;\r
+}\r
+\r
+void CShellUpdater::WorkerThread()\r
+{\r
+       HANDLE hWaitHandles[2];\r
+       hWaitHandles[0] = m_hTerminationEvent;  \r
+       hWaitHandles[1] = m_hWakeEvent;\r
+\r
+       for(;;)\r
+       {\r
+               DWORD waitResult = WaitForMultipleObjects(sizeof(hWaitHandles)/sizeof(hWaitHandles[0]), hWaitHandles, FALSE, INFINITE);\r
+               \r
+               // exit event/working loop if the first event (m_hTerminationEvent)\r
+               // has been signaled or if one of the events has been abandoned\r
+               // (i.e. ~CShellUpdater() is being executed)\r
+               if(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1)\r
+               {\r
+                       // Termination event\r
+                       break;\r
+               }\r
+               // wait some time before we notify the shell\r
+               Sleep(50);\r
+               for(;;)\r
+               {\r
+                       CTSVNPath workingPath;\r
+                       if (!m_bRunning)\r
+                               return;\r
+                       Sleep(0);\r
+                       {\r
+                               AutoLocker lock(m_critSec);\r
+                               if(m_pathsToUpdate.empty())\r
+                               {\r
+                                       // Nothing left to do \r
+                                       break;\r
+                               }\r
+\r
+                               if(m_bItemsAddedSinceLastUpdate)\r
+                               {\r
+                                       m_pathsToUpdate.erase(std::unique(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), &CTSVNPath::PredLeftEquivalentToRight), m_pathsToUpdate.end());\r
+                                       m_bItemsAddedSinceLastUpdate = false;\r
+                               }\r
+\r
+                               workingPath = m_pathsToUpdate.front();\r
+                               m_pathsToUpdate.pop_front();\r
+                       }\r
+                       if (workingPath.IsEmpty())\r
+                               continue;\r
+                       ATLTRACE(_T("Update notifications for: %s\n"), workingPath.GetWinPath());\r
+                       if (workingPath.IsDirectory())\r
+                       {\r
+                               // check if the path is monitored by the watcher. If it isn't, then we have to invalidate the cache\r
+                               // for that path and add it to the watcher.\r
+                               if (!CSVNStatusCache::Instance().IsPathWatched(workingPath))\r
+                               {\r
+                                       if (workingPath.HasAdminDir())\r
+                                               CSVNStatusCache::Instance().AddPathToWatch(workingPath);\r
+                               }\r
+                               // first send a notification about a sub folder change, so explorer doesn't discard\r
+                               // the folder notification. Since we only know for sure that the subversion admin\r
+                               // dir is present, we send a notification for that folder.\r
+                               CString admindir = workingPath.GetWinPathString() + _T("\\") + g_SVNAdminDir.GetAdminDirName();\r
+                               SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, (LPCTSTR)admindir, NULL);\r
+                               SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL);\r
+                               // Sending an UPDATEDIR notification somehow overwrites/deletes the UPDATEITEM message. And without\r
+                               // that message, the folder overlays in the current view don't get updated without hitting F5.\r
+                               // Drawback is, without UPDATEDIR, the left tree view isn't always updated...\r
+                               \r
+                               //SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL);\r
+                       }\r
+                       else\r
+                               SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL);\r
+               }\r
+       }\r
+       _endthread();\r
+}\r
+\r
diff --git a/src/TGitCache/ShellUpdater.h b/src/TGitCache/ShellUpdater.h
new file mode 100644 (file)
index 0000000..8b8041c
--- /dev/null
@@ -0,0 +1,54 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006 - Will Dean, Stefan Kueng\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+\r
+#include "TSVNPath.h"\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * Handles notifications to the shell about status changes. This is done\r
+ * in a separate thread to avoid deadlocks.\r
+ */\r
+class CShellUpdater\r
+{\r
+public:\r
+       CShellUpdater(void);\r
+       ~CShellUpdater(void);\r
+\r
+public:\r
+       void Initialise();\r
+       void AddPathForUpdate(const CTSVNPath& path);\r
+       void Stop();\r
+\r
+private:\r
+       static unsigned int __stdcall ThreadEntry(void* pContext);\r
+       void WorkerThread();\r
+\r
+private:\r
+       CComAutoCriticalSection m_critSec;\r
+       HANDLE m_hThread;\r
+       std::deque<CTSVNPath> m_pathsToUpdate;\r
+       HANDLE m_hTerminationEvent;\r
+       HANDLE m_hWakeEvent;\r
+       \r
+       bool m_bItemsAddedSinceLastUpdate;\r
+       volatile LONG m_bRunning;\r
+};\r
+\r
+\r
diff --git a/src/TGitCache/StatusCacheEntry.cpp b/src/TGitCache/StatusCacheEntry.cpp
new file mode 100644 (file)
index 0000000..5650bcf
--- /dev/null
@@ -0,0 +1,288 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005-2006,2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#include "StdAfx.h"\r
+#include ".\statuscacheentry.h"\r
+#include "SVNStatus.h"\r
+#include "CacheInterface.h"\r
+#include "registry.h"\r
+\r
+DWORD cachetimeout = (DWORD)CRegStdWORD(_T("Software\\TortoiseSVN\\Cachetimeout"), CACHETIMEOUT);\r
+\r
+CStatusCacheEntry::CStatusCacheEntry()\r
+       : m_bSet(false)\r
+       , m_bSVNEntryFieldSet(false)\r
+       , m_kind(svn_node_unknown)\r
+       , m_bReadOnly(false)\r
+       , m_highestPriorityLocalStatus(svn_wc_status_none)\r
+{\r
+       SetAsUnversioned();\r
+}\r
+\r
+CStatusCacheEntry::CStatusCacheEntry(const svn_wc_status2_t* pSVNStatus, __int64 lastWriteTime, bool bReadOnly, DWORD validuntil /* = 0*/)\r
+       : m_bSet(false)\r
+       , m_bSVNEntryFieldSet(false)\r
+       , m_kind(svn_node_unknown)\r
+       , m_bReadOnly(false)\r
+       , m_highestPriorityLocalStatus(svn_wc_status_none)\r
+{\r
+       SetStatus(pSVNStatus);\r
+       m_lastWriteTime = lastWriteTime;\r
+       if (validuntil)\r
+               m_discardAtTime = validuntil;\r
+       else\r
+               m_discardAtTime = GetTickCount()+cachetimeout;\r
+       m_bReadOnly = bReadOnly;\r
+}\r
+\r
+bool CStatusCacheEntry::SaveToDisk(FILE * pFile)\r
+{\r
+#define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;\r
+#define WRITESTRINGTOFILE(x) if (x.IsEmpty()) {value=0;WRITEVALUETOFILE(value);}else{value=x.GetLength();WRITEVALUETOFILE(value);if (fwrite((LPCSTR)x, sizeof(char), value, pFile)!=value) return false;}\r
+\r
+       unsigned int value = 4;\r
+       WRITEVALUETOFILE(value); // 'version' of this save-format\r
+       WRITEVALUETOFILE(m_highestPriorityLocalStatus);\r
+       WRITEVALUETOFILE(m_lastWriteTime);\r
+       WRITEVALUETOFILE(m_bSet);\r
+       WRITEVALUETOFILE(m_bSVNEntryFieldSet);\r
+       WRITEVALUETOFILE(m_commitRevision);\r
+       WRITESTRINGTOFILE(m_sUrl);\r
+       WRITESTRINGTOFILE(m_sOwner);\r
+       WRITESTRINGTOFILE(m_sAuthor);\r
+       WRITEVALUETOFILE(m_kind);\r
+       WRITEVALUETOFILE(m_bReadOnly);\r
+       WRITESTRINGTOFILE(m_sPresentProps);\r
+\r
+       // now save the status struct (without the entry field, because we don't use that)\r
+       WRITEVALUETOFILE(m_svnStatus.copied);\r
+       WRITEVALUETOFILE(m_svnStatus.locked);\r
+       WRITEVALUETOFILE(m_svnStatus.prop_status);\r
+       WRITEVALUETOFILE(m_svnStatus.repos_prop_status);\r
+       WRITEVALUETOFILE(m_svnStatus.repos_text_status);\r
+       WRITEVALUETOFILE(m_svnStatus.switched);\r
+       WRITEVALUETOFILE(m_svnStatus.text_status);\r
+       return true;\r
+}\r
+\r
+bool CStatusCacheEntry::LoadFromDisk(FILE * pFile)\r
+{\r
+#define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;\r
+       try\r
+       {\r
+               unsigned int value = 0;\r
+               LOADVALUEFROMFILE(value);\r
+               if (value != 4)\r
+                       return false;           // not the correct version\r
+               LOADVALUEFROMFILE(m_highestPriorityLocalStatus);\r
+               LOADVALUEFROMFILE(m_lastWriteTime);\r
+               LOADVALUEFROMFILE(m_bSet);\r
+               LOADVALUEFROMFILE(m_bSVNEntryFieldSet);\r
+               LOADVALUEFROMFILE(m_commitRevision);\r
+               LOADVALUEFROMFILE(value);\r
+               if (value != 0)\r
+               {\r
+                       if (value > INTERNET_MAX_URL_LENGTH)\r
+                               return false;           // invalid length for an url\r
+                       if (fread(m_sUrl.GetBuffer(value+1), sizeof(char), value, pFile)!=value)\r
+                       {\r
+                               m_sUrl.ReleaseBuffer(0);\r
+                               return false;\r
+                       }\r
+                       m_sUrl.ReleaseBuffer(value);\r
+               }\r
+               LOADVALUEFROMFILE(value);\r
+               if (value != 0)\r
+               {\r
+                       if (fread(m_sOwner.GetBuffer(value+1), sizeof(char), value, pFile)!=value)\r
+                       {\r
+                               m_sOwner.ReleaseBuffer(0);\r
+                               return false;\r
+                       }\r
+                       m_sOwner.ReleaseBuffer(value);\r
+               }\r
+               LOADVALUEFROMFILE(value);\r
+               if (value != 0)\r
+               {\r
+                       if (fread(m_sAuthor.GetBuffer(value+1), sizeof(char), value, pFile)!=value)\r
+                       {\r
+                               m_sAuthor.ReleaseBuffer(0);\r
+                               return false;\r
+                       }\r
+                       m_sAuthor.ReleaseBuffer(value);\r
+               }\r
+               LOADVALUEFROMFILE(m_kind);\r
+               LOADVALUEFROMFILE(m_bReadOnly);\r
+               LOADVALUEFROMFILE(value);\r
+               if (value != 0)\r
+               {\r
+                       if (fread(m_sPresentProps.GetBuffer(value+1), sizeof(char), value, pFile)!=value)\r
+                       {\r
+                               m_sPresentProps.ReleaseBuffer(0);\r
+                               return false;\r
+                       }\r
+                       m_sPresentProps.ReleaseBuffer(value);\r
+               }\r
+               SecureZeroMemory(&m_svnStatus, sizeof(m_svnStatus));\r
+               LOADVALUEFROMFILE(m_svnStatus.copied);\r
+               LOADVALUEFROMFILE(m_svnStatus.locked);\r
+               LOADVALUEFROMFILE(m_svnStatus.prop_status);\r
+               LOADVALUEFROMFILE(m_svnStatus.repos_prop_status);\r
+               LOADVALUEFROMFILE(m_svnStatus.repos_text_status);\r
+               LOADVALUEFROMFILE(m_svnStatus.switched);\r
+               LOADVALUEFROMFILE(m_svnStatus.text_status);\r
+               m_svnStatus.entry = NULL;\r
+               m_discardAtTime = GetTickCount()+cachetimeout;\r
+       }\r
+       catch ( CAtlException )\r
+       {\r
+               return false;\r
+       }\r
+       return true;\r
+}\r
+\r
+void CStatusCacheEntry::SetStatus(const svn_wc_status2_t* pSVNStatus)\r
+{\r
+       if(pSVNStatus == NULL)\r
+       {\r
+               SetAsUnversioned();\r
+       }\r
+       else\r
+       {\r
+               m_highestPriorityLocalStatus = SVNStatus::GetMoreImportant(pSVNStatus->prop_status, pSVNStatus->text_status);\r
+               m_svnStatus = *pSVNStatus;\r
+\r
+               // Currently we don't deep-copy the whole entry value, but we do take a few members\r
+        if(pSVNStatus->entry != NULL)\r
+               {\r
+                       m_sUrl = pSVNStatus->entry->url;\r
+                       m_commitRevision = pSVNStatus->entry->cmt_rev;\r
+                       m_bSVNEntryFieldSet = true;\r
+                       m_sOwner = pSVNStatus->entry->lock_owner;\r
+                       m_kind = pSVNStatus->entry->kind;\r
+                       m_sAuthor = pSVNStatus->entry->cmt_author;\r
+                       if (pSVNStatus->entry->present_props)\r
+                               m_sPresentProps = pSVNStatus->entry->present_props;\r
+               }\r
+               else\r
+               {\r
+                       m_sUrl.Empty();\r
+                       m_commitRevision = 0;\r
+                       m_bSVNEntryFieldSet = false;\r
+               }\r
+               m_svnStatus.entry = NULL;\r
+       }\r
+       m_discardAtTime = GetTickCount()+cachetimeout;\r
+       m_bSet = true;\r
+}\r
+\r
+\r
+void CStatusCacheEntry::SetAsUnversioned()\r
+{\r
+       SecureZeroMemory(&m_svnStatus, sizeof(m_svnStatus));\r
+       m_discardAtTime = GetTickCount()+cachetimeout;\r
+       svn_wc_status_kind status = svn_wc_status_none;\r
+       if (m_highestPriorityLocalStatus == svn_wc_status_ignored)\r
+               status = svn_wc_status_ignored;\r
+       if (m_highestPriorityLocalStatus == svn_wc_status_unversioned)\r
+               status = svn_wc_status_unversioned;\r
+       m_highestPriorityLocalStatus = status;\r
+       m_svnStatus.prop_status = svn_wc_status_none;\r
+       m_svnStatus.text_status = status;\r
+       m_lastWriteTime = 0;\r
+}\r
+\r
+bool CStatusCacheEntry::HasExpired(long now) const\r
+{\r
+       return m_discardAtTime != 0 && (now - m_discardAtTime) >= 0;\r
+}\r
+\r
+void CStatusCacheEntry::BuildCacheResponse(TSVNCacheResponse& response, DWORD& responseLength) const\r
+{\r
+       SecureZeroMemory(&response, sizeof(response));\r
+       if(m_bSVNEntryFieldSet)\r
+       {\r
+               response.m_status = m_svnStatus;\r
+               response.m_entry.cmt_rev = m_commitRevision;\r
+\r
+               // There is no point trying to set these pointers here, because this is not \r
+               // the process which will be using the data.\r
+               // The process which receives this response (generally the TSVN Shell Extension)\r
+               // must fix-up these pointers when it gets them\r
+               response.m_status.entry = NULL;\r
+               response.m_entry.url = NULL;\r
+\r
+               response.m_kind = m_kind;\r
+               response.m_readonly = m_bReadOnly;\r
+\r
+               if (m_sPresentProps.Find("svn:needs-lock")>=0)\r
+               {\r
+                       response.m_needslock = true;\r
+               }\r
+               else\r
+                       response.m_needslock = false;\r
+               // The whole of response has been zeroed, so this will copy safely \r
+               strncat_s(response.m_url, INTERNET_MAX_URL_LENGTH, m_sUrl, _TRUNCATE);\r
+               strncat_s(response.m_owner, 255, m_sOwner, _TRUNCATE);\r
+               strncat_s(response.m_author, 255, m_sAuthor, _TRUNCATE);\r
+               responseLength = sizeof(response);\r
+       }\r
+       else\r
+       {\r
+               response.m_status = m_svnStatus;\r
+               responseLength = sizeof(response.m_status);\r
+       }\r
+}\r
+\r
+bool CStatusCacheEntry::IsVersioned() const\r
+{\r
+       return m_highestPriorityLocalStatus > svn_wc_status_unversioned;\r
+}\r
+\r
+bool CStatusCacheEntry::DoesFileTimeMatch(__int64 testTime) const\r
+{\r
+       return m_lastWriteTime == testTime;\r
+}\r
+\r
+\r
+bool CStatusCacheEntry::ForceStatus(svn_wc_status_kind forcedStatus)\r
+{\r
+       svn_wc_status_kind newStatus = forcedStatus; \r
+\r
+       if(newStatus != m_highestPriorityLocalStatus)\r
+       {\r
+               // We've had a status change\r
+               m_highestPriorityLocalStatus = newStatus;\r
+               m_svnStatus.text_status = newStatus;\r
+               m_svnStatus.prop_status = newStatus;\r
+               m_discardAtTime = GetTickCount()+cachetimeout;\r
+               return true;\r
+       }\r
+       return false;\r
+}\r
+\r
+bool \r
+CStatusCacheEntry::HasBeenSet() const\r
+{\r
+       return m_bSet;\r
+}\r
+\r
+void CStatusCacheEntry::Invalidate()\r
+{\r
+       m_bSet = false;\r
+}\r
diff --git a/src/TGitCache/StatusCacheEntry.h b/src/TGitCache/StatusCacheEntry.h
new file mode 100644 (file)
index 0000000..2a09020
--- /dev/null
@@ -0,0 +1,69 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006 - Will Dean, Stefan Kueng\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+\r
+struct TSVNCacheResponse;\r
+#define CACHETIMEOUT   0x7FFFFFFF\r
+extern DWORD cachetimeout;\r
+\r
+/**\r
+ * \ingroup TSVNCache\r
+ * Holds all the status data of one file or folder.\r
+ */\r
+class CStatusCacheEntry\r
+{\r
+public:\r
+       CStatusCacheEntry();\r
+       CStatusCacheEntry(const svn_wc_status2_t* pSVNStatus, __int64 lastWriteTime, bool bReadOnly, DWORD validuntil = 0);\r
+       bool HasExpired(long now) const;\r
+       void BuildCacheResponse(TSVNCacheResponse& response, DWORD& responseLength) const;\r
+       bool IsVersioned() const;\r
+       bool DoesFileTimeMatch(__int64 testTime) const;\r
+       bool ForceStatus(svn_wc_status_kind forcedStatus);\r
+       svn_wc_status_kind GetEffectiveStatus() const { return m_highestPriorityLocalStatus; }\r
+       bool IsKindKnown() const { return ((m_kind != svn_node_none)&&(m_kind != svn_node_unknown)); }\r
+       void SetStatus(const svn_wc_status2_t* pSVNStatus);\r
+       bool HasBeenSet() const;\r
+       void Invalidate();\r
+       bool IsDirectory() const {return ((m_kind == svn_node_dir)&&(m_highestPriorityLocalStatus != svn_wc_status_ignored));}\r
+       bool SaveToDisk(FILE * pFile);\r
+       bool LoadFromDisk(FILE * pFile);\r
+       void SetKind(svn_node_kind_t kind) {m_kind = kind;}\r
+private:\r
+       void SetAsUnversioned();\r
+\r
+private:\r
+       long                            m_discardAtTime;\r
+       svn_wc_status_kind      m_highestPriorityLocalStatus;\r
+       svn_wc_status2_t        m_svnStatus;\r
+       __int64                         m_lastWriteTime;\r
+       bool                            m_bSet;\r
+       svn_node_kind_t         m_kind;\r
+       bool                            m_bReadOnly;\r
+\r
+       // Values copied from the 'entries' structure\r
+       bool                            m_bSVNEntryFieldSet;\r
+       CStringA                        m_sUrl;\r
+       CStringA                        m_sOwner;\r
+       CStringA                        m_sAuthor;\r
+       CStringA                        m_sPresentProps;\r
+       svn_revnum_t            m_commitRevision;\r
+\r
+//     friend class CSVNStatusCache;\r
+};\r
diff --git a/src/TGitCache/TSVNCache.cpp b/src/TGitCache/TSVNCache.cpp
new file mode 100644 (file)
index 0000000..5cb9a88
--- /dev/null
@@ -0,0 +1,813 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006 - Will Dean, Stefan Kueng\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+\r
+#include "stdafx.h"\r
+#include "shellapi.h"\r
+#include "TSVNCache.h"\r
+#include "SVNStatusCache.h"\r
+#include "CacheInterface.h"\r
+#include "Resource.h"\r
+#include "registry.h"\r
+#include "..\crashrpt\CrashReport.h"\r
+#include "SVNAdminDir.h"\r
+#include "Dbt.h"\r
+#include <initguid.h>\r
+#include "ioevent.h"\r
+#include "..\version.h"\r
+#include "svn_dso.h"\r
+\r
+#include <ShellAPI.h>\r
+\r
+#ifndef GET_X_LPARAM\r
+#define GET_X_LPARAM(lp)                        ((int)(short)LOWORD(lp))\r
+#endif\r
+#ifndef GET_Y_LPARAM\r
+#define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))\r
+#endif\r
+\r
+\r
+#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")\r
+\r
+CCrashReport crasher("crashreports@tortoisesvn.tigris.org", "Crash Report for TSVNCache " APP_X64_STRING " : " STRPRODUCTVER, TRUE);// crash\r
+\r
+DWORD WINAPI           InstanceThread(LPVOID); \r
+DWORD WINAPI           PipeThread(LPVOID);\r
+DWORD WINAPI           CommandWaitThread(LPVOID);\r
+DWORD WINAPI           CommandThread(LPVOID);\r
+LRESULT CALLBACK       WndProc(HWND, UINT, WPARAM, LPARAM);\r
+bool                           bRun = true;\r
+NOTIFYICONDATA         niData; \r
+HWND                           hWnd;\r
+HWND                           hTrayWnd;\r
+TCHAR                          szCurrentCrawledPath[MAX_CRAWLEDPATHS][MAX_CRAWLEDPATHSLEN];\r
+int                                    nCurrentCrawledpathIndex = 0;\r
+CComAutoCriticalSection critSec;\r
+\r
+volatile LONG          nThreadCount = 0;\r
+\r
+#define PACKVERSION(major,minor) MAKELONG(minor,major)\r
+DWORD GetDllVersion(LPCTSTR lpszDllName)\r
+{\r
+       HINSTANCE hinstDll;\r
+       DWORD dwVersion = 0;\r
+\r
+       /* For security purposes, LoadLibrary should be provided with a \r
+       fully-qualified path to the DLL. The lpszDllName variable should be\r
+       tested to ensure that it is a fully qualified path before it is used. */\r
+       hinstDll = LoadLibrary(lpszDllName);\r
+\r
+       if(hinstDll)\r
+       {\r
+               DLLGETVERSIONPROC pDllGetVersion;\r
+               pDllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hinstDll, \r
+                       "DllGetVersion");\r
+\r
+               /* Because some DLLs might not implement this function, you\r
+               must test for it explicitly. Depending on the particular \r
+               DLL, the lack of a DllGetVersion function can be a useful\r
+               indicator of the version. */\r
+\r
+               if(pDllGetVersion)\r
+               {\r
+                       DLLVERSIONINFO dvi;\r
+                       HRESULT hr;\r
+\r
+                       SecureZeroMemory(&dvi, sizeof(dvi));\r
+                       dvi.cbSize = sizeof(dvi);\r
+\r
+                       hr = (*pDllGetVersion)(&dvi);\r
+\r
+                       if(SUCCEEDED(hr))\r
+                       {\r
+                               dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion);\r
+                       }\r
+               }\r
+\r
+               FreeLibrary(hinstDll);\r
+       }\r
+       return dwVersion;\r
+}\r
+\r
+void DebugOutputLastError()\r
+{\r
+       LPVOID lpMsgBuf;\r
+       if (!FormatMessage( \r
+               FORMAT_MESSAGE_ALLOCATE_BUFFER | \r
+               FORMAT_MESSAGE_FROM_SYSTEM | \r
+               FORMAT_MESSAGE_IGNORE_INSERTS,\r
+               NULL,\r
+               GetLastError(),\r
+               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language\r
+               (LPTSTR) &lpMsgBuf,\r
+               0,\r
+               NULL ))\r
+       {\r
+               return;\r
+       }\r
+\r
+       // Display the string.\r
+       OutputDebugStringA("TSVNCache GetLastError(): ");\r
+       OutputDebugString((LPCTSTR)lpMsgBuf);\r
+       OutputDebugStringA("\n");\r
+\r
+       // Free the buffer.\r
+       LocalFree( lpMsgBuf );\r
+}\r
+\r
+int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*cmdShow*/)\r
+{\r
+       HANDLE hReloadProtection = ::CreateMutex(NULL, FALSE, GetCacheMutexName());\r
+\r
+       if (hReloadProtection == 0 || GetLastError() == ERROR_ALREADY_EXISTS)\r
+       {\r
+               // An instance of TSVNCache is already running\r
+               ATLTRACE("TSVNCache ignoring restart\n");\r
+               return 0;\r
+       }\r
+\r
+       apr_initialize();\r
+       svn_dso_initialize2();\r
+       g_SVNAdminDir.Init();\r
+       CSVNStatusCache::Create();\r
+       CSVNStatusCache::Instance().Init();\r
+\r
+       SecureZeroMemory(szCurrentCrawledPath, sizeof(szCurrentCrawledPath));\r
+       \r
+       DWORD dwThreadId; \r
+       HANDLE hPipeThread; \r
+       HANDLE hCommandWaitThread;\r
+       MSG msg;\r
+       TCHAR szWindowClass[] = {TSVN_CACHE_WINDOW_NAME};\r
+\r
+       // create a hidden window to receive window messages.\r
+       WNDCLASSEX wcex;\r
+       wcex.cbSize = sizeof(WNDCLASSEX); \r
+       wcex.style                      = CS_HREDRAW | CS_VREDRAW;\r
+       wcex.lpfnWndProc        = (WNDPROC)WndProc;\r
+       wcex.cbClsExtra         = 0;\r
+       wcex.cbWndExtra         = 0;\r
+       wcex.hInstance          = hInstance;\r
+       wcex.hIcon                      = 0;\r
+       wcex.hCursor            = 0;\r
+       wcex.hbrBackground      = 0;\r
+       wcex.lpszMenuName       = NULL;\r
+       wcex.lpszClassName      = szWindowClass;\r
+       wcex.hIconSm            = 0;\r
+       RegisterClassEx(&wcex);\r
+       hWnd = CreateWindow(TSVN_CACHE_WINDOW_NAME, TSVN_CACHE_WINDOW_NAME, WS_CAPTION, 0, 0, 800, 300, NULL, 0, hInstance, 0);\r
+       hTrayWnd = hWnd;\r
+       if (hWnd == NULL)\r
+       {\r
+               return 0;\r
+       }\r
+       if (CRegStdWORD(_T("Software\\TortoiseSVN\\CacheTrayIcon"), FALSE)==TRUE)\r
+       {\r
+               SecureZeroMemory(&niData,sizeof(NOTIFYICONDATA));\r
+\r
+               DWORD dwVersion = GetDllVersion(_T("Shell32.dll"));\r
+\r
+               if (dwVersion >= PACKVERSION(6,0))\r
+                       niData.cbSize = sizeof(NOTIFYICONDATA);\r
+               else if (dwVersion >= PACKVERSION(5,0))\r
+                       niData.cbSize = NOTIFYICONDATA_V2_SIZE;\r
+               else \r
+                       niData.cbSize = NOTIFYICONDATA_V1_SIZE;\r
+\r
+               niData.uID = TRAY_ID;           // own tray icon ID\r
+               niData.hWnd      = hWnd;\r
+               niData.uFlags = NIF_ICON|NIF_MESSAGE;\r
+\r
+               // load the icon\r
+               niData.hIcon =\r
+                       (HICON)LoadImage(hInstance,\r
+                       MAKEINTRESOURCE(IDI_TSVNCACHE),\r
+                       IMAGE_ICON,\r
+                       GetSystemMetrics(SM_CXSMICON),\r
+                       GetSystemMetrics(SM_CYSMICON),\r
+                       LR_DEFAULTCOLOR);\r
+\r
+               // set the message to send\r
+               // note: the message value should be in the\r
+               // range of WM_APP through 0xBFFF\r
+               niData.uCallbackMessage = TRAY_CALLBACK;\r
+               Shell_NotifyIcon(NIM_ADD,&niData);\r
+               // free icon handle\r
+               if(niData.hIcon && DestroyIcon(niData.hIcon))\r
+                       niData.hIcon = NULL;\r
+       }\r
+       \r
+       // Create a thread which waits for incoming pipe connections \r
+       hPipeThread = CreateThread( \r
+               NULL,              // no security attribute \r
+               0,                 // default stack size \r
+               PipeThread, \r
+               (LPVOID) &bRun,    // thread parameter \r
+               0,                 // not suspended \r
+               &dwThreadId);      // returns thread ID \r
+\r
+       if (hPipeThread == NULL) \r
+       {\r
+               //OutputDebugStringA("TSVNCache: Could not create pipe thread\n");\r
+               //DebugOutputLastError();\r
+               return 0;\r
+       }\r
+       else CloseHandle(hPipeThread); \r
+\r
+       // Create a thread which waits for incoming pipe connections \r
+       hCommandWaitThread = CreateThread( \r
+               NULL,              // no security attribute \r
+               0,                 // default stack size \r
+               CommandWaitThread, \r
+               (LPVOID) &bRun,    // thread parameter \r
+               0,                 // not suspended \r
+               &dwThreadId);      // returns thread ID \r
+\r
+       if (hCommandWaitThread == NULL) \r
+       {\r
+               //OutputDebugStringA("TSVNCache: Could not create command wait thread\n");\r
+               //DebugOutputLastError();\r
+               return 0;\r
+       }\r
+       else CloseHandle(hCommandWaitThread); \r
+\r
+\r
+       // loop to handle window messages.\r
+       BOOL bLoopRet;\r
+       while (bRun)\r
+       {\r
+               bLoopRet = GetMessage(&msg, NULL, 0, 0);\r
+               if ((bLoopRet != -1)&&(bLoopRet != 0))\r
+               {\r
+                       DispatchMessage(&msg);\r
+               }\r
+       }\r
+\r
+       bRun = false;\r
+\r
+       Shell_NotifyIcon(NIM_DELETE,&niData);\r
+       CSVNStatusCache::Destroy();\r
+       g_SVNAdminDir.Close();\r
+       apr_terminate();\r
+\r
+       return 0;\r
+}\r
+\r
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)\r
+{\r
+       switch (message) \r
+       {\r
+       case TRAY_CALLBACK:\r
+       {\r
+               switch(lParam)\r
+               {\r
+               case WM_LBUTTONDBLCLK:\r
+                       if (IsWindowVisible(hWnd))\r
+                               ShowWindow(hWnd, SW_HIDE);\r
+                       else\r
+                               ShowWindow(hWnd, SW_RESTORE);\r
+                       break;\r
+               case WM_MOUSEMOVE:\r
+                       {\r
+                               CString sInfoTip;\r
+                               NOTIFYICONDATA SystemTray;\r
+                               sInfoTip.Format(_T("Cached Directories : %ld\nWatched paths : %ld"), \r
+                                       CSVNStatusCache::Instance().GetCacheSize(),\r
+                                       CSVNStatusCache::Instance().GetNumberOfWatchedPaths());\r
+\r
+                               SystemTray.cbSize = sizeof(NOTIFYICONDATA);\r
+                               SystemTray.hWnd   = hTrayWnd;\r
+                               SystemTray.uID    = TRAY_ID;\r
+                               SystemTray.uFlags = NIF_TIP;\r
+                               _tcscpy_s(SystemTray.szTip, sInfoTip);\r
+                               Shell_NotifyIcon(NIM_MODIFY, &SystemTray);\r
+                       }\r
+                       break;\r
+               case WM_RBUTTONUP:\r
+               case WM_CONTEXTMENU:\r
+                       {\r
+                               POINT pt;\r
+                               DWORD ptW = GetMessagePos();\r
+                               pt.x = GET_X_LPARAM(ptW);\r
+                               pt.y = GET_Y_LPARAM(ptW);\r
+                               HMENU hMenu = CreatePopupMenu();\r
+                               if(hMenu)\r
+                               {\r
+                                       InsertMenu(hMenu, (UINT)-1, MF_BYPOSITION, TRAYPOP_EXIT, _T("Exit"));\r
+                                       SetForegroundWindow(hWnd);\r
+                                       TrackPopupMenu(hMenu, TPM_BOTTOMALIGN, pt.x, pt.y, 0, hWnd, NULL);\r
+                                       DestroyMenu(hMenu);\r
+                               }\r
+                       }\r
+                       break;\r
+               }\r
+       }\r
+       break;\r
+       case WM_PAINT:\r
+               {\r
+                       PAINTSTRUCT ps;\r
+                       HDC hdc = BeginPaint(hWnd, &ps);\r
+                       RECT rect;\r
+                       GetClientRect(hWnd, &rect);\r
+                       // clear the background\r
+                       HBRUSH background = CreateSolidBrush(::GetSysColor(COLOR_WINDOW));\r
+                       HGDIOBJ oldbrush = SelectObject(hdc, background);\r
+                       FillRect(hdc, &rect, background);\r
+\r
+                       int line = 0;\r
+                       SIZE fontsize = {0};\r
+                       AutoLocker print(critSec);\r
+                       GetTextExtentPoint32( hdc, szCurrentCrawledPath[0], (int)_tcslen(szCurrentCrawledPath[0]), &fontsize );\r
+                       for (int i=nCurrentCrawledpathIndex; i<MAX_CRAWLEDPATHS; ++i)\r
+                       {\r
+                               TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));\r
+                               line++;\r
+                       }\r
+                       for (int i=0; i<nCurrentCrawledpathIndex; ++i)\r
+                       {\r
+                               TextOut(hdc, 0, line*fontsize.cy, szCurrentCrawledPath[i], (int)_tcslen(szCurrentCrawledPath[i]));\r
+                               line++;\r
+                       }\r
+                       \r
+                       \r
+                       SelectObject(hdc,oldbrush);\r
+                       EndPaint(hWnd, &ps); \r
+                       DeleteObject(background);\r
+                       return 0L; \r
+               }\r
+               break;\r
+       case WM_COMMAND:\r
+               {\r
+                       WORD wmId    = LOWORD(wParam);\r
+\r
+                       switch (wmId)\r
+                       {\r
+                       case TRAYPOP_EXIT:\r
+                               DestroyWindow(hWnd);\r
+                               break;\r
+                       }\r
+                       return 1;\r
+               }\r
+       case WM_QUERYENDSESSION:\r
+               {\r
+                       ATLTRACE("WM_QUERYENDSESSION\n");\r
+                       if (CSVNStatusCache::Instance().WaitToWrite(200))\r
+                       {\r
+                               CSVNStatusCache::Instance().Stop();\r
+                               CSVNStatusCache::Instance().Done();\r
+                       }\r
+                       return TRUE;\r
+               }\r
+               break;\r
+       case WM_CLOSE:\r
+       case WM_ENDSESSION:\r
+       case WM_DESTROY:\r
+       case WM_QUIT:\r
+               {\r
+                       ATLTRACE("WM_CLOSE/DESTROY/ENDSESSION/QUIT\n");\r
+                       CSVNStatusCache::Instance().WaitToWrite();\r
+                       CSVNStatusCache::Instance().Stop();\r
+                       CSVNStatusCache::Instance().SaveCache();\r
+                       if (message != WM_QUIT)\r
+                               PostQuitMessage(0);\r
+                       bRun = false;\r
+                       return 1;\r
+               }\r
+               break;\r
+       case WM_DEVICECHANGE:\r
+               {\r
+                       DEV_BROADCAST_HDR * phdr = (DEV_BROADCAST_HDR*)lParam;\r
+                       switch (wParam)\r
+                       {\r
+                       case DBT_CUSTOMEVENT:\r
+                               {\r
+                                       ATLTRACE("WM_DEVICECHANGE with DBT_CUSTOMEVENT\n");\r
+                                       if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)\r
+                                       {\r
+                                               DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;\r
+                                               if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_DISMOUNT))\r
+                                               {\r
+                                                       ATLTRACE("Device to be dismounted\n");\r
+                                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                                       CSVNStatusCache::Instance().CloseWatcherHandles(phandle->dbch_hdevnotify);\r
+                                                       CSVNStatusCache::Instance().Done();\r
+                                               }\r
+                                               if (IsEqualGUID(phandle->dbch_eventguid, GUID_IO_VOLUME_LOCK))\r
+                                               {\r
+                                                       ATLTRACE("Device lock event\n");\r
+                                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                                       CSVNStatusCache::Instance().CloseWatcherHandles(phandle->dbch_hdevnotify);\r
+                                                       CSVNStatusCache::Instance().Done();\r
+                                               }\r
+                                       }\r
+                               }\r
+                               break;\r
+                       case DBT_DEVICEREMOVEPENDING:\r
+                               ATLTRACE("WM_DEVICECHANGE with DBT_DEVICEREMOVEPENDING\n");\r
+                               if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)\r
+                               {\r
+                                       DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(phandle->dbch_hdevnotify);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               else\r
+                               {\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               break;\r
+                       case DBT_DEVICEQUERYREMOVE:\r
+                               ATLTRACE("WM_DEVICECHANGE with DBT_DEVICEQUERYREMOVE\n");\r
+                               if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)\r
+                               {\r
+                                       DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(phandle->dbch_hdevnotify);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               else\r
+                               {\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               break;\r
+                       case DBT_DEVICEREMOVECOMPLETE:\r
+                               ATLTRACE("WM_DEVICECHANGE with DBT_DEVICEREMOVECOMPLETE\n");\r
+                               if (phdr->dbch_devicetype == DBT_DEVTYP_HANDLE)\r
+                               {\r
+                                       DEV_BROADCAST_HANDLE * phandle = (DEV_BROADCAST_HANDLE*)lParam;\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(phandle->dbch_hdevnotify);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               else\r
+                               {\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(INVALID_HANDLE_VALUE);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               break;\r
+                       }\r
+               }\r
+               break;\r
+       default:\r
+               break;\r
+       }\r
+       return DefWindowProc(hWnd, message, wParam, lParam);\r
+}\r
+\r
+//////////////////////////////////////////////////////////////////////////\r
+\r
+VOID GetAnswerToRequest(const TSVNCacheRequest* pRequest, TSVNCacheResponse* pReply, DWORD* pResponseLength)\r
+{\r
+       CTSVNPath path;\r
+       *pResponseLength = 0;\r
+       if(pRequest->flags & TSVNCACHE_FLAGS_FOLDERISKNOWN)\r
+       {\r
+               path.SetFromWin(pRequest->path, !!(pRequest->flags & TSVNCACHE_FLAGS_ISFOLDER));\r
+       }\r
+       else\r
+       {\r
+               path.SetFromWin(pRequest->path);\r
+       }\r
+\r
+       if (CSVNStatusCache::Instance().WaitToRead(2000))\r
+       {\r
+               CSVNStatusCache::Instance().GetStatusForPath(path, pRequest->flags, false).BuildCacheResponse(*pReply, *pResponseLength);\r
+               CSVNStatusCache::Instance().Done();\r
+       }\r
+       else\r
+       {\r
+               CStatusCacheEntry entry;\r
+               entry.BuildCacheResponse(*pReply, *pResponseLength);\r
+       }\r
+}\r
+\r
+DWORD WINAPI PipeThread(LPVOID lpvParam)\r
+{\r
+       ATLTRACE("PipeThread started\n");\r
+       bool * bRun = (bool *)lpvParam;\r
+       // The main loop creates an instance of the named pipe and \r
+       // then waits for a client to connect to it. When the client \r
+       // connects, a thread is created to handle communications \r
+       // with that client, and the loop is repeated. \r
+       DWORD dwThreadId; \r
+       BOOL fConnected;\r
+       HANDLE hPipe = INVALID_HANDLE_VALUE;\r
+       HANDLE hInstanceThread = INVALID_HANDLE_VALUE;\r
+\r
+       while (*bRun) \r
+       { \r
+               hPipe = CreateNamedPipe( \r
+                       GetCachePipeName(),\r
+                       PIPE_ACCESS_DUPLEX,       // read/write access \r
+                       PIPE_TYPE_MESSAGE |       // message type pipe \r
+                       PIPE_READMODE_MESSAGE |   // message-read mode \r
+                       PIPE_WAIT,                // blocking mode \r
+                       PIPE_UNLIMITED_INSTANCES, // max. instances  \r
+                       BUFSIZE,                  // output buffer size \r
+                       BUFSIZE,                  // input buffer size \r
+                       NMPWAIT_USE_DEFAULT_WAIT, // client time-out \r
+                       NULL);                                    // NULL DACL\r
+\r
+               if (hPipe == INVALID_HANDLE_VALUE) \r
+               {\r
+                       //OutputDebugStringA("TSVNCache: CreatePipe failed\n");\r
+                       //DebugOutputLastError();\r
+                       if (*bRun)\r
+                               Sleep(200);\r
+                       continue; // never leave the thread!\r
+               }\r
+\r
+               // Wait for the client to connect; if it succeeds, \r
+               // the function returns a nonzero value. If the function returns \r
+               // zero, GetLastError returns ERROR_PIPE_CONNECTED. \r
+               fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); \r
+               if (fConnected) \r
+               { \r
+                       // Create a thread for this client. \r
+                       hInstanceThread = CreateThread( \r
+                               NULL,              // no security attribute \r
+                               0,                 // default stack size \r
+                               InstanceThread, \r
+                               (LPVOID) hPipe,    // thread parameter \r
+                               0,                 // not suspended \r
+                               &dwThreadId);      // returns thread ID \r
+\r
+                       if (hInstanceThread == NULL) \r
+                       {\r
+                               //OutputDebugStringA("TSVNCache: Could not create Instance thread\n");\r
+                               //DebugOutputLastError();\r
+                               DisconnectNamedPipe(hPipe);\r
+                               CloseHandle(hPipe);\r
+                               // since we're now closing this thread, we also have to close the whole application!\r
+                               // otherwise the thread is dead, but the app is still running, refusing new instances\r
+                               // but no pipe will be available anymore.\r
+                               PostMessage(hWnd, WM_CLOSE, 0, 0);\r
+                               return 1;\r
+                       }\r
+                       else CloseHandle(hInstanceThread); \r
+               } \r
+               else\r
+               {\r
+                       // The client could not connect, so close the pipe. \r
+                       //OutputDebugStringA("TSVNCache: ConnectNamedPipe failed\n");\r
+                       //DebugOutputLastError();\r
+                       CloseHandle(hPipe); \r
+                       if (*bRun)\r
+                               Sleep(200);\r
+                       continue;       // don't end the thread!\r
+               }\r
+       }\r
+       ATLTRACE("Pipe thread exited\n");\r
+       return 0;\r
+}\r
+\r
+DWORD WINAPI CommandWaitThread(LPVOID lpvParam)\r
+{\r
+       ATLTRACE("CommandWaitThread started\n");\r
+       bool * bRun = (bool *)lpvParam;\r
+       // The main loop creates an instance of the named pipe and \r
+       // then waits for a client to connect to it. When the client \r
+       // connects, a thread is created to handle communications \r
+       // with that client, and the loop is repeated. \r
+       DWORD dwThreadId; \r
+       BOOL fConnected;\r
+       HANDLE hPipe = INVALID_HANDLE_VALUE;\r
+       HANDLE hCommandThread = INVALID_HANDLE_VALUE;\r
+\r
+       while (*bRun) \r
+       { \r
+               hPipe = CreateNamedPipe( \r
+                       GetCacheCommandPipeName(),\r
+                       PIPE_ACCESS_DUPLEX,       // read/write access \r
+                       PIPE_TYPE_MESSAGE |       // message type pipe \r
+                       PIPE_READMODE_MESSAGE |   // message-read mode \r
+                       PIPE_WAIT,                // blocking mode \r
+                       PIPE_UNLIMITED_INSTANCES, // max. instances  \r
+                       BUFSIZE,                  // output buffer size \r
+                       BUFSIZE,                  // input buffer size \r
+                       NMPWAIT_USE_DEFAULT_WAIT, // client time-out \r
+                       NULL);                // NULL DACL\r
+\r
+               if (hPipe == INVALID_HANDLE_VALUE) \r
+               {\r
+                       //OutputDebugStringA("TSVNCache: CreatePipe failed\n");\r
+                       //DebugOutputLastError();\r
+                       if (*bRun)\r
+                               Sleep(200);\r
+                       continue; // never leave the thread!\r
+               }\r
+\r
+               // Wait for the client to connect; if it succeeds, \r
+               // the function returns a nonzero value. If the function returns \r
+               // zero, GetLastError returns ERROR_PIPE_CONNECTED. \r
+               fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); \r
+               if (fConnected) \r
+               { \r
+                       // Create a thread for this client. \r
+                       hCommandThread = CreateThread( \r
+                               NULL,              // no security attribute \r
+                               0,                 // default stack size \r
+                               CommandThread, \r
+                               (LPVOID) hPipe,    // thread parameter \r
+                               0,                 // not suspended \r
+                               &dwThreadId);      // returns thread ID \r
+\r
+                       if (hCommandThread == NULL) \r
+                       {\r
+                               //OutputDebugStringA("TSVNCache: Could not create Command thread\n");\r
+                               //DebugOutputLastError();\r
+                               DisconnectNamedPipe(hPipe);\r
+                               CloseHandle(hPipe);\r
+                               // since we're now closing this thread, we also have to close the whole application!\r
+                               // otherwise the thread is dead, but the app is still running, refusing new instances\r
+                               // but no pipe will be available anymore.\r
+                               PostMessage(hWnd, WM_CLOSE, 0, 0);\r
+                               return 1;\r
+                       }\r
+                       else CloseHandle(hCommandThread); \r
+               } \r
+               else\r
+               {\r
+                       // The client could not connect, so close the pipe. \r
+                       //OutputDebugStringA("TSVNCache: ConnectNamedPipe failed\n");\r
+                       //DebugOutputLastError();\r
+                       CloseHandle(hPipe); \r
+                       if (*bRun)\r
+                               Sleep(200);\r
+                       continue;       // don't end the thread!\r
+               }\r
+       }\r
+       ATLTRACE("CommandWait thread exited\n");\r
+       return 0;\r
+}\r
+\r
+DWORD WINAPI InstanceThread(LPVOID lpvParam) \r
+{ \r
+       ATLTRACE("InstanceThread started\n");\r
+       TSVNCacheResponse response; \r
+       DWORD cbBytesRead, cbWritten; \r
+       BOOL fSuccess; \r
+       HANDLE hPipe; \r
+\r
+       // The thread's parameter is a handle to a pipe instance. \r
+\r
+       hPipe = (HANDLE) lpvParam; \r
+       InterlockedIncrement(&nThreadCount);\r
+       while (bRun) \r
+       { \r
+               // Read client requests from the pipe. \r
+               TSVNCacheRequest request;\r
+               fSuccess = ReadFile( \r
+                       hPipe,        // handle to pipe \r
+                       &request,    // buffer to receive data \r
+                       sizeof(request), // size of buffer \r
+                       &cbBytesRead, // number of bytes read \r
+                       NULL);        // not overlapped I/O \r
+\r
+               if (! fSuccess || cbBytesRead == 0)\r
+               {\r
+                       DisconnectNamedPipe(hPipe); \r
+                       CloseHandle(hPipe); \r
+                       ATLTRACE("Instance thread exited\n");\r
+                       InterlockedDecrement(&nThreadCount);\r
+                       if (nThreadCount == 0)\r
+                               PostMessage(hWnd, WM_CLOSE, 0, 0);\r
+                       return 1;\r
+               }\r
+\r
+               DWORD responseLength;\r
+               GetAnswerToRequest(&request, &response, &responseLength); \r
+\r
+               // Write the reply to the pipe. \r
+               fSuccess = WriteFile( \r
+                       hPipe,        // handle to pipe \r
+                       &response,      // buffer to write from \r
+                       responseLength, // number of bytes to write \r
+                       &cbWritten,   // number of bytes written \r
+                       NULL);        // not overlapped I/O \r
+\r
+               if (! fSuccess || responseLength != cbWritten)\r
+               {\r
+                       DisconnectNamedPipe(hPipe); \r
+                       CloseHandle(hPipe); \r
+                       ATLTRACE("Instance thread exited\n");\r
+                       InterlockedDecrement(&nThreadCount);\r
+                       if (nThreadCount == 0)\r
+                               PostMessage(hWnd, WM_CLOSE, 0, 0);\r
+                       return 1;\r
+               }\r
+       } \r
+\r
+       // Flush the pipe to allow the client to read the pipe's contents \r
+       // before disconnecting. Then disconnect the pipe, and close the \r
+       // handle to this pipe instance. \r
+\r
+       FlushFileBuffers(hPipe); \r
+       DisconnectNamedPipe(hPipe); \r
+       CloseHandle(hPipe); \r
+       ATLTRACE("Instance thread exited\n");\r
+       InterlockedDecrement(&nThreadCount);\r
+       if (nThreadCount == 0)\r
+               PostMessage(hWnd, WM_CLOSE, 0, 0);\r
+       return 0;\r
+}\r
+\r
+DWORD WINAPI CommandThread(LPVOID lpvParam) \r
+{ \r
+       ATLTRACE("CommandThread started\n");\r
+       DWORD cbBytesRead; \r
+       BOOL fSuccess; \r
+       HANDLE hPipe; \r
+\r
+       // The thread's parameter is a handle to a pipe instance. \r
+\r
+       hPipe = (HANDLE) lpvParam; \r
+\r
+       while (bRun) \r
+       { \r
+               // Read client requests from the pipe. \r
+               TSVNCacheCommand command;\r
+               fSuccess = ReadFile( \r
+                       hPipe,                          // handle to pipe \r
+                       &command,                       // buffer to receive data \r
+                       sizeof(command),        // size of buffer \r
+                       &cbBytesRead,           // number of bytes read \r
+                       NULL);                          // not overlapped I/O \r
+\r
+               if (! fSuccess || cbBytesRead == 0)\r
+               {\r
+                       DisconnectNamedPipe(hPipe); \r
+                       CloseHandle(hPipe); \r
+                       ATLTRACE("Command thread exited\n");\r
+                       return 1;\r
+               }\r
+               \r
+               switch (command.command)\r
+               {\r
+                       case TSVNCACHECOMMAND_END:\r
+                               FlushFileBuffers(hPipe); \r
+                               DisconnectNamedPipe(hPipe); \r
+                               CloseHandle(hPipe); \r
+                               ATLTRACE("Command thread exited\n");\r
+                               return 0;\r
+                       case TSVNCACHECOMMAND_CRAWL:\r
+                               {\r
+                                       CTSVNPath changedpath;\r
+                                       changedpath.SetFromWin(CString(command.path), true);\r
+                                       // remove the path from our cache - that will 'invalidate' it.\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().RemoveCacheForPath(changedpath);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                                       CSVNStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory());\r
+                               }\r
+                               break;\r
+                       case TSVNCACHECOMMAND_REFRESHALL:\r
+                               CSVNStatusCache::Instance().WaitToWrite();\r
+                               CSVNStatusCache::Instance().Refresh();\r
+                               CSVNStatusCache::Instance().Done();\r
+                               break;\r
+                       case TSVNCACHECOMMAND_RELEASE:\r
+                               {\r
+                                       CTSVNPath changedpath;\r
+                                       changedpath.SetFromWin(CString(command.path), true);\r
+                                       ATLTRACE(_T("release handle for path %s\n"), changedpath.GetWinPath());\r
+                                       CSVNStatusCache::Instance().WaitToWrite();\r
+                                       CSVNStatusCache::Instance().CloseWatcherHandles(changedpath);\r
+                                       CSVNStatusCache::Instance().RemoveCacheForPath(changedpath);\r
+                                       CSVNStatusCache::Instance().Done();\r
+                               }\r
+                               break;\r
+\r
+               }\r
+       } \r
+\r
+       // Flush the pipe to allow the client to read the pipe's contents \r
+       // before disconnecting. Then disconnect the pipe, and close the \r
+       // handle to this pipe instance. \r
+\r
+       FlushFileBuffers(hPipe); \r
+       DisconnectNamedPipe(hPipe); \r
+       CloseHandle(hPipe); \r
+       ATLTRACE("Command thread exited\n");\r
+       return 0;\r
+}\r
diff --git a/src/TGitCache/TSVNCache.h b/src/TGitCache/TSVNCache.h
new file mode 100644 (file)
index 0000000..6efc900
--- /dev/null
@@ -0,0 +1,36 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// External Cache Copyright (C) 2005 - 2006 - Will Dean, Stefan Kueng\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#pragma once\r
+\r
+#include "..\version.h"\r
+\r
+#define BUFSIZE 4096\r
+#define MAX_CRAWLEDPATHS 15\r
+#define MAX_CRAWLEDPATHSLEN (MAX_PATH * 2)\r
+\r
+extern HWND                            hWnd;\r
+extern TCHAR                   szCurrentCrawledPath[MAX_CRAWLEDPATHS][MAX_CRAWLEDPATHSLEN];\r
+\r
+extern int nCurrentCrawledpathIndex;\r
+extern CComAutoCriticalSection critSec;\r
+\r
+#define TRAY_CALLBACK  (WM_APP + 1)\r
+#define TRAYPOP_EXIT   (WM_APP + 1)\r
+#define TRAY_ID                        101\r
+\r
diff --git a/src/TGitCache/TSVNCache.rc b/src/TGitCache/TSVNCache.rc
new file mode 100644 (file)
index 0000000..ad6519a
--- /dev/null
@@ -0,0 +1,86 @@
+// Microsoft Visual C++ generated resource script.\r
+//\r
+#include "resource.h"\r
+\r
+#define APSTUDIO_READONLY_SYMBOLS\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Generated from the TEXTINCLUDE 2 resource.\r
+//\r
+#include "afxres.h"\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+#undef APSTUDIO_READONLY_SYMBOLS\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+// Neutral resources\r
+\r
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU)\r
+#ifdef _WIN32\r
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\r
+#pragma code_page(1252)\r
+#endif //_WIN32\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Icon\r
+//\r
+\r
+// Icon with lowest ID value placed first to ensure application icon\r
+// remains consistent on all systems.\r
+IDI_TSVNCACHE           ICON                    "..\\Resources\\TortoiseCache.ico"\r
+#endif    // Neutral resources\r
+/////////////////////////////////////////////////////////////////////////////\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+// English (U.S.) resources\r
+\r
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r
+#ifdef _WIN32\r
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US\r
+#pragma code_page(1252)\r
+#endif //_WIN32\r
+\r
+#ifdef APSTUDIO_INVOKED\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// TEXTINCLUDE\r
+//\r
+\r
+1 TEXTINCLUDE \r
+BEGIN\r
+    "resource.h\0"\r
+END\r
+\r
+2 TEXTINCLUDE \r
+BEGIN\r
+    "#include ""afxres.h""\r\n"\r
+    "\0"\r
+END\r
+\r
+3 TEXTINCLUDE \r
+BEGIN\r
+    "\r\n"\r
+    "#include ""TSVNCache.rc2""  // non-Microsoft Visual C++ edited resources\r\n"\r
+    "\0"\r
+END\r
+\r
+#endif    // APSTUDIO_INVOKED\r
+\r
+#endif    // English (U.S.) resources\r
+/////////////////////////////////////////////////////////////////////////////\r
+\r
+\r
+\r
+#ifndef APSTUDIO_INVOKED\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Generated from the TEXTINCLUDE 3 resource.\r
+//\r
+\r
+#include "TSVNCache.rc2"  // non-Microsoft Visual C++ edited resources\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+#endif    // not APSTUDIO_INVOKED\r
+\r
diff --git a/src/TGitCache/TSVNCache.rc2 b/src/TGitCache/TSVNCache.rc2
new file mode 100644 (file)
index 0000000..dc2d534
--- /dev/null
@@ -0,0 +1,50 @@
+//\r
+// TSVNCache.RC2 - resources Microsoft Visual C++ does not edit directly\r
+//\r
+\r
+#ifdef APSTUDIO_INVOKED\r
+#error this file is not editable by Microsoft Visual C++\r
+#endif //APSTUDIO_INVOKED\r
+\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+// Add manually edited resources here...\r
+/////////////////////////////////////////////////////////////////////////////\r
+//\r
+// Version\r
+//\r
+#include "..\version.h"\r
+VS_VERSION_INFO VERSIONINFO\r
+ FILEVERSION FILEVER\r
+ PRODUCTVERSION PRODUCTVER\r
+ FILEFLAGSMASK 0x3fL\r
+#ifdef _DEBUG\r
+ FILEFLAGS 0x1L\r
+#else\r
+ FILEFLAGS 0x0L\r
+#endif\r
+ FILEOS 0x4L\r
+ FILETYPE VFT_APP\r
+ FILESUBTYPE 0x0L\r
+BEGIN\r
+    BLOCK "StringFileInfo"\r
+    BEGIN\r
+        BLOCK "040004e4"\r
+        BEGIN\r
+            VALUE "CompanyName", "http://tortoisesvn.net\0"\r
+            VALUE "FileDescription", "TortoiseSVN status cache\0"\r
+            VALUE "FileVersion", STRFILEVER\r
+            VALUE "InternalName", "TSVNCache.exe\0"\r
+            VALUE "LegalCopyright", "Copyright (C) 2003-2008 - TortoiseSVN\0"\r
+            VALUE "OriginalFilename", "TSVNCache.exe\0"\r
+            VALUE "ProductName", "TortoiseSVN\0"\r
+            VALUE "ProductVersion", STRPRODUCTVER\r
+        END\r
+    END\r
+    BLOCK "VarFileInfo"\r
+    BEGIN\r
+        VALUE "Translation", 0x400, 1252\r
+    END\r
+END\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
diff --git a/src/TGitCache/TSVNCache.vcproj b/src/TGitCache/TSVNCache.vcproj
new file mode 100644 (file)
index 0000000..2b3fe8c
--- /dev/null
@@ -0,0 +1,575 @@
+<?xml version="1.0" encoding="Windows-1252"?>\r
+<VisualStudioProject\r
+       ProjectType="Visual C++"\r
+       Version="9.00"\r
+       Name="TSVNCache"\r
+       ProjectGUID="{A00D2EDC-B4F5-4B41-94BA-13961DE5D296}"\r
+       RootNamespace="TSVNCache"\r
+       Keyword="Win32Proj"\r
+       TargetFrameworkVersion="131072"\r
+       >\r
+       <Platforms>\r
+               <Platform\r
+                       Name="Win32"\r
+               />\r
+               <Platform\r
+                       Name="x64"\r
+               />\r
+       </Platforms>\r
+       <ToolFiles>\r
+       </ToolFiles>\r
+       <Configurations>\r
+               <Configuration\r
+                       Name="Debug|Win32"\r
+                       OutputDirectory="..\..\Bin\Debug\Bin"\r
+                       IntermediateDirectory="..\..\obj\TSVNCache\Debug"\r
+                       ConfigurationType="1"\r
+                       UseOfATL="1"\r
+                       CharacterSet="1"\r
+                       >\r
+                       <Tool\r
+                               Name="VCPreBuildEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCustomBuildTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXMLDataGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebServiceProxyGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCMIDLTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCLCompilerTool"\r
+                               Optimization="0"\r
+                               AdditionalIncludeDirectories="&quot;../../ext/apr-util/include&quot;;&quot;../../ext/apr-util/xml/expat/lib&quot;;../../ext/Subversion/subversion/include;../../ext/apr/include;../Utils;..\TortoiseShell;..\SVN;..\..\ext\gettext\include"\r
+                               PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;UNICODE;_UNICODE"\r
+                               MinimalRebuild="true"\r
+                               BasicRuntimeChecks="3"\r
+                               SmallerTypeCheck="true"\r
+                               RuntimeLibrary="2"\r
+                               TreatWChar_tAsBuiltInType="true"\r
+                               ForceConformanceInForLoopScope="true"\r
+                               UsePrecompiledHeader="2"\r
+                               WarningLevel="4"\r
+                               DebugInformationFormat="4"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManagedResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPreLinkEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCLinkerTool"\r
+                               IgnoreImportLibrary="true"\r
+                               AdditionalDependencies="Crypt32.lib ../../ext/Subversion\debug_win32\libsvn_diff-nonet.lib ../../ext/Subversion\debug_win32\libsvn_ra-nonet.lib ../../ext/Subversion\debug_win32\libsvn_wc-nonet.lib ../../ext/Subversion\debug_win32\libsvn_subr-nonet.lib ../../ext/Subversion\debug_win32\libsvn_client-nonet.lib ../../ext/Subversion\debug_win32\libsvn_delta-nonet.lib ../../ext/apr\debug_win32\libapr_tsvn.lib ../../ext/apr-util\debug_win32\libaprutil_tsvn.lib ../../ext/apr-util\xml\expat\lib\debug_win32\xml.lib ../../ext/libintl/libintl3-win32/lib/intl3_tsvn.lib"\r
+                               OutputFile="$(OutDir)/TSVNCache.exe"\r
+                               LinkIncremental="2"\r
+                               IgnoreDefaultLibraryNames="LIBC"\r
+                               GenerateDebugInformation="true"\r
+                               ProgramDatabaseFile="$(OutDir)/TSVNCache.pdb"\r
+                               SubSystem="2"\r
+                               RandomizedBaseAddress="1"\r
+                               DataExecutionPrevention="0"\r
+                               TargetMachine="1"\r
+                       />\r
+                       <Tool\r
+                               Name="VCALinkTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManifestTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXDCMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCBscMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCFxCopTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCAppVerifierTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPostBuildEventTool"\r
+                       />\r
+               </Configuration>\r
+               <Configuration\r
+                       Name="Debug|x64"\r
+                       OutputDirectory="..\..\Bin\Debug64\Bin"\r
+                       IntermediateDirectory="..\..\obj\TSVNCache\Debug64"\r
+                       ConfigurationType="1"\r
+                       UseOfATL="1"\r
+                       CharacterSet="1"\r
+                       >\r
+                       <Tool\r
+                               Name="VCPreBuildEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCustomBuildTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXMLDataGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebServiceProxyGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCMIDLTool"\r
+                               TargetEnvironment="3"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCLCompilerTool"\r
+                               Optimization="0"\r
+                               AdditionalIncludeDirectories="&quot;../../ext/apr-util/include&quot;;&quot;../../ext/apr-util/xml/expat/lib&quot;;../../ext/Subversion/subversion/include;../../ext/apr/include;../Utils;..\TortoiseShell;..\SVN;..\..\ext\gettext\include"\r
+                               PreprocessorDefinitions="WIN64;_DEBUG;_CONSOLE;UNICODE;_UNICODE"\r
+                               MinimalRebuild="true"\r
+                               BasicRuntimeChecks="3"\r
+                               SmallerTypeCheck="true"\r
+                               RuntimeLibrary="2"\r
+                               TreatWChar_tAsBuiltInType="true"\r
+                               ForceConformanceInForLoopScope="true"\r
+                               UsePrecompiledHeader="2"\r
+                               WarningLevel="4"\r
+                               DebugInformationFormat="3"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManagedResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPreLinkEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCLinkerTool"\r
+                               IgnoreImportLibrary="true"\r
+                               AdditionalDependencies="Crypt32.lib ../../ext/Subversion\debug_x64\libsvn_diff-nonet.lib ../../ext/Subversion\debug_x64\libsvn_ra-nonet.lib ../../ext/Subversion\debug_x64\libsvn_wc-nonet.lib ../../ext/Subversion\debug_x64\libsvn_subr-nonet.lib ../../ext/Subversion\debug_x64\libsvn_client-nonet.lib ../../ext/Subversion\debug_x64\libsvn_delta-nonet.lib ../../ext/apr\debug_x64\libapr_tsvn.lib ../../ext/apr-util\debug_x64\libaprutil_tsvn.lib ../../ext/apr-util\xml\expat\lib\debug_x64\xml.lib ../../ext/libintl/libintl3-x64/lib/intl3_tsvn.lib"\r
+                               OutputFile="$(OutDir)/TSVNCache.exe"\r
+                               LinkIncremental="2"\r
+                               IgnoreDefaultLibraryNames="LIBC"\r
+                               GenerateDebugInformation="true"\r
+                               ProgramDatabaseFile="$(OutDir)/TSVNCache.pdb"\r
+                               SubSystem="2"\r
+                               RandomizedBaseAddress="1"\r
+                               DataExecutionPrevention="0"\r
+                               TargetMachine="17"\r
+                       />\r
+                       <Tool\r
+                               Name="VCALinkTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManifestTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXDCMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCBscMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCFxCopTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCAppVerifierTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPostBuildEventTool"\r
+                       />\r
+               </Configuration>\r
+               <Configuration\r
+                       Name="Release|Win32"\r
+                       OutputDirectory="..\..\bin\Release\bin"\r
+                       IntermediateDirectory="..\..\obj\TSVNCache\Release"\r
+                       ConfigurationType="1"\r
+                       UseOfATL="1"\r
+                       CharacterSet="1"\r
+                       WholeProgramOptimization="1"\r
+                       >\r
+                       <Tool\r
+                               Name="VCPreBuildEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCustomBuildTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXMLDataGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebServiceProxyGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCMIDLTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCLCompilerTool"\r
+                               InlineFunctionExpansion="2"\r
+                               AdditionalIncludeDirectories="&quot;../../ext/apr-util/include&quot;;&quot;../../ext/apr-util/xml/expat/lib&quot;;../../ext/Subversion/subversion/include;../../ext/apr/include;../Utils;..\TortoiseShell;..\SVN;..\..\ext\gettext\include"\r
+                               PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;UNICODE;_UNICODE"\r
+                               RuntimeLibrary="2"\r
+                               EnableFunctionLevelLinking="true"\r
+                               TreatWChar_tAsBuiltInType="true"\r
+                               ForceConformanceInForLoopScope="true"\r
+                               UsePrecompiledHeader="2"\r
+                               WarningLevel="4"\r
+                               DebugInformationFormat="3"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManagedResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPreLinkEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCLinkerTool"\r
+                               AdditionalDependencies="Crypt32.lib ../../ext/Subversion\release_win32\libsvn_diff-nonet.lib ../../ext/Subversion\release_win32\libsvn_delta-nonet.lib ../../ext/Subversion\release_win32\libsvn_wc-nonet.lib ../../ext/Subversion\release_win32\libsvn_subr-nonet.lib ../../ext/Subversion\release_win32\libsvn_client-nonet.lib ../../ext/Subversion\release_win32\libsvn_ra-nonet.lib ../../ext/apr\release_win32\libapr_tsvn.lib ../../ext/apr-util\release_win32\libaprutil_tsvn.lib ../../ext/apr-util\xml\expat\lib\release_win32\xml.lib ../../ext/libintl/libintl3-win32/lib/intl3_tsvn.lib"\r
+                               OutputFile="$(OutDir)/TSVNCache.exe"\r
+                               LinkIncremental="1"\r
+                               GenerateDebugInformation="true"\r
+                               SubSystem="2"\r
+                               OptimizeReferences="2"\r
+                               EnableCOMDATFolding="2"\r
+                               OptimizeForWindows98="0"\r
+                               RandomizedBaseAddress="1"\r
+                               DataExecutionPrevention="0"\r
+                               TargetMachine="1"\r
+                       />\r
+                       <Tool\r
+                               Name="VCALinkTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManifestTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXDCMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCBscMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCFxCopTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCAppVerifierTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPostBuildEventTool"\r
+                       />\r
+               </Configuration>\r
+               <Configuration\r
+                       Name="Release|x64"\r
+                       OutputDirectory="..\..\bin\Release64\bin"\r
+                       IntermediateDirectory="..\..\obj\TSVNCache\Release64"\r
+                       ConfigurationType="1"\r
+                       UseOfATL="1"\r
+                       CharacterSet="1"\r
+                       WholeProgramOptimization="1"\r
+                       >\r
+                       <Tool\r
+                               Name="VCPreBuildEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCustomBuildTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXMLDataGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCWebServiceProxyGeneratorTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCMIDLTool"\r
+                               TargetEnvironment="3"\r
+                       />\r
+                       <Tool\r
+                               Name="VCCLCompilerTool"\r
+                               InlineFunctionExpansion="2"\r
+                               AdditionalIncludeDirectories="&quot;../../ext/apr-util/include&quot;;&quot;../../ext/apr-util/xml/expat/lib&quot;;../../ext/Subversion/subversion/include;../../ext/apr/include;../Utils;..\TortoiseShell;..\SVN;..\..\ext\gettext\include"\r
+                               PreprocessorDefinitions="WIN64;NDEBUG;_CONSOLE;UNICODE;_UNICODE"\r
+                               RuntimeLibrary="2"\r
+                               EnableFunctionLevelLinking="true"\r
+                               TreatWChar_tAsBuiltInType="true"\r
+                               ForceConformanceInForLoopScope="true"\r
+                               UsePrecompiledHeader="2"\r
+                               WarningLevel="4"\r
+                               DebugInformationFormat="3"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManagedResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCResourceCompilerTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPreLinkEventTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCLinkerTool"\r
+                               AdditionalDependencies="Crypt32.lib ../../ext/Subversion\release_x64\libsvn_diff-nonet.lib ../../ext/Subversion\release_x64\libsvn_delta-nonet.lib ../../ext/Subversion\release_x64\libsvn_wc-nonet.lib ../../ext/Subversion\release_x64\libsvn_subr-nonet.lib ../../ext/Subversion\release_x64\libsvn_client-nonet.lib ../../ext/Subversion\release_x64\libsvn_ra-nonet.lib ../../ext/apr\release_x64\libapr_tsvn.lib ../../ext/apr-util\release_x64\libaprutil_tsvn.lib ../../ext/apr-util\xml\expat\lib\release_x64\xml.lib ../../ext/libintl/libintl3-x64/lib/intl3_tsvn.lib"\r
+                               OutputFile="$(OutDir)/TSVNCache.exe"\r
+                               LinkIncremental="1"\r
+                               GenerateDebugInformation="true"\r
+                               SubSystem="2"\r
+                               OptimizeReferences="2"\r
+                               EnableCOMDATFolding="2"\r
+                               OptimizeForWindows98="1"\r
+                               RandomizedBaseAddress="1"\r
+                               DataExecutionPrevention="0"\r
+                               TargetMachine="17"\r
+                       />\r
+                       <Tool\r
+                               Name="VCALinkTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCManifestTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCXDCMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCBscMakeTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCFxCopTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCAppVerifierTool"\r
+                       />\r
+                       <Tool\r
+                               Name="VCPostBuildEventTool"\r
+                       />\r
+               </Configuration>\r
+       </Configurations>\r
+       <References>\r
+       </References>\r
+       <Files>\r
+               <Filter\r
+                       Name="Source Files"\r
+                       Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"\r
+                       UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"\r
+                       >\r
+                       <File\r
+                               RelativePath=".\CachedDirectory.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\CacheInterface.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\DirectoryWatcher.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\FolderCrawler.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\PathUtils.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\Registry.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\RWSection.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\ShellUpdater.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\StatusCacheEntry.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\stdafx.cpp"\r
+                               >\r
+                               <FileConfiguration\r
+                                       Name="Debug|Win32"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               UsePrecompiledHeader="1"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Debug|x64"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               UsePrecompiledHeader="1"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Release|Win32"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               UsePrecompiledHeader="1"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Release|x64"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               UsePrecompiledHeader="1"\r
+                                       />\r
+                               </FileConfiguration>\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\SVNAdminDir.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\SVN\SVNGlobal.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\SVNHelpers.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\SVNStatus.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\SVNStatusCache.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\TSVNCache.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\TSVNPath.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\UnicodeUtils.cpp"\r
+                               >\r
+                       </File>\r
+               </Filter>\r
+               <Filter\r
+                       Name="Header Files"\r
+                       Filter="h;hpp;hxx;hm;inl;inc;xsd"\r
+                       UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"\r
+                       >\r
+                       <File\r
+                               RelativePath=".\CachedDirectory.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\CacheInterface.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\DirectoryWatcher.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\FolderCrawler.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\PathUtils.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\registry.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\resource.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\RWSection.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\TortoiseShell\ShellCache.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\ShellUpdater.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\StatusCacheEntry.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\stdafx.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\SVNAdminDir.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\SVN\SVNGlobal.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\SVNHelpers.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\SVNStatus.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\SVNStatusCache.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\TSVNCache.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Svn\TSVNPath.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Utils\UnicodeUtils.h"\r
+                               >\r
+                       </File>\r
+               </Filter>\r
+               <Filter\r
+                       Name="Resource Files"\r
+                       Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"\r
+                       UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"\r
+                       >\r
+                       <File\r
+                               RelativePath="..\Resources\Tortoise.ico"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\Resources\TortoiseCache.ico"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath=".\TSVNCache.rc"\r
+                               >\r
+                       </File>\r
+               </Filter>\r
+       </Files>\r
+       <Globals>\r
+       </Globals>\r
+</VisualStudioProject>\r
diff --git a/src/TGitCache/resource.h b/src/TGitCache/resource.h
new file mode 100644 (file)
index 0000000..9e646b3
--- /dev/null
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}\r
+// Microsoft Visual C++ generated include file.\r
+// Used by TSVNCache.rc\r
+//\r
+#define IDI_TSVNCACHE                   101\r
+\r
+// Next default values for new objects\r
+// \r
+#ifdef APSTUDIO_INVOKED\r
+#ifndef APSTUDIO_READONLY_SYMBOLS\r
+#define _APS_NEXT_RESOURCE_VALUE        102\r
+#define _APS_NEXT_COMMAND_VALUE         40001\r
+#define _APS_NEXT_CONTROL_VALUE         1001\r
+#define _APS_NEXT_SYMED_VALUE           101\r
+#endif\r
+#endif\r
diff --git a/src/TGitCache/stdafx.cpp b/src/TGitCache/stdafx.cpp
new file mode 100644 (file)
index 0000000..e9421f9
--- /dev/null
@@ -0,0 +1,6 @@
+// stdafx.cpp : source file that includes just the standard includes\r
+// TSVNCache.pch will be the pre-compiled header\r
+// stdafx.obj will contain the pre-compiled type information\r
+\r
+#include "stdafx.h"\r
+\r
diff --git a/src/TGitCache/stdafx.h b/src/TGitCache/stdafx.h
new file mode 100644 (file)
index 0000000..e553eb1
--- /dev/null
@@ -0,0 +1,63 @@
+// stdafx.h : include file for standard system include files,\r
+// or project specific include files that are used frequently, but\r
+// are changed infrequently\r
+//\r
+\r
+#pragma once\r
+\r
+#define _WIN32_IE 0x600\r
+#define _WIN32_WINNT 0x0600\r
+\r
+#include <tchar.h>\r
+#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS     // some CString constructors will be explicit\r
+\r
+#include <WinSock2.h>\r
+#include <Ws2tcpip.h>\r
+#include <Wspiapi.h>\r
+\r
+#include <atlbase.h>\r
+#include <atlstr.h>\r
+\r
+#include <conio.h>\r
+\r
+#define CSTRING_AVAILABLE\r
+\r
+\r
+using namespace ATL;\r
+\r
+#pragma warning(push)\r
+#pragma warning(disable: 4702) // Unreachable code warnings in xtree\r
+#include <string>\r
+#include <vector>\r
+#include <map>\r
+#include <algorithm>\r
+#include <deque>\r
+#pragma warning(pop)\r
+\r
+#pragma warning(push)\r
+#include "svn_wc.h"\r
+#include "svn_client.h"\r
+#include "svn_path.h"\r
+#include "svn_pools.h"\r
+#include "svn_utf.h"\r
+#pragma warning(pop)\r
+\r
+\r
+typedef CComCritSecLock<CComAutoCriticalSection> AutoLocker;\r
+\r
+// Temporary fix for people not using the latest SDK\r
+#ifndef PROCESS_MODE_BACKGROUND_BEGIN\r
+#define PROCESS_MODE_BACKGROUND_BEGIN 0x00100000\r
+#endif\r
+#ifndef THREAD_MODE_BACKGROUND_BEGIN\r
+#define THREAD_MODE_BACKGROUND_BEGIN 0x00010000\r
+#endif\r
+#ifndef THREAD_MODE_BACKGROUND_END\r
+#define THREAD_MODE_BACKGROUND_END 0x00020000\r
+#endif\r
+\r
+#ifdef _WIN64\r
+#      define APP_X64_STRING   "x64"\r
+#else\r
+#      define APP_X64_STRING ""\r
+#endif\r