Fixed issue #1737: Make a button to rename remote
[TortoiseGit.git] / src / Utils / PathWatcher.cpp
bloba6d4610d4321a2fae7af249f170e134b6c1f20f5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2007-2012 - 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 "PathWatcher.h"
23 CPathWatcher::CPathWatcher(void) : m_hCompPort(NULL)
24 , m_bRunning(TRUE)
25 , m_bLimitReached(false)
27 // 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 CAutoGeneralHandle hToken;
36 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken.GetPointer()))
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);
49 unsigned int threadId = 0;
50 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
53 CPathWatcher::~CPathWatcher(void)
55 Stop();
56 AutoLocker lock(m_critSec);
57 ClearInfoMap();
60 void CPathWatcher::Stop()
62 InterlockedExchange(&m_bRunning, FALSE);
63 if (m_hCompPort)
65 PostQueuedCompletionStatus(m_hCompPort, 0, NULL, NULL);
66 m_hCompPort.CloseHandle();
69 if (m_hThread)
71 // the background thread sleeps for 200ms,
72 // so lets wait for it to finish for 1000 ms.
74 WaitForSingleObject(m_hThread, 1000);
75 m_hThread.CloseHandle();
79 bool CPathWatcher::RemovePathAndChildren(const CTGitPath& path)
81 bool bRemoved = false;
82 AutoLocker lock(m_critSec);
83 repeat:
84 for (int i=0; i<watchedPaths.GetCount(); ++i)
86 if (path.IsAncestorOf(watchedPaths[i]))
88 watchedPaths.RemovePath(watchedPaths[i]);
89 bRemoved = true;
90 goto repeat;
93 return bRemoved;
96 bool CPathWatcher::AddPath(const CTGitPath& path)
98 AutoLocker lock(m_critSec);
99 for (int i=0; i<watchedPaths.GetCount(); ++i)
101 if (watchedPaths[i].IsAncestorOf(path))
102 return false; // already watched (recursively)
105 // now check if with the new path we might have a new root
106 CTGitPath newroot;
107 for (int i=0; i<watchedPaths.GetCount(); ++i)
109 const CString& watched = watchedPaths[i].GetWinPathString();
110 const CString& sPath = path.GetWinPathString();
111 int minlen = min(sPath.GetLength(), watched.GetLength());
112 int len = 0;
113 for (len = 0; len < minlen; ++len)
115 if (watched.GetAt(len) != sPath.GetAt(len))
117 if ((len > 1)&&(len < minlen))
119 if (sPath.GetAt(len)=='\\')
121 newroot = CTGitPath(sPath.Left(len));
123 else if (watched.GetAt(len)=='\\')
125 newroot = CTGitPath(watched.Left(len));
128 break;
131 if (len == minlen)
133 if (sPath.GetLength() == minlen)
135 if (watched.GetLength() > minlen)
137 if (watched.GetAt(len)=='\\')
139 newroot = path;
141 else if (sPath.GetLength() == 3 && sPath[1] == ':')
143 newroot = path;
147 else
149 if (sPath.GetLength() > minlen)
151 if (sPath.GetAt(len)=='\\')
153 newroot = CTGitPath(watched);
155 else if (watched.GetLength() == 3 && watched[1] == ':')
157 newroot = CTGitPath(watched);
163 if (!newroot.IsEmpty())
165 ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());
166 watchedPaths.AddPath(newroot);
167 watchedPaths.RemoveChildren();
168 m_hCompPort.CloseHandle();
169 return true;
171 ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());
172 watchedPaths.AddPath(path);
173 m_hCompPort.CloseHandle();
174 return true;
178 unsigned int CPathWatcher::ThreadEntry(void* pContext)
180 ((CPathWatcher*)pContext)->WorkerThread();
181 return 0;
184 void CPathWatcher::WorkerThread()
186 DWORD numBytes;
187 CDirWatchInfo * pdi = NULL;
188 LPOVERLAPPED lpOverlapped;
189 const int bufferSize = MAX_PATH * 4;
190 TCHAR buf[bufferSize] = {0};
191 while (m_bRunning)
193 if (watchedPaths.GetCount())
195 if (!m_hCompPort || !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)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))
212 m_hCompPort.CloseHandle();
214 for (int i=0; i<watchedPaths.GetCount(); ++i)
216 CAutoFile hDir = CreateFile(watchedPaths[i].GetWinPath(),
217 FILE_LIST_DIRECTORY,
218 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
219 NULL, //security attributes
220 OPEN_EXISTING,
221 FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
222 FILE_FLAG_OVERLAPPED,
223 NULL);
224 if (!hDir)
226 // this could happen if a watched folder has been removed/renamed
227 m_hCompPort.CloseHandle();
228 AutoLocker lock(m_critSec);
229 watchedPaths.RemovePath(watchedPaths[i]);
230 i--; if (i<0) i=0;
231 break;
234 CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPaths[i]);
235 hDir.Detach(); // the new CDirWatchInfo object owns the handle now
236 m_hCompPort = CreateIoCompletionPort(pDirInfo->m_hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);
237 if (m_hCompPort == NULL)
239 AutoLocker lock(m_critSec);
240 ClearInfoMap();
241 delete pDirInfo;
242 pDirInfo = NULL;
243 watchedPaths.RemovePath(watchedPaths[i]);
244 i--; if (i<0) i=0;
245 break;
247 if (!ReadDirectoryChangesW(pDirInfo->m_hDir,
248 pDirInfo->m_Buffer,
249 bufferSize,
250 TRUE,
251 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
252 &numBytes,// not used
253 &pDirInfo->m_Overlapped,
254 NULL)) //no completion routine!
256 AutoLocker lock(m_critSec);
257 ClearInfoMap();
258 delete pDirInfo;
259 pDirInfo = NULL;
260 watchedPaths.RemovePath(watchedPaths[i]);
261 i--; if (i<0) i=0;
262 break;
264 AutoLocker lock(m_critSec);
265 watchInfoMap[pDirInfo->m_hDir] = pDirInfo;
266 ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());
269 else
271 if (!m_bRunning)
272 return;
273 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
274 // is called again, the higher the chance that we miss some
275 // changes in the file system!
276 if (pdi)
278 if (numBytes == 0)
280 goto continuewatching;
282 PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;
283 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > bufferSize)
284 goto continuewatching;
285 DWORD nOffset = pnotify->NextEntryOffset;
288 nOffset = pnotify->NextEntryOffset;
289 SecureZeroMemory(buf, bufferSize*sizeof(TCHAR));
290 _tcsncpy_s(buf, bufferSize, pdi->m_DirPath, bufferSize);
291 errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), bufferSize-pdi->m_DirPath.GetLength(), pnotify->FileName, min(bufferSize-pdi->m_DirPath.GetLength(), pnotify->FileNameLength/sizeof(TCHAR)));
292 if (err == STRUNCATE)
294 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
295 continue;
297 buf[min(bufferSize-1, pdi->m_DirPath.GetLength()+(pnotify->FileNameLength/sizeof(WCHAR)))] = 0;
298 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
299 ATLTRACE(_T("change notification: %s\n"), buf);
301 AutoLocker lock(m_critSec);
302 if (m_changedPaths.GetCount() < MAX_CHANGED_PATHS)
303 m_changedPaths.AddPath(CTGitPath(buf));
304 else
305 m_bLimitReached = true;
307 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > bufferSize)
308 break;
309 } while (nOffset);
310 continuewatching:
312 AutoLocker lock(m_critSec);
313 m_changedPaths.RemoveDuplicates();
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 bufferSize,
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.empty())
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 m_hCompPort.CloseHandle();
356 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName)
357 : m_hDir(hDir)
358 , m_DirName(DirectoryName)
360 ATLASSERT( hDir && !DirectoryName.IsEmpty());
361 m_Buffer[0] = 0;
362 memset(&m_Overlapped, 0, sizeof(m_Overlapped));
363 m_DirPath = m_DirName.GetWinPathString();
364 if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')
365 m_DirPath += _T("\\");
368 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()
370 CloseDirectoryHandle();
373 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()
375 return m_hDir.CloseHandle();