diff --git a/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp b/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp index b5dc5e968c9..2186fe20ca4 100644 --- a/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp +++ b/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp @@ -6,621 +6,13 @@ */ #include "shelldesktop.h" #include "shlwapi_undoc.h" -#include // for CSimpleArray -#include // for _beginthreadex +#include "CDirectoryWatcher.h" #include // for assert WINE_DEFAULT_DEBUG_CHANNEL(shcn); // TODO: SHCNRF_RecursiveInterrupt -static inline void -NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2) -{ - SHChangeNotify(wEventId | SHCNE_INTERRUPT, SHCNF_PATHW, path1, path2); -} - -////////////////////////////////////////////////////////////////////////////// -// DIRLIST --- directory list - -// TODO: Share a DIRLIST in multiple Explorer - -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 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-sleepex - -// 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 @@ -630,7 +22,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) + CDirectoryWatcher *pDirWatch; // for filesystem notification }; typedef CWinTraits < @@ -691,7 +83,7 @@ private: CSimpleArray m_items; BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker, - DirWatch *pDirWatch); + CDirectoryWatcher *pDirWatch); BOOL RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID); void RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID); void DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker); @@ -711,7 +103,7 @@ CChangeNotifyServer::~CChangeNotifyServer() } BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, - HWND hwndBroker, DirWatch *pDirWatch) + HWND hwndBroker, CDirectoryWatcher *pDirWatch) { // find the empty room for (INT i = 0; i < m_items.GetSize(); ++i) @@ -746,12 +138,10 @@ void CChangeNotifyServer::DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndB } // request termination of pDirWatch if any - DirWatch *pDirWatch = item.pDirWatch; + CDirectoryWatcher *pDirWatch = item.pDirWatch; item.pDirWatch = NULL; - if (pDirWatch && s_hThreadAPC) - { - QueueUserAPC(_RequestTerminationAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch); - } + if (pDirWatch) + pDirWatch->RequestTermination(); // free SHFreeShared(item.hRegEntry, dwOwnerPID); @@ -791,15 +181,29 @@ void CChangeNotifyServer::RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID } } -static BOOL CreateAPCThread(void) +// create a CDirectoryWatcher from a REGENTRY +static CDirectoryWatcher * +CreateDirectoryWatcherFromRegEntry(LPREGENTRY pRegEntry) { - if (s_hThreadAPC != NULL) - return TRUE; + if (pRegEntry->ibPidl == 0) + return NULL; - unsigned tid; - s_fTerminateAllWatches = FALSE; - s_hThreadAPC = (HANDLE)_beginthreadex(NULL, 0, DirWatchThreadFuncAPC, NULL, 0, &tid); - return s_hThreadAPC != 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 CDirectoryWatcher + CDirectoryWatcher *pDirectoryWatcher = CDirectoryWatcher::Create(szPath, pRegEntry->fRecursive); + if (pDirectoryWatcher == NULL) + return NULL; + + return pDirectoryWatcher; } // Message CN_REGISTER: Register the registration entry. @@ -845,20 +249,13 @@ LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, } // create a directory watch if necessary - DirWatch *pDirWatch = CreateDirWatchFromRegEntry(pRegEntry); - if (pDirWatch) + CDirectoryWatcher *pDirWatch = CreateDirectoryWatcherFromRegEntry(pRegEntry); + if (pDirWatch && !pDirWatch->RequestAddWatcher()) { - // create an APC thread for directory watching - if (!CreateAPCThread()) - { - pRegEntry->nRegID = INVALID_REG_ID; - SHUnlockShared(pRegEntry); - delete pDirWatch; - return FALSE; - } - - // request adding the watch - QueueUserAPC(_AddDirectoryProcAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch); + pRegEntry->nRegID = INVALID_REG_ID; + SHUnlockShared(pRegEntry); + delete pDirWatch; + return FALSE; } // unlock the registry entry @@ -933,11 +330,7 @@ LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lPar 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); - } + CDirectoryWatcher::RequestAllWatchersTermination(); return 0; } diff --git a/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp b/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp new file mode 100644 index 00000000000..d2ed1583558 --- /dev/null +++ b/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp @@ -0,0 +1,406 @@ +/* + * PROJECT: shell32 + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Shell change notification + * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#include "shelldesktop.h" +#include "CDirectoryWatcher.h" +#include // for _beginthreadex +#include // for assert + +WINE_DEFAULT_DEBUG_CHANNEL(shcn); + +// Notify filesystem change +static inline void +NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2) +{ + SHChangeNotify(wEventId | SHCNE_INTERRUPT, SHCNF_PATHW, path1, path2); +} + +// The handle of the APC thread +static HANDLE s_hThreadAPC = NULL; + +// Terminate now? +static BOOL s_fTerminateAllWatchers = FALSE; + +// the buffer for ReadDirectoryChangesW +#define BUFFER_SIZE 0x1000 +static BYTE s_buffer[BUFFER_SIZE]; + +// The APC thread function for directory watch +static unsigned __stdcall DirectoryWatcherThreadFuncAPC(void *) +{ + while (!s_fTerminateAllWatchers) + { +#if 1 // FIXME: This is a HACK + WaitForSingleObjectEx(GetCurrentThread(), INFINITE, TRUE); +#else + SleepEx(INFINITE, TRUE); +#endif + } + return 0; +} + +// The APC procedure to add a CDirectoryWatcher and start the directory watch +static void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter) +{ + CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter; + assert(pDirectoryWatcher != NULL); + + pDirectoryWatcher->RestartWatching(); +} + +// The APC procedure to request termination of a CDirectoryWatcher +static void NTAPI _RequestTerminationAPC(ULONG_PTR Parameter) +{ + CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter; + assert(pDirectoryWatcher != NULL); + + pDirectoryWatcher->QuitWatching(); +} + +// The APC procedure to request termination of all the directory watches +static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter) +{ + s_fTerminateAllWatchers = TRUE; + CloseHandle(s_hThreadAPC); + s_hThreadAPC = NULL; +} + +CDirectoryWatcher::CDirectoryWatcher(LPCWSTR pszDirectoryPath, BOOL fSubTree) + : m_fDead(FALSE) + , m_fRecursive(fSubTree) + , m_file_list(pszDirectoryPath, fSubTree) +{ + TRACE("CDirectoryWatcher::CDirectoryWatcher: %p, '%S'\n", this, pszDirectoryPath); + + lstrcpynW(m_szDirectoryPath, pszDirectoryPath, MAX_PATH); + + // open the directory to watch changes (for ReadDirectoryChangesW) + m_hDirectory = CreateFileW(pszDirectoryPath, FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); +} + +/*static*/ CDirectoryWatcher * +CDirectoryWatcher::Create(LPCWSTR pszDirectoryPath, BOOL fSubTree) +{ + WCHAR szFullPath[MAX_PATH]; + GetFullPathNameW(pszDirectoryPath, _countof(szFullPath), szFullPath, NULL); + + CDirectoryWatcher *pDirectoryWatcher = new CDirectoryWatcher(szFullPath, fSubTree); + if (pDirectoryWatcher->m_hDirectory == INVALID_HANDLE_VALUE) + { + ERR("CreateFileW failed\n"); + delete pDirectoryWatcher; + pDirectoryWatcher = NULL; + } + return pDirectoryWatcher; +} + +CDirectoryWatcher::~CDirectoryWatcher() +{ + TRACE("CDirectoryWatcher::~CDirectoryWatcher: %p, '%S'\n", this, m_szDirectoryPath); + + if (m_hDirectory != INVALID_HANDLE_VALUE) + CloseHandle(m_hDirectory); +} + +// 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 pDirectoryWatcher. +void CDirectoryWatcher::ProcessNotification() +{ + 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 (m_fRecursive) + { + // get the first change + if (!m_file_list.GetFirstChange(szPath)) + return; + + // then, notify a SHCNE_UPDATEDIR + if (lstrcmpiW(m_szDirectoryPath, szPath) != 0) + PathRemoveFileSpecW(szPath); + NotifyFileSystemChange(SHCNE_UPDATEDIR, szPath, NULL); + + // refresh directory list + m_file_list.RemoveAll(); + m_file_list.AddPathsFromDirectory(m_szDirectoryPath, TRUE); + return; + } + + // for each entry in s_buffer + szPath[0] = szTempPath[0] = 0; + for (;;) + { + // get name (relative from m_szDirectoryPath) + 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, m_szDirectoryPath, _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); + + // convert SHCNE_DELETE to SHCNE_RMDIR if the path is a directory + if (!fDir && (dwEvent == SHCNE_DELETE) && m_file_list.ContainsPath(szPath, TRUE)) + { + fDir = TRUE; + dwEvent = SHCNE_RMDIR; + } + + // update m_file_list + switch (dwEvent) + { + case SHCNE_MKDIR: + if (!m_file_list.AddPath(szPath, 0, TRUE)) + dwEvent = 0; + break; + case SHCNE_CREATE: + if (!m_file_list.AddPath(szPath, INVALID_FILE_SIZE, FALSE)) + dwEvent = 0; + break; + case SHCNE_RENAMEFOLDER: + if (!m_file_list.RenamePath(szTempPath, szPath, TRUE)) + dwEvent = 0; + break; + case SHCNE_RENAMEITEM: + if (!m_file_list.RenamePath(szTempPath, szPath, FALSE)) + dwEvent = 0; + break; + case SHCNE_RMDIR: + if (!m_file_list.DeletePath(szPath, TRUE)) + dwEvent = 0; + break; + case SHCNE_DELETE: + if (!m_file_list.DeletePath(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); + } +} + +void CDirectoryWatcher::ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered) +{ + // 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 (IsDead()) + delete this; + return; + } + + // is this watch dead? + if (IsDead()) + { + TRACE("IsDead()\n"); + delete this; + return; + } + + // This likely means overflow, so force whole directory refresh. + if (dwNumberOfBytesTransfered == 0) + { + // do notify a SHCNE_UPDATEDIR + NotifyFileSystemChange(SHCNE_UPDATEDIR, m_szDirectoryPath, NULL); + } + else + { + // do notify + ProcessNotification(); + } + + // restart a watch + RestartWatching(); +} + +// 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. + CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)lpOverlapped->hEvent; + assert(pDirectoryWatcher != NULL); + + pDirectoryWatcher->ReadCompletion(dwErrorCode, dwNumberOfBytesTransfered); +} + +// 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 +BOOL CDirectoryWatcher::RestartWatching() +{ + assert(this != NULL); + + if (IsDead()) + { + delete this; + return FALSE; // the watch is dead + } + + // initialize the buffer and the overlapped + ZeroMemory(s_buffer, sizeof(s_buffer)); + ZeroMemory(&m_overlapped, sizeof(m_overlapped)); + m_overlapped.hEvent = (HANDLE)this; + + // start the directory watch + DWORD dwFilter = GetFilterFromEvents(SHCNE_ALLEVENTS); + if (!ReadDirectoryChangesW(m_hDirectory, s_buffer, sizeof(s_buffer), + m_fRecursive, dwFilter, NULL, + &m_overlapped, _NotificationCompletion)) + { + ERR("ReadDirectoryChangesW for '%S' failed (error: %ld)\n", + m_szDirectoryPath, GetLastError()); + return FALSE; // failure + } + + return TRUE; // success +} + +BOOL CDirectoryWatcher::CreateAPCThread() +{ + if (s_hThreadAPC != NULL) + return TRUE; + + unsigned tid; + s_fTerminateAllWatchers = FALSE; + s_hThreadAPC = (HANDLE)_beginthreadex(NULL, 0, DirectoryWatcherThreadFuncAPC, + NULL, 0, &tid); + return s_hThreadAPC != NULL; +} + +BOOL CDirectoryWatcher::RequestAddWatcher() +{ + assert(this != NULL); + + // create an APC thread for directory watching + if (!CreateAPCThread()) + return FALSE; + + // request adding the watch + QueueUserAPC(_AddDirectoryProcAPC, s_hThreadAPC, (ULONG_PTR)this); + return TRUE; +} + +BOOL CDirectoryWatcher::RequestTermination() +{ + assert(this != NULL); + + if (s_hThreadAPC) + { + QueueUserAPC(_RequestTerminationAPC, s_hThreadAPC, (ULONG_PTR)this); + return TRUE; + } + + return FALSE; +} + +/*static*/ void CDirectoryWatcher::RequestAllWatchersTermination() +{ + if (!s_hThreadAPC) + return; + + // request termination of all directory watches + QueueUserAPC(_RequestAllTerminationAPC, s_hThreadAPC, (ULONG_PTR)NULL); +} + +void CDirectoryWatcher::QuitWatching() +{ + assert(this != NULL); + + m_fDead = TRUE; + CancelIo(m_hDirectory); +} + +BOOL CDirectoryWatcher::IsDead() const +{ + return m_fDead; +} diff --git a/dll/win32/shell32/shelldesktop/CDirectoryWatcher.h b/dll/win32/shell32/shelldesktop/CDirectoryWatcher.h new file mode 100644 index 00000000000..667d5fac148 --- /dev/null +++ b/dll/win32/shell32/shelldesktop/CDirectoryWatcher.h @@ -0,0 +1,40 @@ +/* + * PROJECT: shell32 + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Shell change notification + * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#pragma once + +#include "CFilePathList.h" + +// NOTE: Regard to asynchronous procedure call (APC), please see: +// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex + +class CDirectoryWatcher +{ +public: + HANDLE m_hDirectory; + WCHAR m_szDirectoryPath[MAX_PATH]; + + static CDirectoryWatcher *Create(LPCWSTR pszDirectoryPath, BOOL fSubTree); + static void RequestAllWatchersTermination(); + ~CDirectoryWatcher(); + + BOOL IsDead() const; + BOOL RestartWatching(); + void QuitWatching(); + BOOL RequestAddWatcher(); + BOOL RequestTermination(); + void ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered); + +protected: + BOOL m_fDead; + BOOL m_fRecursive; + CFilePathList m_file_list; + OVERLAPPED m_overlapped; + + BOOL CreateAPCThread(); + void ProcessNotification(); + CDirectoryWatcher(LPCWSTR pszDirectoryPath, BOOL fSubTree); +}; diff --git a/dll/win32/shell32/shelldesktop/CFilePathList.cpp b/dll/win32/shell32/shelldesktop/CFilePathList.cpp new file mode 100644 index 00000000000..cb4d505a616 --- /dev/null +++ b/dll/win32/shell32/shelldesktop/CFilePathList.cpp @@ -0,0 +1,192 @@ +/* + * PROJECT: shell32 + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Shell change notification + * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#include "shelldesktop.h" +#include "CFilePathList.h" +#include // for assert + +WINE_DEFAULT_DEBUG_CHANNEL(shcn); + +BOOL CFilePathList::ContainsPath(LPCWSTR pszPath, BOOL fIsDirectory) const +{ + assert(!PathIsRelativeW(pszPath)); + + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].IsEmpty() || fIsDirectory != m_items[i].IsDirectory()) + continue; + + if (m_items[i].EqualPath(pszPath)) + return TRUE; // matched + } + return FALSE; +} + +static DWORD GetSizeOfFile(LPCWSTR pszPath) +{ + WIN32_FIND_DATAW find; + HANDLE hFind = FindFirstFileW(pszPath, &find); + if (hFind == INVALID_HANDLE_VALUE) + return FALSE; + FindClose(hFind); + return find.nFileSizeLow; +} + +BOOL CFilePathList::AddPath(LPCWSTR pszPath, DWORD dwFileSize, BOOL fIsDirectory) +{ + assert(!PathIsRelativeW(pszPath)); + + if (dwFileSize == INVALID_FILE_SIZE) + { + dwFileSize = GetSizeOfFile(pszPath); + } + + CFilePathItem item(pszPath, dwFileSize, fIsDirectory); + return m_items.Add(item); +} + +BOOL CFilePathList::RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2, BOOL fIsDirectory) +{ + assert(!PathIsRelativeW(pszPath1)); + assert(!PathIsRelativeW(pszPath2)); + + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].IsDirectory() == fIsDirectory && m_items[i].EqualPath(pszPath1)) + { + // matched + m_items[i].SetPath(pszPath2); + return TRUE; + } + } + return FALSE; +} + +BOOL CFilePathList::DeletePath(LPCWSTR pszPath, BOOL fIsDirectory) +{ + assert(!PathIsRelativeW(pszPath)); + + for (INT i = 0; i < m_items.GetSize(); ++i) + { + if (m_items[i].IsDirectory() == fIsDirectory && m_items[i].EqualPath(pszPath)) + { + // matched + m_items[i].SetPath(NULL); + return TRUE; + } + } + return FALSE; +} + +BOOL CFilePathList::AddPathsFromDirectory(LPCWSTR pszDirectoryPath, BOOL fRecursive) +{ + // get the full path + WCHAR szPath[MAX_PATH]; + lstrcpynW(szPath, pszDirectoryPath, _countof(szPath)); + assert(!PathIsRelativeW(szPath)); + + // is it a directory? + if (!PathIsDirectoryW(szPath)) + return FALSE; + + // add the path + if (!AddPath(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); + + BOOL fIsDirectory = !!(find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + + // add the path and do recurse + if (fRecursive && fIsDirectory) + AddPathsFromDirectory(szPath, fRecursive); + else + AddPath(szPath, find.nFileSizeLow, fIsDirectory); + } while (FindNextFileW(hFind, &find)); + + FindClose(hFind); + + return TRUE; +} + +BOOL CFilePathList::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].IsDirectory()) // item is a directory + { + if (!PathIsDirectoryW(m_items[i].GetPath())) + { + // mismatched + lstrcpynW(pszPath, m_items[i].GetPath(), MAX_PATH); + return TRUE; + } + } + else // item is a normal file + { + if (!PathFileExistsW(m_items[i].GetPath()) || + PathIsDirectoryW(m_items[i].GetPath())) + { + // mismatched + lstrcpynW(pszPath, m_items[i].GetPath(), 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].IsDirectory()) + continue; + + // get size + hFind = FindFirstFileW(m_items[i].GetPath(), &find); + FindClose(hFind); + + if (hFind == INVALID_HANDLE_VALUE || + find.nFileSizeLow != m_items[i].GetSize()) + { + // different size + lstrcpynW(pszPath, m_items[i].GetPath(), MAX_PATH); + return TRUE; + } + } + + return FALSE; +} diff --git a/dll/win32/shell32/shelldesktop/CFilePathList.h b/dll/win32/shell32/shelldesktop/CFilePathList.h new file mode 100644 index 00000000000..815fdaba4a3 --- /dev/null +++ b/dll/win32/shell32/shelldesktop/CFilePathList.h @@ -0,0 +1,108 @@ +#pragma once + +#include // for CSimpleArray + +////////////////////////////////////////////////////////////////////////////// + +// A pathname with info +class CFilePathItem +{ +public: + CFilePathItem() : m_pszPath(NULL) + { + } + + CFilePathItem(LPCWSTR pszPath, DWORD dwFileSize, BOOL IsDirectory) + { + m_pszPath = _wcsdup(pszPath); + m_dwFileSize = dwFileSize; + m_fIsDirectory = IsDirectory; + } + + CFilePathItem(const CFilePathItem& item) + : m_pszPath(_wcsdup(item.m_pszPath)) + , m_dwFileSize(item.m_dwFileSize) + , m_fIsDirectory(item.m_fIsDirectory) + { + } + + CFilePathItem& operator=(const CFilePathItem& item) + { + free(m_pszPath); + m_pszPath = _wcsdup(item.m_pszPath); + m_dwFileSize = item.m_dwFileSize; + m_fIsDirectory = item.m_fIsDirectory; + return *this; + } + + ~CFilePathItem() + { + free(m_pszPath); + } + + BOOL IsEmpty() const + { + return m_pszPath == NULL; + } + + BOOL IsDirectory() const + { + return m_fIsDirectory; + } + + LPCWSTR GetPath() const + { + return m_pszPath; + } + + void SetPath(LPCWSTR pszPath) + { + free(m_pszPath); + m_pszPath = _wcsdup(pszPath); + } + + BOOL EqualPath(LPCWSTR pszPath) const + { + return m_pszPath != NULL && lstrcmpiW(m_pszPath, pszPath) == 0; + } + + DWORD GetSize() const + { + return m_dwFileSize; + } + +protected: + LPWSTR m_pszPath; // A full path, malloc'ed + DWORD m_dwFileSize; // the size of a file + BOOL m_fIsDirectory; // is it a directory? +}; + +// the file list +class CFilePathList +{ +public: + CFilePathList() + { + } + + CFilePathList(LPCWSTR pszDirectoryPath, BOOL fRecursive) + { + AddPathsFromDirectory(pszDirectoryPath, fRecursive); + } + + BOOL ContainsPath(LPCWSTR pszPath, BOOL fIsDirectory) const; + BOOL GetFirstChange(LPWSTR pszPath) const; + + BOOL AddPath(LPCWSTR pszPath, DWORD dwFileSize, BOOL fIsDirectory); + BOOL AddPathsFromDirectory(LPCWSTR pszDirectoryPath, BOOL fRecursive); + BOOL RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2, BOOL fIsDirectory); + BOOL DeletePath(LPCWSTR pszPath, BOOL fIsDirectory); + + void RemoveAll() + { + m_items.RemoveAll(); + } + +protected: + CSimpleArray m_items; +}; diff --git a/dll/win32/shell32/shelldesktop/CMakeLists.txt b/dll/win32/shell32/shelldesktop/CMakeLists.txt index 6d3be2b53e5..498eb70e024 100644 --- a/dll/win32/shell32/shelldesktop/CMakeLists.txt +++ b/dll/win32/shell32/shelldesktop/CMakeLists.txt @@ -12,6 +12,8 @@ include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/atl) list(APPEND SOURCE CChangeNotifyServer.cpp CDesktopBrowser.cpp + CDirectoryWatcher.cpp + CFilePathList.cpp dde.cpp) add_library(shelldesktop ${SOURCE})