reactos/dll/win32/shell32/shelldesktop/CChangeNotifyServer.cpp
Katayama Hirofumi MZ 11b6f973b7 [SHELL32] Follow-up 2 of #2659
Add TODO comments.
Make CreateAPCThread static.
CORE-13950
2020-05-04 16:22:38 +09:00

1123 lines
32 KiB
C++

/*
* 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 "shlwapi_undoc.h"
#include <atlsimpcoll.h> // for CSimpleArray
#include <process.h> // for _beginthreadex
#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
struct ITEM
{
UINT nRegID; // The registration ID.
DWORD dwUserPID; // The user PID; that is the process ID of the target window.
HANDLE hRegEntry; // The registration entry.
HWND hwndBroker; // Client broker window (if any).
DirWatch *pDirWatch; // for filesystem notification (for SHCNRF_InterruptLevel)
};
typedef CWinTraits <
WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
WS_EX_TOOLWINDOW
> CChangeNotifyServerTraits;
//////////////////////////////////////////////////////////////////////////////
// CChangeNotifyServer
//
// CChangeNotifyServer implements a window that handles all shell change notifications.
// It runs in the context of explorer and specifically in the thread of the shell desktop.
// Shell change notification api exported from shell32 forwards all their calls
// to this window where all processing takes place.
class CChangeNotifyServer :
public CWindowImpl<CChangeNotifyServer, CWindow, CChangeNotifyServerTraits>,
public CComObjectRootEx<CComMultiThreadModelNoCS>,
public IOleWindow
{
public:
CChangeNotifyServer();
virtual ~CChangeNotifyServer();
HRESULT Initialize();
// *** IOleWindow methods ***
virtual HRESULT STDMETHODCALLTYPE GetWindow(HWND *lphwnd);
virtual HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode);
// Message handlers
LRESULT OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
DECLARE_NOT_AGGREGATABLE(CChangeNotifyServer)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CChangeNotifyServer)
COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
END_COM_MAP()
DECLARE_WND_CLASS_EX(L"WorkerW", 0, 0)
BEGIN_MSG_MAP(CChangeNotifyServer)
MESSAGE_HANDLER(CN_REGISTER, OnRegister)
MESSAGE_HANDLER(CN_UNREGISTER, OnUnRegister)
MESSAGE_HANDLER(CN_DELIVER_NOTIFICATION, OnDeliverNotification)
MESSAGE_HANDLER(CN_SUSPEND_RESUME, OnSuspendResume)
MESSAGE_HANDLER(CN_UNREGISTER_PROCESS, OnRemoveByPID);
MESSAGE_HANDLER(WM_DESTROY, OnDestroy);
END_MSG_MAP()
private:
UINT m_nNextRegID;
CSimpleArray<ITEM> m_items;
BOOL AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry, HWND hwndBroker,
DirWatch *pDirWatch);
BOOL RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID);
void RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID);
void DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker);
UINT GetNextRegID();
BOOL DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID);
BOOL ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry);
};
CChangeNotifyServer::CChangeNotifyServer()
: m_nNextRegID(INVALID_REG_ID)
{
}
CChangeNotifyServer::~CChangeNotifyServer()
{
}
BOOL CChangeNotifyServer::AddItem(UINT nRegID, DWORD dwUserPID, HANDLE hRegEntry,
HWND hwndBroker, DirWatch *pDirWatch)
{
// find the empty room
for (INT i = 0; i < m_items.GetSize(); ++i)
{
if (m_items[i].nRegID == INVALID_REG_ID)
{
// found the room, populate it
m_items[i].nRegID = nRegID;
m_items[i].dwUserPID = dwUserPID;
m_items[i].hRegEntry = hRegEntry;
m_items[i].hwndBroker = hwndBroker;
m_items[i].pDirWatch = pDirWatch;
return TRUE;
}
}
// no empty room found
ITEM item = { nRegID, dwUserPID, hRegEntry, hwndBroker, pDirWatch };
m_items.Add(item);
return TRUE;
}
void CChangeNotifyServer::DestroyItem(ITEM& item, DWORD dwOwnerPID, HWND *phwndBroker)
{
// destroy broker if any and if first time
HWND hwndBroker = item.hwndBroker;
item.hwndBroker = NULL;
if (hwndBroker && hwndBroker != *phwndBroker)
{
::DestroyWindow(hwndBroker);
*phwndBroker = hwndBroker;
}
// request termination of pDirWatch if any
DirWatch *pDirWatch = item.pDirWatch;
item.pDirWatch = NULL;
if (pDirWatch && s_hThreadAPC)
{
QueueUserAPC(_RequestTerminationAPC, s_hThreadAPC, (ULONG_PTR)pDirWatch);
}
// free
SHFreeShared(item.hRegEntry, dwOwnerPID);
item.nRegID = INVALID_REG_ID;
item.dwUserPID = 0;
item.hRegEntry = NULL;
item.hwndBroker = NULL;
item.pDirWatch = NULL;
}
BOOL CChangeNotifyServer::RemoveItemsByRegID(UINT nRegID, DWORD dwOwnerPID)
{
BOOL bFound = FALSE;
HWND hwndBroker = NULL;
assert(nRegID != INVALID_REG_ID);
for (INT i = 0; i < m_items.GetSize(); ++i)
{
if (m_items[i].nRegID == nRegID)
{
bFound = TRUE;
DestroyItem(m_items[i], dwOwnerPID, &hwndBroker);
}
}
return bFound;
}
void CChangeNotifyServer::RemoveItemsByProcess(DWORD dwOwnerPID, DWORD dwUserPID)
{
HWND hwndBroker = NULL;
assert(dwUserPID != 0);
for (INT i = 0; i < m_items.GetSize(); ++i)
{
if (m_items[i].dwUserPID == dwUserPID)
{
DestroyItem(m_items[i], dwOwnerPID, &hwndBroker);
}
}
}
static BOOL CreateAPCThread(void)
{
if (s_hThreadAPC != NULL)
return TRUE;
unsigned tid;
s_fTerminateAllWatches = FALSE;
s_hThreadAPC = (HANDLE)_beginthreadex(NULL, 0, DirWatchThreadFuncAPC, NULL, 0, &tid);
return s_hThreadAPC != NULL;
}
// Message CN_REGISTER: Register the registration entry.
// wParam: The handle of registration entry.
// lParam: The owner PID of registration entry.
// return: TRUE if successful.
LRESULT CChangeNotifyServer::OnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRACE("OnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
// lock the registration entry
HANDLE hRegEntry = (HANDLE)wParam;
DWORD dwOwnerPID = (DWORD)lParam;
LPREGENTRY pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, TRUE);
if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC)
{
ERR("pRegEntry is invalid\n");
SHUnlockShared(pRegEntry);
return FALSE;
}
// update registration ID if necessary
if (pRegEntry->nRegID == INVALID_REG_ID)
pRegEntry->nRegID = GetNextRegID();
TRACE("pRegEntry->nRegID: %u\n", pRegEntry->nRegID);
// get the user PID; that is the process ID of the target window
DWORD dwUserPID;
GetWindowThreadProcessId(pRegEntry->hwnd, &dwUserPID);
// get broker if any
HWND hwndBroker = pRegEntry->hwndBroker;
// clone the registration entry
HANDLE hNewEntry = SHAllocShared(pRegEntry, pRegEntry->cbSize, dwOwnerPID);
if (hNewEntry == NULL)
{
ERR("Out of memory\n");
pRegEntry->nRegID = INVALID_REG_ID;
SHUnlockShared(pRegEntry);
return FALSE;
}
// create a directory watch if necessary
DirWatch *pDirWatch = CreateDirWatchFromRegEntry(pRegEntry);
if (pDirWatch)
{
// 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);
}
// unlock the registry entry
SHUnlockShared(pRegEntry);
// add an ITEM
return AddItem(m_nNextRegID, dwUserPID, hNewEntry, hwndBroker, pDirWatch);
}
// Message CN_UNREGISTER: Unregister registration entries.
// wParam: The registration ID.
// lParam: Ignored.
// return: TRUE if successful.
LRESULT CChangeNotifyServer::OnUnRegister(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRACE("OnUnRegister(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
// validate registration ID
UINT nRegID = (UINT)wParam;
if (nRegID == INVALID_REG_ID)
{
ERR("INVALID_REG_ID\n");
return FALSE;
}
// remove it
DWORD dwOwnerPID;
GetWindowThreadProcessId(m_hWnd, &dwOwnerPID);
return RemoveItemsByRegID(nRegID, dwOwnerPID);
}
// Message CN_DELIVER_NOTIFICATION: Perform a delivery.
// wParam: The handle of delivery ticket.
// lParam: The owner PID of delivery ticket.
// return: TRUE if necessary.
LRESULT CChangeNotifyServer::OnDeliverNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRACE("OnDeliverNotification(%p, %u, %p, %p)\n", m_hWnd, uMsg, wParam, lParam);
HANDLE hTicket = (HANDLE)wParam;
DWORD dwOwnerPID = (DWORD)lParam;
// do delivery
BOOL ret = DeliverNotification(hTicket, dwOwnerPID);
// free the ticket
SHFreeShared(hTicket, dwOwnerPID);
return ret;
}
// Message CN_SUSPEND_RESUME: Suspend or resume the change notification.
// (specification is unknown)
LRESULT CChangeNotifyServer::OnSuspendResume(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRACE("OnSuspendResume\n");
// FIXME
return FALSE;
}
// Message CN_UNREGISTER_PROCESS: Remove registration entries by PID.
// wParam: The user PID.
// lParam: Ignored.
// return: Zero.
LRESULT CChangeNotifyServer::OnRemoveByPID(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DWORD dwOwnerPID, dwUserPID = (DWORD)wParam;
GetWindowThreadProcessId(m_hWnd, &dwOwnerPID);
RemoveItemsByProcess(dwOwnerPID, dwUserPID);
return 0;
}
LRESULT CChangeNotifyServer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (s_hThreadAPC)
{
// request termination of all directory watches
QueueUserAPC(_RequestAllTerminationAPC, s_hThreadAPC, (ULONG_PTR)NULL);
}
return 0;
}
// get next valid registration ID
UINT CChangeNotifyServer::GetNextRegID()
{
m_nNextRegID++;
if (m_nNextRegID == INVALID_REG_ID)
m_nNextRegID++;
return m_nNextRegID;
}
// This function is called from CChangeNotifyServer::OnDeliverNotification.
// The function notifies to the registration entries that should be notified.
BOOL CChangeNotifyServer::DeliverNotification(HANDLE hTicket, DWORD dwOwnerPID)
{
TRACE("DeliverNotification(%p, %p, 0x%lx)\n", m_hWnd, hTicket, dwOwnerPID);
// lock the delivery ticket
LPDELITICKET pTicket = (LPDELITICKET)SHLockSharedEx(hTicket, dwOwnerPID, FALSE);
if (pTicket == NULL || pTicket->dwMagic != DELITICKET_MAGIC)
{
ERR("pTicket is invalid\n");
SHUnlockShared(pTicket);
return FALSE;
}
// for all items
for (INT i = 0; i < m_items.GetSize(); ++i)
{
// validate the item
if (m_items[i].nRegID == INVALID_REG_ID)
continue;
HANDLE hRegEntry = m_items[i].hRegEntry;
if (hRegEntry == NULL)
continue;
// lock the registration entry
LPREGENTRY pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, FALSE);
if (pRegEntry == NULL || pRegEntry->dwMagic != REGENTRY_MAGIC)
{
ERR("pRegEntry is invalid\n");
SHUnlockShared(pRegEntry);
continue;
}
// should we notify for it?
BOOL bNotify = ShouldNotify(pTicket, pRegEntry);
if (bNotify)
{
// do notify
TRACE("Notifying: %p, 0x%x, %p, %lu\n",
pRegEntry->hwnd, pRegEntry->uMsg, hTicket, dwOwnerPID);
SendMessageW(pRegEntry->hwnd, pRegEntry->uMsg, (WPARAM)hTicket, dwOwnerPID);
}
// unlock the registration entry
SHUnlockShared(pRegEntry);
}
// unlock the ticket
SHUnlockShared(pTicket);
return TRUE;
}
BOOL CChangeNotifyServer::ShouldNotify(LPDELITICKET pTicket, LPREGENTRY pRegEntry)
{
LPITEMIDLIST pidl, pidl1 = NULL, pidl2 = NULL;
WCHAR szPath[MAX_PATH], szPath1[MAX_PATH], szPath2[MAX_PATH];
INT cch, cch1, cch2;
// check fSources
if (pTicket->uFlags & SHCNE_INTERRUPT)
{
if (!(pRegEntry->fSources & SHCNRF_InterruptLevel))
return FALSE;
}
else
{
if (!(pRegEntry->fSources & SHCNRF_ShellLevel))
return FALSE;
}
if (pRegEntry->ibPidl == 0)
return TRUE; // there is no PIDL
// get the stored pidl
pidl = (LPITEMIDLIST)((LPBYTE)pRegEntry + pRegEntry->ibPidl);
if (pidl->mkid.cb == 0 && pRegEntry->fRecursive)
return TRUE; // desktop is the root
// check pidl1
if (pTicket->ibOffset1)
{
pidl1 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset1);
if (ILIsEqual(pidl, pidl1) || ILIsParent(pidl, pidl1, !pRegEntry->fRecursive))
return TRUE;
}
// check pidl2
if (pTicket->ibOffset2)
{
pidl2 = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset2);
if (ILIsEqual(pidl, pidl2) || ILIsParent(pidl, pidl2, !pRegEntry->fRecursive))
return TRUE;
}
// The paths:
// "C:\\Path\\To\\File1"
// "C:\\Path\\To\\File1Test"
// should be distinguished in comparison, so we add backslash at last as follows:
// "C:\\Path\\To\\File1\\"
// "C:\\Path\\To\\File1Test\\"
if (SHGetPathFromIDListW(pidl, szPath))
{
PathAddBackslashW(szPath);
cch = lstrlenW(szPath);
if (pidl1 && SHGetPathFromIDListW(pidl1, szPath1))
{
PathAddBackslashW(szPath1);
cch1 = lstrlenW(szPath1);
// Is szPath1 a subfile or subdirectory of szPath?
if (cch < cch1 &&
(pRegEntry->fRecursive ||
wcschr(&szPath1[cch], L'\\') == &szPath1[cch1 - 1]))
{
szPath1[cch] = 0;
if (lstrcmpiW(szPath, szPath1) == 0)
return TRUE;
}
}
if (pidl2 && SHGetPathFromIDListW(pidl2, szPath2))
{
PathAddBackslashW(szPath2);
cch2 = lstrlenW(szPath2);
// Is szPath2 a subfile or subdirectory of szPath?
if (cch < cch2 &&
(pRegEntry->fRecursive ||
wcschr(&szPath2[cch], L'\\') == &szPath2[cch2 - 1]))
{
szPath2[cch] = 0;
if (lstrcmpiW(szPath, szPath2) == 0)
return TRUE;
}
}
}
return FALSE;
}
HRESULT WINAPI CChangeNotifyServer::GetWindow(HWND* phwnd)
{
if (!phwnd)
return E_INVALIDARG;
*phwnd = m_hWnd;
return S_OK;
}
HRESULT WINAPI CChangeNotifyServer::ContextSensitiveHelp(BOOL fEnterMode)
{
return E_NOTIMPL;
}
HRESULT CChangeNotifyServer::Initialize()
{
// This is called by CChangeNotifyServer_CreateInstance right after instantiation.
// Create the window of the server here.
Create(0);
if (!m_hWnd)
return E_FAIL;
return S_OK;
}
HRESULT CChangeNotifyServer_CreateInstance(REFIID riid, void **ppv)
{
return ShellObjectCreatorInit<CChangeNotifyServer>(riid, ppv);
}