diff --git a/dll/win32/shell32/shelldesktop/CDirectoryList.cpp b/dll/win32/shell32/shelldesktop/CDirectoryList.cpp index 2bdd36f0eb0..096543e73e2 100644 --- a/dll/win32/shell32/shelldesktop/CDirectoryList.cpp +++ b/dll/win32/shell32/shelldesktop/CDirectoryList.cpp @@ -2,131 +2,307 @@ * 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) + * COPYRIGHT: Copyright 2020-2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) */ #include "shelldesktop.h" #include "CDirectoryList.h" -#include // for assert +#include WINE_DEFAULT_DEBUG_CHANNEL(shcn); -BOOL CDirectoryList::ContainsPath(LPCWSTR pszPath) const +/////////////////////////////////////////////////////////////////////////////////////// +// File-system path iterator + +class CFSPathIterator { - assert(!PathIsRelativeW(pszPath)); +public: + CStringW m_strFullName; + INT m_ich; - for (INT i = 0; i < m_items.GetSize(); ++i) + CFSPathIterator(CStringW strFullName) : m_strFullName(strFullName), m_ich(0) { - if (m_items[i].IsEmpty()) - continue; + } - if (m_items[i].EqualPath(pszPath)) - return TRUE; // matched + bool Next(CStringW& strNext); +}; + +/////////////////////////////////////////////////////////////////////////////////////// + +bool CFSPathIterator::Next(CStringW& strNext) +{ + if (m_ich >= m_strFullName.GetLength()) + return false; + + auto ich = m_strFullName.Find(L'\\', m_ich); + if (ich < 0) + { + ich = m_strFullName.GetLength(); + strNext = m_strFullName.Mid(m_ich, ich - m_ich); + m_ich = ich; + } + else + { + strNext = m_strFullName.Mid(m_ich, ich - m_ich); + m_ich = ich + 1; + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////// +// File-system node + +class CFSNode +{ +public: + CStringW m_strName; + + CFSNode(const CStringW& strName, CFSNode* pParent = NULL); + ~CFSNode(); + + CStringW GetFullName(); + CFSNode* BuildPath(const CStringW& strFullName, BOOL bMarkNotExpanded = TRUE); + + CFSNode* FindChild(const CStringW& strName); + CFSNode* Find(const CStringW& strFullName); + + BOOL RemoveChild(CFSNode *pNode); + BOOL Remove(); + + void MarkNotExpanded(); + + void Expand(); + void clear(); + +protected: + BOOL m_bExpand; + CFSNode* m_pParent; + CSimpleArray m_children; +}; + +/////////////////////////////////////////////////////////////////////////////////////// + +CFSNode::CFSNode(const CStringW& strName, CFSNode* pParent) + : m_strName(strName) + , m_bExpand(FALSE) + , m_pParent(pParent) +{ +} + +CFSNode::~CFSNode() +{ + clear(); +} + +CStringW CFSNode::GetFullName() +{ + CStringW ret; + if (m_pParent) + ret = m_pParent->GetFullName(); + if (ret.GetLength()) + ret += L'\\'; + ret += m_strName; + return ret; +} + +CFSNode* CFSNode::FindChild(const CStringW& strName) +{ + for (INT iItem = 0; iItem < m_children.GetSize(); ++iItem) + { + auto pChild = m_children[iItem]; + if (pChild && + pChild->m_strName.GetLength() == strName.GetLength() && + lstrcmpiW(pChild->m_strName, strName) == 0) + { + return pChild; + } + } + return NULL; +} + +BOOL CFSNode::RemoveChild(CFSNode *pNode) +{ + for (INT iItem = 0; iItem < m_children.GetSize(); ++iItem) + { + auto& pChild = m_children[iItem]; + if (pChild == pNode) + { + auto pOld = pChild; + pChild = NULL; + delete pOld; + return TRUE; + } } return FALSE; } +BOOL CFSNode::Remove() +{ + if (m_pParent) + return m_pParent->RemoveChild(this); + return FALSE; +} + +CFSNode* CFSNode::Find(const CStringW& strFullName) +{ + CFSPathIterator it(strFullName); + CStringW strName; + CFSNode *pChild, *pNode; + for (pNode = this; it.Next(strName); pNode = pChild) + { + pChild = pNode->FindChild(strName); + if (!pChild) + return NULL; + } + return pNode; +} + +void CFSNode::MarkNotExpanded() +{ + for (auto pNode = this; pNode; pNode = pNode->m_pParent) + pNode->m_bExpand = FALSE; +} + +CFSNode* CFSNode::BuildPath(const CStringW& strFullName, BOOL bMarkNotExpanded) +{ + CFSPathIterator it(strFullName); + CStringW strName; + CFSNode *pNode, *pChild = NULL; + for (pNode = this; it.Next(strName); pNode = pChild) + { + pChild = pNode->FindChild(strName); + if (pChild) + continue; + + pChild = new CFSNode(strName, pNode); + pNode->m_children.Add(pChild); + if (bMarkNotExpanded) + pNode->MarkNotExpanded(); + } + return pNode; +} + +void CFSNode::Expand() +{ + if (m_bExpand) + return; + + auto strSpec = GetFullName(); + strSpec += L"\\*"; + + WIN32_FIND_DATAW find; + HANDLE hFind = ::FindFirstFileW(strSpec, &find); + if (hFind == INVALID_HANDLE_VALUE) + return; + + do + { + if (lstrcmpW(find.cFileName, L".") == 0 || + lstrcmpW(find.cFileName, L"..") == 0 || + !(find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || + (find.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + { + continue; + } + + auto pNode = FindChild(find.cFileName); + if (!pNode) + { + pNode = new CFSNode(find.cFileName, this); + m_children.Add(pNode); + } + pNode->Expand(); + } while (::FindNextFileW(hFind, &find)); + ::FindClose(hFind); + + m_bExpand = TRUE; +} + +void CFSNode::clear() +{ + for (INT iItem = 0; iItem < m_children.GetSize(); ++iItem) + { + auto& pChild = m_children[iItem]; + delete pChild; + pChild = NULL; + } + m_children.RemoveAll(); +} + +/////////////////////////////////////////////////////////////////////////////////////// +// CDirectoryList + +CDirectoryList::CDirectoryList(CFSNode *pRoot) + : m_pRoot(pRoot ? pRoot : (new CFSNode(L""))) + , m_fRecursive(FALSE) +{ +} + +CDirectoryList::CDirectoryList(CFSNode *pRoot, LPCWSTR pszDirectoryPath, BOOL fRecursive) + : m_pRoot(pRoot ? pRoot : (new CFSNode(L""))) + , m_fRecursive(fRecursive) +{ + AddPathsFromDirectory(pszDirectoryPath); +} + +CDirectoryList::~CDirectoryList() +{ + delete m_pRoot; +} + +BOOL CDirectoryList::ContainsPath(LPCWSTR pszPath) const +{ + ATLASSERT(!PathIsRelativeW(pszPath)); + + return !!m_pRoot->Find(pszPath); +} + BOOL CDirectoryList::AddPath(LPCWSTR pszPath) { - assert(!PathIsRelativeW(pszPath)); - if (ContainsPath(pszPath)) - return FALSE; - for (INT i = 0; i < m_items.GetSize(); ++i) - { - if (m_items[i].IsEmpty()) - { - m_items[i].SetPath(pszPath); - return TRUE; - } - } - return m_items.Add(pszPath); + ATLASSERT(!PathIsRelativeW(pszPath)); + + auto pNode = m_pRoot->BuildPath(pszPath); + if (pNode && m_fRecursive) + pNode->Expand(); + + return TRUE; } BOOL CDirectoryList::RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2) { - assert(!PathIsRelativeW(pszPath1)); - assert(!PathIsRelativeW(pszPath2)); + ATLASSERT(!PathIsRelativeW(pszPath1)); + ATLASSERT(!PathIsRelativeW(pszPath2)); - for (INT i = 0; i < m_items.GetSize(); ++i) - { - if (m_items[i].EqualPath(pszPath1)) - { - // matched - m_items[i].SetPath(pszPath2); - return TRUE; - } - } - return FALSE; + auto pNode = m_pRoot->Find(pszPath1); + if (!pNode) + return FALSE; + + LPWSTR pch = wcsrchr(pszPath2, L'\\'); + if (!pch) + return FALSE; + + pNode->m_strName = pch + 1; + return TRUE; } BOOL CDirectoryList::DeletePath(LPCWSTR pszPath) { - assert(!PathIsRelativeW(pszPath)); + ATLASSERT(!PathIsRelativeW(pszPath)); - for (INT i = 0; i < m_items.GetSize(); ++i) - { - if (m_items[i].EqualPath(pszPath)) - { - // matched - m_items[i].SetPath(NULL); - return TRUE; - } - } - return FALSE; + auto pNode = m_pRoot->Find(pszPath); + if (!pNode) + return FALSE; + + pNode->Remove(); + return TRUE; } BOOL CDirectoryList::AddPathsFromDirectory(LPCWSTR pszDirectoryPath) { - // get the full path - WCHAR szPath[MAX_PATH]; - lstrcpynW(szPath, pszDirectoryPath, _countof(szPath)); - assert(!PathIsRelativeW(szPath)); + ATLASSERT(!PathIsRelativeW(pszPath)); - // is it a directory? - if (!PathIsDirectoryW(szPath)) - return FALSE; - - // add the path - if (!AddPath(szPath)) - 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; - } - - LPWSTR pch; - do - { - // ignore "." and ".." - pch = find.cFileName; - if (pch[0] == L'.' && (pch[1] == 0 || (pch[1] == L'.' && pch[2] == 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 (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - if (m_fRecursive) - AddPathsFromDirectory(szPath); - else - AddPath(szPath); - } - } while (FindNextFileW(hFind, &find)); - - FindClose(hFind); + auto pNode = m_pRoot->BuildPath(pszDirectoryPath); + if (pNode) + pNode->Expand(); return TRUE; } diff --git a/dll/win32/shell32/shelldesktop/CDirectoryList.h b/dll/win32/shell32/shelldesktop/CDirectoryList.h index 1e9559c4b24..3d6abda19eb 100644 --- a/dll/win32/shell32/shelldesktop/CDirectoryList.h +++ b/dll/win32/shell32/shelldesktop/CDirectoryList.h @@ -1,93 +1,34 @@ +/* + * PROJECT: shell32 + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Shell change notification + * COPYRIGHT: Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ + #pragma once #include // for CSimpleArray ////////////////////////////////////////////////////////////////////////////// -// A pathname with info -class CDirectoryItem -{ -public: - CDirectoryItem() : m_pszPath(NULL) - { - } - - CDirectoryItem(LPCWSTR pszPath) - { - m_pszPath = _wcsdup(pszPath); - } - - CDirectoryItem(const CDirectoryItem& item) - : m_pszPath(_wcsdup(item.m_pszPath)) - { - } - - CDirectoryItem& operator=(const CDirectoryItem& item) - { - if (this != &item) - { - free(m_pszPath); - m_pszPath = _wcsdup(item.m_pszPath); - } - return *this; - } - - ~CDirectoryItem() - { - free(m_pszPath); - } - - BOOL IsEmpty() const - { - return m_pszPath == NULL; - } - - 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; - } - -protected: - LPWSTR m_pszPath; // A full path, malloc'ed -}; +class CFSNode; // the directory list class CDirectoryList { public: - CDirectoryList() : m_fRecursive(FALSE) - { - } - - CDirectoryList(LPCWSTR pszDirectoryPath, BOOL fRecursive) - : m_fRecursive(fRecursive) - { - AddPathsFromDirectory(pszDirectoryPath); - } + CDirectoryList(CFSNode *pRoot); + CDirectoryList(CFSNode *pRoot, LPCWSTR pszDirectoryPath, BOOL fRecursive); + ~CDirectoryList(); BOOL ContainsPath(LPCWSTR pszPath) const; BOOL AddPath(LPCWSTR pszPath); BOOL AddPathsFromDirectory(LPCWSTR pszDirectoryPath); BOOL RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2); BOOL DeletePath(LPCWSTR pszPath); - - void RemoveAll() - { - m_items.RemoveAll(); - } + void RemoveAll(); protected: + CFSNode *m_pRoot; BOOL m_fRecursive; - CSimpleArray m_items; }; diff --git a/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp b/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp index d5056a64faa..ce3fac7f8b6 100644 --- a/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp +++ b/dll/win32/shell32/shelldesktop/CDirectoryWatcher.cpp @@ -71,7 +71,7 @@ static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter) CDirectoryWatcher::CDirectoryWatcher(LPCWSTR pszDirectoryPath, BOOL fSubTree) : m_fDead(FALSE) , m_fRecursive(fSubTree) - , m_dir_list(pszDirectoryPath, fSubTree) + , m_dir_list(NULL, pszDirectoryPath, fSubTree) { TRACE("CDirectoryWatcher::CDirectoryWatcher: %p, '%S'\n", this, pszDirectoryPath);