Leaked source code of windows server 2003
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.

1424 lines
48 KiB

  1. #include "precomp.hxx"
  2. #pragma hdrstop
  3. #include <ccstock2.h> // DataObj_GetHIDA, IDA_ILClone, HIDA_ReleaseStgMedium
  4. #include <winnlsp.h> // NORM_STOP_ON_NULL
  5. #include "timewarp.h"
  6. #include "twprop.h"
  7. #include "util.h"
  8. #include "resource.h"
  9. #include "helpids.h"
  10. #include "access.h"
  11. // {596AB062-B4D2-4215-9F74-E9109B0A8153} CLSID_TimeWarpProp
  12. const CLSID CLSID_TimeWarpProp = {0x596AB062, 0xB4D2, 0x4215, {0x9F, 0x74, 0xE9, 0x10, 0x9B, 0x0A, 0x81, 0x53}};
  13. WCHAR const c_szHelpFile[] = L"twclient.hlp";
  14. WCHAR const c_szChmPath[] = L"%SystemRoot%\\Help\\twclient.chm";
  15. WCHAR const c_szTimeWarpFolderID[] = L"::{208D2C60-3AEA-1069-A2D7-08002B30309D}\\::{9DB7A13C-F208-4981-8353-73CC61AE2783},";
  16. WCHAR const c_szCopyMoveTo_RegKey[] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer";
  17. WCHAR const c_szCopyMoveTo_SubKey[] = L"CopyMoveTo";
  18. WCHAR const c_szCopyMoveTo_Value[] = L"LastFolder";
  19. // help IDs
  20. const static DWORD rgdwTimeWarpPropHelp[] =
  21. {
  22. IDC_TWICON, -1,
  23. IDC_TOPTEXT, -1,
  24. IDC_LIST, IDH_TIMEWARP_SNAPSHOTLIST,
  25. IDC_VIEW, IDH_TIMEWARP_OPENSNAP,
  26. IDC_COPY, IDH_TIMEWARP_SAVESNAP,
  27. IDC_REVERT, IDH_TIMEWARP_RESTORESNAP,
  28. 0, 0
  29. };
  30. static int CALLBACK BrowseCallback(HWND hDlg, UINT uMsg, LPARAM lParam, LPARAM pData);
  31. // Simple accessibility wrapper class which concatenates accDescription onto accName
  32. class CNameDescriptionAccessibleWrapper : public CAccessibleWrapper
  33. {
  34. public:
  35. CNameDescriptionAccessibleWrapper(IAccessible *pAcc, LPARAM) : CAccessibleWrapper(pAcc) {}
  36. STDMETHODIMP get_accName(VARIANT varChild, BSTR* pstrName);
  37. };
  38. static void SnapCheck_CacheResult(LPCWSTR pszPath, LPCWSTR pszShadowPath, BOOL bHasShadowCopy);
  39. static BOOL SnapCheck_LookupResult(LPCWSTR pszPath, BOOL *pbHasShadowCopy);
  40. HRESULT CTimeWarpProp::CreateInstance(IUnknown* /*punkOuter*/, IUnknown **ppunk, LPCOBJECTINFO /*poi*/)
  41. {
  42. CTimeWarpProp* pmp = new CTimeWarpProp();
  43. if (pmp)
  44. {
  45. *ppunk = SAFECAST(pmp, IShellExtInit*);
  46. return S_OK;
  47. }
  48. *ppunk = NULL;
  49. return E_OUTOFMEMORY;
  50. }
  51. CTimeWarpProp::CTimeWarpProp() : _cRef(1), _hDlg(NULL), _hList(NULL),
  52. _pszPath(NULL), _pszDisplayName(NULL), _pszSnapList(NULL),
  53. _fItemAttributes(0)
  54. {
  55. DllAddRef();
  56. }
  57. CTimeWarpProp::~CTimeWarpProp()
  58. {
  59. LocalFree(_pszPath); // NULL is OK
  60. LocalFree(_pszDisplayName);
  61. LocalFree(_pszSnapList);
  62. DllRelease();
  63. }
  64. STDMETHODIMP CTimeWarpProp::QueryInterface(REFIID riid, void **ppv)
  65. {
  66. static const QITAB qit[] =
  67. {
  68. QITABENT(CTimeWarpProp, IShellExtInit),
  69. QITABENT(CTimeWarpProp, IShellPropSheetExt),
  70. QITABENT(CTimeWarpProp, IPreviousVersionsInfo),
  71. { 0 },
  72. };
  73. return QISearch(this, qit, riid, ppv);
  74. }
  75. STDMETHODIMP_ (ULONG) CTimeWarpProp::AddRef()
  76. {
  77. return InterlockedIncrement(&_cRef);
  78. }
  79. STDMETHODIMP_ (ULONG) CTimeWarpProp::Release()
  80. {
  81. ASSERT( 0 != _cRef );
  82. ULONG cRef = InterlockedDecrement(&_cRef);
  83. if ( 0 == cRef )
  84. {
  85. delete this;
  86. }
  87. return cRef;
  88. }
  89. STDMETHODIMP CTimeWarpProp::Initialize(PCIDLIST_ABSOLUTE /*pidlFolder*/, IDataObject *pdobj, HKEY /*hkey*/)
  90. {
  91. HRESULT hr = E_FAIL;
  92. STGMEDIUM medium;
  93. LPIDA pida = DataObj_GetHIDA(pdobj, &medium);
  94. if (pida)
  95. {
  96. // Bail on multiple selection
  97. if (pida->cidl == 1)
  98. {
  99. // Bind to the parent folder
  100. IShellFolder *psf;
  101. hr = SHBindToObjectEx(NULL, IDA_GetPIDLFolder(pida), NULL, IID_IShellFolder, (void**)&psf);
  102. if (SUCCEEDED(hr))
  103. {
  104. PCUITEMID_CHILD pidlChild = IDA_GetPIDLItem(pida, 0);
  105. // Keep track of file vs folder
  106. _fItemAttributes = SFGAO_FOLDER | SFGAO_STREAM | SFGAO_LINK;
  107. hr = psf->GetAttributesOf(1, &pidlChild, &_fItemAttributes);
  108. if (SUCCEEDED(hr))
  109. {
  110. WCHAR szTemp[MAX_PATH];
  111. // For folder shortcuts, we use the target.
  112. if (_IsFolder() && _IsShortcut())
  113. {
  114. IShellLink *psl;
  115. hr = psf->BindToObject(pidlChild, NULL, IID_PPV_ARG(IShellLink, &psl));
  116. if (SUCCEEDED(hr))
  117. {
  118. WIN32_FIND_DATA fd;
  119. hr = psl->GetPath(szTemp, ARRAYSIZE(szTemp), &fd, SLGP_UNCPRIORITY);
  120. psl->Release();
  121. }
  122. }
  123. else
  124. {
  125. // Get the full path
  126. hr = DisplayNameOf(psf, pidlChild, SHGDN_FORPARSING, szTemp, ARRAYSIZE(szTemp));
  127. }
  128. if (SUCCEEDED(hr))
  129. {
  130. // We only work with network paths.
  131. if (PathIsNetworkPathW(szTemp) && !PathIsUNCServer(szTemp))
  132. {
  133. FILETIME ft;
  134. // If this is already a snapshot path, bail. Otherwise
  135. // we get into this weird recursive state where the
  136. // snapshot paths have 2 GMT strings in them and the
  137. // date is always the same (the first GMT string is
  138. // identical for all of them).
  139. if (NOERROR == GetSnapshotTimeFromPath(szTemp, &ft))
  140. {
  141. hr = E_FAIL;
  142. }
  143. else
  144. {
  145. // Remember the path
  146. _pszPath = StrDup(szTemp);
  147. if (NULL != _pszPath)
  148. {
  149. // Get the display name (continue on failure here)
  150. if (SUCCEEDED(DisplayNameOf(psf, pidlChild, SHGDN_INFOLDER, szTemp, ARRAYSIZE(szTemp))))
  151. {
  152. _pszDisplayName = StrDup(szTemp);
  153. }
  154. // Get the system icon index
  155. _iIcon = SHMapPIDLToSystemImageListIndex(psf, pidlChild, NULL);
  156. }
  157. else
  158. {
  159. hr = E_OUTOFMEMORY;
  160. }
  161. }
  162. }
  163. else
  164. {
  165. hr = E_FAIL;
  166. }
  167. }
  168. }
  169. psf->Release();
  170. }
  171. }
  172. HIDA_ReleaseStgMedium(pida, &medium);
  173. }
  174. return hr;
  175. }
  176. STDMETHODIMP CTimeWarpProp::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
  177. {
  178. HRESULT hr = S_OK;
  179. if (NULL != _pszPath)
  180. {
  181. BOOL bSnapsAvailable = FALSE;
  182. // Are snapshots available on this server?
  183. if (S_OK == AreSnapshotsAvailable(_pszPath, TRUE, &bSnapsAvailable) && bSnapsAvailable)
  184. {
  185. PROPSHEETPAGE psp;
  186. psp.dwSize = sizeof(psp);
  187. psp.dwFlags = PSP_DEFAULT | PSP_USECALLBACK | PSP_HASHELP;
  188. psp.hInstance = g_hInstance;
  189. psp.pszTemplate = MAKEINTRESOURCE(_IsFolder() ? DLG_TIMEWARPPROP_FOLDER : DLG_TIMEWARPPROP_FILE);
  190. psp.pfnDlgProc = CTimeWarpProp::DlgProc;
  191. psp.pfnCallback = CTimeWarpProp::PSPCallback;
  192. psp.lParam = (LPARAM)this;
  193. HPROPSHEETPAGE hPage = CreatePropertySheetPage(&psp);
  194. if (hPage)
  195. {
  196. this->AddRef();
  197. if (!pfnAddPage(hPage, lParam))
  198. {
  199. DestroyPropertySheetPage(hPage);
  200. hr = E_FAIL;
  201. }
  202. }
  203. }
  204. }
  205. return hr;
  206. }
  207. STDMETHODIMP CTimeWarpProp::ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM)
  208. {
  209. return E_NOTIMPL;
  210. }
  211. STDMETHODIMP CTimeWarpProp::AreSnapshotsAvailable(LPCWSTR pszPath, BOOL fOkToBeSlow, BOOL *pfAvailable)
  212. {
  213. FILETIME ft;
  214. if (NULL == pfAvailable)
  215. return E_POINTER;
  216. // Default answer is No.
  217. *pfAvailable = FALSE;
  218. if (NULL == pszPath || L'\0' == *pszPath)
  219. return E_INVALIDARG;
  220. // It must be a network path, but can't be a snapshot path already.
  221. if (PathIsNetworkPathW(pszPath) && !PathIsUNCServerW(pszPath) &&
  222. NOERROR != GetSnapshotTimeFromPath(pszPath, &ft))
  223. {
  224. // Check the cache
  225. if (SnapCheck_LookupResult(pszPath, pfAvailable))
  226. {
  227. // nothing to do
  228. }
  229. else if (fOkToBeSlow)
  230. {
  231. LPWSTR pszSnapList = NULL;
  232. DWORD cSnaps;
  233. // Hit the net
  234. DWORD dwErr = QuerySnapshotsForPath(pszPath, 0, &pszSnapList, &cSnaps);
  235. if (NOERROR == dwErr && NULL != pszSnapList)
  236. {
  237. // Snapshots are available
  238. *pfAvailable = TRUE;
  239. }
  240. // Remember the result
  241. SnapCheck_CacheResult(pszPath, pszSnapList, *pfAvailable);
  242. LocalFree(pszSnapList);
  243. }
  244. else
  245. {
  246. // Tell caller to call again with fOkToBeSlow = TRUE
  247. return E_PENDING;
  248. }
  249. }
  250. return S_OK;
  251. }
  252. void CTimeWarpProp::_OnInit(HWND hDlg)
  253. {
  254. _hDlg = hDlg;
  255. SendDlgItemMessage(hDlg, IDC_TWICON, STM_SETICON, (WPARAM)LoadIcon(g_hInstance,MAKEINTRESOURCE(IDI_TIMEWARP)), 0);
  256. // One-time listview initialization
  257. _hList = GetDlgItem(hDlg, IDC_LIST);
  258. if (NULL != _hList)
  259. {
  260. HIMAGELIST himlSmall;
  261. RECT rc;
  262. WCHAR szName[64];
  263. LVCOLUMN lvCol;
  264. ListView_SetExtendedListViewStyle(_hList, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP);
  265. Shell_GetImageLists(NULL, &himlSmall);
  266. ListView_SetImageList(_hList, himlSmall, LVSIL_SMALL);
  267. GetClientRect(_hList, &rc);
  268. lvCol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT;
  269. lvCol.fmt = LVCFMT_LEFT;
  270. lvCol.pszText = szName;
  271. LoadString(g_hInstance, IDS_NAMECOL, szName, ARRAYSIZE(szName));
  272. lvCol.cx = (rc.right / 3);
  273. lvCol.iSubItem = 0;
  274. ListView_InsertColumn(_hList, 0, &lvCol);
  275. LoadString(g_hInstance, IDS_DATECOL, szName, ARRAYSIZE(szName));
  276. lvCol.cx = rc.right - lvCol.cx;
  277. lvCol.iSubItem = 1;
  278. ListView_InsertColumn(_hList, 1, &lvCol);
  279. // Continue on failure here
  280. WrapAccessibleControl<CNameDescriptionAccessibleWrapper>(_hList);
  281. }
  282. // Query for snapshots and load the list
  283. _OnRefresh();
  284. }
  285. void CTimeWarpProp::_OnRefresh()
  286. {
  287. HCURSOR hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
  288. if (NULL != _hList)
  289. {
  290. DWORD cSnaps;
  291. // Start by emptying the list
  292. ListView_DeleteAllItems(_hList);
  293. // Free the old data
  294. LocalFree(_pszSnapList);
  295. _pszSnapList = NULL;
  296. // Hit the net
  297. ASSERT(NULL != _pszPath);
  298. DWORD dwErr = QuerySnapshotsForPath(_pszPath, _IsFile() ? QUERY_SNAPSHOT_DIFFERENT : QUERY_SNAPSHOT_EXISTING, &_pszSnapList, &cSnaps);
  299. // Fill the list
  300. if (NOERROR == dwErr && NULL != _pszSnapList)
  301. {
  302. UINT cItems = 0;
  303. LPCWSTR pszSnap;
  304. for (pszSnap = _pszSnapList; *pszSnap != L'\0'; pszSnap += lstrlenW(pszSnap)+1)
  305. {
  306. FILETIME ft;
  307. if (NOERROR == GetSnapshotTimeFromPath(pszSnap, &ft))
  308. {
  309. LVITEM lvItem;
  310. lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
  311. lvItem.iItem = cItems;
  312. lvItem.iSubItem = 0;
  313. lvItem.pszText = _pszDisplayName ? _pszDisplayName : PathFindFileNameW(_pszPath);
  314. lvItem.iImage = _iIcon;
  315. lvItem.lParam = (LPARAM)pszSnap;
  316. lvItem.iItem = ListView_InsertItem(_hList, &lvItem);
  317. if (-1 != lvItem.iItem)
  318. {
  319. ++cItems;
  320. WCHAR szDate[MAX_PATH];
  321. DWORD dwDateFlags = FDTF_RELATIVE | FDTF_LONGDATE | FDTF_SHORTTIME;
  322. SHFormatDateTime(&ft, &dwDateFlags, szDate, ARRAYSIZE(szDate));
  323. lvItem.mask = LVIF_TEXT;
  324. lvItem.iSubItem = 1;
  325. lvItem.pszText = szDate;
  326. ListView_SetItem(_hList, &lvItem);
  327. }
  328. }
  329. }
  330. if (cItems != 0)
  331. {
  332. // Select the first item
  333. ListView_SetItemState(_hList, 0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
  334. }
  335. }
  336. }
  337. _UpdateButtons();
  338. SetCursor(hcur);
  339. }
  340. void CTimeWarpProp::_OnSize()
  341. {
  342. #define _MOVE_X 0x0001
  343. #define _MOVE_Y 0x0002
  344. #define _SIZE_WIDTH 0x0004
  345. #define _SIZE_HEIGHT 0x0008
  346. static const struct
  347. {
  348. int idCtrl;
  349. DWORD dwFlags;
  350. } rgControls[] =
  351. {
  352. { IDC_TOPTEXT, _SIZE_WIDTH },
  353. { IDC_LIST, _SIZE_WIDTH | _SIZE_HEIGHT },
  354. { IDC_VIEW, _MOVE_X | _MOVE_Y },
  355. { IDC_COPY, _MOVE_X | _MOVE_Y },
  356. { IDC_REVERT, _MOVE_X | _MOVE_Y },
  357. };
  358. if (NULL != _hDlg)
  359. {
  360. RECT rcDlg;
  361. RECT rc;
  362. // Get the icon position (upper left ctrl) to find the margins
  363. GetWindowRect(GetDlgItem(_hDlg, IDC_TWICON), &rc);
  364. MapWindowPoints(NULL, _hDlg, (LPPOINT)&rc, 2);
  365. // Get the full dlg dimensions and adjust for margins
  366. GetClientRect(_hDlg, &rcDlg);
  367. rcDlg.right -= rc.left;
  368. rcDlg.bottom -= rc.top;
  369. // Get the Restore button pos (lower right ctrl) to calculate offsets
  370. GetWindowRect(GetDlgItem(_hDlg, IDC_REVERT), &rc);
  371. MapWindowPoints(NULL, _hDlg, (LPPOINT)&rc, 2);
  372. // This is how much things need to move or grow
  373. rcDlg.right -= rc.right; // x-offset
  374. rcDlg.bottom -= rc.bottom; // y-offset
  375. for (int i = 0; i < ARRAYSIZE(rgControls); i++)
  376. {
  377. HWND hwndCtrl = GetDlgItem(_hDlg, rgControls[i].idCtrl);
  378. GetWindowRect(hwndCtrl, &rc);
  379. MapWindowPoints(NULL, _hDlg, (LPPOINT)&rc, 2);
  380. rc.right -= rc.left; // "width"
  381. rc.bottom -= rc.top; // "height"
  382. if (rgControls[i].dwFlags & _MOVE_X) rc.left += rcDlg.right;
  383. if (rgControls[i].dwFlags & _MOVE_Y) rc.top += rcDlg.bottom;
  384. if (rgControls[i].dwFlags & _SIZE_WIDTH) rc.right += rcDlg.right;
  385. if (rgControls[i].dwFlags & _SIZE_HEIGHT) rc.bottom += rcDlg.bottom;
  386. MoveWindow(hwndCtrl, rc.left, rc.top, rc.right, rc.bottom, TRUE);
  387. }
  388. }
  389. }
  390. void CTimeWarpProp::_UpdateButtons()
  391. {
  392. // Enable or disable the pushbuttons based on whether something
  393. // is selected in the listview
  394. BOOL bEnable = (NULL != _GetSelectedItemPath());
  395. for (int i = IDC_VIEW; i <= IDC_REVERT; i++)
  396. {
  397. HWND hwndCtrl = GetDlgItem(_hDlg, i);
  398. // If we're disabling the buttons, check for focus and move
  399. // focus to the listview if necessary.
  400. if (!bEnable && GetFocus() == hwndCtrl)
  401. {
  402. SetFocus(_hList);
  403. }
  404. EnableWindow(hwndCtrl, bEnable);
  405. }
  406. }
  407. void CTimeWarpProp::_OnView()
  408. {
  409. LPCWSTR pszSnapShotPath = _GetSelectedItemPath();
  410. if (NULL != pszSnapShotPath)
  411. {
  412. // Test for existence. QuerySnapshotsForPath already tested for
  413. // existence, but if the server has since gone down, or deleted
  414. // the snapshot, the resulting error message shown by ShellExecute
  415. // is quite ugly.
  416. if (-1 != GetFileAttributesW(pszSnapShotPath))
  417. {
  418. SHELLEXECUTEINFOW sei;
  419. LPWSTR pszPathAlloc = NULL;
  420. HCURSOR hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
  421. if (_IsFolder())
  422. {
  423. const ULONG cchFolderID = ARRAYSIZE(c_szTimeWarpFolderID) - 1; // ARRAYSIZE counts '\0'
  424. ULONG cchFullPath = cchFolderID + lstrlen(pszSnapShotPath) + 1;
  425. pszPathAlloc = (LPWSTR)LocalAlloc(LPTR, cchFullPath*sizeof(WCHAR));
  426. if (pszPathAlloc)
  427. {
  428. // "::{CLSID_NetworkPlaces}\\::{CLSID_TimeWarpFolder},\\server\share\@GMT\dir"
  429. lstrcpynW(pszPathAlloc, c_szTimeWarpFolderID, cchFullPath);
  430. lstrcpynW(pszPathAlloc + cchFolderID, pszSnapShotPath, cchFullPath - cchFolderID);
  431. pszSnapShotPath = pszPathAlloc;
  432. }
  433. else
  434. {
  435. // Low memory. Try to launch a normal file system folder
  436. // (do nothing here).
  437. }
  438. }
  439. else if (SUCCEEDED(SHStrDup(pszSnapShotPath, &pszPathAlloc)))
  440. {
  441. pszSnapShotPath = pszPathAlloc;
  442. }
  443. if (pszPathAlloc)
  444. {
  445. // Some apps have problems with the "\\?\" prefix, including
  446. // the common dialog code.
  447. EliminatePathPrefix(pszPathAlloc);
  448. }
  449. sei.cbSize = sizeof(sei);
  450. sei.fMask = 0;
  451. sei.hwnd = _hDlg;
  452. sei.lpVerb = NULL;
  453. sei.lpFile = pszSnapShotPath;
  454. sei.lpParameters = NULL;
  455. sei.lpDirectory = NULL;
  456. sei.nShow = SW_SHOWNORMAL;
  457. ShellExecuteExW(&sei);
  458. LocalFree(pszPathAlloc);
  459. SetCursor(hcur);
  460. }
  461. else
  462. {
  463. // Show this error ourselves. The ShellExecuteEx version is rather ugly.
  464. TraceMsg(TF_TWPROP, "Snapshot unavailable (%d)", GetLastError());
  465. ShellMessageBoxW(g_hInstance, _hDlg,
  466. MAKEINTRESOURCE(_IsFolder() ? IDS_CANTFINDSNAPSHOT_FOLDER : IDS_CANTFINDSNAPSHOT_FILE),
  467. MAKEINTRESOURCE(IDS_TIMEWARP_TITLE),
  468. MB_ICONWARNING | MB_OK,
  469. _pszDisplayName);
  470. }
  471. }
  472. }
  473. void CTimeWarpProp::_OnCopy()
  474. {
  475. LPCWSTR pszSnapShotPath = _GetSelectedItemPath();
  476. if (NULL != pszSnapShotPath)
  477. {
  478. WCHAR szPath[2*MAX_PATH];
  479. // SHBrowseForFolder
  480. if (S_OK == _InvokeBFFDialog(szPath, ARRAYSIZE(szPath)))
  481. {
  482. int iCreateDirError = ERROR_ALREADY_EXISTS;
  483. //
  484. // If we're dealing with a folder, we have to be careful because
  485. // the GMT segment might be the last part of the source path.
  486. // If so, when SHFileOperation eventually passes this path to
  487. // FindFirstFile, it fails because no subfolder with that name
  488. // exists. To get around this, we append a wildcard '*' to the
  489. // source path (see _CopySnapShot and _MakeDoubleNullString).
  490. //
  491. // But that means we also have to add _pszDisplayName to the
  492. // destination path and create that directory first, in order
  493. // to get the expected behavior from SHFileOperation.
  494. //
  495. // Note that if the directory contains files, we don't really need
  496. // to create the directory first, since SHFileOperation hits the
  497. // CopyMoveRetry code path in DoFile_Copy, which creates the parent
  498. // dir. But if there are only subdirs and no files, it goes through
  499. // EnterDir_Copy first, which fails without calling CopyMoveRetry.
  500. // (EnterDir_Move does the CopyMoveRetry thing, so this seems like
  501. // a bug in EnterDir_Copy, but normal shell operations never hit it.)
  502. //
  503. if (!_IsFile())
  504. {
  505. UINT idErrorString = 0;
  506. WCHAR szDriveLetter[2];
  507. LPCWSTR pszDirName = NULL;
  508. // Append the directory name. Need to special case the root.
  509. if (PathIsRootW(_pszPath))
  510. {
  511. if (PathIsUNCW(_pszPath))
  512. {
  513. ASSERT(PathIsUNCServerShareW(_pszPath));
  514. pszDirName = wcschr(_pszPath+2, L'\\');
  515. if (pszDirName)
  516. {
  517. ++pszDirName;
  518. }
  519. // else continue without a subdir
  520. // (don't fall back on _pszDisplayName here)
  521. }
  522. else
  523. {
  524. szDriveLetter[0] = _pszPath[0];
  525. szDriveLetter[1] = L'\0';
  526. pszDirName = szDriveLetter;
  527. }
  528. }
  529. else
  530. {
  531. // Normal case
  532. pszDirName = PathFindFileNameW(_pszPath);
  533. if (!pszDirName)
  534. pszDirName = _pszDisplayName;
  535. }
  536. if (pszDirName)
  537. {
  538. // We could reduce szPath to MAX_PATH and use PathAppend here.
  539. UINT cch = lstrlenW(szPath);
  540. if (cch > 0 && szPath[cch-1] != L'\\')
  541. {
  542. if (cch+1 < ARRAYSIZE(szPath))
  543. {
  544. szPath[cch] = L'\\';
  545. ++cch;
  546. }
  547. else
  548. {
  549. iCreateDirError = ERROR_FILENAME_EXCED_RANGE;
  550. }
  551. }
  552. if (iCreateDirError != ERROR_FILENAME_EXCED_RANGE &&
  553. cch + lstrlenW(pszDirName) < ARRAYSIZE(szPath))
  554. {
  555. lstrcpynW(&szPath[cch], pszDirName, ARRAYSIZE(szPath)-cch);
  556. }
  557. else
  558. {
  559. iCreateDirError = ERROR_FILENAME_EXCED_RANGE;
  560. }
  561. }
  562. // Create the destination directory
  563. if (iCreateDirError != ERROR_FILENAME_EXCED_RANGE)
  564. {
  565. iCreateDirError = SHCreateDirectory(_hDlg, szPath);
  566. }
  567. switch (iCreateDirError)
  568. {
  569. case ERROR_SUCCESS:
  570. SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, szPath, NULL);
  571. break;
  572. case ERROR_FILENAME_EXCED_RANGE:
  573. idErrorString = IDS_ERROR_FILENAME_EXCED_RANGE;
  574. break;
  575. case ERROR_ALREADY_EXISTS:
  576. // We get this if there is an existing file or directory
  577. // with the same name.
  578. if (!(FILE_ATTRIBUTE_DIRECTORY & GetFileAttributesW(szPath)))
  579. {
  580. // It's a file; show an error.
  581. idErrorString = IDS_ERROR_FILE_EXISTS;
  582. }
  583. else
  584. {
  585. // It's a directory; continue normally.
  586. }
  587. break;
  588. default:
  589. // For other errors, SHCreateDirectory shows a popup
  590. // and returns ERROR_CANCELLED.
  591. break;
  592. }
  593. if (0 != idErrorString)
  594. {
  595. szPath[0] = L'\0';
  596. LoadStringW(g_hInstance, idErrorString, szPath, ARRAYSIZE(szPath));
  597. ShellMessageBoxW(g_hInstance, _hDlg,
  598. MAKEINTRESOURCE(IDS_CANNOTCREATEFOLDER),
  599. MAKEINTRESOURCE(IDS_TIMEWARP_TITLE),
  600. MB_ICONWARNING | MB_OK,
  601. pszDirName, szPath);
  602. iCreateDirError = ERROR_CANCELLED; // prevent copy below
  603. }
  604. }
  605. if (ERROR_SUCCESS == iCreateDirError || ERROR_ALREADY_EXISTS == iCreateDirError)
  606. {
  607. // OK, save now
  608. if (!_CopySnapShot(pszSnapShotPath, szPath, FOF_NOCONFIRMMKDIR))
  609. {
  610. // SHFileOperation shows an error message if necessary
  611. if (!_IsFile() && ERROR_SUCCESS == iCreateDirError)
  612. {
  613. // We created a folder above, so try to clean up now.
  614. // This is best effort only. Ignore failure.
  615. if (RemoveDirectory(szPath))
  616. {
  617. SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, szPath, NULL);
  618. }
  619. }
  620. }
  621. }
  622. }
  623. }
  624. }
  625. void CTimeWarpProp::_OnRevert()
  626. {
  627. LPCWSTR pszSnapShotPath = _GetSelectedItemPath();
  628. if (NULL != pszSnapShotPath)
  629. {
  630. // Confirm first
  631. if (IDYES == ShellMessageBoxW(g_hInstance, _hDlg,
  632. MAKEINTRESOURCE(_IsFolder() ? IDS_CONFIRM_REVERT_FOLDER : IDS_CONFIRM_REVERT_FILE),
  633. MAKEINTRESOURCE(IDS_TIMEWARP_TITLE),
  634. MB_ICONQUESTION | MB_YESNO))
  635. {
  636. LPCWSTR pszDest = _pszPath;
  637. LPWSTR pszAlloc = NULL;
  638. // There is a debate about whether to delete current files before
  639. // copying the old files over. This mainly affects files that
  640. // were created after the snapshot that we are restoring.
  641. if (!_IsFile())
  642. {
  643. #if 0
  644. SHFILEOPSTRUCTW fo;
  645. // First try to delete current folder contents, since files
  646. // may have been created after the snapshot was taken.
  647. ASSERT(NULL != _pszPath);
  648. fo.hwnd = _hDlg;
  649. fo.wFunc = FO_DELETE;
  650. fo.pFrom = _MakeDoubleNullString(_pszPath, TRUE);
  651. fo.pTo = NULL;
  652. fo.fFlags = FOF_NOCONFIRMATION;
  653. if (NULL != fo.pFrom)
  654. {
  655. SHFileOperationW(&fo);
  656. LocalFree((LPWSTR)fo.pFrom);
  657. }
  658. #endif
  659. }
  660. else
  661. {
  662. // Remove the filename from the destination, otherwise
  663. // SHFileOperation tries to create a directory with that name.
  664. if (SUCCEEDED(SHStrDup(pszDest, &pszAlloc)))
  665. {
  666. LPWSTR pszFile = PathFindFileNameW(pszAlloc);
  667. if (pszFile)
  668. {
  669. *pszFile = L'\0';
  670. pszDest = pszAlloc;
  671. }
  672. }
  673. }
  674. // NTRAID#NTBUG9-497729-2001/11/27-jeffreys
  675. // Don't want 2 reverts happening at the same time
  676. EnableWindow(_hDlg, FALSE);
  677. // OK, copy the old version over
  678. if (_CopySnapShot(pszSnapShotPath, pszDest, FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR))
  679. {
  680. // QUERY_SNAPSHOT_DIFFERENT may return different
  681. // results now, so update the list.
  682. if (_IsFile())
  683. {
  684. _OnRefresh();
  685. }
  686. // Let the user know we succeeded
  687. ShellMessageBoxW(g_hInstance, _hDlg,
  688. MAKEINTRESOURCE(_IsFolder() ? IDS_SUCCESS_REVERT_FOLDER : IDS_SUCCESS_REVERT_FILE),
  689. MAKEINTRESOURCE(IDS_TIMEWARP_TITLE),
  690. MB_ICONINFORMATION | MB_OK);
  691. }
  692. else
  693. {
  694. // SHFileOperation shows an error message if necessary
  695. }
  696. EnableWindow(_hDlg, TRUE);
  697. LocalFree(pszAlloc);
  698. }
  699. }
  700. }
  701. LPCWSTR CTimeWarpProp::_GetSelectedItemPath()
  702. {
  703. if (NULL != _hList)
  704. {
  705. int iItem = ListView_GetNextItem(_hList, -1, LVNI_SELECTED);
  706. if (-1 != iItem)
  707. {
  708. LVITEM lvItem;
  709. lvItem.mask = LVIF_PARAM;
  710. lvItem.iItem = iItem;
  711. lvItem.iSubItem = 0;
  712. if (ListView_GetItem(_hList, &lvItem))
  713. {
  714. return (LPCWSTR)lvItem.lParam;
  715. }
  716. }
  717. }
  718. return NULL;
  719. }
  720. LPWSTR CTimeWarpProp::_MakeDoubleNullString(LPCWSTR psz, BOOL bAddWildcard)
  721. {
  722. //
  723. // SHFileOperation eventually passes the source path to FindFirstFile.
  724. // If this path looks like "\\server\share\@GMT", this fails with
  725. // ERROR_PATH_NOT_FOUND. We have to add a wildcard to the source
  726. // path to make SHFileOperation work.
  727. //
  728. int cch = lstrlenW(psz);
  729. int cchAlloc = cch + 2; // double-NULL
  730. if (bAddWildcard)
  731. cchAlloc += 2; // "\\*"
  732. LPWSTR pszResult = (LPWSTR)LocalAlloc(LPTR, cchAlloc*sizeof(WCHAR));
  733. if (NULL != pszResult)
  734. {
  735. // Note that the buffer is zero-initialized, so it automatically
  736. // has a double-NULL at the end.
  737. CopyMemory(pszResult, psz, cch*sizeof(WCHAR));
  738. if (bAddWildcard)
  739. {
  740. if (cch > 0 && pszResult[cch-1] != L'\\')
  741. {
  742. pszResult[cch] = L'\\';
  743. ++cch;
  744. }
  745. pszResult[cch] = L'*';
  746. }
  747. }
  748. return pszResult;
  749. }
  750. BOOL CTimeWarpProp::_CopySnapShot(LPCWSTR pszSource, LPCWSTR pszDest, FILEOP_FLAGS foFlags)
  751. {
  752. BOOL bResult = FALSE;
  753. SHFILEOPSTRUCTW fo;
  754. ASSERT(NULL != pszSource && L'\0' != *pszSource);
  755. ASSERT(NULL != pszDest && L'\0' != *pszDest);
  756. fo.hwnd = _hDlg;
  757. fo.wFunc = FO_COPY;
  758. fo.pFrom = _MakeDoubleNullString(pszSource, !_IsFile());
  759. fo.pTo = _MakeDoubleNullString(pszDest, FALSE);
  760. fo.fFlags = foFlags;
  761. fo.fAnyOperationsAborted = FALSE;
  762. if (NULL != fo.pFrom && NULL != fo.pTo)
  763. {
  764. TraceMsg(TF_TWPROP, "Copying from '%s'", fo.pFrom);
  765. TraceMsg(TF_TWPROP, "Copying to '%s'", fo.pTo);
  766. // NTRAID#NTBUG9-497725-2001/11/27-jeffreys
  767. // Cancelling usually results in a return value of ERROR_CANCELLED,
  768. // but if you cancel during the "Preparing to Copy" phase, SHFileOp
  769. // returns ERROR_SUCCESS. Need to check fAnyOperationsAborted to
  770. // catch that case.
  771. bResult = !SHFileOperationW(&fo) && !fo.fAnyOperationsAborted;
  772. }
  773. LocalFree((LPWSTR)fo.pFrom);
  774. LocalFree((LPWSTR)fo.pTo);
  775. return bResult;
  776. }
  777. /**
  778. * Determines if the pidl still exists. If it does not, if frees it
  779. * and replaces it with a My Documents pidl
  780. */
  781. void _BFFSwitchToMyDocsIfPidlNotExist(PIDLIST_ABSOLUTE *ppidl)
  782. {
  783. IShellFolder *psf;
  784. PCUITEMID_CHILD pidlChild;
  785. if (SUCCEEDED(SHBindToIDListParent(*ppidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
  786. {
  787. DWORD dwAttr = SFGAO_VALIDATE;
  788. if (FAILED(psf->GetAttributesOf(1, &pidlChild, &dwAttr)))
  789. {
  790. // This means the pidl no longer exists.
  791. // Use my documents instead.
  792. PIDLIST_ABSOLUTE pidlMyDocs;
  793. if (SUCCEEDED(SHGetFolderLocation(NULL, CSIDL_PERSONAL, NULL, 0, &pidlMyDocs)))
  794. {
  795. // Good. Now we can get rid of the old pidl and use this one.
  796. SHILFree(*ppidl);
  797. *ppidl = pidlMyDocs;
  798. }
  799. }
  800. psf->Release();
  801. }
  802. }
  803. HRESULT CTimeWarpProp::_InvokeBFFDialog(LPWSTR pszDest, UINT cchDest)
  804. {
  805. HRESULT hr;
  806. BROWSEINFOW bi;
  807. LPWSTR pszTitle = NULL;
  808. HKEY hkey = NULL;
  809. IStream *pstrm = NULL;
  810. PIDLIST_ABSOLUTE pidlSelectedFolder = NULL;
  811. PIDLIST_ABSOLUTE pidlTarget = NULL;
  812. // "Select the place where you want to copy '%1'. Then click the Copy button."
  813. if (!FormatString(&pszTitle, g_hInstance, MAKEINTRESOURCE(IDS_BROWSE_INTRO_COPY), _pszDisplayName))
  814. {
  815. // "Select the place where you want to copy the selected item(s). Then click the Copy button."
  816. LoadStringAlloc(&pszTitle, g_hInstance, IDS_BROWSE_INTRO_COPY2);
  817. }
  818. if (RegOpenKeyEx(HKEY_CURRENT_USER, c_szCopyMoveTo_RegKey, 0, KEY_READ | KEY_WRITE, &hkey) == ERROR_SUCCESS)
  819. {
  820. pstrm = OpenRegStream(hkey, c_szCopyMoveTo_SubKey, c_szCopyMoveTo_Value, STGM_READWRITE);
  821. if (pstrm) // OpenRegStream will fail if the reg key is empty.
  822. ILLoadFromStream(pstrm, (PIDLIST_RELATIVE*)&pidlSelectedFolder);
  823. // This will switch the pidl to My Docs if the pidl does not exist.
  824. // This prevents us from having My Computer as the default (that's what happens if our
  825. // initial set selected call fails).
  826. // Note: ideally, we would check in BFFM_INITIALIZED, if our BFFM_SETSELECTION failed
  827. // then do a BFFM_SETSELECTION on My Documents instead. However, BFFM_SETSELECTION always
  828. // returns zero (it's doc'd to do this to, so we can't change). So we do the validation
  829. // here instead. There is still a small chance that this folder will be deleted in between our
  830. // check here, and when we call BFFM_SETSELECTION, but oh well.
  831. _BFFSwitchToMyDocsIfPidlNotExist(&pidlSelectedFolder);
  832. }
  833. bi.hwndOwner = _hDlg;
  834. bi.pidlRoot = NULL;
  835. bi.pszDisplayName = NULL;
  836. bi.lpszTitle = pszTitle;
  837. bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_VALIDATE | BIF_UAHINT /* | BIF_NOTRANSLATETARGETS*/;
  838. bi.lpfn = BrowseCallback;
  839. bi.lParam = (LPARAM)pidlSelectedFolder;
  840. bi.iImage = 0;
  841. pidlTarget = (PIDLIST_ABSOLUTE)SHBrowseForFolder(&bi);
  842. if (pidlTarget)
  843. {
  844. hr = SHGetNameAndFlagsW(pidlTarget, SHGDN_FORPARSING, pszDest, cchDest, NULL);
  845. }
  846. else
  847. {
  848. // Either user cancelled, or failure. Doesn't matter.
  849. hr = S_FALSE;
  850. }
  851. if (pstrm)
  852. {
  853. if (S_OK == hr && !PathIsNetworkPathW(pszDest))
  854. {
  855. LARGE_INTEGER li0 = {0};
  856. ULARGE_INTEGER uli;
  857. // rewind the stream to the beginning so that when we
  858. // add a new pidl it does not get appended to the first one
  859. pstrm->Seek(li0, STREAM_SEEK_SET, &uli);
  860. ILSaveToStream(pstrm, pidlTarget);
  861. }
  862. pstrm->Release();
  863. }
  864. if (hkey)
  865. {
  866. RegCloseKey(hkey);
  867. }
  868. SHILFree(pidlTarget);
  869. SHILFree(pidlSelectedFolder);
  870. LocalFree(pszTitle);
  871. return hr;
  872. }
  873. UINT CALLBACK CTimeWarpProp::PSPCallback(HWND /*hDlg*/, UINT uMsg, LPPROPSHEETPAGE ppsp)
  874. {
  875. switch (uMsg)
  876. {
  877. case PSPCB_RELEASE:
  878. ((CTimeWarpProp*)ppsp->lParam)->Release();
  879. break;
  880. }
  881. return 1;
  882. }
  883. INT_PTR CALLBACK CTimeWarpProp::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  884. {
  885. CTimeWarpProp *ptwp = (CTimeWarpProp*)GetWindowLongPtr(hDlg, DWLP_USER);
  886. if (uMsg == WM_INITDIALOG)
  887. {
  888. PROPSHEETPAGE *pPropSheetPage = (PROPSHEETPAGE*)lParam;
  889. if (pPropSheetPage)
  890. {
  891. ptwp = (CTimeWarpProp*) pPropSheetPage->lParam;
  892. if (ptwp)
  893. {
  894. SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)ptwp);
  895. ptwp->_OnInit(hDlg);
  896. return 1;
  897. }
  898. }
  899. }
  900. else if (ptwp)
  901. {
  902. switch (uMsg)
  903. {
  904. case WM_DESTROY:
  905. SetWindowLongPtr(hDlg, DWLP_USER, 0);
  906. return 1;
  907. case WM_COMMAND:
  908. switch (GET_WM_COMMAND_ID(wParam, lParam))
  909. {
  910. case IDC_REVERT:
  911. ptwp->_OnRevert();
  912. return 1;
  913. case IDC_VIEW:
  914. ptwp->_OnView();
  915. return 1;
  916. case IDC_COPY:
  917. ptwp->_OnCopy();
  918. return 1;
  919. }
  920. break;
  921. case WM_NOTIFY:
  922. {
  923. NMHDR *pnmh = (NMHDR*)lParam;
  924. switch (pnmh->code)
  925. {
  926. case NM_DBLCLK:
  927. if (IDC_LIST == pnmh->idFrom)
  928. {
  929. ptwp->_OnView();
  930. }
  931. break;
  932. case LVN_ITEMCHANGED:
  933. if (IDC_LIST == pnmh->idFrom)
  934. {
  935. NMLISTVIEW *pnmlv = (NMLISTVIEW*)lParam;
  936. if (pnmlv->uChanged & LVIF_STATE)
  937. {
  938. ptwp->_UpdateButtons();
  939. }
  940. }
  941. break;
  942. case PSN_TRANSLATEACCELERATOR:
  943. {
  944. MSG *pMsg = (MSG*)(((PSHNOTIFY*)lParam)->lParam);
  945. if (WM_KEYUP == pMsg->message && VK_F5 == pMsg->wParam)
  946. {
  947. ptwp->_OnRefresh();
  948. }
  949. }
  950. break;
  951. case PSN_HELP:
  952. {
  953. SHELLEXECUTEINFOW sei;
  954. sei.cbSize = sizeof(sei);
  955. sei.fMask = SEE_MASK_DOENVSUBST;
  956. sei.hwnd = hDlg;
  957. sei.lpVerb = NULL;
  958. sei.lpFile = c_szChmPath;
  959. sei.lpParameters = NULL;
  960. sei.lpDirectory = NULL;
  961. sei.nShow = SW_SHOWNORMAL;
  962. ShellExecuteExW(&sei);
  963. }
  964. break;
  965. }
  966. }
  967. break;
  968. case WM_SIZE:
  969. ptwp->_OnSize();
  970. break;
  971. case WM_HELP: /* F1 or title-bar help button */
  972. WinHelpW((HWND)((LPHELPINFO) lParam)->hItemHandle, c_szHelpFile, HELP_WM_HELP, (DWORD_PTR)rgdwTimeWarpPropHelp);
  973. break;
  974. case WM_CONTEXTMENU: /* right mouse click */
  975. WinHelpW((HWND)wParam, c_szHelpFile, HELP_CONTEXTMENU, (DWORD_PTR)rgdwTimeWarpPropHelp);
  976. break;
  977. }
  978. }
  979. return 0;
  980. }
  981. int CALLBACK BrowseCallback(HWND hDlg, UINT uMsg, LPARAM lParam, LPARAM pData)
  982. {
  983. if (BFFM_INITIALIZED == uMsg)
  984. {
  985. // Set the caption ("Copy Items")
  986. TCHAR szTemp[100];
  987. if (LoadString(g_hInstance, IDS_BROWSE_TITLE_COPY, szTemp, ARRAYSIZE(szTemp)))
  988. {
  989. SetWindowText(hDlg, szTemp);
  990. }
  991. // Set the text of the Ok Button ("Copy")
  992. if (LoadString(g_hInstance, IDS_COPY, szTemp, ARRAYSIZE(szTemp))) // 0x1031 in shell32
  993. {
  994. SendMessage(hDlg, BFFM_SETOKTEXT, 0, (LPARAM)szTemp);
  995. }
  996. // Set My Computer expanded
  997. PIDLIST_ABSOLUTE pidlMyComputer;
  998. HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidlMyComputer);
  999. if (SUCCEEDED(hr))
  1000. {
  1001. SendMessage(hDlg, BFFM_SETEXPANDED, FALSE, (LPARAM)pidlMyComputer);
  1002. SHILFree(pidlMyComputer);
  1003. }
  1004. // Set the default selected pidl
  1005. SendMessage(hDlg, BFFM_SETSELECTION, FALSE, pData);
  1006. }
  1007. return 0;
  1008. }
  1009. //
  1010. // Because the Name is the same for each entry in the listview, we have to
  1011. // expose more info in accName to make this usable in accessibility scenarios,
  1012. // e.g. to a screen reader. We override get_accName and concatenate accDescription
  1013. // onto the name.
  1014. //
  1015. STDMETHODIMP CNameDescriptionAccessibleWrapper::get_accName(VARIANT varChild, BSTR* pstrName)
  1016. {
  1017. // Call the base class first in all cases.
  1018. HRESULT hr = CAccessibleWrapper::get_accName(varChild, pstrName);
  1019. // varChild.lVal specifies which sub-part of the component is being queried.
  1020. // CHILDID_SELF (0) specifies the overall component - other values specify a child.
  1021. if (SUCCEEDED(hr) && varChild.vt == VT_I4 && varChild.lVal != CHILDID_SELF)
  1022. {
  1023. BSTR strDescription = NULL;
  1024. // Get accDescription and concatenate onto accName
  1025. //
  1026. // If anything fails, we return the result from above
  1027. if (SUCCEEDED(CAccessibleWrapper::get_accDescription(varChild, &strDescription)))
  1028. {
  1029. LPWSTR pszNewName = NULL;
  1030. if (FormatString(&pszNewName, g_hInstance, MAKEINTRESOURCE(IDS_ACCNAME_FORMAT), *pstrName, strDescription))
  1031. {
  1032. BSTR strNewName = SysAllocString(pszNewName);
  1033. if (strNewName)
  1034. {
  1035. SysFreeString(*pstrName);
  1036. *pstrName = strNewName;
  1037. }
  1038. LocalFree(pszNewName);
  1039. }
  1040. SysFreeString(strDescription);
  1041. }
  1042. }
  1043. return hr;
  1044. }
  1045. extern "C"
  1046. LPCWSTR FindSnapshotPathSplit(LPCWSTR lpszPath); // timewarp.c
  1047. typedef struct
  1048. {
  1049. BOOL bHasShadowCopy;
  1050. DWORD dwCacheTime;
  1051. ULONG cchPath;
  1052. WCHAR szPath[1];
  1053. } SNAPCHECK_CACHE_ENTRY;
  1054. // 5 minutes
  1055. #define _CACHE_AGE_LIMIT (5*60*1000)
  1056. CRITICAL_SECTION g_csSnapCheckCache;
  1057. HDPA g_dpaSnapCheckCache = NULL;
  1058. int CALLBACK _LocalFreeCallback(void *p, void*)
  1059. {
  1060. // OK to pass NULL to LocalFree
  1061. LocalFree(p);
  1062. return 1;
  1063. }
  1064. void InitSnapCheckCache(void)
  1065. {
  1066. InitializeCriticalSection(&g_csSnapCheckCache);
  1067. }
  1068. void DestroySnapCheckCache(void)
  1069. {
  1070. if (NULL != g_dpaSnapCheckCache)
  1071. {
  1072. DPA_DestroyCallback(g_dpaSnapCheckCache, _LocalFreeCallback, 0);
  1073. }
  1074. DeleteCriticalSection(&g_csSnapCheckCache);
  1075. }
  1076. static int CALLBACK _CompareServerEntries(void *p1, void *p2, LPARAM lParam)
  1077. {
  1078. int nResult;
  1079. SNAPCHECK_CACHE_ENTRY *pEntry1 = (SNAPCHECK_CACHE_ENTRY*)p1;
  1080. SNAPCHECK_CACHE_ENTRY *pEntry2 = (SNAPCHECK_CACHE_ENTRY*)p2;
  1081. BOOL *pbExact = (BOOL*)lParam;
  1082. ASSERT(NULL != pEntry1);
  1083. ASSERT(NULL != pEntry2);
  1084. ASSERT(NULL != pbExact);
  1085. nResult = CompareString(LOCALE_SYSTEM_DEFAULT, SORT_STRINGSORT | NORM_IGNORECASE | NORM_STOP_ON_NULL,
  1086. pEntry1->szPath, pEntry1->cchPath,
  1087. pEntry2->szPath, pEntry2->cchPath) - CSTR_EQUAL;
  1088. if (0 == nResult)
  1089. {
  1090. *pbExact = TRUE;
  1091. }
  1092. return nResult;
  1093. }
  1094. static void SnapCheck_CacheResult(LPCWSTR pszPath, LPCWSTR pszShadowPath, BOOL bHasShadowCopy)
  1095. {
  1096. LPWSTR pszServer = NULL;
  1097. if (bHasShadowCopy)
  1098. {
  1099. // Use the shadow path instead
  1100. ASSERT(NULL != pszShadowPath);
  1101. pszPath = pszShadowPath;
  1102. }
  1103. if (SUCCEEDED(SHStrDup(pszPath, &pszServer)))
  1104. {
  1105. // FindSnapshotPathSplit hits the net, so try to avoid it.
  1106. LPWSTR pszTail = bHasShadowCopy ? wcsstr(pszServer, SNAPSHOT_MARKER) : (LPWSTR)FindSnapshotPathSplit(pszServer);
  1107. if (pszTail)
  1108. {
  1109. *pszTail = L'\0';
  1110. }
  1111. EliminatePathPrefix(pszServer);
  1112. PathRemoveBackslashW(pszServer);
  1113. int cchServer = lstrlen(pszServer);
  1114. SNAPCHECK_CACHE_ENTRY *pEntry = (SNAPCHECK_CACHE_ENTRY*)LocalAlloc(LPTR, sizeof(SNAPCHECK_CACHE_ENTRY) + sizeof(WCHAR)*cchServer);
  1115. if (pEntry)
  1116. {
  1117. pEntry->bHasShadowCopy = bHasShadowCopy;
  1118. pEntry->cchPath = cchServer;
  1119. lstrcpynW(pEntry->szPath, pszServer, cchServer+1);
  1120. EnterCriticalSection(&g_csSnapCheckCache);
  1121. if (NULL == g_dpaSnapCheckCache)
  1122. {
  1123. // This ref is not balanced. This causes us to remain loaded
  1124. // until the process terminates, so the cache isn't deleted
  1125. // prematurely (i.e. if AlwaysUnloadDlls is set).
  1126. DllAddRef();
  1127. g_dpaSnapCheckCache = DPA_Create(4);
  1128. }
  1129. if (NULL != g_dpaSnapCheckCache)
  1130. {
  1131. pEntry->dwCacheTime = GetTickCount();
  1132. BOOL bExact = FALSE;
  1133. int iIndex = DPA_Search(g_dpaSnapCheckCache, pEntry, 0, _CompareServerEntries, (LPARAM)&bExact, DPAS_SORTED | DPAS_INSERTBEFORE);
  1134. if (bExact)
  1135. {
  1136. // Found a duplicate. Replace it.
  1137. SNAPCHECK_CACHE_ENTRY *pOldEntry = (SNAPCHECK_CACHE_ENTRY*)DPA_FastGetPtr(g_dpaSnapCheckCache, iIndex);
  1138. DPA_SetPtr(g_dpaSnapCheckCache, iIndex, pEntry);
  1139. LocalFree(pOldEntry);
  1140. }
  1141. else if (-1 == DPA_InsertPtr(g_dpaSnapCheckCache, iIndex, pEntry))
  1142. {
  1143. LocalFree(pEntry);
  1144. }
  1145. }
  1146. else
  1147. {
  1148. LocalFree(pEntry);
  1149. }
  1150. LeaveCriticalSection(&g_csSnapCheckCache);
  1151. }
  1152. LocalFree(pszServer);
  1153. }
  1154. }
  1155. static int CALLBACK _SearchServerEntries(void *p1, void *p2, LPARAM lParam)
  1156. {
  1157. int nResult = 0;
  1158. LPCWSTR pszFind = (LPCWSTR)p1;
  1159. ULONG cchFind = (ULONG)lParam;
  1160. SNAPCHECK_CACHE_ENTRY *pEntry = (SNAPCHECK_CACHE_ENTRY*)p2;
  1161. ASSERT(NULL != pszFind);
  1162. ASSERT(NULL != pEntry);
  1163. // Compare the first pEntry->cchPath chars of both strings
  1164. nResult = CompareString(LOCALE_SYSTEM_DEFAULT, SORT_STRINGSORT | NORM_IGNORECASE | NORM_STOP_ON_NULL,
  1165. pszFind, pEntry->cchPath,
  1166. pEntry->szPath, pEntry->cchPath) - CSTR_EQUAL;
  1167. if (0 == nResult)
  1168. {
  1169. //
  1170. // Check whether pszFind is longer than pEntry->szPath, but allow
  1171. // extra path segments in pszFind.
  1172. //
  1173. // For example, if
  1174. // pEntry->szPath = "\\server\share"
  1175. // pszFind = "\\server\share2"
  1176. // then we don't have a match. But if
  1177. // pEntry->szPath = "\\server\share"
  1178. // pszFind = "\\server\share\dir"
  1179. // the we DO have a match.
  1180. //
  1181. // Also, at the root of a mapped drive, pEntry->szPath includes
  1182. // a trailing backslash, so we may have this:
  1183. // pEntry->szPath = "X:\"
  1184. // pszFind = "X:\dir"
  1185. // which we consider to be a match.
  1186. //
  1187. if (cchFind > pEntry->cchPath && pszFind[pEntry->cchPath] != L'\\'
  1188. && (PathIsUNCW(pEntry->szPath) || !PathIsRootW(pEntry->szPath)))
  1189. {
  1190. ASSERT(pszFind[pEntry->cchPath] != L'\0'); // otherwise, cchFind == pEntry->cchPath and we don't get here
  1191. nResult = 1;
  1192. }
  1193. }
  1194. return nResult;
  1195. }
  1196. static BOOL SnapCheck_LookupResult(LPCWSTR pszPath, BOOL *pbHasShadowCopy)
  1197. {
  1198. BOOL bFound = FALSE;
  1199. *pbHasShadowCopy = FALSE;
  1200. if (NULL == g_dpaSnapCheckCache)
  1201. return FALSE;
  1202. EnterCriticalSection(&g_csSnapCheckCache);
  1203. int iIndex = DPA_Search(g_dpaSnapCheckCache, (void*)pszPath, 0, _SearchServerEntries, lstrlenW(pszPath), DPAS_SORTED);
  1204. if (-1 != iIndex)
  1205. {
  1206. // Found a match
  1207. SNAPCHECK_CACHE_ENTRY *pEntry = (SNAPCHECK_CACHE_ENTRY*)DPA_FastGetPtr(g_dpaSnapCheckCache, iIndex);
  1208. DWORD dwCurrentTime = GetTickCount();
  1209. if (dwCurrentTime > pEntry->dwCacheTime && dwCurrentTime - pEntry->dwCacheTime < _CACHE_AGE_LIMIT)
  1210. {
  1211. *pbHasShadowCopy = pEntry->bHasShadowCopy;
  1212. bFound = TRUE;
  1213. }
  1214. else
  1215. {
  1216. // The entry has aged out
  1217. DPA_DeletePtr(g_dpaSnapCheckCache, iIndex);
  1218. LocalFree(pEntry);
  1219. }
  1220. }
  1221. LeaveCriticalSection(&g_csSnapCheckCache);
  1222. return bFound;
  1223. }