mirror of
https://github.com/reactos/reactos.git
synced 2024-12-27 09:34:43 +00:00
[SHELL32] SHChangeNotify: Use tree for CDirectoryList (#6784)
Optimize for speed and memory. JIRA issue: CORE-13950 CDirectoryList class exists just for remembering which file item is a directory or not, in order to notify the filesystem item changes. This information can become a tree data structure. - Add CFSPathIterator and CFSNode helper classes. - CFSNode is a class for tree nodes. - Re-implement CDirectoryList class by using tree nodes. - Delete CDirectoryItem class.
This commit is contained in:
parent
c0c270e90e
commit
91acf823d8
3 changed files with 284 additions and 167 deletions
|
@ -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 <assert.h> // for assert
|
||||
#include <atlstr.h>
|
||||
|
||||
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<CFSNode*> 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;
|
||||
}
|
||||
|
|
|
@ -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 <atlsimpcoll.h> // 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<CDirectoryItem> m_items;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue