ReactOS 0.4.15-dev-8434-g155a7c7
CDirectoryWatcher.cpp
Go to the documentation of this file.
1/*
2 * PROJECT: shell32
3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4 * PURPOSE: Shell change notification
5 * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6 */
7#include "shelldesktop.h"
8#include "CDirectoryWatcher.h"
9#include <process.h> // for _beginthreadex
10#include <assert.h> // for assert
11
13
14// Notify filesystem change
15static inline void
17{
19}
20
21// The handle of the APC thread
23
24// Terminate now?
26
27// the buffer for ReadDirectoryChangesW
28#define BUFFER_SIZE 0x1000
30
31// The APC thread function for directory watch
33{
35 {
36#if 1 // FIXME: This is a HACK
38#else
40#endif
41 }
42 return 0;
43}
44
45// The APC procedure to add a CDirectoryWatcher and start the directory watch
47{
48 CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter;
49 assert(pDirectoryWatcher != NULL);
50
51 pDirectoryWatcher->RestartWatching();
52}
53
54// The APC procedure to request termination of a CDirectoryWatcher
56{
57 CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter;
58 assert(pDirectoryWatcher != NULL);
59
60 pDirectoryWatcher->QuitWatching();
61}
62
63// The APC procedure to request termination of all the directory watches
65{
69}
70
71CDirectoryWatcher::CDirectoryWatcher(HWND hNotifyWnd, LPCWSTR pszDirectoryPath, BOOL fSubTree)
72 : m_hNotifyWnd(hNotifyWnd)
73 , m_fDead(FALSE)
74 , m_fRecursive(fSubTree)
75 , m_dir_list(pszDirectoryPath, fSubTree)
76{
77 TRACE("%p, '%S'\n", this, pszDirectoryPath);
78
80
81 // open the directory to watch changes (for ReadDirectoryChangesW)
86 NULL);
87}
88
89/*static*/ CDirectoryWatcher *
90CDirectoryWatcher::Create(HWND hNotifyWnd, LPCWSTR pszDirectoryPath, BOOL fSubTree)
91{
92 CDirectoryWatcher *pDirectoryWatcher =
93 new CDirectoryWatcher(hNotifyWnd, pszDirectoryPath, fSubTree);
94 if (pDirectoryWatcher->m_hDirectory == INVALID_HANDLE_VALUE)
95 {
96 ERR("CreateFileW failed\n");
97 delete pDirectoryWatcher;
98 pDirectoryWatcher = NULL;
99 }
100 return pDirectoryWatcher;
101}
102
104{
105 TRACE("%p, '%S'\n", this, m_szDirectoryPath);
106
109}
110
111// convert the file action to an event
112static DWORD
114{
115 switch (Action)
116 {
118 return (fDir ? SHCNE_MKDIR : SHCNE_CREATE);
120 return (fDir ? SHCNE_RMDIR : SHCNE_DELETE);
122 return (fDir ? SHCNE_UPDATEDIR : SHCNE_UPDATEITEM);
124 break;
126 return (fDir ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM);
127 default:
128 break;
129 }
130 return 0;
131}
132
133// Notify a filesystem notification using pDirectoryWatcher.
135{
138 DWORD dwEvent, cbName;
139 BOOL fDir;
140 TRACE("CDirectoryWatcher::ProcessNotification: enter\n");
141
142 // for each entry in s_buffer
143 szPath[0] = szTempPath[0] = 0;
144 for (;;)
145 {
146 // get name (relative from m_szDirectoryPath)
147 cbName = pInfo->FileNameLength;
148 if (sizeof(szName) - sizeof(UNICODE_NULL) < cbName)
149 {
150 ERR("pInfo->FileName is longer than szName\n");
151 break;
152 }
153 // NOTE: FILE_NOTIFY_INFORMATION.FileName is not null-terminated.
154 ZeroMemory(szName, sizeof(szName));
155 CopyMemory(szName, pInfo->FileName, cbName);
156
157 // get full path
160
161 // convert to long pathname if it contains '~'
162 if (StrChrW(szPath, L'~') != NULL)
163 {
166 {
168 }
169 }
170
171 // convert action to event
172 fDir = PathIsDirectoryW(szPath);
173 dwEvent = ConvertActionToEvent(pInfo->Action, fDir);
174
175 // convert SHCNE_DELETE to SHCNE_RMDIR if the path is a directory
176 if (!fDir && (dwEvent == SHCNE_DELETE) && m_dir_list.ContainsPath(szPath))
177 {
178 fDir = TRUE;
179 dwEvent = SHCNE_RMDIR;
180 }
181
182 // update m_dir_list
183 switch (dwEvent)
184 {
185 case SHCNE_MKDIR:
187 dwEvent = 0;
188 break;
189 case SHCNE_CREATE:
191 dwEvent = 0;
192 break;
195 dwEvent = 0;
196 break;
197 case SHCNE_RENAMEITEM:
199 dwEvent = 0;
200 break;
201 case SHCNE_RMDIR:
203 dwEvent = 0;
204 break;
205 case SHCNE_DELETE:
207 dwEvent = 0;
208 break;
209 }
210
211 if (dwEvent != 0)
212 {
213 // notify
216 else
218 }
219 else if (pInfo->Action == FILE_ACTION_RENAMED_OLD_NAME)
220 {
221 // save path for next FILE_ACTION_RENAMED_NEW_NAME
223 }
224
225 if (pInfo->NextEntryOffset == 0)
226 break; // there is no next entry
227
228 // go next entry
229 pInfo = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pInfo + pInfo->NextEntryOffset);
230 }
231
232 TRACE("CDirectoryWatcher::ProcessNotification: leave\n");
233}
234
235void CDirectoryWatcher::ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
236{
237 // If the FSD doesn't support directory change notifications, there's no
238 // no need to retry and requeue notification
239 if (dwErrorCode == ERROR_INVALID_FUNCTION)
240 {
241 ERR("ERROR_INVALID_FUNCTION\n");
242 return;
243 }
244
245 // Also, if the notify operation was canceled (like, user moved to another
246 // directory), then, don't requeue notification.
247 if (dwErrorCode == ERROR_OPERATION_ABORTED)
248 {
249 TRACE("ERROR_OPERATION_ABORTED\n");
250 if (IsDead())
251 delete this;
252 return;
253 }
254
255 // is this watch dead?
256 if (IsDead())
257 {
258 TRACE("IsDead()\n");
259 delete this;
260 return;
261 }
262
263 // This likely means overflow, so force whole directory refresh.
264 if (dwNumberOfBytesTransfered == 0)
265 {
266 // do notify a SHCNE_UPDATEDIR
268 }
269 else
270 {
271 // do notify
273 }
274
275 // restart a watch
277}
278
279// The completion routine of ReadDirectoryChangesW.
280static void CALLBACK
282 DWORD dwNumberOfBytesTransfered,
284{
285 // MSDN: The hEvent member of the OVERLAPPED structure is not used by the
286 // system in this case, so you can use it yourself. We do just this, storing
287 // a pointer to the working struct in the overlapped structure.
288 CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)lpOverlapped->hEvent;
289 assert(pDirectoryWatcher != NULL);
290
291 pDirectoryWatcher->ReadCompletion(dwErrorCode, dwNumberOfBytesTransfered);
292}
293
294// convert events to notification filter
295static DWORD
297{
298 // FIXME
303}
304
305// Restart a watch by using ReadDirectoryChangesW function
307{
308 assert(this != NULL);
309
310 if (IsDead())
311 {
312 delete this;
313 return FALSE; // the watch is dead
314 }
315
316 // initialize the buffer and the overlapped
317 ZeroMemory(s_buffer, sizeof(s_buffer));
319 m_overlapped.hEvent = (HANDLE)this;
320
321 // start the directory watch
324 m_fRecursive, dwFilter, NULL,
326 {
327 ERR("ReadDirectoryChangesW for '%S' failed (error: %ld)\n",
329 return FALSE; // failure
330 }
331
332 return TRUE; // success
333}
334
336{
337 if (s_hThreadAPC != NULL)
338 return TRUE;
339
340 unsigned tid;
343 NULL, 0, &tid);
344 return s_hThreadAPC != NULL;
345}
346
348{
349 assert(this != NULL);
350
351 // create an APC thread for directory watching
352 if (!CreateAPCThread())
353 return FALSE;
354
355 // request adding the watch
357 return TRUE;
358}
359
361{
362 assert(this != NULL);
363
364 if (s_hThreadAPC)
365 {
367 return TRUE;
368 }
369
370 return FALSE;
371}
372
374{
375 if (!s_hThreadAPC)
376 return;
377
378 // request termination of all directory watches
380}
381
383{
384 assert(this != NULL);
385
386 m_fDead = TRUE;
389}
390
392{
394 {
396 m_fDead = TRUE;
398 }
399 return m_fDead;
400}
static void NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2)
static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter)
static DWORD GetFilterFromEvents(DWORD fEvents)
static BOOL s_fTerminateAllWatchers
static BYTE s_buffer[BUFFER_SIZE]
#define BUFFER_SIZE
static void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter)
static void CALLBACK _NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
static void NTAPI _RequestTerminationAPC(ULONG_PTR Parameter)
static DWORD ConvertActionToEvent(DWORD Action, BOOL fDir)
static HANDLE s_hThreadAPC
static unsigned __stdcall DirectoryWatcherThreadFuncAPC(void *)
#define WINE_DEFAULT_DEBUG_CHANNEL(t)
Definition: precomp.h:23
#define ERR(fmt,...)
Definition: precomp.h:57
EXTERN_C void WINAPI SHChangeNotify(LONG wEventId, UINT uFlags, LPCVOID dwItem1, LPCVOID dwItem2)
BOOL AddPath(LPCWSTR pszPath)
BOOL ContainsPath(LPCWSTR pszPath) const
BOOL DeletePath(LPCWSTR pszPath)
BOOL RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2)
static CDirectoryWatcher * Create(HWND hNotifyWnd, LPCWSTR pszDirectoryPath, BOOL fSubTree)
void ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
static void RequestAllWatchersTermination()
CDirectoryWatcher(HWND hNotifyWnd, LPCWSTR pszDirectoryPath, BOOL fSubTree)
WCHAR m_szDirectoryPath[MAX_PATH]
CDirectoryList m_dir_list
BOOL WINAPI ReadDirectoryChangesW(IN HANDLE hDirectory, IN LPVOID lpBuffer OPTIONAL, IN DWORD nBufferLength, IN BOOL bWatchSubtree, IN DWORD dwNotifyFilter, OUT LPDWORD lpBytesReturned, IN LPOVERLAPPED lpOverlapped OPTIONAL, IN LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
Definition: cnotify.c:253
#define ERROR_INVALID_FUNCTION
Definition: dderror.h:6
BOOL WINAPI CancelIo(IN HANDLE hFile)
Definition: deviceio.c:290
#define NULL
Definition: types.h:112
#define TRUE
Definition: types.h:120
#define FALSE
Definition: types.h:117
LPWSTR WINAPI StrChrW(LPCWSTR lpszStr, WCHAR ch)
Definition: string.c:468
#define CloseHandle
Definition: compat.h:739
#define OPEN_EXISTING
Definition: compat.h:775
#define INVALID_HANDLE_VALUE
Definition: compat.h:731
#define MAX_PATH
Definition: compat.h:34
#define CreateFileW
Definition: compat.h:741
#define CALLBACK
Definition: compat.h:35
#define FILE_SHARE_READ
Definition: compat.h:136
#define lstrcpynW
Definition: compat.h:738
DWORD WINAPI GetLongPathNameW(IN LPCWSTR lpszShortPath, OUT LPWSTR lpszLongPath, IN DWORD cchBuffer)
Definition: path.c:1456
DWORD WINAPI GetFullPathNameW(IN LPCWSTR lpFileName, IN DWORD nBufferLength, OUT LPWSTR lpBuffer, OUT LPWSTR *lpFilePart)
Definition: path.c:1106
DWORD WINAPI QueueUserAPC(IN PAPCFUNC pfnAPC, IN HANDLE hThread, IN ULONG_PTR dwData)
Definition: thread.c:959
BOOL WINAPI PathFileExistsW(LPCWSTR lpszPath)
Definition: path.c:1777
BOOL WINAPI PathIsDirectoryW(LPCWSTR lpszPath)
Definition: path.c:1723
BOOL WINAPI PathIsRelativeW(LPCWSTR lpszPath)
Definition: path.c:1579
#define assert(x)
Definition: debug.h:53
#define INFINITE
Definition: serial.h:102
unsigned int BOOL
Definition: ntddk_ex.h:94
unsigned long DWORD
Definition: ntddk_ex.h:95
#define FILE_FLAG_OVERLAPPED
Definition: disk.h:46
#define FILE_FLAG_BACKUP_SEMANTICS
Definition: disk.h:41
LPCWSTR szPath
Definition: env.c:37
static char szTempPath[MAX_PATH]
Definition: data.c:16
static TfClientId tid
static const WCHAR path1[]
Definition: path.c:28
static const WCHAR path2[]
Definition: path.c:29
_In_ HANDLE _In_ DWORD _In_ DWORD _Inout_opt_ LPOVERLAPPED lpOverlapped
Definition: mswsock.h:93
#define FILE_SHARE_WRITE
Definition: nt_native.h:681
#define FILE_LIST_DIRECTORY
Definition: nt_native.h:629
#define FILE_SHARE_DELETE
Definition: nt_native.h:682
#define UNICODE_NULL
#define L(x)
Definition: ntvdm.h:50
#define PathAppendW
Definition: pathcch.h:309
long LONG
Definition: pedump.c:60
static const WCHAR szName[]
Definition: powrprof.c:45
_CRTIMP uintptr_t __cdecl _beginthreadex(_In_opt_ void *_Security, _In_ unsigned _StackSize, _In_ unsigned(__stdcall *_StartAddress)(void *), _In_opt_ void *_ArgList, _In_ unsigned _InitFlag, _Out_opt_ unsigned *_ThrdAddr)
#define SHCNE_RMDIR
Definition: shlobj.h:1893
#define SHCNE_DELETE
Definition: shlobj.h:1891
#define SHCNE_MKDIR
Definition: shlobj.h:1892
#define SHCNE_UPDATEITEM
Definition: shlobj.h:1902
#define SHCNE_RENAMEITEM
Definition: shlobj.h:1889
#define SHCNE_UPDATEDIR
Definition: shlobj.h:1901
#define SHCNE_CREATE
Definition: shlobj.h:1890
#define SHCNE_RENAMEFOLDER
Definition: shlobj.h:1906
#define SHCNF_FLUSH
Definition: shlobj.h:1927
#define SHCNF_PATHW
Definition: shlobj.h:1924
#define SHCNE_ALLEVENTS
Definition: shlobj.h:1913
#define SHCNE_INTERRUPT
Definition: shlobj.h:1914
#define _countof(array)
Definition: sndvol32.h:70
#define TRACE(s)
Definition: solgame.cpp:4
HANDLE hEvent
Definition: winbase.h:820
DWORD WINAPI SleepEx(IN DWORD dwMilliseconds, IN BOOL bAlertable)
Definition: synch.c:802
DWORD WINAPI WaitForSingleObjectEx(IN HANDLE hHandle, IN DWORD dwMilliseconds, IN BOOL bAlertable)
Definition: synch.c:94
unsigned char * LPBYTE
Definition: typedefs.h:53
#define NTAPI
Definition: typedefs.h:36
PVOID HANDLE
Definition: typedefs.h:73
#define __stdcall
Definition: typedefs.h:25
uint32_t ULONG_PTR
Definition: typedefs.h:65
_In_ WDFIOTARGET _In_ _Strict_type_match_ WDF_IO_TARGET_SENT_IO_ACTION Action
Definition: wdfiotarget.h:510
#define ZeroMemory
Definition: winbase.h:1712
DWORD WINAPI GetLastError(void)
Definition: except.c:1042
HANDLE WINAPI GetCurrentThread(void)
Definition: proc.c:1148
#define CopyMemory
Definition: winbase.h:1710
#define ERROR_OPERATION_ABORTED
Definition: winerror.h:575
struct _FILE_NOTIFY_INFORMATION * PFILE_NOTIFY_INFORMATION
BOOL WINAPI IsWindow(_In_opt_ HWND)
#define FILE_NOTIFY_CHANGE_SIZE
#define FILE_ACTION_MODIFIED
#define FILE_ACTION_RENAMED_OLD_NAME
#define FILE_ACTION_REMOVED
#define FILE_NOTIFY_CHANGE_FILE_NAME
#define FILE_ACTION_RENAMED_NEW_NAME
#define FILE_NOTIFY_CHANGE_CREATION
#define FILE_ACTION_ADDED
#define FILE_NOTIFY_CHANGE_DIR_NAME
_Inout_opt_ PVOID Parameter
Definition: rtltypes.h:336
__wchar_t WCHAR
Definition: xmlstorage.h:180
const WCHAR * LPCWSTR
Definition: xmlstorage.h:185
unsigned char BYTE
Definition: xxhash.c:193