--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+//\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
--- /dev/null
+<?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=""../../ext/apr-util/include";"../../ext/apr-util/xml/expat/lib";../../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=""../../ext/apr-util/include";"../../ext/apr-util/xml/expat/lib";../../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=""../../ext/apr-util/include";"../../ext/apr-util/xml/expat/lib";../../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=""../../ext/apr-util/include";"../../ext/apr-util/xml/expat/lib";../../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
--- /dev/null
+//{{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
--- /dev/null
+// 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
--- /dev/null
+// 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