Update version number to 1.4.3.0
[TortoiseGit.git] / src / TGitCache / DirectoryWatcher.cpp
blobdc44f20ef06461e1729ec1ce0ffed9f18dfefba6
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005 - 2007 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "Dbt.h"
21 #include "GitStatusCache.h"
22 #include ".\directorywatcher.h"
24 extern HWND hWnd;
26 CDirectoryWatcher::CDirectoryWatcher(void) : m_hCompPort(NULL)
27 , m_bRunning(TRUE)
28 , m_FolderCrawler(NULL)
29 , blockTickCount(0)
31 // enable the required privileges for this process
33 LPCTSTR arPrivelegeNames[] = { SE_BACKUP_NAME,
34 SE_RESTORE_NAME,
35 SE_CHANGE_NOTIFY_NAME
38 for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
40 HANDLE hToken;
41 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
43 TOKEN_PRIVILEGES tp = { 1 };
45 if (LookupPrivilegeValue(NULL, arPrivelegeNames[i], &tp.Privileges[0].Luid))
47 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
49 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
51 CloseHandle(hToken);
55 unsigned int threadId;
56 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
59 CDirectoryWatcher::~CDirectoryWatcher(void)
61 InterlockedExchange(&m_bRunning, FALSE);
62 if (m_hThread != INVALID_HANDLE_VALUE)
64 CloseHandle(m_hThread);
65 m_hThread = INVALID_HANDLE_VALUE;
67 AutoLocker lock(m_critSec);
68 ClearInfoMap();
71 void CDirectoryWatcher::Stop()
73 InterlockedExchange(&m_bRunning, FALSE);
74 if (m_hThread != INVALID_HANDLE_VALUE)
75 CloseHandle(m_hThread);
76 m_hThread = INVALID_HANDLE_VALUE;
77 if (m_hCompPort != INVALID_HANDLE_VALUE)
78 CloseHandle(m_hCompPort);
79 m_hCompPort = INVALID_HANDLE_VALUE;
82 void CDirectoryWatcher::SetFolderCrawler(CFolderCrawler * crawler)
84 m_FolderCrawler = crawler;
87 bool CDirectoryWatcher::RemovePathAndChildren(const CTGitPath& path)
89 bool bRemoved = false;
90 AutoLocker lock(m_critSec);
91 repeat:
92 for (int i=0; i<watchedPaths.GetCount(); ++i)
94 if (path.IsAncestorOf(watchedPaths[i]))
96 watchedPaths.RemovePath(watchedPaths[i]);
97 bRemoved = true;
98 goto repeat;
101 return bRemoved;
104 void CDirectoryWatcher::BlockPath(const CTGitPath& path)
106 blockedPath = path;
107 // block the path from being watched for 4 seconds
108 blockTickCount = GetTickCount()+4000;
109 ATLTRACE(_T("Blocking path: %s\n"), path.GetWinPath());
112 bool CDirectoryWatcher::AddPath(const CTGitPath& path)
114 if (!CGitStatusCache::Instance().IsPathAllowed(path))
115 return false;
116 if ((!blockedPath.IsEmpty())&&(blockedPath.IsAncestorOf(path)))
118 if (GetTickCount() < blockTickCount)
120 ATLTRACE(_T("Path %s prevented from being watched\n"), path.GetWinPath());
121 return false;
124 AutoLocker lock(m_critSec);
125 for (int i=0; i<watchedPaths.GetCount(); ++i)
127 if (watchedPaths[i].IsAncestorOf(path))
128 return false; // already watched (recursively)
131 // now check if with the new path we might have a new root
132 CTGitPath newroot;
133 for (int i=0; i<watchedPaths.GetCount(); ++i)
135 const CString& watched = watchedPaths[i].GetWinPathString();
136 const CString& sPath = path.GetWinPathString();
137 int minlen = min(sPath.GetLength(), watched.GetLength());
138 int len = 0;
139 for (len = 0; len < minlen; ++len)
141 if (watched.GetAt(len) != sPath.GetAt(len))
143 if ((len > 1)&&(len < minlen))
145 if (sPath.GetAt(len)=='\\')
147 newroot = CTGitPath(sPath.Left(len));
149 else if (watched.GetAt(len)=='\\')
151 newroot = CTGitPath(watched.Left(len));
154 break;
157 if (len == minlen)
159 if (sPath.GetLength() == minlen)
161 if (watched.GetLength() > minlen)
163 if (watched.GetAt(len)=='\\')
165 newroot = path;
167 else if (sPath.GetLength() == 3 && sPath[1] == ':')
169 newroot = path;
173 else
175 if (sPath.GetLength() > minlen)
177 if (sPath.GetAt(len)=='\\')
179 newroot = CTGitPath(watched);
181 else if (watched.GetLength() == 3 && watched[1] == ':')
183 newroot = CTGitPath(watched);
189 if (!newroot.IsEmpty())
191 ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());
192 watchedPaths.AddPath(newroot);
193 watchedPaths.RemoveChildren();
194 CloseInfoMap();
195 m_hCompPort = INVALID_HANDLE_VALUE;
196 return true;
198 ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());
199 watchedPaths.AddPath(path);
200 CloseInfoMap();
201 m_hCompPort = INVALID_HANDLE_VALUE;
202 return true;
205 bool CDirectoryWatcher::IsPathWatched(const CTGitPath& path)
207 AutoLocker lock(m_critSec);
208 for (int i=0; i<watchedPaths.GetCount(); ++i)
210 if (watchedPaths[i].IsAncestorOf(path))
211 return true;
213 return false;
216 unsigned int CDirectoryWatcher::ThreadEntry(void* pContext)
218 ((CDirectoryWatcher*)pContext)->WorkerThread();
219 return 0;
222 void CDirectoryWatcher::WorkerThread()
224 DWORD numBytes;
225 CDirWatchInfo * pdi = NULL;
226 LPOVERLAPPED lpOverlapped;
227 WCHAR buf[READ_DIR_CHANGE_BUFFER_SIZE] = {0};
228 WCHAR * pFound = NULL;
229 while (m_bRunning)
231 if (watchedPaths.GetCount())
233 if (!GetQueuedCompletionStatus(m_hCompPort,
234 &numBytes,
235 (PULONG_PTR) &pdi,
236 &lpOverlapped,
237 INFINITE))
239 // Error retrieving changes
240 // Clear the list of watched objects and recreate that list
241 if (!m_bRunning)
242 return;
244 AutoLocker lock(m_critSec);
245 ClearInfoMap();
247 DWORD lasterr = GetLastError();
248 if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))
250 CloseHandle(m_hCompPort);
251 m_hCompPort = INVALID_HANDLE_VALUE;
253 // Since we pass m_hCompPort to CreateIoCompletionPort, we
254 // have to set this to NULL to have that API create a new
255 // handle.
256 m_hCompPort = NULL;
257 for (int i=0; i<watchedPaths.GetCount(); ++i)
259 CTGitPath watchedPath = watchedPaths[i];
261 HANDLE hDir = CreateFile(watchedPath.GetWinPath(),
262 FILE_LIST_DIRECTORY,
263 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
264 NULL, //security attributes
265 OPEN_EXISTING,
266 FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
267 FILE_FLAG_OVERLAPPED,
268 NULL);
269 if (hDir == INVALID_HANDLE_VALUE)
271 // this could happen if a watched folder has been removed/renamed
272 ATLTRACE(_T("CDirectoryWatcher: CreateFile failed. Can't watch directory %s\n"), watchedPaths[i].GetWinPath());
273 CloseHandle(m_hCompPort);
274 m_hCompPort = INVALID_HANDLE_VALUE;
275 AutoLocker lock(m_critSec);
276 watchedPaths.RemovePath(watchedPath);
277 i--; if (i<0) i=0;
278 break;
281 DEV_BROADCAST_HANDLE NotificationFilter;
282 SecureZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
283 NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
284 NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
285 NotificationFilter.dbch_handle = hDir;
286 NotificationFilter.dbch_hdevnotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
288 CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPath);
289 pDirInfo->m_hDevNotify = NotificationFilter.dbch_hdevnotify;
290 m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);
291 if (m_hCompPort == NULL)
293 ATLTRACE(_T("CDirectoryWatcher: CreateIoCompletionPort failed. Can't watch directory %s\n"), watchedPath.GetWinPath());
294 AutoLocker lock(m_critSec);
295 ClearInfoMap();
296 delete pDirInfo;
297 pDirInfo = NULL;
298 watchedPaths.RemovePath(watchedPath);
299 i--; if (i<0) i=0;
300 break;
302 if (!ReadDirectoryChangesW(pDirInfo->m_hDir,
303 pDirInfo->m_Buffer,
304 READ_DIR_CHANGE_BUFFER_SIZE,
305 TRUE,
306 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
307 &numBytes,// not used
308 &pDirInfo->m_Overlapped,
309 NULL)) //no completion routine!
311 ATLTRACE(_T("CDirectoryWatcher: ReadDirectoryChangesW failed. Can't watch directory %s\n"), watchedPath.GetWinPath());
312 AutoLocker lock(m_critSec);
313 ClearInfoMap();
314 delete pDirInfo;
315 pDirInfo = NULL;
316 watchedPaths.RemovePath(watchedPath);
317 i--; if (i<0) i=0;
318 break;
320 AutoLocker lock(m_critSec);
321 watchInfoMap[pDirInfo->m_hDir] = pDirInfo;
322 ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());
325 else
327 if (!m_bRunning)
328 return;
329 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
330 // is called again, the higher the chance that we miss some
331 // changes in the file system!
332 if (pdi)
334 if (numBytes == 0)
336 goto continuewatching;
338 PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;
339 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
340 goto continuewatching;
341 DWORD nOffset = pnotify->NextEntryOffset;
344 nOffset = pnotify->NextEntryOffset;
345 if (pnotify->FileNameLength >= (READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR)))
346 continue;
347 SecureZeroMemory(buf, READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR));
348 _tcsncpy_s(buf, READ_DIR_CHANGE_BUFFER_SIZE, pdi->m_DirPath, READ_DIR_CHANGE_BUFFER_SIZE);
349 errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), READ_DIR_CHANGE_BUFFER_SIZE-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE);
350 if (err == STRUNCATE)
352 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
353 continue;
355 buf[(pnotify->FileNameLength/sizeof(TCHAR))+pdi->m_DirPath.GetLength()] = 0;
356 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
357 if (m_FolderCrawler)
359 if ((pFound = wcsstr(buf, L"\\tmp"))!=NULL)
361 pFound += 4;
362 if (((*pFound)=='\\')||((*pFound)=='\0'))
364 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
365 break;
366 continue;
369 if ((pFound = wcsstr(buf, L":\\RECYCLER\\"))!=NULL)
371 if ((pFound-buf) < 5)
373 // a notification for the recycle bin - ignore it
374 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
375 break;
376 continue;
379 if ((pFound = wcsstr(buf, L":\\$Recycle.Bin\\"))!=NULL)
381 if ((pFound-buf) < 5)
383 // a notification for the recycle bin - ignore it
384 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
385 break;
386 continue;
389 if ((pFound = wcsstr(buf, L".tmp"))!=NULL)
391 // assume files with a .tmp extension are not versioned and interesting,
392 // so ignore them.
393 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
394 break;
395 continue;
397 ATLTRACE(_T("change notification: %s\n"), buf);
398 m_FolderCrawler->AddPathForUpdate(CTGitPath(buf));
400 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
401 break;
402 } while (nOffset);
403 continuewatching:
404 SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer));
405 SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED));
406 if (!ReadDirectoryChangesW(pdi->m_hDir,
407 pdi->m_Buffer,
408 READ_DIR_CHANGE_BUFFER_SIZE,
409 TRUE,
410 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
411 &numBytes,// not used
412 &pdi->m_Overlapped,
413 NULL)) //no completion routine!
415 // Since the call to ReadDirectoryChangesW failed, just
416 // wait a while. We don't want to have this thread
417 // running using 100% CPU if something goes completely
418 // wrong.
419 Sleep(200);
423 }// if (watchedPaths.GetCount())
424 else
425 Sleep(200);
426 }// while (m_bRunning)
429 void CDirectoryWatcher::ClearInfoMap()
431 if (watchInfoMap.size()!=0)
433 AutoLocker lock(m_critSec);
434 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
436 CDirectoryWatcher::CDirWatchInfo * info = I->second;
437 delete info;
438 info = NULL;
441 watchInfoMap.clear();
442 if (m_hCompPort != INVALID_HANDLE_VALUE)
443 CloseHandle(m_hCompPort);
444 m_hCompPort = INVALID_HANDLE_VALUE;
447 CTGitPath CDirectoryWatcher::CloseInfoMap(HDEVNOTIFY hdev)
449 CTGitPath path;
450 if (watchInfoMap.size() == 0)
451 return path;
452 AutoLocker lock(m_critSec);
453 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
455 CDirectoryWatcher::CDirWatchInfo * info = I->second;
456 if (info->m_hDevNotify == hdev)
458 path = info->m_DirName;
459 RemovePathAndChildren(path);
460 BlockPath(path);
462 info->CloseDirectoryHandle();
464 watchInfoMap.clear();
465 if (m_hCompPort != INVALID_HANDLE_VALUE)
466 CloseHandle(m_hCompPort);
467 m_hCompPort = INVALID_HANDLE_VALUE;
468 return path;
471 bool CDirectoryWatcher::CloseHandlesForPath(const CTGitPath& path)
473 if (watchInfoMap.size() == 0)
474 return false;
475 AutoLocker lock(m_critSec);
476 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
478 CDirectoryWatcher::CDirWatchInfo * info = I->second;
479 CTGitPath p = CTGitPath(info->m_DirPath);
480 if (path.IsAncestorOf(p))
482 RemovePathAndChildren(p);
483 BlockPath(p);
485 info->CloseDirectoryHandle();
487 watchInfoMap.clear();
488 if (m_hCompPort != INVALID_HANDLE_VALUE)
489 CloseHandle(m_hCompPort);
490 m_hCompPort = INVALID_HANDLE_VALUE;
491 return true;
494 CDirectoryWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName) :
495 m_hDir(hDir),
496 m_DirName(DirectoryName)
498 ATLASSERT( hDir != INVALID_HANDLE_VALUE
499 && !DirectoryName.IsEmpty());
500 memset(&m_Overlapped, 0, sizeof(m_Overlapped));
501 m_DirPath = m_DirName.GetWinPathString();
502 if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')
503 m_DirPath += _T("\\");
504 m_hDevNotify = INVALID_HANDLE_VALUE;
507 CDirectoryWatcher::CDirWatchInfo::~CDirWatchInfo()
509 CloseDirectoryHandle();
512 bool CDirectoryWatcher::CDirWatchInfo::CloseDirectoryHandle()
514 bool b = TRUE;
515 if( m_hDir != INVALID_HANDLE_VALUE )
517 b = !!CloseHandle(m_hDir);
518 m_hDir = INVALID_HANDLE_VALUE;
520 if (m_hDevNotify != INVALID_HANDLE_VALUE)
522 UnregisterDeviceNotification(m_hDevNotify);
524 return b;