Pick Ref: Browse Ref buttons added to dialogs derived from ChooseVersion.
[TortoiseGit.git] / src / Utils / PathWatcher.cpp
blob429bcad1104ecae2a2f793072f4202a453312ce4
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2007 - 2007 - Stefan Kueng
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 "PathWatcher.h"
23 CPathWatcher::CPathWatcher(void) : m_hCompPort(NULL)
24 , m_bRunning(TRUE)
26 // enable the required privileges for this process
28 LPCTSTR arPrivelegeNames[] = { SE_BACKUP_NAME,
29 SE_RESTORE_NAME,
30 SE_CHANGE_NOTIFY_NAME
33 for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
35 HANDLE hToken;
36 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
38 TOKEN_PRIVILEGES tp = { 1 };
40 if (LookupPrivilegeValue(NULL, arPrivelegeNames[i], &tp.Privileges[0].Luid))
42 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
44 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
46 CloseHandle(hToken);
50 unsigned int threadId;
51 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
54 CPathWatcher::~CPathWatcher(void)
56 InterlockedExchange(&m_bRunning, FALSE);
57 if (m_hThread != INVALID_HANDLE_VALUE)
59 CloseHandle(m_hThread);
60 m_hThread = INVALID_HANDLE_VALUE;
62 AutoLocker lock(m_critSec);
63 ClearInfoMap();
66 void CPathWatcher::Stop()
68 InterlockedExchange(&m_bRunning, FALSE);
69 if (m_hCompPort != INVALID_HANDLE_VALUE)
71 PostQueuedCompletionStatus(m_hCompPort, 0, NULL, NULL);
73 if (m_hThread != INVALID_HANDLE_VALUE)
74 CloseHandle(m_hThread);
76 m_hThread = INVALID_HANDLE_VALUE;
77 m_hCompPort = INVALID_HANDLE_VALUE;
80 bool CPathWatcher::RemovePathAndChildren(const CTGitPath& path)
82 bool bRemoved = false;
83 AutoLocker lock(m_critSec);
84 repeat:
85 for (int i=0; i<watchedPaths.GetCount(); ++i)
87 if (path.IsAncestorOf(watchedPaths[i]))
89 watchedPaths.RemovePath(watchedPaths[i]);
90 bRemoved = true;
91 goto repeat;
94 return bRemoved;
97 bool CPathWatcher::AddPath(const CTGitPath& path)
99 AutoLocker lock(m_critSec);
100 for (int i=0; i<watchedPaths.GetCount(); ++i)
102 if (watchedPaths[i].IsAncestorOf(path))
103 return false; // already watched (recursively)
106 // now check if with the new path we might have a new root
107 CTGitPath newroot;
108 for (int i=0; i<watchedPaths.GetCount(); ++i)
110 const CString& watched = watchedPaths[i].GetWinPathString();
111 const CString& sPath = path.GetWinPathString();
112 int minlen = min(sPath.GetLength(), watched.GetLength());
113 int len = 0;
114 for (len = 0; len < minlen; ++len)
116 if (watched.GetAt(len) != sPath.GetAt(len))
118 if ((len > 1)&&(len < minlen))
120 if (sPath.GetAt(len)=='\\')
122 newroot = CTGitPath(sPath.Left(len));
124 else if (watched.GetAt(len)=='\\')
126 newroot = CTGitPath(watched.Left(len));
129 break;
132 if (len == minlen)
134 if (sPath.GetLength() == minlen)
136 if (watched.GetLength() > minlen)
138 if (watched.GetAt(len)=='\\')
140 newroot = path;
142 else if (sPath.GetLength() == 3 && sPath[1] == ':')
144 newroot = path;
148 else
150 if (sPath.GetLength() > minlen)
152 if (sPath.GetAt(len)=='\\')
154 newroot = CTGitPath(watched);
156 else if (watched.GetLength() == 3 && watched[1] == ':')
158 newroot = CTGitPath(watched);
164 if (!newroot.IsEmpty())
166 ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());
167 watchedPaths.AddPath(newroot);
168 watchedPaths.RemoveChildren();
169 m_hCompPort = INVALID_HANDLE_VALUE;
170 return true;
172 ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());
173 watchedPaths.AddPath(path);
174 m_hCompPort = INVALID_HANDLE_VALUE;
175 return true;
179 unsigned int CPathWatcher::ThreadEntry(void* pContext)
181 ((CPathWatcher*)pContext)->WorkerThread();
182 return 0;
185 void CPathWatcher::WorkerThread()
187 DWORD numBytes;
188 CDirWatchInfo * pdi = NULL;
189 LPOVERLAPPED lpOverlapped;
190 WCHAR buf[MAX_PATH*4] = {0};
191 while (m_bRunning)
193 if (watchedPaths.GetCount())
195 if (!GetQueuedCompletionStatus(m_hCompPort,
196 &numBytes,
197 (PULONG_PTR) &pdi,
198 &lpOverlapped,
199 INFINITE))
201 // Error retrieving changes
202 // Clear the list of watched objects and recreate that list
203 if (!m_bRunning)
204 return;
206 AutoLocker lock(m_critSec);
207 ClearInfoMap();
209 DWORD lasterr = GetLastError();
210 if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))
212 CloseHandle(m_hCompPort);
213 m_hCompPort = INVALID_HANDLE_VALUE;
215 // Since we pass m_hCompPort to CreateIoCompletionPort, we
216 // have to set this to NULL to have that API create a new
217 // handle.
218 m_hCompPort = NULL;
219 for (int i=0; i<watchedPaths.GetCount(); ++i)
221 HANDLE hDir = CreateFile(watchedPaths[i].GetWinPath(),
222 FILE_LIST_DIRECTORY,
223 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
224 NULL, //security attributes
225 OPEN_EXISTING,
226 FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
227 FILE_FLAG_OVERLAPPED,
228 NULL);
229 if (hDir == INVALID_HANDLE_VALUE)
231 // this could happen if a watched folder has been removed/renamed
232 CloseHandle(m_hCompPort);
233 m_hCompPort = INVALID_HANDLE_VALUE;
234 AutoLocker lock(m_critSec);
235 watchedPaths.RemovePath(watchedPaths[i]);
236 i--; if (i<0) i=0;
237 break;
240 CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPaths[i]);
241 m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);
242 if (m_hCompPort == NULL)
244 AutoLocker lock(m_critSec);
245 ClearInfoMap();
246 delete pDirInfo;
247 pDirInfo = NULL;
248 watchedPaths.RemovePath(watchedPaths[i]);
249 i--; if (i<0) i=0;
250 break;
252 if (!ReadDirectoryChangesW(pDirInfo->m_hDir,
253 pDirInfo->m_Buffer,
254 READ_DIR_CHANGE_BUFFER_SIZE,
255 TRUE,
256 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
257 &numBytes,// not used
258 &pDirInfo->m_Overlapped,
259 NULL)) //no completion routine!
261 AutoLocker lock(m_critSec);
262 ClearInfoMap();
263 delete pDirInfo;
264 pDirInfo = NULL;
265 watchedPaths.RemovePath(watchedPaths[i]);
266 i--; if (i<0) i=0;
267 break;
269 AutoLocker lock(m_critSec);
270 watchInfoMap[pDirInfo->m_hDir] = pDirInfo;
271 ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());
274 else
276 if (!m_bRunning)
277 return;
278 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
279 // is called again, the higher the chance that we miss some
280 // changes in the file system!
281 if (pdi)
283 if (numBytes == 0)
285 goto continuewatching;
287 PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;
288 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
289 goto continuewatching;
290 DWORD nOffset = pnotify->NextEntryOffset;
293 nOffset = pnotify->NextEntryOffset;
294 SecureZeroMemory(buf, MAX_PATH*4*sizeof(TCHAR));
295 _tcsncpy_s(buf, MAX_PATH*4, pdi->m_DirPath, MAX_PATH*4);
296 errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), (MAX_PATH*4)-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE);
297 if (err == STRUNCATE)
299 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
300 continue;
302 buf[min(MAX_PATH*4-1, pdi->m_DirPath.GetLength()+(pnotify->FileNameLength/sizeof(WCHAR)))] = 0;
303 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
304 ATLTRACE(_T("change notification: %s\n"), buf);
305 m_changedPaths.AddPath(CTGitPath(buf));
306 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
307 break;
308 } while (nOffset);
309 continuewatching:
310 SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer));
311 SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED));
312 if (!ReadDirectoryChangesW(pdi->m_hDir,
313 pdi->m_Buffer,
314 READ_DIR_CHANGE_BUFFER_SIZE,
315 TRUE,
316 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
317 &numBytes,// not used
318 &pdi->m_Overlapped,
319 NULL)) //no completion routine!
321 // Since the call to ReadDirectoryChangesW failed, just
322 // wait a while. We don't want to have this thread
323 // running using 100% CPU if something goes completely
324 // wrong.
325 Sleep(200);
329 }// if (watchedPaths.GetCount())
330 else
331 Sleep(200);
332 }// while (m_bRunning)
335 void CPathWatcher::ClearInfoMap()
337 if (watchInfoMap.size()!=0)
339 AutoLocker lock(m_critSec);
340 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
342 CPathWatcher::CDirWatchInfo * info = I->second;
343 delete info;
344 info = NULL;
347 watchInfoMap.clear();
348 if (m_hCompPort != INVALID_HANDLE_VALUE)
349 CloseHandle(m_hCompPort);
350 m_hCompPort = INVALID_HANDLE_VALUE;
353 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName) :
354 m_hDir(hDir),
355 m_DirName(DirectoryName)
357 ATLASSERT( hDir != INVALID_HANDLE_VALUE
358 && !DirectoryName.IsEmpty());
359 memset(&m_Overlapped, 0, sizeof(m_Overlapped));
360 m_DirPath = m_DirName.GetWinPathString();
361 if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')
362 m_DirPath += _T("\\");
365 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()
367 CloseDirectoryHandle();
370 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()
372 bool b = TRUE;
373 if( m_hDir != INVALID_HANDLE_VALUE )
375 b = !!CloseHandle(m_hDir);
376 m_hDir = INVALID_HANDLE_VALUE;
378 return b;