Windows2000/private/shell/shell32/treewkcb.cpp
2020-09-30 17:12:32 +02:00

670 lines
19 KiB
C++

// Copyright (c) Microsoft Corporation
// File: treewkcb.cpp
// This file contains the implementation of CBaseTreeWalkerCB, the base class for
// the shell Tree Walker Callback objects who implement IShellTreeWalkerCallBack.
// And ...
// CFolderSizeTreeWalkerCB, which walks the File System tree on a folder and computes the size
// of the folder
// CLinkResolveTreeWalkerCB, which search through the File System to resolve a lost link
// History:
// 12-9-97 by dli implemented CFolderSizeTreeWalkerCB
// 12-17-97 by dli implemendted CBaseTreeWalkerCB, added CLinkResolveTreeWalkerCB
#include "shellprv.h"
#include "treewkcb.h"
#include "resolve.h"
#define TF_TREEWKCB 0
CBaseTreeWalkerCB::CBaseTreeWalkerCB(): _cRef(1)
{
ASSERT(_pstw == NULL);
_hrInit = SHCoInitialize();
}
CBaseTreeWalkerCB::~CBaseTreeWalkerCB()
{
ATOMICRELEASE(_pstw);
SHCoUninitialize(_hrInit);
}
HRESULT CBaseTreeWalkerCB::Initialize()
{
return CoCreateInstance(CLSID_CShellTreeWalker, NULL, CLSCTX_INPROC_SERVER, IID_IShellTreeWalker, (LPVOID *)&_pstw);
}
HRESULT CBaseTreeWalkerCB::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
ASSERT(ppvObj != NULL);
static const QITAB qit[] = {
QITABENT(CBaseTreeWalkerCB, IShellTreeWalkerCallBack),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
ULONG CBaseTreeWalkerCB::AddRef()
{
_cRef++;
return _cRef;
}
ULONG CBaseTreeWalkerCB::Release()
{
_cRef--;
if (_cRef > 0)
return _cRef;
delete this;
return 0;
}
HRESULT CBaseTreeWalkerCB::FoundFile(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd)
{
return E_NOTIMPL;
}
HRESULT CBaseTreeWalkerCB::EnterFolder(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd)
{
return E_NOTIMPL;
}
HRESULT CBaseTreeWalkerCB::LeaveFolder(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws)
{
return E_NOTIMPL;
}
HRESULT CBaseTreeWalkerCB::HandleError(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, HRESULT ErrorCode)
{
return E_NOTIMPL;
}
// Folder size computation tree walker callback class
class CFolderSizeTreeWalkerCB : public CBaseTreeWalkerCB
{
friend HRESULT _stdcall FolderSize(LPCTSTR pszDir, FOLDERCONTENTSINFO * pfci);
public:
CFolderSizeTreeWalkerCB(FOLDERCONTENTSINFO * pfci);
// *** IShellTreeWalkerCallBack overridden methods ***
virtual STDMETHODIMP FoundFile(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd);
virtual STDMETHODIMP EnterFolder(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd);
virtual STDMETHODIMP Initialize();
protected:
FOLDERCONTENTSINFO * _pfci;
TREEWALKERSTATS _twsInitial;
};
CFolderSizeTreeWalkerCB::CFolderSizeTreeWalkerCB(FOLDERCONTENTSINFO * pfci): _pfci(pfci)
{
}
// CFolderSizeTreeWalkerCB::Initialize
HRESULT CFolderSizeTreeWalkerCB::Initialize()
{
HRESULT hr;
// we call the base class which will create the _pstw, and
// if that succeeds then we initialize it to have the starting
// size values of _pcfi.
hr = CBaseTreeWalkerCB::Initialize();
if (FAILED(hr))
{
return hr;
}
ASSERT(_pfci);
// set the starting values for the twsInitial so we can have cumulative results
_twsInitial.nFiles = _pfci->cFiles;
_twsInitial.nFolders = _pfci->cFolders;
_twsInitial.ulTotalSize = _pfci->cbSize;
_twsInitial.ulActualSize = _pfci->cbActualSize;
return S_OK;
}
// CFolderSizeTreeWalkerCB::FoundFile
HRESULT CFolderSizeTreeWalkerCB::FoundFile(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd)
{
if (!_pfci->bContinue)
{
// we have been signaled to stop, so return failure
return E_FAIL;
}
_pfci->cbSize = _twsInitial.ulTotalSize + ptws->ulTotalSize;
_pfci->cbActualSize = _twsInitial.ulActualSize + ptws->ulActualSize;
_pfci->cFiles = _twsInitial.nFiles + ptws->nFiles;
return S_OK;
}
// CFolderSizeTreeWalkerCB::EnterFolder
HRESULT CFolderSizeTreeWalkerCB::EnterFolder(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd)
{
if (!_pfci->bContinue)
{
// we have been signaled to stop, so return failure
return E_FAIL;
}
_pfci->cFolders = _twsInitial.nFolders + ptws->nFolders;
return S_OK;
}
// Main function for folder size computation, friend of the CFolderSizeTreeWalkerCB class
STDAPI FolderSize(LPCTSTR pszDir, FOLDERCONTENTSINFO * pfci)
{
HRESULT hres = E_FAIL;
CFolderSizeTreeWalkerCB * pfstwcb = new CFolderSizeTreeWalkerCB(pfci);
if (pfstwcb)
{
if (SUCCEEDED(pfstwcb->Initialize()))
{
LPCWSTR pwszDir;
ASSERT(IS_VALID_CODE_PTR(pfstwcb->_pstw, IShellTreeWalker));
#ifdef UNICODE
pwszDir = pszDir;
#else
WCHAR wszDir[MAX_PATH];
SHTCharToUnicode(pszDir, wszDir, ARRAYSIZE(wszDir));
pwszDir = wszDir;
#endif
hres = pfstwcb->_pstw->WalkTree(WT_NOTIFYFOLDERENTER, pwszDir, NULL, 0, SAFECAST(pfstwcb, IShellTreeWalkerCallBack *));
}
pfstwcb->Release();
}
return hres;
}
// Link Resolution tree walker callback class
class CLinkResolveTreeWalkerCB : public CBaseTreeWalkerCB
{
friend VOID DoDownLevelSearch(RESOLVE_SEARCH_DATA *prs, TCHAR szFolderPath[], int iStopScore);
public:
CLinkResolveTreeWalkerCB(RESOLVE_SEARCH_DATA *prs, int iStopScore);
// *** IShellTreeWalkerCallBack overridden methods ***
virtual STDMETHODIMP FoundFile(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd);
virtual STDMETHODIMP EnterFolder(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd);
BOOL SearchInFolder(LPCTSTR pszFolder, int cLevels);
protected:
RESOLVE_SEARCH_DATA * _prs;
int _iStopScore;
DWORD _dwSearchFlags;
int _iFolderBonus;
WCHAR _wszSearchSpec[64];
LPCWSTR _pwszSearchSpec;
int _ScoreFindData(const WIN32_FIND_DATA *pfd);
HRESULT _ProcessFoundFile(LPCTSTR pszPath, WIN32_FIND_DATAW * pwfdw);
};
CLinkResolveTreeWalkerCB::CLinkResolveTreeWalkerCB(RESOLVE_SEARCH_DATA *prs, int iStopScore): _prs(prs), _iStopScore(iStopScore)
{
ASSERT(_pwszSearchSpec == NULL);
// Note: We only search files with the same extension, this saves us a lot
// of useless work and from the humiliation of coming up with a ridiculous answer
_dwSearchFlags = WT_NOTIFYFOLDERENTER | WT_EXCLUDEWALKROOT;
if (_prs->dwMatch & FILE_ATTRIBUTE_DIRECTORY)
_dwSearchFlags |= WT_FOLDERONLY;
else
{
// Note that this does the right thing if the file has no extension
LPTSTR pszExt = PathFindExtension(prs->pfd->cFileName);
_wszSearchSpec[0] = L'*';
SHTCharToUnicode(pszExt, &_wszSearchSpec[1], ARRAYSIZE(_wszSearchSpec) - 1);
_pwszSearchSpec = _wszSearchSpec;
}
}
// Compare two FileTime structures. First, see if they're really equal
// (using CompareFileTime). If not, see if one has 10ms granularity,
// and if the other rounds down to the same value. This is done
// to handle the case where a file is moved from NTFS to FAT;
// FAT file tiems are 10ms granularity, while NTFS is 100ns. When
// an NTFS file is moved to FAT, its time is rounded down.
#define NTFS_UNITS_PER_FAT_UNIT 100000
BOOL IsEqualFileTimesWithTruncation( const FILETIME *pft1, const FILETIME *pft2 )
{
ULARGE_INTEGER uli1, uli2;
ULARGE_INTEGER *puliFAT, *puliNTFS;
FILETIME ftFAT, ftNTFS;
if( 0 == CompareFileTime( pft1, pft2 ))
return( TRUE );
uli1.LowPart = pft1->dwLowDateTime;
uli1.HighPart = pft1->dwHighDateTime;
uli2.LowPart = pft2->dwLowDateTime;
uli2.HighPart = pft2->dwHighDateTime;
// Is one of the times 10ms granular?
if( 0 == (uli1.QuadPart % NTFS_UNITS_PER_FAT_UNIT) )
{
puliFAT = &uli1;
puliNTFS = &uli2;
}
else if( 0 == (uli2.QuadPart % NTFS_UNITS_PER_FAT_UNIT) )
{
puliFAT = &uli2;
puliNTFS = &uli1;
}
else
{
// Neither time appears to be FAT, so they're
// really different.
return( FALSE );
}
// If uliNTFS is already 10ms granular, then again the two times
// are really different.
if( 0 == (puliNTFS->QuadPart % NTFS_UNITS_PER_FAT_UNIT) )
return( FALSE );
// Now see if the FAT time is the same as the NTFS time
// when the latter is rounded down to the nearest 10ms.
puliNTFS->QuadPart = (puliNTFS->QuadPart / NTFS_UNITS_PER_FAT_UNIT) * NTFS_UNITS_PER_FAT_UNIT;
ftNTFS.dwLowDateTime = puliNTFS->LowPart;
ftNTFS.dwHighDateTime = puliNTFS->HighPart;
ftFAT.dwLowDateTime = puliFAT->LowPart;
ftFAT.dwHighDateTime = puliFAT->HighPart;
return( 0 == CompareFileTime( &ftFAT, &ftNTFS ));
}
// compute a weighted score for a given find
int CLinkResolveTreeWalkerCB::_ScoreFindData(const WIN32_FIND_DATA *pfd)
{
int iScore = 0;
BOOL bSameName, bSameCreateDate, bSameWriteTime, bSameExt, bHasCreateDate;
bSameName = lstrcmpi(_prs->pfd->cFileName, pfd->cFileName) == 0;
bSameExt = lstrcmpi(PathFindExtension(_prs->pfd->cFileName), PathFindExtension(pfd->cFileName)) == 0;
bHasCreateDate = !IsNullTime(&pfd->ftCreationTime);
bSameCreateDate = bHasCreateDate &&
IsEqualFileTimesWithTruncation(&pfd->ftCreationTime, &_prs->pfd->ftCreationTime);
bSameWriteTime = !IsNullTime(&pfd->ftLastWriteTime) &&
IsEqualFileTimesWithTruncation(&pfd->ftLastWriteTime, &_prs->pfd->ftLastWriteTime);
if (bSameName || bSameCreateDate)
{
if (bSameName)
iScore += bHasCreateDate ? 16 : 32;
if (bSameCreateDate)
{
iScore += 32;
if (bSameExt)
iScore += 8;
}
if (bSameWriteTime)
iScore += 8;
if (pfd->nFileSizeLow == _prs->pfd->nFileSizeLow)
iScore += 4;
// if it is in the same folder as the original give it a slight bonus
iScore += _iFolderBonus;
}
else
{
// doesn't have create date, apply different rules
if (bSameExt)
iScore += 8;
if (bSameWriteTime)
iScore += 8;
if (pfd->nFileSizeLow == _prs->pfd->nFileSizeLow)
iScore += 4;
}
return iScore;
}
// Helper function for both EnterFolder and FoundFile
HRESULT CLinkResolveTreeWalkerCB::_ProcessFoundFile(LPCTSTR pszPath, WIN32_FIND_DATAW * pwfdw)
{
HRESULT hres = S_OK;
#ifdef UNICODE
WIN32_FIND_DATA * pwfd = pwfdw;
#else
WIN32_FIND_DATA wfd;
WIN32_FIND_DATA * pwfd = &wfd;
hmemcpy(&wfd, pwfdw, SIZEOF(WIN32_FIND_DATA));
SHUnicodeToTChar(pwfdw->cFileName, wfd.cFileName, ARRAYSIZE(wfd.cFileName));
SHUnicodeToTChar(pwfdw->cAlternateFileName, wfd.cAlternateFileName, ARRAYSIZE(wfd.cAlternateFileName));
pwfd = &wfd;
#endif
if (!PathIsLnk(pwfd->cFileName))
{
// both are files or folders, see how it scores
int iScore = _ScoreFindData(pwfd);
if (iScore > _prs->iScore)
{
_prs->fdFound = *pwfd;
TraceMsg(TF_TREEWKCB, "Better match found %s, %d", pszPath, iScore);
// store the score and fully qualified path
_prs->iScore = iScore;
lstrcpyn(_prs->fdFound.cFileName, pszPath, ARRAYSIZE(_prs->fdFound.cFileName));
}
}
if ((_prs->iScore >= _iStopScore) || (GetTickCount() >= _prs->dwTimeLimit))
{
_prs->bContinue = FALSE;
hres = E_FAIL;
}
return hres;
}
// IShellTreeWalkerCallBack::FoundFile
HRESULT CLinkResolveTreeWalkerCB::FoundFile(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd)
{
ASSERT(IS_VALID_STRING_PTRW(pwszPath, -1));
ASSERT(pwfd);
// Respond quickly to the Cancel button.
if (!_prs->bContinue)
{
return E_FAIL;
}
// We should've excluded files if we're looking for a folder
ASSERT(!(_prs->dwMatch & FILE_ATTRIBUTE_DIRECTORY));
LPCTSTR ptszPath;
#ifdef UNICODE
ptszPath = pwszPath;
#else
TCHAR szPath[MAX_PATH];
SHUnicodeToTChar(pwszPath, szPath, ARRAYSIZE(szPath));
ptszPath = szPath;
#endif
return _ProcessFoundFile(ptszPath, pwfd);
}
// IShellTreeWalkerCallBack::EnterFolder
HRESULT CLinkResolveTreeWalkerCB::EnterFolder(LPCWSTR pwszPath, LPTREEWALKERSTATS ptws, WIN32_FIND_DATAW * pwfd)
{
HRESULT hres = S_OK;
ASSERT(IS_VALID_STRING_PTRW(pwszPath, -1));
ASSERT(pwfd);
// Respond quickly to the Cancel button.
if (!_prs->bContinue)
{
return E_FAIL;
}
LPCTSTR ptszPath;
#ifdef UNICODE
ptszPath = pwszPath;
#else
TCHAR szPath[MAX_PATH];
SHUnicodeToTChar(pwszPath, szPath, ARRAYSIZE(szPath));
ptszPath = szPath;
#endif
// Once we enter a directory, we lose the "we are still in the starting
// folder" bonus.
_iFolderBonus = 0;
// If we're about to enter a directory we've already looked in,
// or if this is a dead directory, then skip it.
if (PathIsPrefix(ptszPath, _prs->pszSearchOrigin) || IsFileInBitBucket(ptszPath))
return S_FALSE;
// If our target was a folder, treat this folder as a file found
if (_prs->dwMatch & FILE_ATTRIBUTE_DIRECTORY)
{
hres = _ProcessFoundFile(ptszPath, pwfd);
}
return hres;
}
// Wrapper around WalkTree
BOOL CLinkResolveTreeWalkerCB::SearchInFolder(LPCTSTR pszFolder, int cLevels)
{
LPCWSTR pwszFolder;
#ifdef UNICODE
pwszFolder = pszFolder;
#else
WCHAR wszFolder[MAX_PATH];
SHTCharToUnicode(pszFolder, wszFolder, ARRAYSIZE(wszFolder));
pwszFolder = wszFolder;
#endif
int iMaxDepth = 0;
// cLevels == -1 means inifinite depth
if (cLevels != -1)
{
_dwSearchFlags |= WT_MAXDEPTH;
iMaxDepth = cLevels;
}
else
_dwSearchFlags &= ~WT_MAXDEPTH;
// Our folder bonus code lies on the fact that files in the
// starting folder come before anything else.
ASSERT(!(_dwSearchFlags & WT_FOLDERFIRST));
#ifdef DEBUG
// Test the QueryInterface code path by QI'ing ourselves for IUnknown and
// making sure we get ourselves back. We have to use IUnknown because
// anything else could get usurped into a tear-off by QIStub.
IUnknown *punkSelf;
EVAL(SUCCEEDED(this->QueryInterface(IID_IShellTreeWalkerCallBack, (LPVOID *)&punkSelf)));
ASSERT(punkSelf == SAFECAST(this, IUnknown *));
punkSelf->Release();
#endif
ASSERT(IS_VALID_CODE_PTR(_pstw, IShellTreeWalker));
_pstw->WalkTree(_dwSearchFlags, pwszFolder, _pwszSearchSpec, iMaxDepth, SAFECAST(this, IShellTreeWalkerCallBack *));
_iFolderBonus = 0; // You only get one chance at the folder bonus
return _prs->bContinue;
}
// Main search function for link resolution, the result will be in prs->fdFound.cFileName
VOID DoDownLevelSearch(RESOLVE_SEARCH_DATA *prs, LPTSTR pszFolderPath, int iStopScore)
{
// Skip this search if it's restricted
if (SHRestricted(REST_NORESOLVESEARCH)
||
SLR_NOSEARCH & prs->uFlags )
{
return;
}
CLinkResolveTreeWalkerCB * plrtwcb = new CLinkResolveTreeWalkerCB(prs, iStopScore);
if (plrtwcb)
{
if (SUCCEEDED(plrtwcb->Initialize()))
{
int cUp = LNKTRACK_HINTED_UPLEVELS;
LPITEMIDLIST pidl;
BOOL bSearchOrigin = TRUE;
TCHAR szRealSearchOrigin[MAX_PATH];
// search up from old location
// In the olden days pszSearchOriginFirst was verified to be a valid directory
// (ie it returned TRUE to PathIsDirectory) and DoDownLevelSearch was never called
// if this was not true. Alas, this is no more. Why not search the desktop and
// fixed drives anyway? In the interest of saving some time the check that used
// to be in FindInFolder which caused an early out is now here instead. The rub
// is that we only skip the downlevel search of the original volume instead of
// skipping the entire link resolution phase.
lstrcpy(szRealSearchOrigin, prs->pszSearchOriginFirst);
while (!PathIsDirectory(szRealSearchOrigin))
{
if (PathIsRoot(szRealSearchOrigin) || !PathRemoveFileSpec(szRealSearchOrigin))
{
DebugMsg(DM_TRACE, TEXT("root path does not exists %s"), szRealSearchOrigin);
bSearchOrigin = FALSE;
break;
}
}
if ( bSearchOrigin )
{
lstrcpy(pszFolderPath, szRealSearchOrigin);
prs->pszSearchOrigin = szRealSearchOrigin;
// Files found in the starting folder get a slight bonus.
// _iFolderBonus is set to zero by
// CLinkResolveTreeWalkerCB::EnterFolder when we leave
// the starting folder and enter a new one.
plrtwcb->_iFolderBonus = 2;
while (cUp-- != 0 && plrtwcb->SearchInFolder(pszFolderPath, LNKTRACK_HINTED_DOWNLEVELS))
{
if (PathIsRoot(pszFolderPath) || !PathRemoveFileSpec(pszFolderPath))
break;
}
}
if (prs->bContinue)
{
// search down from desktop
HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl);
if (hr == NOERROR)
{
if (SHGetPathFromIDList(pidl, pszFolderPath))
{
prs->pszSearchOrigin = pszFolderPath;
plrtwcb->SearchInFolder(pszFolderPath, LNKTRACK_DESKTOP_DOWNLEVELS);
}
ILFree(pidl);
}
}
if (prs->bContinue)
{
// search down from root of fixed drives
TCHAR atch[4];
// BUGBUG: Is it valid to assume that no fixed drive can have a letter
// less than c:? I say no, I remember a PC98 machine that had b: as
// it's hard drive letter. Research the effects of making this start
// at a: (or using GetLogicalDriveStrings?)
lstrcpy(atch, TEXT("A:\\"));
prs->pszSearchOrigin = atch;
for (; prs->bContinue && atch[0] <= TEXT('Z'); atch[0]++)
{
if (GetDriveType(atch) == DRIVE_FIXED)
{
lstrcpy(pszFolderPath, atch);
plrtwcb->SearchInFolder(pszFolderPath, LNKTRACK_ROOT_DOWNLEVELS);
}
}
}
if (prs->bContinue && bSearchOrigin)
{
// resume search of last volume (should do an exclude list)
lstrcpy(pszFolderPath, szRealSearchOrigin);
prs->pszSearchOrigin = szRealSearchOrigin;
while (plrtwcb->SearchInFolder(pszFolderPath, -1))
{
if (PathIsRoot(pszFolderPath) || !PathRemoveFileSpec(pszFolderPath))
break;
}
}
}
plrtwcb->Release();
}
}