https://git.reactos.org/?p=reactos.git;a=commitdiff;h=5c589b55370406362a034…
commit 5c589b55370406362a0344a77664b342433e4db5
Author: Katayama Hirofumi MZ <katayama.hirofumi.mz(a)gmail.com>
AuthorDate: Mon May 4 14:53:23 2020 +0900
Commit: GitHub <noreply(a)github.com>
CommitDate: Mon May 4 14:53:23 2020 +0900
[SHELL32] Notify filesystem changes (#2659)
Notify filesystem change notifications by using ReadDirectoryChangesW function.
Creating/Deleting files/folders will be responsive in Explorer. CORE-13950
---
dll/win32/shell32/CDefView.cpp | 7 +-
dll/win32/shell32/changenotify.cpp | 2 +-
.../shell32/shelldesktop/CChangeNotifyServer.cpp | 690 ++++++++++++++++++++-
3 files changed, 686 insertions(+), 13 deletions(-)
diff --git a/dll/win32/shell32/CDefView.cpp b/dll/win32/shell32/CDefView.cpp
index fbca53391f7..06d7c659f03 100644
--- a/dll/win32/shell32/CDefView.cpp
+++ b/dll/win32/shell32/CDefView.cpp
@@ -1184,8 +1184,11 @@ LRESULT CDefView::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL &bHandl
ntreg[0].fRecursive = FALSE;
ntreg[0].pidl = m_pidlParent;
}
- m_hNotify = SHChangeNotifyRegister(m_hWnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel,
- SHCNE_ALLEVENTS, SHV_CHANGE_NOTIFY, nRegCount,
ntreg);
+ m_hNotify = SHChangeNotifyRegister(m_hWnd,
+ SHCNRF_InterruptLevel | SHCNRF_ShellLevel |
+ SHCNRF_NewDelivery,
+ SHCNE_ALLEVENTS, SHV_CHANGE_NOTIFY,
+ nRegCount, ntreg);
if (nRegCount == 3)
{
ILFree(pidls[0]);
diff --git a/dll/win32/shell32/changenotify.cpp b/dll/win32/shell32/changenotify.cpp
index 3f2b7cf1f20..ae5e0e8bdb4 100644
--- a/dll/win32/shell32/changenotify.cpp
+++ b/dll/win32/shell32/changenotify.cpp
@@ -573,7 +573,7 @@ SHChangeNotification_Lock(HANDLE hTicket, DWORD dwOwnerPID,
LPITEMIDLIST **lppid
if (lppidls)
*lppidls = pHandbag->pidls;
if (lpwEventId)
- *lpwEventId = pHandbag->pTicket->wEventId;
+ *lpwEventId = (pHandbag->pTicket->wEventId & ~SHCNE_INTERRUPT);
// return the handbag
return pHandbag;
diff --git a/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp
b/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp
index 5c2184f7527..a5852976f1e 100644
--- a/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp
+++ b/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp
@@ -6,11 +6,619 @@
*/
#include "shelldesktop.h"
#include "shlwapi_undoc.h"
-#include <atlsimpcoll.h>
-#include <assert.h>
+#include <atlsimpcoll.h> // for CSimpleArray
+#include <process.h> // for _beginthreadex
+#include <assert.h> // for assert
WINE_DEFAULT_DEBUG_CHANNEL(shcn);
+static inline void
+NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2)
+{
+ SHChangeNotify(wEventId | SHCNE_INTERRUPT, SHCNF_PATHW, path1, path2);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// DIRLIST --- directory list
+
+struct DIRLISTITEM
+{
+ WCHAR szPath[MAX_PATH];
+ DWORD dwFileSize;
+ BOOL fDir;
+
+ DIRLISTITEM(LPCWSTR pszPath, DWORD dwSize, BOOL is_dir)
+ {
+ lstrcpynW(szPath, pszPath, _countof(szPath));
+ dwFileSize = dwSize;
+ fDir = is_dir;
+ }
+
+ BOOL IsEmpty() const
+ {
+ return szPath[0] == 0;
+ }
+
+ BOOL EqualPath(LPCWSTR pszPath) const
+ {
+ return lstrcmpiW(szPath, pszPath) == 0;
+ }
+};
+
+class DIRLIST
+{
+public:
+ DIRLIST()
+ {
+ }
+
+ DIRLIST(LPCWSTR pszDir, BOOL fRecursive)
+ {
+ GetDirList(pszDir, fRecursive);
+ }
+
+ BOOL AddItem(LPCWSTR pszPath, DWORD dwFileSize, BOOL fDir);
+ BOOL GetDirList(LPCWSTR pszDir, BOOL fRecursive);
+ BOOL Contains(LPCWSTR pszPath, BOOL fDir) const;
+ BOOL RenameItem(LPCWSTR pszPath1, LPCWSTR pszPath2, BOOL fDir);
+ BOOL DeleteItem(LPCWSTR pszPath, BOOL fDir);
+ BOOL GetFirstChange(LPWSTR pszPath) const;
+
+ void RemoveAll()
+ {
+ m_items.RemoveAll();
+ }
+
+protected:
+ CSimpleArray<DIRLISTITEM> m_items;
+};
+
+BOOL DIRLIST::Contains(LPCWSTR pszPath, BOOL fDir) const
+{
+ assert(!PathIsRelativeW(pszPath));
+
+ for (INT i = 0; i < m_items.GetSize(); ++i)
+ {
+ if (m_items[i].IsEmpty() || fDir != m_items[i].fDir)
+ continue;
+
+ if (m_items[i].EqualPath(pszPath))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL DIRLIST::AddItem(LPCWSTR pszPath, DWORD dwFileSize, BOOL fDir)
+{
+ assert(!PathIsRelativeW(pszPath));
+
+ if (dwFileSize == INVALID_FILE_SIZE)
+ {
+ WIN32_FIND_DATAW find;
+ HANDLE hFind = FindFirstFileW(pszPath, &find);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+ FindClose(hFind);
+ dwFileSize = find.nFileSizeLow;
+ }
+
+ DIRLISTITEM item(pszPath, dwFileSize, fDir);
+ return m_items.Add(item);
+}
+
+BOOL DIRLIST::RenameItem(LPCWSTR pszPath1, LPCWSTR pszPath2, BOOL fDir)
+{
+ assert(!PathIsRelativeW(pszPath1));
+ assert(!PathIsRelativeW(pszPath2));
+
+ for (INT i = 0; i < m_items.GetSize(); ++i)
+ {
+ if (m_items[i].fDir == fDir && m_items[i].EqualPath(pszPath1))
+ {
+ lstrcpynW(m_items[i].szPath, pszPath2, _countof(m_items[i].szPath));
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+BOOL DIRLIST::DeleteItem(LPCWSTR pszPath, BOOL fDir)
+{
+ assert(!PathIsRelativeW(pszPath));
+
+ for (INT i = 0; i < m_items.GetSize(); ++i)
+ {
+ if (m_items[i].fDir == fDir && m_items[i].EqualPath(pszPath))
+ {
+ m_items[i].szPath[0] = 0;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+BOOL DIRLIST::GetDirList(LPCWSTR pszDir, BOOL fRecursive)
+{
+ // get the full path
+ WCHAR szPath[MAX_PATH];
+ lstrcpynW(szPath, pszDir, _countof(szPath));
+ assert(!PathIsRelativeW(szPath));
+
+ // is it a directory?
+ if (!PathIsDirectoryW(szPath))
+ return FALSE;
+
+ // add the path
+ if (!AddItem(szPath, 0, TRUE))
+ return FALSE;
+
+ // enumerate the file items to remember
+ PathAppendW(szPath, L"*");
+ WIN32_FIND_DATAW find;
+ HANDLE hFind = FindFirstFileW(szPath, &find);
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ ERR("FindFirstFileW failed\n");
+ return FALSE;
+ }
+
+ do
+ {
+ // ignore "." and ".."
+ if (lstrcmpW(find.cFileName, L".") == 0 ||
+ lstrcmpW(find.cFileName, L"..") == 0)
+ {
+ continue;
+ }
+
+ // build a path
+ PathRemoveFileSpecW(szPath);
+ if (lstrlenW(szPath) + lstrlenW(find.cFileName) + 1 > MAX_PATH)
+ {
+ ERR("szPath is too long\n");
+ continue;
+ }
+ PathAppendW(szPath, find.cFileName);
+
+ // add the path and do recurse
+ if (fRecursive && (find.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY))
+ GetDirList(szPath, fRecursive);
+ else
+ AddItem(szPath, find.nFileSizeLow, FALSE);
+ } while (FindNextFileW(hFind, &find));
+
+ FindClose(hFind);
+
+ return TRUE;
+}
+
+BOOL DIRLIST::GetFirstChange(LPWSTR pszPath) const
+{
+ // validate paths
+ for (INT i = 0; i < m_items.GetSize(); ++i)
+ {
+ if (m_items[i].IsEmpty())
+ continue;
+
+ if (m_items[i].fDir) // item is a directory
+ {
+ if (!PathIsDirectoryW(m_items[i].szPath))
+ {
+ // mismatched
+ lstrcpynW(pszPath, m_items[i].szPath, MAX_PATH);
+ return TRUE;
+ }
+ }
+ else // item is a normal file
+ {
+ if (!PathFileExistsW(m_items[i].szPath) ||
+ PathIsDirectoryW(m_items[i].szPath))
+ {
+ // mismatched
+ lstrcpynW(pszPath, m_items[i].szPath, MAX_PATH);
+ return TRUE;
+ }
+ }
+ }
+
+ // check sizes
+ HANDLE hFind;
+ WIN32_FIND_DATAW find;
+ for (INT i = 0; i < m_items.GetSize(); ++i)
+ {
+ if (m_items[i].IsEmpty() || m_items[i].fDir)
+ continue;
+
+ // get size
+ hFind = FindFirstFileW(m_items[i].szPath, &find);
+ FindClose(hFind);
+
+ if (hFind == INVALID_HANDLE_VALUE ||
+ find.nFileSizeLow != m_items[i].dwFileSize)
+ {
+ // different size
+ lstrcpynW(pszPath, m_items[i].szPath, MAX_PATH);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// DirWatch --- directory watcher using ReadDirectoryChangesW
+
+static HANDLE s_hThreadAPC = NULL;
+static BOOL s_fTerminateAllWatches = FALSE;
+
+// NOTE: Regard to asynchronous procedure call (APC), please see:
+//
https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sle…
+
+// The APC thread function for directory watch
+static unsigned __stdcall DirWatchThreadFuncAPC(void *)
+{
+ while (!s_fTerminateAllWatches)
+ {
+#if 1 // FIXME: This is a HACK
+ WaitForSingleObjectEx(GetCurrentThread(), INFINITE, TRUE);
+#else
+ SleepEx(INFINITE, TRUE);
+#endif
+ }
+ return 0;
+}
+
+// the buffer for ReadDirectoryChangesW
+#define BUFFER_SIZE 0x1000
+static BYTE s_buffer[BUFFER_SIZE];
+
+class DirWatch
+{
+public:
+ HANDLE m_hDir;
+ WCHAR m_szDir[MAX_PATH];
+ BOOL m_fDeadWatch;
+ BOOL m_fRecursive;
+ OVERLAPPED m_overlapped; // for async I/O
+ DIRLIST m_DirList;
+
+ static DirWatch *Create(LPCWSTR pszDir, BOOL fSubTree = FALSE);
+ ~DirWatch();
+
+protected:
+ DirWatch(LPCWSTR pszDir, BOOL fSubTree);
+};
+
+DirWatch::DirWatch(LPCWSTR pszDir, BOOL fSubTree)
+ : m_fDeadWatch(FALSE)
+ , m_fRecursive(fSubTree)
+ , m_DirList(pszDir, fSubTree)
+{
+ TRACE("DirWatch::DirWatch: %p, '%S'\n", this, pszDir);
+
+ lstrcpynW(m_szDir, pszDir, MAX_PATH);
+
+ // open the directory to watch changes (for ReadDirectoryChangesW)
+ m_hDir = CreateFileW(pszDir, FILE_LIST_DIRECTORY,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+ NULL);
+}
+
+/*static*/ DirWatch *DirWatch::Create(LPCWSTR pszDir, BOOL fSubTree)
+{
+ WCHAR szFullPath[MAX_PATH];
+ GetFullPathNameW(pszDir, _countof(szFullPath), szFullPath, NULL);
+
+ DirWatch *pDirWatch = new DirWatch(szFullPath, fSubTree);
+ if (pDirWatch->m_hDir == INVALID_HANDLE_VALUE)
+ {
+ ERR("CreateFileW failed\n");
+ delete pDirWatch;
+ pDirWatch = NULL;
+ }
+ return pDirWatch;
+}
+
+DirWatch::~DirWatch()
+{
+ TRACE("DirWatch::~DirWatch: %p, '%S'\n", this, m_szDir);
+
+ if (m_hDir != INVALID_HANDLE_VALUE)
+ CloseHandle(m_hDir);
+}
+
+static BOOL _BeginRead(DirWatch *pDirWatch);
+
+// The APC procedure to add a DirWatch and start the directory watch
+static void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter)
+{
+ DirWatch *pDirWatch = (DirWatch *)Parameter;
+ assert(pDirWatch != NULL);
+
+ _BeginRead(pDirWatch);
+}
+
+// The APC procedure to request termination of a DirWatch
+static void NTAPI _RequestTerminationAPC(ULONG_PTR Parameter)
+{
+ DirWatch *pDirWatch = (DirWatch *)Parameter;
+ assert(pDirWatch != NULL);
+
+ pDirWatch->m_fDeadWatch = TRUE;
+ CancelIo(pDirWatch->m_hDir);
+}
+
+// The APC procedure to request termination of all the directory watches
+static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter)
+{
+ s_fTerminateAllWatches = TRUE;
+ CloseHandle(s_hThreadAPC);
+ s_hThreadAPC = NULL;
+}
+
+// convert the file action to an event
+static DWORD
+ConvertActionToEvent(DWORD Action, BOOL fDir)
+{
+ switch (Action)
+ {
+ case FILE_ACTION_ADDED:
+ return (fDir ? SHCNE_MKDIR : SHCNE_CREATE);
+ case FILE_ACTION_REMOVED:
+ return (fDir ? SHCNE_RMDIR : SHCNE_DELETE);
+ case FILE_ACTION_MODIFIED:
+ return (fDir ? SHCNE_UPDATEDIR : SHCNE_UPDATEITEM);
+ case FILE_ACTION_RENAMED_OLD_NAME:
+ break;
+ case FILE_ACTION_RENAMED_NEW_NAME:
+ return (fDir ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM);
+ default:
+ break;
+ }
+ return 0;
+}
+
+// Notify a filesystem notification using pDirWatch.
+static void _ProcessNotification(DirWatch *pDirWatch)
+{
+ PFILE_NOTIFY_INFORMATION pInfo = (PFILE_NOTIFY_INFORMATION)s_buffer;
+ WCHAR szName[MAX_PATH], szPath[MAX_PATH], szTempPath[MAX_PATH];
+ DWORD dwEvent, cbName;
+ BOOL fDir;
+
+ // if the watch is recursive
+ if (pDirWatch->m_fRecursive)
+ {
+ // get the first change
+ if (!pDirWatch->m_DirList.GetFirstChange(szPath))
+ return;
+
+ // then, notify a SHCNE_UPDATEDIR
+ if (lstrcmpiW(pDirWatch->m_szDir, szPath) != 0)
+ PathRemoveFileSpecW(szPath);
+ NotifyFileSystemChange(SHCNE_UPDATEDIR, szPath, NULL);
+
+ // refresh directory list
+ pDirWatch->m_DirList.RemoveAll();
+ pDirWatch->m_DirList.GetDirList(pDirWatch->m_szDir, TRUE);
+ return;
+ }
+
+ // for each entry in s_buffer
+ szPath[0] = szTempPath[0] = 0;
+ for (;;)
+ {
+ // get name (relative from pDirWatch->m_szDir)
+ cbName = pInfo->FileNameLength;
+ if (sizeof(szName) - sizeof(UNICODE_NULL) < cbName)
+ {
+ ERR("pInfo->FileName is longer than szName\n");
+ break;
+ }
+ // NOTE: FILE_NOTIFY_INFORMATION.FileName is not null-terminated.
+ ZeroMemory(szName, sizeof(szName));
+ CopyMemory(szName, pInfo->FileName, cbName);
+
+ // get full path
+ lstrcpynW(szPath, pDirWatch->m_szDir, _countof(szPath));
+ PathAppendW(szPath, szName);
+
+ // convert to long pathname if it contains '~'
+ if (StrChrW(szPath, L'~') != NULL)
+ {
+ GetLongPathNameW(szPath, szName, _countof(szName));
+ lstrcpynW(szPath, szName, _countof(szPath));
+ }
+
+ // convert action to event
+ fDir = PathIsDirectoryW(szPath);
+ dwEvent = ConvertActionToEvent(pInfo->Action, fDir);
+
+ // get the directory list of pDirWatch
+ DIRLIST& List = pDirWatch->m_DirList;
+
+ // convert SHCNE_DELETE to SHCNE_RMDIR if the path is a directory
+ if (!fDir && (dwEvent == SHCNE_DELETE) && List.Contains(szPath,
TRUE))
+ {
+ fDir = TRUE;
+ dwEvent = SHCNE_RMDIR;
+ }
+
+ // update List
+ switch (dwEvent)
+ {
+ case SHCNE_MKDIR:
+ if (!List.AddItem(szPath, 0, TRUE))
+ dwEvent = 0;
+ break;
+ case SHCNE_CREATE:
+ if (!List.AddItem(szPath, INVALID_FILE_SIZE, FALSE))
+ dwEvent = 0;
+ break;
+ case SHCNE_RENAMEFOLDER:
+ if (!List.RenameItem(szTempPath, szPath, TRUE))
+ dwEvent = 0;
+ break;
+ case SHCNE_RENAMEITEM:
+ if (!List.RenameItem(szTempPath, szPath, FALSE))
+ dwEvent = 0;
+ break;
+ case SHCNE_RMDIR:
+ if (!List.DeleteItem(szPath, TRUE))
+ dwEvent = 0;
+ break;
+ case SHCNE_DELETE:
+ if (!List.DeleteItem(szPath, FALSE))
+ dwEvent = 0;
+ break;
+ }
+
+ if (dwEvent != 0)
+ {
+ // notify
+ if (pInfo->Action == FILE_ACTION_RENAMED_NEW_NAME)
+ NotifyFileSystemChange(dwEvent, szTempPath, szPath);
+ else
+ NotifyFileSystemChange(dwEvent, szPath, NULL);
+ }
+ else if (pInfo->Action == FILE_ACTION_RENAMED_OLD_NAME)
+ {
+ // save path for next FILE_ACTION_RENAMED_NEW_NAME
+ lstrcpynW(szTempPath, szPath, MAX_PATH);
+ }
+
+ if (pInfo->NextEntryOffset == 0)
+ break; // there is no next entry
+
+ // go next entry
+ pInfo = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pInfo + pInfo->NextEntryOffset);
+ }
+}
+
+// The completion routine of ReadDirectoryChangesW.
+static void CALLBACK
+_NotificationCompletion(DWORD dwErrorCode,
+ DWORD dwNumberOfBytesTransfered,
+ LPOVERLAPPED lpOverlapped)
+{
+ // MSDN: The hEvent member of the OVERLAPPED structure is not used by the
+ // system in this case, so you can use it yourself. We do just this, storing
+ // a pointer to the working struct in the overlapped structure.
+ DirWatch *pDirWatch = (DirWatch *)lpOverlapped->hEvent;
+ assert(pDirWatch != NULL);
+
+ // If the FSD doesn't support directory change notifications, there's no
+ // no need to retry and requeue notification
+ if (dwErrorCode == ERROR_INVALID_FUNCTION)
+ {
+ ERR("ERROR_INVALID_FUNCTION\n");
+ return;
+ }
+
+ // Also, if the notify operation was canceled (like, user moved to another
+ // directory), then, don't requeue notification.
+ if (dwErrorCode == ERROR_OPERATION_ABORTED)
+ {
+ TRACE("ERROR_OPERATION_ABORTED\n");
+ if (pDirWatch->m_fDeadWatch)
+ delete pDirWatch;
+ return;
+ }
+
+ // is this watch dead?
+ if (pDirWatch->m_fDeadWatch)
+ {
+ TRACE("m_fDeadWatch\n");
+ delete pDirWatch;
+ return;
+ }
+
+ // This likely means overflow, so force whole directory refresh.
+ if (dwNumberOfBytesTransfered == 0)
+ {
+ // do notify a SHCNE_UPDATEDIR
+ NotifyFileSystemChange(SHCNE_UPDATEDIR, pDirWatch->m_szDir, NULL);
+ }
+ else
+ {
+ // do notify
+ _ProcessNotification(pDirWatch);
+ }
+
+ // restart a watch
+ _BeginRead(pDirWatch);
+}
+
+// convert events to notification filter
+static DWORD
+GetFilterFromEvents(DWORD fEvents)
+{
+ // FIXME
+ return (FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME |
+ FILE_NOTIFY_CHANGE_CREATION |
+ FILE_NOTIFY_CHANGE_SIZE);
+}
+
+// Restart a watch by using ReadDirectoryChangesW function
+static BOOL _BeginRead(DirWatch *pDirWatch)
+{
+ assert(pDirWatch != NULL);
+
+ if (pDirWatch->m_fDeadWatch)
+ {
+ delete pDirWatch;
+ return FALSE; // the watch is dead
+ }
+
+ // initialize the buffer and the overlapped
+ ZeroMemory(s_buffer, sizeof(s_buffer));
+ ZeroMemory(&pDirWatch->m_overlapped, sizeof(pDirWatch->m_overlapped));
+ pDirWatch->m_overlapped.hEvent = (HANDLE)pDirWatch;
+
+ // start the directory watch
+ DWORD dwFilter = GetFilterFromEvents(SHCNE_ALLEVENTS);
+ if (!ReadDirectoryChangesW(pDirWatch->m_hDir, s_buffer, sizeof(s_buffer),
+ pDirWatch->m_fRecursive, dwFilter, NULL,
+ &pDirWatch->m_overlapped,
_NotificationCompletion))
+ {
+ ERR("ReadDirectoryChangesW for '%S' failed (error: %ld)\n",
+ pDirWatch->m_szDir, GetLastError());
+ return FALSE; // failure
+ }
+
+ return TRUE; // success
+}
+
+// create a DirWatch from a REGENTRY
+static DirWatch *
+CreateDirWatchFromRegEntry(LPREGENTRY pRegEntry)
+{
+ if (pRegEntry->ibPidl == 0)
+ return NULL;
+
+ // it must be interrupt level if pRegEntry is a filesystem watch
+ if (!(pRegEntry->fSources & SHCNRF_InterruptLevel))
+ return NULL;
+
+ // get the path
+ WCHAR szPath[MAX_PATH];
+ LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);
+ if (!SHGetPathFromIDListW(pidl, szPath) || !PathIsDirectoryW(szPath))
+ return NULL;
+
+ // create a DirWatch
+ DirWatch *pDirWatch = DirWatch::Create(szPath, pRegEntry->fRecursive);
+ if (pDirWatch == NULL)
+ return NULL;
+
+ return pDirWatch;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
// notification target item
struct ITEM
{
@@ -18,6 +626,7 @@ struct ITEM
DWORD dwUserPID; // The user PID; that is the process ID of the target window.
HANDLE hRegEntry; // The registration entry.
HWND hwndBroker; // Client broker window (if any).
+ DirWatch *pDirWatch; // for filesystem notification (for SHCNRF_InterruptLevel)
};
typedef CWinTraits <
@@ -53,6 +662,7 @@ public:
LRESULT OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
bHandled);
LRESULT OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
bHandled);
LRESULT OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+ LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
DECLARE_NOT_AGGREGATABLE(CChangeNotifyServer)
@@ -69,13 +679,15 @@ public:
MESSAGE_HANDLER(CN_DELIVER_NOTIFICATION, OnDeliverNotification)
MESSAGE_HANDLER(CN_SUSPEND_RESUME, OnSuspendResume)
MESSAGE_HANDLER(CN_UNREGISTER_PROCESS, OnRemoveByPID);
+ MESSAGE_HANDLER(WM_DESTROY, OnDestroy);
END_MSG_MAP()
private:
UINT m_nNextRegID;
CSimpleArray<ITEM> m_items;
- BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker);
+ BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker,
+ DirWatch *pDirWatch);
BOOL RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID);
void RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID);
void DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker);
@@ -94,7 +706,8 @@ CChangeNotifyServer::~CChangeNotifyServer()
{
}
-BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND
hwndBroker)
+BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry,
+ HWND hwndBroker, DirWatch *pDirWatch)
{
// find the empty room
for (INT i = 0; i < m_items.GetSize(); ++i)
@@ -106,12 +719,13 @@ BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID,
HANDLE hRegEntry
m_items[i].dwUserPID = dwUserPID;
m_items[i].hRegEntry = hRegEntry;
m_items[i].hwndBroker = hwndBroker;
+ m_items[i].pDirWatch = pDirWatch;
return TRUE;
}
}
// no empty room found
- ITEM item = { nRegID, dwUserPID, hRegEntry, hwndBroker };
+ ITEM item = { nRegID, dwUserPID, hRegEntry, hwndBroker, pDirWatch };
m_items.Add(item);
return TRUE;
}
@@ -119,10 +733,20 @@ BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID,
HANDLE hRegEntry
void CChangeNotifyServer::DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND
*phwndBroker)
{
// destroy broker if any and if first time
- if (item.hwndBroker && item.hwndBroker != *phwndBroker)
+ HWND hwndBroker = item.hwndBroker;
+ item.hwndBroker = NULL;
+ if (hwndBroker && hwndBroker != *phwndBroker)
+ {
+ ::DestroyWindow(hwndBroker);
+ *phwndBroker = hwndBroker;
+ }
+
+ // request termination of pDirWatch if any
+ DirWatch *pDirWatch = item.pDirWatch;
+ item.pDirWatch = NULL;
+ if (pDirWatch && s_hThreadAPC)
{
- ::DestroyWindow(item.hwndBroker);
- *phwndBroker = item.hwndBroker;
+ QueueUserAPC(_RequestTerminationAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch);
}
// free
@@ -131,6 +755,7 @@ void CChangeNotifyServer::DestroyItem(ITEM& item, DWORD
dwOwnerPID, HWND *phwndB
item.dwUserPID = 0;
item.hRegEntry = NULL;
item.hwndBroker = NULL;
+ item.pDirWatch = NULL;
}
BOOL CChangeNotifyServer::RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID)
@@ -204,11 +829,34 @@ LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam,
LPARAM lParam,
return FALSE;
}
+ // create a directory watch if necessary
+ DirWatch *pDirWatch = CreateDirWatchFromRegEntry(pRegEntry);
+ if (pDirWatch)
+ {
+ // create an APC thread for directory watching
+ if (s_hThreadAPC == NULL)
+ {
+ unsigned tid;
+ s_fTerminateAllWatches = FALSE;
+ s_hThreadAPC = (HANDLE)_beginthreadex(NULL, 0, DirWatchThreadFuncAPC, NULL,
0, &tid);
+ if (s_hThreadAPC == NULL)
+ {
+ pRegEntry->nRegID = INVALID_REG_ID;
+ SHUnlockShared(pRegEntry);
+ delete pDirWatch;
+ return FALSE;
+ }
+ }
+
+ // request adding the watch
+ QueueUserAPC(_AddDirectoryProcAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch);
+ }
+
// unlock the registry entry
SHUnlockShared(pRegEntry);
// add an ITEM
- return AddItem(m_nNextRegID, dwUserPID, hNewEntry, hwndBroker);
+ return AddItem(m_nNextRegID, dwUserPID, hNewEntry, hwndBroker, pDirWatch);
}
// Message CN_UNREGISTER: Unregister registration entries.
@@ -274,6 +922,16 @@ LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam,
LPARAM lPar
return 0;
}
+LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
bHandled)
+{
+ if (s_hThreadAPC)
+ {
+ // request termination of all directory watches
+ QueueUserAPC(_RequestAllTerminationAPC, s_hThreadAPC, (ULONG_PTR)NULL);
+ }
+ return 0;
+}
+
// get next valid registration ID
UINT CChangeNotifyServer::GetNextRegID()
{
@@ -344,8 +1002,20 @@ BOOL CChangeNotifyServer::ShouldNotify(LPDELITICKET pTicket,
LPREGENTRY pRegEntr
WCHAR szPath[MAX_PATH], szPath1[MAX_PATH], szPath2[MAX_PATH];
INT cch, cch1, cch2;
+ // check fSources
+ if (pTicket->uFlags & SHCNE_INTERRUPT)
+ {
+ if (!(pRegEntry->fSources & SHCNRF_InterruptLevel))
+ return FALSE;
+ }
+ else
+ {
+ if (!(pRegEntry->fSources & SHCNRF_ShellLevel))
+ return FALSE;
+ }
+
if (pRegEntry->ibPidl == 0)
- return TRUE;
+ return TRUE; // there is no PIDL
// get the stored pidl
pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);