mirror of
https://github.com/reactos/reactos.git
synced 2025-05-27 21:18:15 +00:00
[SHELL32] Improve readability of filesystem notification (#2737)
Improve human readability of filesystem change notification. - Rename the identifiers for human readabilities. - Split the code into CFilePathList and CDirectoryWatcher classes. - Encapsulation of code. CORE-13950
This commit is contained in:
parent
3dad100ea4
commit
7c9a8c0b89
6 changed files with 783 additions and 642 deletions
|
@ -6,621 +6,13 @@
|
|||
*/
|
||||
#include "shelldesktop.h"
|
||||
#include "shlwapi_undoc.h"
|
||||
#include <atlsimpcoll.h> // for CSimpleArray
|
||||
#include <process.h> // for _beginthreadex
|
||||
#include "CDirectoryWatcher.h"
|
||||
#include <assert.h> // 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<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-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<ITEM> 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;
|
||||
}
|
||||
|
||||
|
|
406
dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp
Normal file
406
dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp
Normal file
|
@ -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 <process.h> // for _beginthreadex
|
||||
#include <assert.h> // 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;
|
||||
}
|
40
dll/win32/shell32/shelldesktop/CDirectoryWatcher.h
Normal file
40
dll/win32/shell32/shelldesktop/CDirectoryWatcher.h
Normal file
|
@ -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);
|
||||
};
|
192
dll/win32/shell32/shelldesktop/CFilePathList.cpp
Normal file
192
dll/win32/shell32/shelldesktop/CFilePathList.cpp
Normal file
|
@ -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 <assert.h> // 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;
|
||||
}
|
108
dll/win32/shell32/shelldesktop/CFilePathList.h
Normal file
108
dll/win32/shell32/shelldesktop/CFilePathList.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include <atlsimpcoll.h> // 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<CFilePathItem> m_items;
|
||||
};
|
|
@ -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})
|
||||
|
|
Loading…
Reference in a new issue