fixed the ContextMenuStash.png screenshot
[TortoiseGit.git] / src / Utils / PathWatcher.cpp
blobb03182127052c7792f251b4f625364d47dca602f
1 // TortoiseGit - 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
27 LPCTSTR arPrivelegeNames[] = { SE_BACKUP_NAME,
28 SE_RESTORE_NAME,
29 SE_CHANGE_NOTIFY_NAME
32 for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
34 HANDLE hToken;
35 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
37 TOKEN_PRIVILEGES tp = { 1 };
39 if (LookupPrivilegeValue(NULL, arPrivelegeNames[i], &tp.Privileges[0].Luid))
41 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
43 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
45 CloseHandle(hToken);
49 unsigned int threadId;
50 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
53 CPathWatcher::~CPathWatcher(void)
55 InterlockedExchange(&m_bRunning, FALSE);
56 if (m_hThread != INVALID_HANDLE_VALUE)
58 CloseHandle(m_hThread);
59 m_hThread = INVALID_HANDLE_VALUE;
61 AutoLocker lock(m_critSec);
62 ClearInfoMap();
65 void CPathWatcher::Stop()
67 InterlockedExchange(&m_bRunning, FALSE);
68 if (m_hCompPort != INVALID_HANDLE_VALUE)
70 PostQueuedCompletionStatus(m_hCompPort, 0, NULL, NULL);
73 if (m_hThread != INVALID_HANDLE_VALUE)
75 if( WaitForSingleObject(m_hThread, 1000) != WAIT_OBJECT_0 )
77 TerminateThread(m_hThread, (DWORD)-1);
79 CloseHandle(m_hThread);
81 m_hThread = INVALID_HANDLE_VALUE;
82 m_hCompPort = INVALID_HANDLE_VALUE;
85 bool CPathWatcher::RemovePathAndChildren(const CTGitPath& path)
87 bool bRemoved = false;
88 AutoLocker lock(m_critSec);
89 repeat:
90 for (int i=0; i<watchedPaths.GetCount(); ++i)
92 if (path.IsAncestorOf(watchedPaths[i]))
94 watchedPaths.RemovePath(watchedPaths[i]);
95 bRemoved = true;
96 goto repeat;
99 return bRemoved;
102 bool CPathWatcher::AddPath(const CTGitPath& path)
104 AutoLocker lock(m_critSec);
105 for (int i=0; i<watchedPaths.GetCount(); ++i)
107 if (watchedPaths[i].IsAncestorOf(path))
108 return false; // already watched (recursively)
111 // now check if with the new path we might have a new root
112 CTGitPath newroot;
113 for (int i=0; i<watchedPaths.GetCount(); ++i)
115 const CString& watched = watchedPaths[i].GetWinPathString();
116 const CString& sPath = path.GetWinPathString();
117 int minlen = min(sPath.GetLength(), watched.GetLength());
118 int len = 0;
119 for (len = 0; len < minlen; ++len)
121 if (watched.GetAt(len) != sPath.GetAt(len))
123 if ((len > 1)&&(len < minlen))
125 if (sPath.GetAt(len)=='\\')
127 newroot = CTGitPath(sPath.Left(len));
129 else if (watched.GetAt(len)=='\\')
131 newroot = CTGitPath(watched.Left(len));
134 break;
137 if (len == minlen)
139 if (sPath.GetLength() == minlen)
141 if (watched.GetLength() > minlen)
143 if (watched.GetAt(len)=='\\')
145 newroot = path;
147 else if (sPath.GetLength() == 3 && sPath[1] == ':')
149 newroot = path;
153 else
155 if (sPath.GetLength() > minlen)
157 if (sPath.GetAt(len)=='\\')
159 newroot = CTGitPath(watched);
161 else if (watched.GetLength() == 3 && watched[1] == ':')
163 newroot = CTGitPath(watched);
169 if (!newroot.IsEmpty())
171 ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());
172 watchedPaths.AddPath(newroot);
173 watchedPaths.RemoveChildren();
174 m_hCompPort = INVALID_HANDLE_VALUE;
175 return true;
177 ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());
178 watchedPaths.AddPath(path);
179 m_hCompPort = INVALID_HANDLE_VALUE;
180 return true;
184 unsigned int CPathWatcher::ThreadEntry(void* pContext)
186 ((CPathWatcher*)pContext)->WorkerThread();
187 return 0;
190 void CPathWatcher::WorkerThread()
192 DWORD numBytes;
193 CDirWatchInfo * pdi = NULL;
194 LPOVERLAPPED lpOverlapped;
195 WCHAR buf[MAX_PATH*4] = {0};
196 while (m_bRunning)
198 if (watchedPaths.GetCount())
200 if (!GetQueuedCompletionStatus(m_hCompPort,
201 &numBytes,
202 (PULONG_PTR) &pdi,
203 &lpOverlapped,
204 INFINITE))
206 // Error retrieving changes
207 // Clear the list of watched objects and recreate that list
208 if (!m_bRunning)
209 return;
211 AutoLocker lock(m_critSec);
212 ClearInfoMap();
214 DWORD lasterr = GetLastError();
215 if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))
217 CloseHandle(m_hCompPort);
218 m_hCompPort = INVALID_HANDLE_VALUE;
220 // Since we pass m_hCompPort to CreateIoCompletionPort, we
221 // have to set this to NULL to have that API create a new
222 // handle.
223 m_hCompPort = NULL;
224 for (int i=0; i<watchedPaths.GetCount(); ++i)
226 HANDLE hDir = CreateFile(watchedPaths[i].GetWinPath(),
227 FILE_LIST_DIRECTORY,
228 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
229 NULL, //security attributes
230 OPEN_EXISTING,
231 FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
232 FILE_FLAG_OVERLAPPED,
233 NULL);
234 if (hDir == INVALID_HANDLE_VALUE)
236 // this could happen if a watched folder has been removed/renamed
237 CloseHandle(m_hCompPort);
238 m_hCompPort = INVALID_HANDLE_VALUE;
239 AutoLocker lock(m_critSec);
240 watchedPaths.RemovePath(watchedPaths[i]);
241 i--; if (i<0) i=0;
242 break;
245 CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPaths[i]);
246 m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);
247 if (m_hCompPort == NULL)
249 AutoLocker lock(m_critSec);
250 ClearInfoMap();
251 delete pDirInfo;
252 pDirInfo = NULL;
253 watchedPaths.RemovePath(watchedPaths[i]);
254 i--; if (i<0) i=0;
255 break;
257 if (!ReadDirectoryChangesW(pDirInfo->m_hDir,
258 pDirInfo->m_Buffer,
259 READ_DIR_CHANGE_BUFFER_SIZE,
260 TRUE,
261 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
262 &numBytes,// not used
263 &pDirInfo->m_Overlapped,
264 NULL)) //no completion routine!
266 AutoLocker lock(m_critSec);
267 ClearInfoMap();
268 delete pDirInfo;
269 pDirInfo = NULL;
270 watchedPaths.RemovePath(watchedPaths[i]);
271 i--; if (i<0) i=0;
272 break;
274 AutoLocker lock(m_critSec);
275 watchInfoMap[pDirInfo->m_hDir] = pDirInfo;
276 ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());
279 else
281 if (!m_bRunning)
282 return;
283 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
284 // is called again, the higher the chance that we miss some
285 // changes in the file system!
286 if (pdi)
288 if (numBytes == 0)
290 goto continuewatching;
292 PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;
293 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
294 goto continuewatching;
295 DWORD nOffset = pnotify->NextEntryOffset;
298 nOffset = pnotify->NextEntryOffset;
299 SecureZeroMemory(buf, MAX_PATH*4*sizeof(TCHAR));
300 _tcsncpy_s(buf, MAX_PATH*4, pdi->m_DirPath, MAX_PATH*4);
301 errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), (MAX_PATH*4)-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE);
302 if (err == STRUNCATE)
304 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
305 continue;
307 buf[min(MAX_PATH*4-1, pdi->m_DirPath.GetLength()+(pnotify->FileNameLength/sizeof(WCHAR)))] = 0;
308 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
309 ATLTRACE(_T("change notification: %s\n"), buf);
310 m_changedPaths.AddPath(CTGitPath(buf));
311 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
312 break;
313 } while (nOffset);
314 continuewatching:
315 SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer));
316 SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED));
317 if (!ReadDirectoryChangesW(pdi->m_hDir,
318 pdi->m_Buffer,
319 READ_DIR_CHANGE_BUFFER_SIZE,
320 TRUE,
321 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
322 &numBytes,// not used
323 &pdi->m_Overlapped,
324 NULL)) //no completion routine!
326 // Since the call to ReadDirectoryChangesW failed, just
327 // wait a while. We don't want to have this thread
328 // running using 100% CPU if something goes completely
329 // wrong.
330 Sleep(200);
334 }// if (watchedPaths.GetCount())
335 else
336 Sleep(200);
337 }// while (m_bRunning)
340 void CPathWatcher::ClearInfoMap()
342 if (watchInfoMap.size()!=0)
344 AutoLocker lock(m_critSec);
345 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
347 CPathWatcher::CDirWatchInfo * info = I->second;
348 delete info;
349 info = NULL;
352 watchInfoMap.clear();
353 if (m_hCompPort != INVALID_HANDLE_VALUE)
354 CloseHandle(m_hCompPort);
355 m_hCompPort = INVALID_HANDLE_VALUE;
358 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName) :
359 m_hDir(hDir),
360 m_DirName(DirectoryName)
362 ATLASSERT( hDir != INVALID_HANDLE_VALUE
363 && !DirectoryName.IsEmpty());
364 memset(&m_Overlapped, 0, sizeof(m_Overlapped));
365 m_DirPath = m_DirName.GetWinPathString();
366 if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')
367 m_DirPath += _T("\\");
370 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()
372 CloseDirectoryHandle();
375 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()
377 bool b = TRUE;
378 if( m_hDir != INVALID_HANDLE_VALUE )
380 b = !!CloseHandle(m_hDir);
381 m_hDir = INVALID_HANDLE_VALUE;
383 return b;