mirror of
https://github.com/reactos/reactos.git
synced 2025-06-01 23:48:12 +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 "shelldesktop.h"
|
||||||
#include "shlwapi_undoc.h"
|
#include "shlwapi_undoc.h"
|
||||||
#include <atlsimpcoll.h> // for CSimpleArray
|
#include "CDirectoryWatcher.h"
|
||||||
#include <process.h> // for _beginthreadex
|
|
||||||
#include <assert.h> // for assert
|
#include <assert.h> // for assert
|
||||||
|
|
||||||
WINE_DEFAULT_DEBUG_CHANNEL(shcn);
|
WINE_DEFAULT_DEBUG_CHANNEL(shcn);
|
||||||
|
|
||||||
// TODO: SHCNRF_RecursiveInterrupt
|
// 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
|
// notification target item
|
||||||
|
@ -630,7 +22,7 @@ struct ITEM
|
||||||
DWORD dwUserPID; // The user PID; that is the process ID of the target window.
|
DWORD dwUserPID; // The user PID; that is the process ID of the target window.
|
||||||
HANDLE hRegEntry; // The registration entry.
|
HANDLE hRegEntry; // The registration entry.
|
||||||
HWND hwndBroker; // Client broker window (if any).
|
HWND hwndBroker; // Client broker window (if any).
|
||||||
DirWatch *pDirWatch; // for filesystem notification (for SHCNRF_InterruptLevel)
|
CDirectoryWatcher *pDirWatch; // for filesystem notification
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef CWinTraits <
|
typedef CWinTraits <
|
||||||
|
@ -691,7 +83,7 @@ private:
|
||||||
CSimpleArray<ITEM> m_items;
|
CSimpleArray<ITEM> m_items;
|
||||||
|
|
||||||
BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker,
|
BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker,
|
||||||
DirWatch *pDirWatch);
|
CDirectoryWatcher *pDirWatch);
|
||||||
BOOL RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID);
|
BOOL RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID);
|
||||||
void RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID);
|
void RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID);
|
||||||
void DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker);
|
void DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker);
|
||||||
|
@ -711,7 +103,7 @@ CChangeNotifyServer::~CChangeNotifyServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry,
|
BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry,
|
||||||
HWND hwndBroker, DirWatch *pDirWatch)
|
HWND hwndBroker, CDirectoryWatcher *pDirWatch)
|
||||||
{
|
{
|
||||||
// find the empty room
|
// find the empty room
|
||||||
for (INT i = 0; i < m_items.GetSize(); ++i)
|
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
|
// request termination of pDirWatch if any
|
||||||
DirWatch *pDirWatch = item.pDirWatch;
|
CDirectoryWatcher *pDirWatch = item.pDirWatch;
|
||||||
item.pDirWatch = NULL;
|
item.pDirWatch = NULL;
|
||||||
if (pDirWatch && s_hThreadAPC)
|
if (pDirWatch)
|
||||||
{
|
pDirWatch->RequestTermination();
|
||||||
QueueUserAPC(_RequestTerminationAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// free
|
// free
|
||||||
SHFreeShared(item.hRegEntry, dwOwnerPID);
|
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)
|
if (pRegEntry->ibPidl == 0)
|
||||||
return TRUE;
|
return NULL;
|
||||||
|
|
||||||
unsigned tid;
|
// it must be interrupt level if pRegEntry is a filesystem watch
|
||||||
s_fTerminateAllWatches = FALSE;
|
if (!(pRegEntry->fSources & SHCNRF_InterruptLevel))
|
||||||
s_hThreadAPC = (HANDLE)_beginthreadex(NULL, 0, DirWatchThreadFuncAPC, NULL, 0, &tid);
|
return NULL;
|
||||||
return s_hThreadAPC != 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.
|
// Message CN_REGISTER: Register the registration entry.
|
||||||
|
@ -845,11 +249,8 @@ LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a directory watch if necessary
|
// create a directory watch if necessary
|
||||||
DirWatch *pDirWatch = CreateDirWatchFromRegEntry(pRegEntry);
|
CDirectoryWatcher *pDirWatch = CreateDirectoryWatcherFromRegEntry(pRegEntry);
|
||||||
if (pDirWatch)
|
if (pDirWatch && !pDirWatch->RequestAddWatcher())
|
||||||
{
|
|
||||||
// create an APC thread for directory watching
|
|
||||||
if (!CreateAPCThread())
|
|
||||||
{
|
{
|
||||||
pRegEntry->nRegID = INVALID_REG_ID;
|
pRegEntry->nRegID = INVALID_REG_ID;
|
||||||
SHUnlockShared(pRegEntry);
|
SHUnlockShared(pRegEntry);
|
||||||
|
@ -857,10 +258,6 @@ LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam,
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// request adding the watch
|
|
||||||
QueueUserAPC(_AddDirectoryProcAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unlock the registry entry
|
// unlock the registry entry
|
||||||
SHUnlockShared(pRegEntry);
|
SHUnlockShared(pRegEntry);
|
||||||
|
|
||||||
|
@ -933,11 +330,7 @@ LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lPar
|
||||||
|
|
||||||
LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
||||||
{
|
{
|
||||||
if (s_hThreadAPC)
|
CDirectoryWatcher::RequestAllWatchersTermination();
|
||||||
{
|
|
||||||
// request termination of all directory watches
|
|
||||||
QueueUserAPC(_RequestAllTerminationAPC, s_hThreadAPC, (ULONG_PTR)NULL);
|
|
||||||
}
|
|
||||||
return 0;
|
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
|
list(APPEND SOURCE
|
||||||
CChangeNotifyServer.cpp
|
CChangeNotifyServer.cpp
|
||||||
CDesktopBrowser.cpp
|
CDesktopBrowser.cpp
|
||||||
|
CDirectoryWatcher.cpp
|
||||||
|
CFilePathList.cpp
|
||||||
dde.cpp)
|
dde.cpp)
|
||||||
|
|
||||||
add_library(shelldesktop ${SOURCE})
|
add_library(shelldesktop ${SOURCE})
|
||||||
|
|
Loading…
Reference in a new issue