Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402/* * 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 <process.h> // for _beginthreadex#include <assert.h> // for assert
WINE_DEFAULT_DEBUG_CHANNEL(shcn);
// Notify filesystem changestatic inline voidNotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2){ SHChangeNotify(wEventId | SHCNE_INTERRUPT, SHCNF_PATHW | SHCNF_FLUSH, path1, path2);}
// The handle of the APC threadstatic HANDLE s_hThreadAPC = NULL;
// Terminate now?static BOOL s_fTerminateAllWatchers = FALSE;
// the buffer for ReadDirectoryChangesW#define BUFFER_SIZE 0x1000static BYTE s_buffer[BUFFER_SIZE];
// The APC thread function for directory watchstatic 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 watchstatic void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter){ CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter; assert(pDirectoryWatcher != NULL);
pDirectoryWatcher->RestartWatching();}
// The APC procedure to request termination of a CDirectoryWatcherstatic 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 watchesstatic void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter){ s_fTerminateAllWatchers = TRUE; CloseHandle(s_hThreadAPC); s_hThreadAPC = NULL;}
CDirectoryWatcher::CDirectoryWatcher(HWND hNotifyWnd, LPCWSTR pszDirectoryPath, BOOL fSubTree) : m_hNotifyWnd(hNotifyWnd) , m_fDead(FALSE) , m_fRecursive(fSubTree) , m_dir_list(pszDirectoryPath, fSubTree){ TRACE("%p, '%S'\n", this, pszDirectoryPath);
GetFullPathNameW(pszDirectoryPath, _countof(m_szDirectoryPath), m_szDirectoryPath, NULL);
// open the directory to watch changes (for ReadDirectoryChangesW) m_hDirectory = CreateFileW(m_szDirectoryPath, 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(HWND hNotifyWnd, LPCWSTR pszDirectoryPath, BOOL fSubTree){ CDirectoryWatcher *pDirectoryWatcher = new CDirectoryWatcher(hNotifyWnd, pszDirectoryPath, fSubTree); if (pDirectoryWatcher->m_hDirectory == INVALID_HANDLE_VALUE) { ERR("CreateFileW failed\n"); delete pDirectoryWatcher; pDirectoryWatcher = NULL; } return pDirectoryWatcher;}
CDirectoryWatcher::~CDirectoryWatcher(){ TRACE("%p, '%S'\n", this, m_szDirectoryPath);
if (m_hDirectory != INVALID_HANDLE_VALUE) CloseHandle(m_hDirectory);}
// convert the file action to an eventstatic DWORDConvertActionToEvent(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; TRACE("CDirectoryWatcher::ProcessNotification: enter\n");
// 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) { if (GetLongPathNameW(szPath, szName, _countof(szName)) && !PathIsRelativeW(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_dir_list.ContainsPath(szPath)) { fDir = TRUE; dwEvent = SHCNE_RMDIR; }
// update m_dir_list switch (dwEvent) { case SHCNE_MKDIR: if (!PathIsDirectoryW(szPath) || !m_dir_list.AddPath(szPath)) dwEvent = 0; break; case SHCNE_CREATE: if (!PathFileExistsW(szPath) || PathIsDirectoryW(szPath)) dwEvent = 0; break; case SHCNE_RENAMEFOLDER: if (!PathIsDirectoryW(szPath) || !m_dir_list.RenamePath(szTempPath, szPath)) dwEvent = 0; break; case SHCNE_RENAMEITEM: if (!PathFileExistsW(szPath) || PathIsDirectoryW(szPath)) dwEvent = 0; break; case SHCNE_RMDIR: if (PathIsDirectoryW(szPath) || !m_dir_list.DeletePath(szPath)) dwEvent = 0; break; case SHCNE_DELETE: if (PathFileExistsW(szPath)) 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); }
TRACE("CDirectoryWatcher::ProcessNotification: leave\n");}
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 filterstatic DWORDGetFilterFromEvents(DWORD fEvents){ // FIXME return (FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_ATTRIBUTES);}
// Restart a watch by using ReadDirectoryChangesW functionBOOL 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; m_hNotifyWnd = NULL; CancelIo(m_hDirectory);}
BOOL CDirectoryWatcher::IsDead(){ if (m_hNotifyWnd && !::IsWindow(m_hNotifyWnd)) { m_hNotifyWnd = NULL; m_fDead = TRUE; CancelIo(m_hDirectory); } return m_fDead;}