#include "pch.h" #include "thisdll.h" #include "cowsite.h" #include #include "ids.h" // Context menu offset IDs enum { CMD_ORGANIZE = 0, CMD_ORGANIZE_DEEP = 1, CMD_ORGANIZE_FLAT = 2, CMD_MAX = 3, }; class COrganizeFiles; class COrganizeFiles : public IContextMenu, IShellExtInit, INamespaceWalkCB, CObjectWithSite { public: COrganizeFiles(); // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void **ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hKeyID); // IContextMenu STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pcmici); STDMETHODIMP GetCommandString(UINT_PTR uID, UINT uFlags, UINT *res, LPSTR pName, UINT ccMax); // INamespaceWalkCB STDMETHODIMP FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl); STDMETHODIMP EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl); STDMETHODIMP LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl); STDMETHOD(InitializeProgressDialog)(LPWSTR *ppszTitle, LPWSTR *ppszCancel) { *ppszTitle = NULL; *ppszCancel = NULL; return E_NOTIMPL; } private: ~COrganizeFiles(); class CMD_THREAD_DATA { public: CMD_THREAD_DATA(COrganizeFiles *pof, UINT idCmd) : _pof(pof), _idCmd(idCmd) { _pof->AddRef(); _pstmDataObj = NULL; } ~CMD_THREAD_DATA() { _pof->Release(); ATOMICRELEASE(_pstmDataObj); } COrganizeFiles *_pof; // back ptr to object UINT _idCmd; IStream *_pstmDataObj; // the IDataObject marshalled to the background thread }; static DWORD CALLBACK COrganizeFiles::_CmdThreadProc(void *pv); void _CreateCmdThread(UINT idCmd); void _DoCmd(UINT idCmd, IDataObject *pdtobj); void _DoOrganizeMusic(UINT idCmd, IDataObject *pdtobj); HRESULT _GetPropertyUI(IPropertyUI **pppui); HRESULT _NameFromPropertiesString(LPCTSTR pszProps, IPropertySetStorage *ppss, LPTSTR pszRoot, LPTSTR pszName, UINT cchName); LONG _cRef; IDataObject *_pdtobj; IPropertyUI *_ppui; BOOL _bCountFiles; // call back is in "count files" mode UINT _cFilesTotal; // total computed in the count UINT _cFileCur; // current, for progress UI IProgressDialog *_ppd; TCHAR _szRootFolder[MAX_PATH]; LPCTSTR _pszProps; LPTSTR _pszTemplateFlat; LPTSTR _pszTemplate; }; COrganizeFiles::COrganizeFiles() : _cRef(1) { } COrganizeFiles::~COrganizeFiles() { CoTaskMemFree(_pszTemplateFlat); CoTaskMemFree(_pszTemplate); IUnknown_Set(&_punkSite, NULL); IUnknown_Set((IUnknown**)&_pdtobj, NULL); IUnknown_Set((IUnknown**)&_ppui, NULL); } STDAPI COrganizeFiles_CreateInstance(IUnknown *pUnkOuter, IUnknown **ppunk, LPCOBJECTINFO poi) { COrganizeFiles *psid = new COrganizeFiles(); if (!psid) { *ppunk = NULL; // incase of failure return E_OUTOFMEMORY; } HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk)); psid->Release(); return hr; } STDMETHODIMP COrganizeFiles::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(COrganizeFiles, IShellExtInit), QITABENT(COrganizeFiles, IContextMenu), QITABENT(COrganizeFiles, IObjectWithSite), QITABENT(COrganizeFiles, INamespaceWalkCB), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) COrganizeFiles::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) COrganizeFiles::Release() { if (InterlockedDecrement(&_cRef)) return _cRef; delete this; return 0; } // IShellExtInit STDMETHODIMP COrganizeFiles::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hKeyID) { IUnknown_Set((IUnknown**)&_pdtobj, pdtobj); return S_OK; } // IContextMenu STDMETHODIMP COrganizeFiles::QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags) { if (NULL == _pszTemplate) { IAssociationArray *paa; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_CtxQueryAssociations, IID_PPV_ARG(IAssociationArray, &paa)))) { paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQN_NAMED_VALUE, L"OrganizeTemplate", &_pszTemplate); paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQN_NAMED_VALUE, L"OrganizeTemplateFlat", &_pszTemplateFlat); paa->Release(); } } if (!(uFlags & CMF_DEFAULTONLY)) { InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); TCHAR szBuffer[128]; LoadString(m_hInst, IDS_ORGANIZE_MUSIC, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + CMD_ORGANIZE, szBuffer); // if (uFlags & CMF_EXTENDEDVERBS) { LoadString(m_hInst, IDS_ORGANIZE_MUSIC_FLAT, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + CMD_ORGANIZE_FLAT, szBuffer); } } return MAKE_HRESULT(SEVERITY_SUCCESS, 0, CMD_MAX); } typedef struct { LPCTSTR psz; UINT id; } CMD_MAP; const CMD_MAP c_szVerbs[] = { {TEXT("OrganizeMusic"), CMD_ORGANIZE}, {TEXT("OrganizeMusicFlat"), CMD_ORGANIZE_FLAT}, {TEXT("OrganizeMusicDeep"), CMD_ORGANIZE_DEEP}, }; UINT GetCommandId(LPCMINVOKECOMMANDINFO pcmici, const CMD_MAP rgMap[], UINT cMap) { UINT id = -1; if (IS_INTRESOURCE(pcmici->lpVerb)) { id = LOWORD((UINT_PTR)pcmici->lpVerb); } else { TCHAR szCmd[64]; szCmd[0] = 0; // Check first whether the caller passed a EX structure by checking // the size... if (pcmici->cbSize == sizeof(CMINVOKECOMMANDINFOEX)) { LPCMINVOKECOMMANDINFOEX pcmiciEX = (LPCMINVOKECOMMANDINFOEX)pcmici; if (pcmici->fMask & CMIC_MASK_UNICODE) { SHUnicodeToTChar(pcmiciEX->lpVerbW, szCmd, ARRAYSIZE(szCmd)); } } // If we don't yet have a command verb, it must have been passed // ANSI or in a regular CMINVOKECOMMANDINFO structure (which means // it is by default ANSI). In either case, we need to convert it to // UNICODE before proceeding... if (szCmd[0] == 0) { SHAnsiToTChar(pcmici->lpVerb, szCmd, ARRAYSIZE(szCmd)); } for (UINT i = 0; i < cMap; i++) { if (!StrCmpIC(szCmd, rgMap[i].psz)) { id = rgMap[i].id; break; } } } return id; } STDMETHODIMP COrganizeFiles::InvokeCommand(LPCMINVOKECOMMANDINFO pcmici) { HRESULT hr = S_OK; UINT idCmd = GetCommandId(pcmici, c_szVerbs, ARRAYSIZE(c_szVerbs)); switch (idCmd) { case CMD_ORGANIZE: case CMD_ORGANIZE_DEEP: case CMD_ORGANIZE_FLAT: _CreateCmdThread(idCmd); break; default: hr = E_INVALIDARG; break; } return hr; } STDMETHODIMP COrganizeFiles::GetCommandString(UINT_PTR uID, UINT uFlags, UINT *res, LPSTR pName, UINT cchMax) { HRESULT hr = S_OK; UINT idSel = (UINT)uID; switch (uFlags) { case GCS_VERBW: case GCS_VERBA: if (idSel < ARRAYSIZE(c_szVerbs)) { if (uFlags == GCS_VERBW) { SHTCharToUnicode(c_szVerbs[idSel].psz, (LPWSTR)pName, cchMax); } else { SHTCharToAnsi(c_szVerbs[idSel].psz, pName, cchMax); } } break; case GCS_HELPTEXTA: case GCS_HELPTEXTW: case GCS_VALIDATEA: case GCS_VALIDATEW: hr = E_NOTIMPL; break; } return hr; } DWORD CALLBACK COrganizeFiles::_CmdThreadProc(void *pv) { CMD_THREAD_DATA *potd = (CMD_THREAD_DATA *)pv; IDataObject *pdtobj; HRESULT hr = CoGetInterfaceAndReleaseStream(potd->_pstmDataObj, IID_PPV_ARG(IDataObject, &pdtobj)); potd->_pstmDataObj = NULL; if (SUCCEEDED(hr)) { potd->_pof->_DoCmd(potd->_idCmd, pdtobj); pdtobj->Release(); } delete potd; return 0; } void COrganizeFiles::_CreateCmdThread(UINT idCmd) { CMD_THREAD_DATA *potd = new CMD_THREAD_DATA(this, idCmd); if (potd) { if (FAILED(CoMarshalInterThreadInterfaceInStream(IID_IDataObject, _pdtobj, &potd->_pstmDataObj)) || !SHCreateThread(_CmdThreadProc, potd, CTF_COINIT, NULL)) { delete potd; } } } void COrganizeFiles::_DoCmd(UINT idCmd, IDataObject *pdtobj) { switch (idCmd) { case CMD_ORGANIZE: case CMD_ORGANIZE_DEEP: case CMD_ORGANIZE_FLAT: _DoOrganizeMusic(idCmd, pdtobj); break; } } HRESULT ReadPropertyAsString(IPropertyUI *ppui, IPropertySetStorage *ppss, LPCSHCOLUMNID pscid, LPTSTR psz, UINT cch) { *psz = 0; IPropertyStorage *pps; HRESULT hr = ppss->Open(pscid->fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, &pps); if (SUCCEEDED(hr)) { PROPSPEC ps = {PRSPEC_PROPID, pscid->pid}; PROPVARIANT v = {0}; hr = pps->ReadMultiple(1, &ps, &v); if (SUCCEEDED(hr)) { hr = ppui->FormatForDisplay(pscid->fmtid, pscid->pid, &v, PUIFFDF_DEFAULT, psz, cch); if (SUCCEEDED(hr) && (0 == *psz)) { hr = E_FAIL; } PropVariantClear(&v); } pps->Release(); } return hr; } void FixBadFilenameChars(LPTSTR pszName) { while (*pszName) { switch (*pszName) { case '?': case '*': case '/': case '\\': case '!': *pszName = '_'; break; case '\"': *pszName = '\''; break; case ':': *pszName = '-'; break; } pszName++; } } LPTSTR PathCombineRemoveDups(LPTSTR pszNewPath, LPCTSTR pszRoot, LPCTSTR pszTail) { for (LPCTSTR psz = pszTail; (psz && *psz); psz = PathFindNextComponent(psz)) { LPCTSTR pszNext = PathFindNextComponent(psz); if (pszNext && *pszNext) { TCHAR szPart[MAX_PATH]; UINT_PTR len = pszNext - psz; StrCpyN(szPart, psz, (int)min(ARRAYSIZE(szPart), len)); LPCTSTR pszFound = StrStr(pszRoot, szPart); if ((pszFound > pszRoot) && (*(pszFound - 1) == TEXT('\\')) && ((*(pszFound + len - 1) == TEXT('\\')) || (*(pszFound + len - 1) == 0))) { pszTail = pszNext; } } else { break; } } return PathCombine(pszNewPath, pszRoot, pszTail); } LPTSTR PathRemovePart(LPTSTR pszPath, LPCTSTR pszToRemove) { LPTSTR pszFound = StrStr(pszPath, pszToRemove); if (pszFound && (pszFound > pszPath) && (*(pszFound - 1) == TEXT('\\'))) { LPTSTR pszTail = pszFound + lstrlen(pszToRemove); if ((*pszTail == TEXT('\\')) || (*pszTail == 0)) { *pszFound = 0; if (*pszTail) { PathAppend(pszPath, pszTail + 1); } else { PathRemoveBackslash(pszPath); // trim extra stuff } } } return pszPath; } // pszProps == "%Artist% - %Album% - %Track% - %DocTitle%" HRESULT COrganizeFiles::_NameFromPropertiesString(LPCTSTR pszProps, IPropertySetStorage *ppss, LPTSTR pszRoot, LPTSTR pszName, UINT cchName) { *pszName = 0; // start empty, we build this up BOOL bSomeProps = FALSE; IPropertyUI *ppui; if (SUCCEEDED(_GetPropertyUI(&ppui))) { LPCTSTR psz = StrChr(pszProps, TEXT('%')); while (psz) { psz++; // skip first % LPCTSTR pszTail = StrChr(psz, TEXT('%')); if (pszTail) { TCHAR szPropName[128]; StrCpyN(szPropName, psz, (int)min(ARRAYSIZE(szPropName), pszTail - psz + 1)); psz = pszTail + 1; // just past second %, make sure we advance every time through the loop if (szPropName[0]) { SHCOLUMNID scid; ULONG chEaten = 0; // gets incremented by ParsePropertyName if (SUCCEEDED(ppui->ParsePropertyName(szPropName, &scid.fmtid, &scid.pid, &chEaten))) { TCHAR szValue[128]; szValue[0] = 0; if (SUCCEEDED(ReadPropertyAsString(ppui, ppss, &scid, szValue, ARRAYSIZE(szValue)))) { bSomeProps = TRUE; FixBadFilenameChars(szValue); } else { TCHAR szUnknown[64], szPropDisplayName[128]; LoadString(m_hInst, IDS_UNKNOWN, szUnknown, ARRAYSIZE(szUnknown)); if (SUCCEEDED(ppui->GetDisplayName(scid.fmtid, scid.pid, PUIFNF_DEFAULT, szPropDisplayName, ARRAYSIZE(szPropDisplayName)))) { wnsprintf(szValue, ARRAYSIZE(szValue), TEXT("%s %s"), szUnknown, szPropDisplayName); } else { StrCpyN(szValue, szUnknown, ARRAYSIZE(szValue)); } } // clean stuff out of the root path that we find PathRemovePart(pszRoot, szValue); if (szValue[0]) { StrCatBuff(pszName, szValue, cchName); // the value // add extra formatting stuff next LPCTSTR pszNext = StrChr(psz, TEXT('%')); if (NULL == pszNext) pszNext = psz + lstrlen(psz); // end of string case // get extra stuff TCHAR szExtra[64]; StrCpyN(szExtra, psz, (int)min((UINT)(pszNext - psz + 1), ARRAYSIZE(szExtra))); StrCatBuff(pszName, szExtra, cchName); psz = *pszNext ? pszNext : NULL; // maybe end, maybe more } } } } else { psz = NULL; } } ppui->Release(); } return bSomeProps ? S_OK : S_FALSE; } // do both files binary compare BOOL IsSameFile(LPCTSTR pszFile1, LPCTSTR pszFile2) { BOOL bSame = FALSE; IStream *pstm1; HRESULT hr = SHCreateStreamOnFileEx(pszFile1, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &pstm1); if (SUCCEEDED(hr)) { IStream *pstm2; hr = SHCreateStreamOnFileEx(pszFile2, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &pstm2); if (SUCCEEDED(hr)) { ULONG cb1; do { char buf1[4096]; hr = pstm1->Read(buf1, sizeof(buf1), &cb1); if (SUCCEEDED(hr)) { char buf2[4096]; ULONG cb2; hr = pstm2->Read(buf2, sizeof(buf2), &cb2); if (SUCCEEDED(hr)) { if (cb1 == cb2) { if (0 == memcmp(buf1, buf2, cb1)) bSame = TRUE; } else { bSame = FALSE; } } } } while (bSame && SUCCEEDED(hr) && cb1); pstm2->Release(); } pstm1->Release(); } return bSame; } // returns win32 error code // 0 == success int MoveFileAndCreateDirectory(LPCTSTR pszOldPath, LPCTSTR pszNewPath) { int err = ERROR_SUCCESS; if (!MoveFile(pszOldPath, pszNewPath)) { err = GetLastError(); if (ERROR_PATH_NOT_FOUND == err) { // maybe the target folder does not exist, lets // create it and try again TCHAR szNewPath[MAX_PATH]; StrCpyN(szNewPath, pszNewPath, ARRAYSIZE(szNewPath)); PathRemoveFileSpec(szNewPath); if (ERROR_SUCCESS == SHCreateDirectoryEx(NULL, szNewPath, NULL)) { if (MoveFile(pszOldPath, pszNewPath)) { // failure now success err = ERROR_SUCCESS; } } } } return err; } LPCWSTR LoadStr(UINT id, LPWSTR psz, UINT cch) { LoadString(m_hInst, id, psz, cch); return psz; } // INamespaceWalkCB STDMETHODIMP COrganizeFiles::FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl) { if (_bCountFiles) { _cFilesTotal++; } else { _cFileCur++; // if we were invoked on just a file we never got an ::EnterFolder() if (0 == _szRootFolder[0]) { DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szRootFolder, ARRAYSIZE(_szRootFolder)); PathRemoveFileSpec(_szRootFolder); } TCHAR szOldPath[MAX_PATH]; DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szOldPath, ARRAYSIZE(szOldPath)); _ppd->SetLine(2, szOldPath, TRUE, NULL); _ppd->SetProgress64(_cFileCur, _cFilesTotal); IPropertySetStorage *ppss; if (SUCCEEDED(psf->BindToObject(pidl, NULL, IID_PPV_ARG(IPropertySetStorage, &ppss)))) { TCHAR szRoot[MAX_PATH]; TCHAR szNewName[MAX_PATH]; StrCpyN(szRoot, _szRootFolder, ARRAYSIZE(szRoot)); if (S_OK == _NameFromPropertiesString(_pszProps, ppss, szRoot, szNewName, ARRAYSIZE(szNewName))) { PathRenameExtension(szNewName, PathFindExtension(szOldPath)); TCHAR szNewPath[MAX_PATH]; // PathCombineRemoveDups(szNewPath, szRoot, szNewName); PathCombine(szNewPath, szRoot, szNewName); ASSERT(0 != *PathFindExtension(szNewPath)); if (StrCmpC(szNewPath, szOldPath)) { int err = MoveFileAndCreateDirectory(szOldPath, szNewPath); if (ERROR_ALREADY_EXISTS == err) { // maybe the source and target are the same file // if so lets get rid of one of them WCHAR szBuf[64]; _ppd->SetLine(2, LoadStr(IDS_COMPARING_FILES, szBuf, ARRAYSIZE(szBuf)), FALSE, NULL); if (IsSameFile(szOldPath, szNewPath)) { DeleteFile(szOldPath); } _ppd->SetLine(2, L"", FALSE, NULL); } } } ppss->Release(); } } return S_OK; } STDMETHODIMP COrganizeFiles::EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl) { if (0 == _szRootFolder[0]) { DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szRootFolder, ARRAYSIZE(_szRootFolder)); } return S_OK; } BOOL IsEmptyFolder(IShellFolder *psf) { // don't look for SHCONTF_INCLUDEHIDDEN items, as we want to still delete the folder // if there are some of these IEnumIDList *penum; HRESULT hr = psf->EnumObjects(NULL, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &penum); if (S_OK == hr) { LPITEMIDLIST pidl; hr = penum->Next(1, &pidl, NULL); if (S_OK == hr) { ILFree(pidl); } penum->Release(); } return S_FALSE == hr; } void RemoveEmptyFolder(IShellFolder *psf, LPCITEMIDLIST pidl) { IShellFolder *psfItem; if (SUCCEEDED(psf->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem)))) { if (IsEmptyFolder(psfItem)) { TCHAR szPath[MAX_PATH] = {0}; // zero init to dbl null terminate DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath)); SHFILEOPSTRUCT fo = { NULL, FO_DELETE, szPath, NULL, FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI }; SHFileOperation(&fo); } psfItem->Release(); } } STDMETHODIMP COrganizeFiles::LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl) { if (!_bCountFiles) { // if the folder is not empty we try to remove it knowing that this call will // fail if there are files in the folder RemoveEmptyFolder(psf, pidl); } return S_OK; } HRESULT COrganizeFiles::_GetPropertyUI(IPropertyUI **pppui) { if (!_ppui) SHCoCreateInstance(NULL, &CLSID_PropertiesUI, NULL, IID_PPV_ARG(IPropertyUI, &_ppui)); return _ppui ? _ppui->QueryInterface(IID_PPV_ARG(IPropertyUI, pppui)) : E_NOTIMPL; } void COrganizeFiles::_DoOrganizeMusic(UINT idCmd, IDataObject *pdtobj) { _pszProps = (idCmd == CMD_ORGANIZE_FLAT) ? (_pszTemplateFlat ? _pszTemplateFlat : TEXT("%Artist% - %Album% - %Track% - %DocTitle%")) : (_pszTemplate ? _pszTemplate : TEXT("%Artist%\\%Album%\\%Track% - %DocTitle%")); INamespaceWalk *pnsw; HRESULT hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC, IID_PPV_ARG(INamespaceWalk, &pnsw)); if (SUCCEEDED(hr)) { IProgressDialog *ppd; hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IProgressDialog, &ppd)); if (SUCCEEDED(hr)) { ppd->StartProgressDialog(NULL, NULL, PROGDLG_AUTOTIME, NULL); WCHAR szBuf[64]; ppd->SetTitle(LoadStr(IDS_ORGANIZE_MUSIC, szBuf, ARRAYSIZE(szBuf))); ppd->SetLine(1, LoadStr(IDS_FINDING_FILES, szBuf, ARRAYSIZE(szBuf)), FALSE, NULL); _ppd = ppd; _bCountFiles = TRUE; _cFileCur = _cFilesTotal = 0; // everything happens in our callback interface hr = pnsw->Walk(pdtobj, NSWF_DONT_TRAVERSE_LINKS | NSWF_DONT_ACCUMULATE_RESULT, 8, SAFECAST(this, INamespaceWalkCB *)); if (SUCCEEDED(hr)) { ppd->SetLine(1, LoadStr(IDS_ORGANIZING_FILES, szBuf, ARRAYSIZE(szBuf)), FALSE, NULL); _bCountFiles = FALSE; hr = pnsw->Walk(pdtobj, NSWF_DONT_TRAVERSE_LINKS | NSWF_DONT_ACCUMULATE_RESULT, 8, SAFECAST(this, INamespaceWalkCB *)); } ppd->StopProgressDialog(); ppd->Release(); } pnsw->Release(); } if (_szRootFolder[0]) { SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, _szRootFolder, NULL); } }