Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

779 lines
23 KiB

  1. #include "pch.h"
  2. #include "thisdll.h"
  3. #include "cowsite.h"
  4. #include <shlobj.h>
  5. #include "ids.h"
  6. // Context menu offset IDs
  7. enum {
  8. CMD_ORGANIZE = 0,
  9. CMD_ORGANIZE_DEEP = 1,
  10. CMD_ORGANIZE_FLAT = 2,
  11. CMD_MAX = 3,
  12. };
  13. class COrganizeFiles;
  14. class COrganizeFiles : public IContextMenu, IShellExtInit, INamespaceWalkCB, CObjectWithSite
  15. {
  16. public:
  17. COrganizeFiles();
  18. // IUnknown
  19. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  20. STDMETHODIMP_(ULONG) AddRef();
  21. STDMETHODIMP_(ULONG) Release();
  22. // IShellExtInit
  23. STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hKeyID);
  24. // IContextMenu
  25. STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags);
  26. STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pcmici);
  27. STDMETHODIMP GetCommandString(UINT_PTR uID, UINT uFlags, UINT *res, LPSTR pName, UINT ccMax);
  28. // INamespaceWalkCB
  29. STDMETHODIMP FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl);
  30. STDMETHODIMP EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl);
  31. STDMETHODIMP LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl);
  32. STDMETHOD(InitializeProgressDialog)(LPWSTR *ppszTitle, LPWSTR *ppszCancel)
  33. { *ppszTitle = NULL; *ppszCancel = NULL; return E_NOTIMPL; }
  34. private:
  35. ~COrganizeFiles();
  36. class CMD_THREAD_DATA
  37. {
  38. public:
  39. CMD_THREAD_DATA(COrganizeFiles *pof, UINT idCmd) : _pof(pof), _idCmd(idCmd)
  40. {
  41. _pof->AddRef();
  42. _pstmDataObj = NULL;
  43. }
  44. ~CMD_THREAD_DATA()
  45. {
  46. _pof->Release();
  47. ATOMICRELEASE(_pstmDataObj);
  48. }
  49. COrganizeFiles *_pof; // back ptr to object
  50. UINT _idCmd;
  51. IStream *_pstmDataObj; // the IDataObject marshalled to the background thread
  52. };
  53. static DWORD CALLBACK COrganizeFiles::_CmdThreadProc(void *pv);
  54. void _CreateCmdThread(UINT idCmd);
  55. void _DoCmd(UINT idCmd, IDataObject *pdtobj);
  56. void _DoOrganizeMusic(UINT idCmd, IDataObject *pdtobj);
  57. HRESULT _GetPropertyUI(IPropertyUI **pppui);
  58. HRESULT _NameFromPropertiesString(LPCTSTR pszProps, IPropertySetStorage *ppss, LPTSTR pszRoot, LPTSTR pszName, UINT cchName);
  59. LONG _cRef;
  60. IDataObject *_pdtobj;
  61. IPropertyUI *_ppui;
  62. BOOL _bCountFiles; // call back is in "count files" mode
  63. UINT _cFilesTotal; // total computed in the count
  64. UINT _cFileCur; // current, for progress UI
  65. IProgressDialog *_ppd;
  66. TCHAR _szRootFolder[MAX_PATH];
  67. LPCTSTR _pszProps;
  68. LPTSTR _pszTemplateFlat;
  69. LPTSTR _pszTemplate;
  70. };
  71. COrganizeFiles::COrganizeFiles() : _cRef(1)
  72. {
  73. }
  74. COrganizeFiles::~COrganizeFiles()
  75. {
  76. CoTaskMemFree(_pszTemplateFlat);
  77. CoTaskMemFree(_pszTemplate);
  78. IUnknown_Set(&_punkSite, NULL);
  79. IUnknown_Set((IUnknown**)&_pdtobj, NULL);
  80. IUnknown_Set((IUnknown**)&_ppui, NULL);
  81. }
  82. STDAPI COrganizeFiles_CreateInstance(IUnknown *pUnkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
  83. {
  84. COrganizeFiles *psid = new COrganizeFiles();
  85. if (!psid)
  86. {
  87. *ppunk = NULL; // incase of failure
  88. return E_OUTOFMEMORY;
  89. }
  90. HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
  91. psid->Release();
  92. return hr;
  93. }
  94. STDMETHODIMP COrganizeFiles::QueryInterface(REFIID riid, void **ppv)
  95. {
  96. static const QITAB qit[] =
  97. {
  98. QITABENT(COrganizeFiles, IShellExtInit),
  99. QITABENT(COrganizeFiles, IContextMenu),
  100. QITABENT(COrganizeFiles, IObjectWithSite),
  101. QITABENT(COrganizeFiles, INamespaceWalkCB),
  102. { 0 },
  103. };
  104. return QISearch(this, qit, riid, ppv);
  105. }
  106. STDMETHODIMP_(ULONG) COrganizeFiles::AddRef()
  107. {
  108. return InterlockedIncrement(&_cRef);
  109. }
  110. STDMETHODIMP_(ULONG) COrganizeFiles::Release()
  111. {
  112. if (InterlockedDecrement(&_cRef))
  113. return _cRef;
  114. delete this;
  115. return 0;
  116. }
  117. // IShellExtInit
  118. STDMETHODIMP COrganizeFiles::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hKeyID)
  119. {
  120. IUnknown_Set((IUnknown**)&_pdtobj, pdtobj);
  121. return S_OK;
  122. }
  123. // IContextMenu
  124. STDMETHODIMP COrganizeFiles::QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags)
  125. {
  126. if (NULL == _pszTemplate)
  127. {
  128. IAssociationArray *paa;
  129. if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_CtxQueryAssociations, IID_PPV_ARG(IAssociationArray, &paa))))
  130. {
  131. paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQN_NAMED_VALUE, L"OrganizeTemplate", &_pszTemplate);
  132. paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQN_NAMED_VALUE, L"OrganizeTemplateFlat", &_pszTemplateFlat);
  133. paa->Release();
  134. }
  135. }
  136. if (!(uFlags & CMF_DEFAULTONLY))
  137. {
  138. InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
  139. TCHAR szBuffer[128];
  140. LoadString(m_hInst, IDS_ORGANIZE_MUSIC, szBuffer, ARRAYSIZE(szBuffer));
  141. InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + CMD_ORGANIZE, szBuffer);
  142. // if (uFlags & CMF_EXTENDEDVERBS)
  143. {
  144. LoadString(m_hInst, IDS_ORGANIZE_MUSIC_FLAT, szBuffer, ARRAYSIZE(szBuffer));
  145. InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + CMD_ORGANIZE_FLAT, szBuffer);
  146. }
  147. }
  148. return MAKE_HRESULT(SEVERITY_SUCCESS, 0, CMD_MAX);
  149. }
  150. typedef struct
  151. {
  152. LPCTSTR psz;
  153. UINT id;
  154. } CMD_MAP;
  155. const CMD_MAP c_szVerbs[] =
  156. {
  157. {TEXT("OrganizeMusic"), CMD_ORGANIZE},
  158. {TEXT("OrganizeMusicFlat"), CMD_ORGANIZE_FLAT},
  159. {TEXT("OrganizeMusicDeep"), CMD_ORGANIZE_DEEP},
  160. };
  161. UINT GetCommandId(LPCMINVOKECOMMANDINFO pcmici, const CMD_MAP rgMap[], UINT cMap)
  162. {
  163. UINT id = -1;
  164. if (IS_INTRESOURCE(pcmici->lpVerb))
  165. {
  166. id = LOWORD((UINT_PTR)pcmici->lpVerb);
  167. }
  168. else
  169. {
  170. TCHAR szCmd[64];
  171. szCmd[0] = 0;
  172. // Check first whether the caller passed a EX structure by checking
  173. // the size...
  174. if (pcmici->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
  175. {
  176. LPCMINVOKECOMMANDINFOEX pcmiciEX = (LPCMINVOKECOMMANDINFOEX)pcmici;
  177. if (pcmici->fMask & CMIC_MASK_UNICODE)
  178. {
  179. SHUnicodeToTChar(pcmiciEX->lpVerbW, szCmd, ARRAYSIZE(szCmd));
  180. }
  181. }
  182. // If we don't yet have a command verb, it must have been passed
  183. // ANSI or in a regular CMINVOKECOMMANDINFO structure (which means
  184. // it is by default ANSI). In either case, we need to convert it to
  185. // UNICODE before proceeding...
  186. if (szCmd[0] == 0)
  187. {
  188. SHAnsiToTChar(pcmici->lpVerb, szCmd, ARRAYSIZE(szCmd));
  189. }
  190. for (UINT i = 0; i < cMap; i++)
  191. {
  192. if (!StrCmpIC(szCmd, rgMap[i].psz))
  193. {
  194. id = rgMap[i].id;
  195. break;
  196. }
  197. }
  198. }
  199. return id;
  200. }
  201. STDMETHODIMP COrganizeFiles::InvokeCommand(LPCMINVOKECOMMANDINFO pcmici)
  202. {
  203. HRESULT hr = S_OK;
  204. UINT idCmd = GetCommandId(pcmici, c_szVerbs, ARRAYSIZE(c_szVerbs));
  205. switch (idCmd)
  206. {
  207. case CMD_ORGANIZE:
  208. case CMD_ORGANIZE_DEEP:
  209. case CMD_ORGANIZE_FLAT:
  210. _CreateCmdThread(idCmd);
  211. break;
  212. default:
  213. hr = E_INVALIDARG;
  214. break;
  215. }
  216. return hr;
  217. }
  218. STDMETHODIMP COrganizeFiles::GetCommandString(UINT_PTR uID, UINT uFlags, UINT *res, LPSTR pName, UINT cchMax)
  219. {
  220. HRESULT hr = S_OK;
  221. UINT idSel = (UINT)uID;
  222. switch (uFlags)
  223. {
  224. case GCS_VERBW:
  225. case GCS_VERBA:
  226. if (idSel < ARRAYSIZE(c_szVerbs))
  227. {
  228. if (uFlags == GCS_VERBW)
  229. {
  230. SHTCharToUnicode(c_szVerbs[idSel].psz, (LPWSTR)pName, cchMax);
  231. }
  232. else
  233. {
  234. SHTCharToAnsi(c_szVerbs[idSel].psz, pName, cchMax);
  235. }
  236. }
  237. break;
  238. case GCS_HELPTEXTA:
  239. case GCS_HELPTEXTW:
  240. case GCS_VALIDATEA:
  241. case GCS_VALIDATEW:
  242. hr = E_NOTIMPL;
  243. break;
  244. }
  245. return hr;
  246. }
  247. DWORD CALLBACK COrganizeFiles::_CmdThreadProc(void *pv)
  248. {
  249. CMD_THREAD_DATA *potd = (CMD_THREAD_DATA *)pv;
  250. IDataObject *pdtobj;
  251. HRESULT hr = CoGetInterfaceAndReleaseStream(potd->_pstmDataObj, IID_PPV_ARG(IDataObject, &pdtobj));
  252. potd->_pstmDataObj = NULL;
  253. if (SUCCEEDED(hr))
  254. {
  255. potd->_pof->_DoCmd(potd->_idCmd, pdtobj);
  256. pdtobj->Release();
  257. }
  258. delete potd;
  259. return 0;
  260. }
  261. void COrganizeFiles::_CreateCmdThread(UINT idCmd)
  262. {
  263. CMD_THREAD_DATA *potd = new CMD_THREAD_DATA(this, idCmd);
  264. if (potd)
  265. {
  266. if (FAILED(CoMarshalInterThreadInterfaceInStream(IID_IDataObject, _pdtobj, &potd->_pstmDataObj)) ||
  267. !SHCreateThread(_CmdThreadProc, potd, CTF_COINIT, NULL))
  268. {
  269. delete potd;
  270. }
  271. }
  272. }
  273. void COrganizeFiles::_DoCmd(UINT idCmd, IDataObject *pdtobj)
  274. {
  275. switch (idCmd)
  276. {
  277. case CMD_ORGANIZE:
  278. case CMD_ORGANIZE_DEEP:
  279. case CMD_ORGANIZE_FLAT:
  280. _DoOrganizeMusic(idCmd, pdtobj);
  281. break;
  282. }
  283. }
  284. HRESULT ReadPropertyAsString(IPropertyUI *ppui, IPropertySetStorage *ppss, LPCSHCOLUMNID pscid, LPTSTR psz, UINT cch)
  285. {
  286. *psz = 0;
  287. IPropertyStorage *pps;
  288. HRESULT hr = ppss->Open(pscid->fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, &pps);
  289. if (SUCCEEDED(hr))
  290. {
  291. PROPSPEC ps = {PRSPEC_PROPID, pscid->pid};
  292. PROPVARIANT v = {0};
  293. hr = pps->ReadMultiple(1, &ps, &v);
  294. if (SUCCEEDED(hr))
  295. {
  296. hr = ppui->FormatForDisplay(pscid->fmtid, pscid->pid, &v, PUIFFDF_DEFAULT, psz, cch);
  297. if (SUCCEEDED(hr) && (0 == *psz))
  298. {
  299. hr = E_FAIL;
  300. }
  301. PropVariantClear(&v);
  302. }
  303. pps->Release();
  304. }
  305. return hr;
  306. }
  307. void FixBadFilenameChars(LPTSTR pszName)
  308. {
  309. while (*pszName)
  310. {
  311. switch (*pszName)
  312. {
  313. case '?':
  314. case '*':
  315. case '/':
  316. case '\\':
  317. case '!':
  318. *pszName = '_';
  319. break;
  320. case '\"':
  321. *pszName = '\'';
  322. break;
  323. case ':':
  324. *pszName = '-';
  325. break;
  326. }
  327. pszName++;
  328. }
  329. }
  330. LPTSTR PathCombineRemoveDups(LPTSTR pszNewPath, LPCTSTR pszRoot, LPCTSTR pszTail)
  331. {
  332. for (LPCTSTR psz = pszTail; (psz && *psz); psz = PathFindNextComponent(psz))
  333. {
  334. LPCTSTR pszNext = PathFindNextComponent(psz);
  335. if (pszNext && *pszNext)
  336. {
  337. TCHAR szPart[MAX_PATH];
  338. UINT_PTR len = pszNext - psz;
  339. StrCpyN(szPart, psz, (int)min(ARRAYSIZE(szPart), len));
  340. LPCTSTR pszFound = StrStr(pszRoot, szPart);
  341. if ((pszFound > pszRoot) &&
  342. (*(pszFound - 1) == TEXT('\\')) &&
  343. ((*(pszFound + len - 1) == TEXT('\\')) || (*(pszFound + len - 1) == 0)))
  344. {
  345. pszTail = pszNext;
  346. }
  347. }
  348. else
  349. {
  350. break;
  351. }
  352. }
  353. return PathCombine(pszNewPath, pszRoot, pszTail);
  354. }
  355. LPTSTR PathRemovePart(LPTSTR pszPath, LPCTSTR pszToRemove)
  356. {
  357. LPTSTR pszFound = StrStr(pszPath, pszToRemove);
  358. if (pszFound && (pszFound > pszPath) &&
  359. (*(pszFound - 1) == TEXT('\\')))
  360. {
  361. LPTSTR pszTail = pszFound + lstrlen(pszToRemove);
  362. if ((*pszTail == TEXT('\\')) || (*pszTail == 0))
  363. {
  364. *pszFound = 0;
  365. if (*pszTail)
  366. {
  367. PathAppend(pszPath, pszTail + 1);
  368. }
  369. else
  370. {
  371. PathRemoveBackslash(pszPath); // trim extra stuff
  372. }
  373. }
  374. }
  375. return pszPath;
  376. }
  377. // pszProps == "%Artist% - %Album% - %Track% - %DocTitle%"
  378. HRESULT COrganizeFiles::_NameFromPropertiesString(LPCTSTR pszProps, IPropertySetStorage *ppss, LPTSTR pszRoot, LPTSTR pszName, UINT cchName)
  379. {
  380. *pszName = 0; // start empty, we build this up
  381. BOOL bSomeProps = FALSE;
  382. IPropertyUI *ppui;
  383. if (SUCCEEDED(_GetPropertyUI(&ppui)))
  384. {
  385. LPCTSTR psz = StrChr(pszProps, TEXT('%'));
  386. while (psz)
  387. {
  388. psz++; // skip first %
  389. LPCTSTR pszTail = StrChr(psz, TEXT('%'));
  390. if (pszTail)
  391. {
  392. TCHAR szPropName[128];
  393. StrCpyN(szPropName, psz, (int)min(ARRAYSIZE(szPropName), pszTail - psz + 1));
  394. psz = pszTail + 1; // just past second %, make sure we advance every time through the loop
  395. if (szPropName[0])
  396. {
  397. SHCOLUMNID scid;
  398. ULONG chEaten = 0; // gets incremented by ParsePropertyName
  399. if (SUCCEEDED(ppui->ParsePropertyName(szPropName, &scid.fmtid, &scid.pid, &chEaten)))
  400. {
  401. TCHAR szValue[128];
  402. szValue[0] = 0;
  403. if (SUCCEEDED(ReadPropertyAsString(ppui, ppss, &scid, szValue, ARRAYSIZE(szValue))))
  404. {
  405. bSomeProps = TRUE;
  406. FixBadFilenameChars(szValue);
  407. }
  408. else
  409. {
  410. TCHAR szUnknown[64], szPropDisplayName[128];
  411. LoadString(m_hInst, IDS_UNKNOWN, szUnknown, ARRAYSIZE(szUnknown));
  412. if (SUCCEEDED(ppui->GetDisplayName(scid.fmtid, scid.pid, PUIFNF_DEFAULT, szPropDisplayName, ARRAYSIZE(szPropDisplayName))))
  413. {
  414. wnsprintf(szValue, ARRAYSIZE(szValue), TEXT("%s %s"), szUnknown, szPropDisplayName);
  415. }
  416. else
  417. {
  418. StrCpyN(szValue, szUnknown, ARRAYSIZE(szValue));
  419. }
  420. }
  421. // clean stuff out of the root path that we find
  422. PathRemovePart(pszRoot, szValue);
  423. if (szValue[0])
  424. {
  425. StrCatBuff(pszName, szValue, cchName); // the value
  426. // add extra formatting stuff next
  427. LPCTSTR pszNext = StrChr(psz, TEXT('%'));
  428. if (NULL == pszNext)
  429. pszNext = psz + lstrlen(psz); // end of string case
  430. // get extra stuff
  431. TCHAR szExtra[64];
  432. StrCpyN(szExtra, psz, (int)min((UINT)(pszNext - psz + 1), ARRAYSIZE(szExtra)));
  433. StrCatBuff(pszName, szExtra, cchName);
  434. psz = *pszNext ? pszNext : NULL; // maybe end, maybe more
  435. }
  436. }
  437. }
  438. }
  439. else
  440. {
  441. psz = NULL;
  442. }
  443. }
  444. ppui->Release();
  445. }
  446. return bSomeProps ? S_OK : S_FALSE;
  447. }
  448. // do both files binary compare
  449. BOOL IsSameFile(LPCTSTR pszFile1, LPCTSTR pszFile2)
  450. {
  451. BOOL bSame = FALSE;
  452. IStream *pstm1;
  453. HRESULT hr = SHCreateStreamOnFileEx(pszFile1, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &pstm1);
  454. if (SUCCEEDED(hr))
  455. {
  456. IStream *pstm2;
  457. hr = SHCreateStreamOnFileEx(pszFile2, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &pstm2);
  458. if (SUCCEEDED(hr))
  459. {
  460. ULONG cb1;
  461. do
  462. {
  463. char buf1[4096];
  464. hr = pstm1->Read(buf1, sizeof(buf1), &cb1);
  465. if (SUCCEEDED(hr))
  466. {
  467. char buf2[4096];
  468. ULONG cb2;
  469. hr = pstm2->Read(buf2, sizeof(buf2), &cb2);
  470. if (SUCCEEDED(hr))
  471. {
  472. if (cb1 == cb2)
  473. {
  474. if (0 == memcmp(buf1, buf2, cb1))
  475. bSame = TRUE;
  476. }
  477. else
  478. {
  479. bSame = FALSE;
  480. }
  481. }
  482. }
  483. }
  484. while (bSame && SUCCEEDED(hr) && cb1);
  485. pstm2->Release();
  486. }
  487. pstm1->Release();
  488. }
  489. return bSame;
  490. }
  491. // returns win32 error code
  492. // 0 == success
  493. int MoveFileAndCreateDirectory(LPCTSTR pszOldPath, LPCTSTR pszNewPath)
  494. {
  495. int err = ERROR_SUCCESS;
  496. if (!MoveFile(pszOldPath, pszNewPath))
  497. {
  498. err = GetLastError();
  499. if (ERROR_PATH_NOT_FOUND == err)
  500. {
  501. // maybe the target folder does not exist, lets
  502. // create it and try again
  503. TCHAR szNewPath[MAX_PATH];
  504. StrCpyN(szNewPath, pszNewPath, ARRAYSIZE(szNewPath));
  505. PathRemoveFileSpec(szNewPath);
  506. if (ERROR_SUCCESS == SHCreateDirectoryEx(NULL, szNewPath, NULL))
  507. {
  508. if (MoveFile(pszOldPath, pszNewPath))
  509. {
  510. // failure now success
  511. err = ERROR_SUCCESS;
  512. }
  513. }
  514. }
  515. }
  516. return err;
  517. }
  518. LPCWSTR LoadStr(UINT id, LPWSTR psz, UINT cch)
  519. {
  520. LoadString(m_hInst, id, psz, cch);
  521. return psz;
  522. }
  523. // INamespaceWalkCB
  524. STDMETHODIMP COrganizeFiles::FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl)
  525. {
  526. if (_bCountFiles)
  527. {
  528. _cFilesTotal++;
  529. }
  530. else
  531. {
  532. _cFileCur++;
  533. // if we were invoked on just a file we never got an ::EnterFolder()
  534. if (0 == _szRootFolder[0])
  535. {
  536. DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szRootFolder, ARRAYSIZE(_szRootFolder));
  537. PathRemoveFileSpec(_szRootFolder);
  538. }
  539. TCHAR szOldPath[MAX_PATH];
  540. DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szOldPath, ARRAYSIZE(szOldPath));
  541. _ppd->SetLine(2, szOldPath, TRUE, NULL);
  542. _ppd->SetProgress64(_cFileCur, _cFilesTotal);
  543. IPropertySetStorage *ppss;
  544. if (SUCCEEDED(psf->BindToObject(pidl, NULL, IID_PPV_ARG(IPropertySetStorage, &ppss))))
  545. {
  546. TCHAR szRoot[MAX_PATH];
  547. TCHAR szNewName[MAX_PATH];
  548. StrCpyN(szRoot, _szRootFolder, ARRAYSIZE(szRoot));
  549. if (S_OK == _NameFromPropertiesString(_pszProps, ppss, szRoot, szNewName, ARRAYSIZE(szNewName)))
  550. {
  551. PathRenameExtension(szNewName, PathFindExtension(szOldPath));
  552. TCHAR szNewPath[MAX_PATH];
  553. // PathCombineRemoveDups(szNewPath, szRoot, szNewName);
  554. PathCombine(szNewPath, szRoot, szNewName);
  555. ASSERT(0 != *PathFindExtension(szNewPath));
  556. if (StrCmpC(szNewPath, szOldPath))
  557. {
  558. int err = MoveFileAndCreateDirectory(szOldPath, szNewPath);
  559. if (ERROR_ALREADY_EXISTS == err)
  560. {
  561. // maybe the source and target are the same file
  562. // if so lets get rid of one of them
  563. WCHAR szBuf[64];
  564. _ppd->SetLine(2, LoadStr(IDS_COMPARING_FILES, szBuf, ARRAYSIZE(szBuf)), FALSE, NULL);
  565. if (IsSameFile(szOldPath, szNewPath))
  566. {
  567. DeleteFile(szOldPath);
  568. }
  569. _ppd->SetLine(2, L"", FALSE, NULL);
  570. }
  571. }
  572. }
  573. ppss->Release();
  574. }
  575. }
  576. return S_OK;
  577. }
  578. STDMETHODIMP COrganizeFiles::EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
  579. {
  580. if (0 == _szRootFolder[0])
  581. {
  582. DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szRootFolder, ARRAYSIZE(_szRootFolder));
  583. }
  584. return S_OK;
  585. }
  586. BOOL IsEmptyFolder(IShellFolder *psf)
  587. {
  588. // don't look for SHCONTF_INCLUDEHIDDEN items, as we want to still delete the folder
  589. // if there are some of these
  590. IEnumIDList *penum;
  591. HRESULT hr = psf->EnumObjects(NULL, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &penum);
  592. if (S_OK == hr)
  593. {
  594. LPITEMIDLIST pidl;
  595. hr = penum->Next(1, &pidl, NULL);
  596. if (S_OK == hr)
  597. {
  598. ILFree(pidl);
  599. }
  600. penum->Release();
  601. }
  602. return S_FALSE == hr;
  603. }
  604. void RemoveEmptyFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
  605. {
  606. IShellFolder *psfItem;
  607. if (SUCCEEDED(psf->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem))))
  608. {
  609. if (IsEmptyFolder(psfItem))
  610. {
  611. TCHAR szPath[MAX_PATH] = {0}; // zero init to dbl null terminate
  612. DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath));
  613. SHFILEOPSTRUCT fo = { NULL, FO_DELETE, szPath, NULL,
  614. FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI };
  615. SHFileOperation(&fo);
  616. }
  617. psfItem->Release();
  618. }
  619. }
  620. STDMETHODIMP COrganizeFiles::LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
  621. {
  622. if (!_bCountFiles)
  623. {
  624. // if the folder is not empty we try to remove it knowing that this call will
  625. // fail if there are files in the folder
  626. RemoveEmptyFolder(psf, pidl);
  627. }
  628. return S_OK;
  629. }
  630. HRESULT COrganizeFiles::_GetPropertyUI(IPropertyUI **pppui)
  631. {
  632. if (!_ppui)
  633. SHCoCreateInstance(NULL, &CLSID_PropertiesUI, NULL, IID_PPV_ARG(IPropertyUI, &_ppui));
  634. return _ppui ? _ppui->QueryInterface(IID_PPV_ARG(IPropertyUI, pppui)) : E_NOTIMPL;
  635. }
  636. void COrganizeFiles::_DoOrganizeMusic(UINT idCmd, IDataObject *pdtobj)
  637. {
  638. _pszProps = (idCmd == CMD_ORGANIZE_FLAT) ?
  639. (_pszTemplateFlat ? _pszTemplateFlat : TEXT("%Artist% - %Album% - %Track% - %DocTitle%")) :
  640. (_pszTemplate ? _pszTemplate : TEXT("%Artist%\\%Album%\\%Track% - %DocTitle%"));
  641. INamespaceWalk *pnsw;
  642. HRESULT hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC, IID_PPV_ARG(INamespaceWalk, &pnsw));
  643. if (SUCCEEDED(hr))
  644. {
  645. IProgressDialog *ppd;
  646. hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IProgressDialog, &ppd));
  647. if (SUCCEEDED(hr))
  648. {
  649. ppd->StartProgressDialog(NULL, NULL, PROGDLG_AUTOTIME, NULL);
  650. WCHAR szBuf[64];
  651. ppd->SetTitle(LoadStr(IDS_ORGANIZE_MUSIC, szBuf, ARRAYSIZE(szBuf)));
  652. ppd->SetLine(1, LoadStr(IDS_FINDING_FILES, szBuf, ARRAYSIZE(szBuf)), FALSE, NULL);
  653. _ppd = ppd;
  654. _bCountFiles = TRUE;
  655. _cFileCur = _cFilesTotal = 0;
  656. // everything happens in our callback interface
  657. hr = pnsw->Walk(pdtobj, NSWF_DONT_TRAVERSE_LINKS | NSWF_DONT_ACCUMULATE_RESULT, 8, SAFECAST(this, INamespaceWalkCB *));
  658. if (SUCCEEDED(hr))
  659. {
  660. ppd->SetLine(1, LoadStr(IDS_ORGANIZING_FILES, szBuf, ARRAYSIZE(szBuf)), FALSE, NULL);
  661. _bCountFiles = FALSE;
  662. hr = pnsw->Walk(pdtobj, NSWF_DONT_TRAVERSE_LINKS | NSWF_DONT_ACCUMULATE_RESULT, 8, SAFECAST(this, INamespaceWalkCB *));
  663. }
  664. ppd->StopProgressDialog();
  665. ppd->Release();
  666. }
  667. pnsw->Release();
  668. }
  669. if (_szRootFolder[0])
  670. {
  671. SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, _szRootFolder, NULL);
  672. }
  673. }