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.
21 #include "GitStatusCache.h"
22 #include ".\directorywatcher.h"
26 CDirectoryWatcher::CDirectoryWatcher(void) : m_hCompPort(NULL
)
28 , m_FolderCrawler(NULL
)
31 // enable the required privileges for this process
33 LPCTSTR arPrivelegeNames
[] = { SE_BACKUP_NAME
,
38 for (int i
=0; i
<(sizeof(arPrivelegeNames
)/sizeof(LPCTSTR
)); ++i
)
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
);
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
);
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
);
92 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
94 if (path
.IsAncestorOf(watchedPaths
[i
]))
96 watchedPaths
.RemovePath(watchedPaths
[i
]);
104 void CDirectoryWatcher::BlockPath(const CTGitPath
& 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
))
116 if ((!blockedPath
.IsEmpty())&&(blockedPath
.IsAncestorOf(path
)))
118 if (GetTickCount() < blockTickCount
)
120 ATLTRACE(_T("Path %s prevented from being watched\n"), path
.GetWinPath());
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
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());
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
));
159 if (sPath
.GetLength() == minlen
)
161 if (watched
.GetLength() > minlen
)
163 if (watched
.GetAt(len
)=='\\')
167 else if (sPath
.GetLength() == 3 && sPath
[1] == ':')
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();
195 m_hCompPort
= INVALID_HANDLE_VALUE
;
198 ATLTRACE(_T("add path to watch %s\n"), path
.GetWinPath());
199 watchedPaths
.AddPath(path
);
201 m_hCompPort
= INVALID_HANDLE_VALUE
;
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
))
216 unsigned int CDirectoryWatcher::ThreadEntry(void* pContext
)
218 ((CDirectoryWatcher
*)pContext
)->WorkerThread();
222 void CDirectoryWatcher::WorkerThread()
225 CDirWatchInfo
* pdi
= NULL
;
226 LPOVERLAPPED lpOverlapped
;
227 WCHAR buf
[READ_DIR_CHANGE_BUFFER_SIZE
] = {0};
228 WCHAR
* pFound
= NULL
;
231 if (watchedPaths
.GetCount())
233 if (!GetQueuedCompletionStatus(m_hCompPort
,
239 // Error retrieving changes
240 // Clear the list of watched objects and recreate that list
244 AutoLocker
lock(m_critSec
);
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
257 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
259 CTGitPath watchedPath
= watchedPaths
[i
];
261 HANDLE hDir
= CreateFile(watchedPath
.GetWinPath(),
263 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
264 NULL
, //security attributes
266 FILE_FLAG_BACKUP_SEMANTICS
| //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
267 FILE_FLAG_OVERLAPPED
,
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
);
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
);
298 watchedPaths
.RemovePath(watchedPath
);
302 if (!ReadDirectoryChangesW(pDirInfo
->m_hDir
,
304 READ_DIR_CHANGE_BUFFER_SIZE
,
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
);
316 watchedPaths
.RemovePath(watchedPath
);
320 AutoLocker
lock(m_critSec
);
321 watchInfoMap
[pDirInfo
->m_hDir
] = pDirInfo
;
322 ATLTRACE(_T("watching path %s\n"), pDirInfo
->m_DirName
.GetWinPath());
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!
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
)))
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
);
355 buf
[(pnotify
->FileNameLength
/sizeof(TCHAR
))+pdi
->m_DirPath
.GetLength()] = 0;
356 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
359 if ((pFound
= wcsstr(buf
, L
"\\tmp"))!=NULL
)
362 if (((*pFound
)=='\\')||((*pFound
)=='\0'))
364 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
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
)
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
)
389 if ((pFound
= wcsstr(buf
, L
".tmp"))!=NULL
)
391 // assume files with a .tmp extension are not versioned and interesting,
393 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
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
)
404 SecureZeroMemory(pdi
->m_Buffer
, sizeof(pdi
->m_Buffer
));
405 SecureZeroMemory(&pdi
->m_Overlapped
, sizeof(OVERLAPPED
));
406 if (!ReadDirectoryChangesW(pdi
->m_hDir
,
408 READ_DIR_CHANGE_BUFFER_SIZE
,
410 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
411 &numBytes
,// not used
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
423 }// if (watchedPaths.GetCount())
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
;
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
)
450 if (watchInfoMap
.size() == 0)
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
);
462 info
->CloseDirectoryHandle();
464 watchInfoMap
.clear();
465 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
466 CloseHandle(m_hCompPort
);
467 m_hCompPort
= INVALID_HANDLE_VALUE
;
471 bool CDirectoryWatcher::CloseHandlesForPath(const CTGitPath
& path
)
473 if (watchInfoMap
.size() == 0)
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
);
485 info
->CloseDirectoryHandle();
487 watchInfoMap
.clear();
488 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
489 CloseHandle(m_hCompPort
);
490 m_hCompPort
= INVALID_HANDLE_VALUE
;
494 CDirectoryWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir
, const CTGitPath
& DirectoryName
) :
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()
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
);