#include "precomp.hxx" #pragma hdrstop #include // DataObj_GetHIDA, IDA_ILClone, HIDA_ReleaseStgMedium #include // NORM_STOP_ON_NULL #include "timewarp.h" #include "twprop.h" #include "util.h" #include "resource.h" #include "helpids.h" #include "access.h" // {596AB062-B4D2-4215-9F74-E9109B0A8153} CLSID_TimeWarpProp const CLSID CLSID_TimeWarpProp = {0x596AB062, 0xB4D2, 0x4215, {0x9F, 0x74, 0xE9, 0x10, 0x9B, 0x0A, 0x81, 0x53}}; WCHAR const c_szHelpFile[] = L"twclient.hlp"; WCHAR const c_szChmPath[] = L"%SystemRoot%\\Help\\twclient.chm"; WCHAR const c_szTimeWarpFolderID[] = L"::{208D2C60-3AEA-1069-A2D7-08002B30309D}\\::{9DB7A13C-F208-4981-8353-73CC61AE2783},"; WCHAR const c_szCopyMoveTo_RegKey[] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer"; WCHAR const c_szCopyMoveTo_SubKey[] = L"CopyMoveTo"; WCHAR const c_szCopyMoveTo_Value[] = L"LastFolder"; // help IDs const static DWORD rgdwTimeWarpPropHelp[] = { IDC_TWICON, -1, IDC_TOPTEXT, -1, IDC_LIST, IDH_TIMEWARP_SNAPSHOTLIST, IDC_VIEW, IDH_TIMEWARP_OPENSNAP, IDC_COPY, IDH_TIMEWARP_SAVESNAP, IDC_REVERT, IDH_TIMEWARP_RESTORESNAP, 0, 0 }; static int CALLBACK BrowseCallback(HWND hDlg, UINT uMsg, LPARAM lParam, LPARAM pData); // Simple accessibility wrapper class which concatenates accDescription onto accName class CNameDescriptionAccessibleWrapper : public CAccessibleWrapper { public: CNameDescriptionAccessibleWrapper(IAccessible *pAcc, LPARAM) : CAccessibleWrapper(pAcc) {} STDMETHODIMP get_accName(VARIANT varChild, BSTR* pstrName); }; static void SnapCheck_CacheResult(LPCWSTR pszPath, LPCWSTR pszShadowPath, BOOL bHasShadowCopy); static BOOL SnapCheck_LookupResult(LPCWSTR pszPath, BOOL *pbHasShadowCopy); HRESULT CTimeWarpProp::CreateInstance(IUnknown* /*punkOuter*/, IUnknown **ppunk, LPCOBJECTINFO /*poi*/) { CTimeWarpProp* pmp = new CTimeWarpProp(); if (pmp) { *ppunk = SAFECAST(pmp, IShellExtInit*); return S_OK; } *ppunk = NULL; return E_OUTOFMEMORY; } CTimeWarpProp::CTimeWarpProp() : _cRef(1), _hDlg(NULL), _hList(NULL), _pszPath(NULL), _pszDisplayName(NULL), _pszSnapList(NULL), _fItemAttributes(0) { DllAddRef(); } CTimeWarpProp::~CTimeWarpProp() { LocalFree(_pszPath); // NULL is OK LocalFree(_pszDisplayName); LocalFree(_pszSnapList); DllRelease(); } STDMETHODIMP CTimeWarpProp::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CTimeWarpProp, IShellExtInit), QITABENT(CTimeWarpProp, IShellPropSheetExt), QITABENT(CTimeWarpProp, IPreviousVersionsInfo), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_ (ULONG) CTimeWarpProp::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_ (ULONG) CTimeWarpProp::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } STDMETHODIMP CTimeWarpProp::Initialize(PCIDLIST_ABSOLUTE /*pidlFolder*/, IDataObject *pdobj, HKEY /*hkey*/) { HRESULT hr = E_FAIL; STGMEDIUM medium; LPIDA pida = DataObj_GetHIDA(pdobj, &medium); if (pida) { // Bail on multiple selection if (pida->cidl == 1) { // Bind to the parent folder IShellFolder *psf; hr = SHBindToObjectEx(NULL, IDA_GetPIDLFolder(pida), NULL, IID_IShellFolder, (void**)&psf); if (SUCCEEDED(hr)) { PCUITEMID_CHILD pidlChild = IDA_GetPIDLItem(pida, 0); // Keep track of file vs folder _fItemAttributes = SFGAO_FOLDER | SFGAO_STREAM | SFGAO_LINK; hr = psf->GetAttributesOf(1, &pidlChild, &_fItemAttributes); if (SUCCEEDED(hr)) { WCHAR szTemp[MAX_PATH]; // For folder shortcuts, we use the target. if (_IsFolder() && _IsShortcut()) { IShellLink *psl; hr = psf->BindToObject(pidlChild, NULL, IID_PPV_ARG(IShellLink, &psl)); if (SUCCEEDED(hr)) { WIN32_FIND_DATA fd; hr = psl->GetPath(szTemp, ARRAYSIZE(szTemp), &fd, SLGP_UNCPRIORITY); psl->Release(); } } else { // Get the full path hr = DisplayNameOf(psf, pidlChild, SHGDN_FORPARSING, szTemp, ARRAYSIZE(szTemp)); } if (SUCCEEDED(hr)) { // We only work with network paths. if (PathIsNetworkPathW(szTemp) && !PathIsUNCServer(szTemp)) { FILETIME ft; // If this is already a snapshot path, bail. Otherwise // we get into this weird recursive state where the // snapshot paths have 2 GMT strings in them and the // date is always the same (the first GMT string is // identical for all of them). if (NOERROR == GetSnapshotTimeFromPath(szTemp, &ft)) { hr = E_FAIL; } else { // Remember the path _pszPath = StrDup(szTemp); if (NULL != _pszPath) { // Get the display name (continue on failure here) if (SUCCEEDED(DisplayNameOf(psf, pidlChild, SHGDN_INFOLDER, szTemp, ARRAYSIZE(szTemp)))) { _pszDisplayName = StrDup(szTemp); } // Get the system icon index _iIcon = SHMapPIDLToSystemImageListIndex(psf, pidlChild, NULL); } else { hr = E_OUTOFMEMORY; } } } else { hr = E_FAIL; } } } psf->Release(); } } HIDA_ReleaseStgMedium(pida, &medium); } return hr; } STDMETHODIMP CTimeWarpProp::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) { HRESULT hr = S_OK; if (NULL != _pszPath) { BOOL bSnapsAvailable = FALSE; // Are snapshots available on this server? if (S_OK == AreSnapshotsAvailable(_pszPath, TRUE, &bSnapsAvailable) && bSnapsAvailable) { PROPSHEETPAGE psp; psp.dwSize = sizeof(psp); psp.dwFlags = PSP_DEFAULT | PSP_USECALLBACK | PSP_HASHELP; psp.hInstance = g_hInstance; psp.pszTemplate = MAKEINTRESOURCE(_IsFolder() ? DLG_TIMEWARPPROP_FOLDER : DLG_TIMEWARPPROP_FILE); psp.pfnDlgProc = CTimeWarpProp::DlgProc; psp.pfnCallback = CTimeWarpProp::PSPCallback; psp.lParam = (LPARAM)this; HPROPSHEETPAGE hPage = CreatePropertySheetPage(&psp); if (hPage) { this->AddRef(); if (!pfnAddPage(hPage, lParam)) { DestroyPropertySheetPage(hPage); hr = E_FAIL; } } } } return hr; } STDMETHODIMP CTimeWarpProp::ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM) { return E_NOTIMPL; } STDMETHODIMP CTimeWarpProp::AreSnapshotsAvailable(LPCWSTR pszPath, BOOL fOkToBeSlow, BOOL *pfAvailable) { FILETIME ft; if (NULL == pfAvailable) return E_POINTER; // Default answer is No. *pfAvailable = FALSE; if (NULL == pszPath || L'\0' == *pszPath) return E_INVALIDARG; // It must be a network path, but can't be a snapshot path already. if (PathIsNetworkPathW(pszPath) && !PathIsUNCServerW(pszPath) && NOERROR != GetSnapshotTimeFromPath(pszPath, &ft)) { // Check the cache if (SnapCheck_LookupResult(pszPath, pfAvailable)) { // nothing to do } else if (fOkToBeSlow) { LPWSTR pszSnapList = NULL; DWORD cSnaps; // Hit the net DWORD dwErr = QuerySnapshotsForPath(pszPath, 0, &pszSnapList, &cSnaps); if (NOERROR == dwErr && NULL != pszSnapList) { // Snapshots are available *pfAvailable = TRUE; } // Remember the result SnapCheck_CacheResult(pszPath, pszSnapList, *pfAvailable); LocalFree(pszSnapList); } else { // Tell caller to call again with fOkToBeSlow = TRUE return E_PENDING; } } return S_OK; } void CTimeWarpProp::_OnInit(HWND hDlg) { _hDlg = hDlg; SendDlgItemMessage(hDlg, IDC_TWICON, STM_SETICON, (WPARAM)LoadIcon(g_hInstance,MAKEINTRESOURCE(IDI_TIMEWARP)), 0); // One-time listview initialization _hList = GetDlgItem(hDlg, IDC_LIST); if (NULL != _hList) { HIMAGELIST himlSmall; RECT rc; WCHAR szName[64]; LVCOLUMN lvCol; ListView_SetExtendedListViewStyle(_hList, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); Shell_GetImageLists(NULL, &himlSmall); ListView_SetImageList(_hList, himlSmall, LVSIL_SMALL); GetClientRect(_hList, &rc); lvCol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT; lvCol.fmt = LVCFMT_LEFT; lvCol.pszText = szName; LoadString(g_hInstance, IDS_NAMECOL, szName, ARRAYSIZE(szName)); lvCol.cx = (rc.right / 3); lvCol.iSubItem = 0; ListView_InsertColumn(_hList, 0, &lvCol); LoadString(g_hInstance, IDS_DATECOL, szName, ARRAYSIZE(szName)); lvCol.cx = rc.right - lvCol.cx; lvCol.iSubItem = 1; ListView_InsertColumn(_hList, 1, &lvCol); // Continue on failure here WrapAccessibleControl(_hList); } // Query for snapshots and load the list _OnRefresh(); } void CTimeWarpProp::_OnRefresh() { HCURSOR hcur = SetCursor(LoadCursor(NULL, IDC_WAIT)); if (NULL != _hList) { DWORD cSnaps; // Start by emptying the list ListView_DeleteAllItems(_hList); // Free the old data LocalFree(_pszSnapList); _pszSnapList = NULL; // Hit the net ASSERT(NULL != _pszPath); DWORD dwErr = QuerySnapshotsForPath(_pszPath, _IsFile() ? QUERY_SNAPSHOT_DIFFERENT : QUERY_SNAPSHOT_EXISTING, &_pszSnapList, &cSnaps); // Fill the list if (NOERROR == dwErr && NULL != _pszSnapList) { UINT cItems = 0; LPCWSTR pszSnap; for (pszSnap = _pszSnapList; *pszSnap != L'\0'; pszSnap += lstrlenW(pszSnap)+1) { FILETIME ft; if (NOERROR == GetSnapshotTimeFromPath(pszSnap, &ft)) { LVITEM lvItem; lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; lvItem.iItem = cItems; lvItem.iSubItem = 0; lvItem.pszText = _pszDisplayName ? _pszDisplayName : PathFindFileNameW(_pszPath); lvItem.iImage = _iIcon; lvItem.lParam = (LPARAM)pszSnap; lvItem.iItem = ListView_InsertItem(_hList, &lvItem); if (-1 != lvItem.iItem) { ++cItems; WCHAR szDate[MAX_PATH]; DWORD dwDateFlags = FDTF_RELATIVE | FDTF_LONGDATE | FDTF_SHORTTIME; SHFormatDateTime(&ft, &dwDateFlags, szDate, ARRAYSIZE(szDate)); lvItem.mask = LVIF_TEXT; lvItem.iSubItem = 1; lvItem.pszText = szDate; ListView_SetItem(_hList, &lvItem); } } } if (cItems != 0) { // Select the first item ListView_SetItemState(_hList, 0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); } } } _UpdateButtons(); SetCursor(hcur); } void CTimeWarpProp::_OnSize() { #define _MOVE_X 0x0001 #define _MOVE_Y 0x0002 #define _SIZE_WIDTH 0x0004 #define _SIZE_HEIGHT 0x0008 static const struct { int idCtrl; DWORD dwFlags; } rgControls[] = { { IDC_TOPTEXT, _SIZE_WIDTH }, { IDC_LIST, _SIZE_WIDTH | _SIZE_HEIGHT }, { IDC_VIEW, _MOVE_X | _MOVE_Y }, { IDC_COPY, _MOVE_X | _MOVE_Y }, { IDC_REVERT, _MOVE_X | _MOVE_Y }, }; if (NULL != _hDlg) { RECT rcDlg; RECT rc; // Get the icon position (upper left ctrl) to find the margins GetWindowRect(GetDlgItem(_hDlg, IDC_TWICON), &rc); MapWindowPoints(NULL, _hDlg, (LPPOINT)&rc, 2); // Get the full dlg dimensions and adjust for margins GetClientRect(_hDlg, &rcDlg); rcDlg.right -= rc.left; rcDlg.bottom -= rc.top; // Get the Restore button pos (lower right ctrl) to calculate offsets GetWindowRect(GetDlgItem(_hDlg, IDC_REVERT), &rc); MapWindowPoints(NULL, _hDlg, (LPPOINT)&rc, 2); // This is how much things need to move or grow rcDlg.right -= rc.right; // x-offset rcDlg.bottom -= rc.bottom; // y-offset for (int i = 0; i < ARRAYSIZE(rgControls); i++) { HWND hwndCtrl = GetDlgItem(_hDlg, rgControls[i].idCtrl); GetWindowRect(hwndCtrl, &rc); MapWindowPoints(NULL, _hDlg, (LPPOINT)&rc, 2); rc.right -= rc.left; // "width" rc.bottom -= rc.top; // "height" if (rgControls[i].dwFlags & _MOVE_X) rc.left += rcDlg.right; if (rgControls[i].dwFlags & _MOVE_Y) rc.top += rcDlg.bottom; if (rgControls[i].dwFlags & _SIZE_WIDTH) rc.right += rcDlg.right; if (rgControls[i].dwFlags & _SIZE_HEIGHT) rc.bottom += rcDlg.bottom; MoveWindow(hwndCtrl, rc.left, rc.top, rc.right, rc.bottom, TRUE); } } } void CTimeWarpProp::_UpdateButtons() { // Enable or disable the pushbuttons based on whether something // is selected in the listview BOOL bEnable = (NULL != _GetSelectedItemPath()); for (int i = IDC_VIEW; i <= IDC_REVERT; i++) { HWND hwndCtrl = GetDlgItem(_hDlg, i); // If we're disabling the buttons, check for focus and move // focus to the listview if necessary. if (!bEnable && GetFocus() == hwndCtrl) { SetFocus(_hList); } EnableWindow(hwndCtrl, bEnable); } } void CTimeWarpProp::_OnView() { LPCWSTR pszSnapShotPath = _GetSelectedItemPath(); if (NULL != pszSnapShotPath) { // Test for existence. QuerySnapshotsForPath already tested for // existence, but if the server has since gone down, or deleted // the snapshot, the resulting error message shown by ShellExecute // is quite ugly. if (-1 != GetFileAttributesW(pszSnapShotPath)) { SHELLEXECUTEINFOW sei; LPWSTR pszPathAlloc = NULL; HCURSOR hcur = SetCursor(LoadCursor(NULL, IDC_WAIT)); if (_IsFolder()) { const ULONG cchFolderID = ARRAYSIZE(c_szTimeWarpFolderID) - 1; // ARRAYSIZE counts '\0' ULONG cchFullPath = cchFolderID + lstrlen(pszSnapShotPath) + 1; pszPathAlloc = (LPWSTR)LocalAlloc(LPTR, cchFullPath*sizeof(WCHAR)); if (pszPathAlloc) { // "::{CLSID_NetworkPlaces}\\::{CLSID_TimeWarpFolder},\\server\share\@GMT\dir" lstrcpynW(pszPathAlloc, c_szTimeWarpFolderID, cchFullPath); lstrcpynW(pszPathAlloc + cchFolderID, pszSnapShotPath, cchFullPath - cchFolderID); pszSnapShotPath = pszPathAlloc; } else { // Low memory. Try to launch a normal file system folder // (do nothing here). } } else if (SUCCEEDED(SHStrDup(pszSnapShotPath, &pszPathAlloc))) { pszSnapShotPath = pszPathAlloc; } if (pszPathAlloc) { // Some apps have problems with the "\\?\" prefix, including // the common dialog code. EliminatePathPrefix(pszPathAlloc); } sei.cbSize = sizeof(sei); sei.fMask = 0; sei.hwnd = _hDlg; sei.lpVerb = NULL; sei.lpFile = pszSnapShotPath; sei.lpParameters = NULL; sei.lpDirectory = NULL; sei.nShow = SW_SHOWNORMAL; ShellExecuteExW(&sei); LocalFree(pszPathAlloc); SetCursor(hcur); } else { // Show this error ourselves. The ShellExecuteEx version is rather ugly. TraceMsg(TF_TWPROP, "Snapshot unavailable (%d)", GetLastError()); ShellMessageBoxW(g_hInstance, _hDlg, MAKEINTRESOURCE(_IsFolder() ? IDS_CANTFINDSNAPSHOT_FOLDER : IDS_CANTFINDSNAPSHOT_FILE), MAKEINTRESOURCE(IDS_TIMEWARP_TITLE), MB_ICONWARNING | MB_OK, _pszDisplayName); } } } void CTimeWarpProp::_OnCopy() { LPCWSTR pszSnapShotPath = _GetSelectedItemPath(); if (NULL != pszSnapShotPath) { WCHAR szPath[2*MAX_PATH]; // SHBrowseForFolder if (S_OK == _InvokeBFFDialog(szPath, ARRAYSIZE(szPath))) { int iCreateDirError = ERROR_ALREADY_EXISTS; // // If we're dealing with a folder, we have to be careful because // the GMT segment might be the last part of the source path. // If so, when SHFileOperation eventually passes this path to // FindFirstFile, it fails because no subfolder with that name // exists. To get around this, we append a wildcard '*' to the // source path (see _CopySnapShot and _MakeDoubleNullString). // // But that means we also have to add _pszDisplayName to the // destination path and create that directory first, in order // to get the expected behavior from SHFileOperation. // // Note that if the directory contains files, we don't really need // to create the directory first, since SHFileOperation hits the // CopyMoveRetry code path in DoFile_Copy, which creates the parent // dir. But if there are only subdirs and no files, it goes through // EnterDir_Copy first, which fails without calling CopyMoveRetry. // (EnterDir_Move does the CopyMoveRetry thing, so this seems like // a bug in EnterDir_Copy, but normal shell operations never hit it.) // if (!_IsFile()) { UINT idErrorString = 0; WCHAR szDriveLetter[2]; LPCWSTR pszDirName = NULL; // Append the directory name. Need to special case the root. if (PathIsRootW(_pszPath)) { if (PathIsUNCW(_pszPath)) { ASSERT(PathIsUNCServerShareW(_pszPath)); pszDirName = wcschr(_pszPath+2, L'\\'); if (pszDirName) { ++pszDirName; } // else continue without a subdir // (don't fall back on _pszDisplayName here) } else { szDriveLetter[0] = _pszPath[0]; szDriveLetter[1] = L'\0'; pszDirName = szDriveLetter; } } else { // Normal case pszDirName = PathFindFileNameW(_pszPath); if (!pszDirName) pszDirName = _pszDisplayName; } if (pszDirName) { // We could reduce szPath to MAX_PATH and use PathAppend here. UINT cch = lstrlenW(szPath); if (cch > 0 && szPath[cch-1] != L'\\') { if (cch+1 < ARRAYSIZE(szPath)) { szPath[cch] = L'\\'; ++cch; } else { iCreateDirError = ERROR_FILENAME_EXCED_RANGE; } } if (iCreateDirError != ERROR_FILENAME_EXCED_RANGE && cch + lstrlenW(pszDirName) < ARRAYSIZE(szPath)) { lstrcpynW(&szPath[cch], pszDirName, ARRAYSIZE(szPath)-cch); } else { iCreateDirError = ERROR_FILENAME_EXCED_RANGE; } } // Create the destination directory if (iCreateDirError != ERROR_FILENAME_EXCED_RANGE) { iCreateDirError = SHCreateDirectory(_hDlg, szPath); } switch (iCreateDirError) { case ERROR_SUCCESS: SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, szPath, NULL); break; case ERROR_FILENAME_EXCED_RANGE: idErrorString = IDS_ERROR_FILENAME_EXCED_RANGE; break; case ERROR_ALREADY_EXISTS: // We get this if there is an existing file or directory // with the same name. if (!(FILE_ATTRIBUTE_DIRECTORY & GetFileAttributesW(szPath))) { // It's a file; show an error. idErrorString = IDS_ERROR_FILE_EXISTS; } else { // It's a directory; continue normally. } break; default: // For other errors, SHCreateDirectory shows a popup // and returns ERROR_CANCELLED. break; } if (0 != idErrorString) { szPath[0] = L'\0'; LoadStringW(g_hInstance, idErrorString, szPath, ARRAYSIZE(szPath)); ShellMessageBoxW(g_hInstance, _hDlg, MAKEINTRESOURCE(IDS_CANNOTCREATEFOLDER), MAKEINTRESOURCE(IDS_TIMEWARP_TITLE), MB_ICONWARNING | MB_OK, pszDirName, szPath); iCreateDirError = ERROR_CANCELLED; // prevent copy below } } if (ERROR_SUCCESS == iCreateDirError || ERROR_ALREADY_EXISTS == iCreateDirError) { // OK, save now if (!_CopySnapShot(pszSnapShotPath, szPath, FOF_NOCONFIRMMKDIR)) { // SHFileOperation shows an error message if necessary if (!_IsFile() && ERROR_SUCCESS == iCreateDirError) { // We created a folder above, so try to clean up now. // This is best effort only. Ignore failure. if (RemoveDirectory(szPath)) { SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, szPath, NULL); } } } } } } } void CTimeWarpProp::_OnRevert() { LPCWSTR pszSnapShotPath = _GetSelectedItemPath(); if (NULL != pszSnapShotPath) { // Confirm first if (IDYES == ShellMessageBoxW(g_hInstance, _hDlg, MAKEINTRESOURCE(_IsFolder() ? IDS_CONFIRM_REVERT_FOLDER : IDS_CONFIRM_REVERT_FILE), MAKEINTRESOURCE(IDS_TIMEWARP_TITLE), MB_ICONQUESTION | MB_YESNO)) { LPCWSTR pszDest = _pszPath; LPWSTR pszAlloc = NULL; // There is a debate about whether to delete current files before // copying the old files over. This mainly affects files that // were created after the snapshot that we are restoring. if (!_IsFile()) { #if 0 SHFILEOPSTRUCTW fo; // First try to delete current folder contents, since files // may have been created after the snapshot was taken. ASSERT(NULL != _pszPath); fo.hwnd = _hDlg; fo.wFunc = FO_DELETE; fo.pFrom = _MakeDoubleNullString(_pszPath, TRUE); fo.pTo = NULL; fo.fFlags = FOF_NOCONFIRMATION; if (NULL != fo.pFrom) { SHFileOperationW(&fo); LocalFree((LPWSTR)fo.pFrom); } #endif } else { // Remove the filename from the destination, otherwise // SHFileOperation tries to create a directory with that name. if (SUCCEEDED(SHStrDup(pszDest, &pszAlloc))) { LPWSTR pszFile = PathFindFileNameW(pszAlloc); if (pszFile) { *pszFile = L'\0'; pszDest = pszAlloc; } } } // NTRAID#NTBUG9-497729-2001/11/27-jeffreys // Don't want 2 reverts happening at the same time EnableWindow(_hDlg, FALSE); // OK, copy the old version over if (_CopySnapShot(pszSnapShotPath, pszDest, FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR)) { // QUERY_SNAPSHOT_DIFFERENT may return different // results now, so update the list. if (_IsFile()) { _OnRefresh(); } // Let the user know we succeeded ShellMessageBoxW(g_hInstance, _hDlg, MAKEINTRESOURCE(_IsFolder() ? IDS_SUCCESS_REVERT_FOLDER : IDS_SUCCESS_REVERT_FILE), MAKEINTRESOURCE(IDS_TIMEWARP_TITLE), MB_ICONINFORMATION | MB_OK); } else { // SHFileOperation shows an error message if necessary } EnableWindow(_hDlg, TRUE); LocalFree(pszAlloc); } } } LPCWSTR CTimeWarpProp::_GetSelectedItemPath() { if (NULL != _hList) { int iItem = ListView_GetNextItem(_hList, -1, LVNI_SELECTED); if (-1 != iItem) { LVITEM lvItem; lvItem.mask = LVIF_PARAM; lvItem.iItem = iItem; lvItem.iSubItem = 0; if (ListView_GetItem(_hList, &lvItem)) { return (LPCWSTR)lvItem.lParam; } } } return NULL; } LPWSTR CTimeWarpProp::_MakeDoubleNullString(LPCWSTR psz, BOOL bAddWildcard) { // // SHFileOperation eventually passes the source path to FindFirstFile. // If this path looks like "\\server\share\@GMT", this fails with // ERROR_PATH_NOT_FOUND. We have to add a wildcard to the source // path to make SHFileOperation work. // int cch = lstrlenW(psz); int cchAlloc = cch + 2; // double-NULL if (bAddWildcard) cchAlloc += 2; // "\\*" LPWSTR pszResult = (LPWSTR)LocalAlloc(LPTR, cchAlloc*sizeof(WCHAR)); if (NULL != pszResult) { // Note that the buffer is zero-initialized, so it automatically // has a double-NULL at the end. CopyMemory(pszResult, psz, cch*sizeof(WCHAR)); if (bAddWildcard) { if (cch > 0 && pszResult[cch-1] != L'\\') { pszResult[cch] = L'\\'; ++cch; } pszResult[cch] = L'*'; } } return pszResult; } BOOL CTimeWarpProp::_CopySnapShot(LPCWSTR pszSource, LPCWSTR pszDest, FILEOP_FLAGS foFlags) { BOOL bResult = FALSE; SHFILEOPSTRUCTW fo; ASSERT(NULL != pszSource && L'\0' != *pszSource); ASSERT(NULL != pszDest && L'\0' != *pszDest); fo.hwnd = _hDlg; fo.wFunc = FO_COPY; fo.pFrom = _MakeDoubleNullString(pszSource, !_IsFile()); fo.pTo = _MakeDoubleNullString(pszDest, FALSE); fo.fFlags = foFlags; fo.fAnyOperationsAborted = FALSE; if (NULL != fo.pFrom && NULL != fo.pTo) { TraceMsg(TF_TWPROP, "Copying from '%s'", fo.pFrom); TraceMsg(TF_TWPROP, "Copying to '%s'", fo.pTo); // NTRAID#NTBUG9-497725-2001/11/27-jeffreys // Cancelling usually results in a return value of ERROR_CANCELLED, // but if you cancel during the "Preparing to Copy" phase, SHFileOp // returns ERROR_SUCCESS. Need to check fAnyOperationsAborted to // catch that case. bResult = !SHFileOperationW(&fo) && !fo.fAnyOperationsAborted; } LocalFree((LPWSTR)fo.pFrom); LocalFree((LPWSTR)fo.pTo); return bResult; } /** * Determines if the pidl still exists. If it does not, if frees it * and replaces it with a My Documents pidl */ void _BFFSwitchToMyDocsIfPidlNotExist(PIDLIST_ABSOLUTE *ppidl) { IShellFolder *psf; PCUITEMID_CHILD pidlChild; if (SUCCEEDED(SHBindToIDListParent(*ppidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild))) { DWORD dwAttr = SFGAO_VALIDATE; if (FAILED(psf->GetAttributesOf(1, &pidlChild, &dwAttr))) { // This means the pidl no longer exists. // Use my documents instead. PIDLIST_ABSOLUTE pidlMyDocs; if (SUCCEEDED(SHGetFolderLocation(NULL, CSIDL_PERSONAL, NULL, 0, &pidlMyDocs))) { // Good. Now we can get rid of the old pidl and use this one. SHILFree(*ppidl); *ppidl = pidlMyDocs; } } psf->Release(); } } HRESULT CTimeWarpProp::_InvokeBFFDialog(LPWSTR pszDest, UINT cchDest) { HRESULT hr; BROWSEINFOW bi; LPWSTR pszTitle = NULL; HKEY hkey = NULL; IStream *pstrm = NULL; PIDLIST_ABSOLUTE pidlSelectedFolder = NULL; PIDLIST_ABSOLUTE pidlTarget = NULL; // "Select the place where you want to copy '%1'. Then click the Copy button." if (!FormatString(&pszTitle, g_hInstance, MAKEINTRESOURCE(IDS_BROWSE_INTRO_COPY), _pszDisplayName)) { // "Select the place where you want to copy the selected item(s). Then click the Copy button." LoadStringAlloc(&pszTitle, g_hInstance, IDS_BROWSE_INTRO_COPY2); } if (RegOpenKeyEx(HKEY_CURRENT_USER, c_szCopyMoveTo_RegKey, 0, KEY_READ | KEY_WRITE, &hkey) == ERROR_SUCCESS) { pstrm = OpenRegStream(hkey, c_szCopyMoveTo_SubKey, c_szCopyMoveTo_Value, STGM_READWRITE); if (pstrm) // OpenRegStream will fail if the reg key is empty. ILLoadFromStream(pstrm, (PIDLIST_RELATIVE*)&pidlSelectedFolder); // This will switch the pidl to My Docs if the pidl does not exist. // This prevents us from having My Computer as the default (that's what happens if our // initial set selected call fails). // Note: ideally, we would check in BFFM_INITIALIZED, if our BFFM_SETSELECTION failed // then do a BFFM_SETSELECTION on My Documents instead. However, BFFM_SETSELECTION always // returns zero (it's doc'd to do this to, so we can't change). So we do the validation // here instead. There is still a small chance that this folder will be deleted in between our // check here, and when we call BFFM_SETSELECTION, but oh well. _BFFSwitchToMyDocsIfPidlNotExist(&pidlSelectedFolder); } bi.hwndOwner = _hDlg; bi.pidlRoot = NULL; bi.pszDisplayName = NULL; bi.lpszTitle = pszTitle; bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_VALIDATE | BIF_UAHINT /* | BIF_NOTRANSLATETARGETS*/; bi.lpfn = BrowseCallback; bi.lParam = (LPARAM)pidlSelectedFolder; bi.iImage = 0; pidlTarget = (PIDLIST_ABSOLUTE)SHBrowseForFolder(&bi); if (pidlTarget) { hr = SHGetNameAndFlagsW(pidlTarget, SHGDN_FORPARSING, pszDest, cchDest, NULL); } else { // Either user cancelled, or failure. Doesn't matter. hr = S_FALSE; } if (pstrm) { if (S_OK == hr && !PathIsNetworkPathW(pszDest)) { LARGE_INTEGER li0 = {0}; ULARGE_INTEGER uli; // rewind the stream to the beginning so that when we // add a new pidl it does not get appended to the first one pstrm->Seek(li0, STREAM_SEEK_SET, &uli); ILSaveToStream(pstrm, pidlTarget); } pstrm->Release(); } if (hkey) { RegCloseKey(hkey); } SHILFree(pidlTarget); SHILFree(pidlSelectedFolder); LocalFree(pszTitle); return hr; } UINT CALLBACK CTimeWarpProp::PSPCallback(HWND /*hDlg*/, UINT uMsg, LPPROPSHEETPAGE ppsp) { switch (uMsg) { case PSPCB_RELEASE: ((CTimeWarpProp*)ppsp->lParam)->Release(); break; } return 1; } INT_PTR CALLBACK CTimeWarpProp::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { CTimeWarpProp *ptwp = (CTimeWarpProp*)GetWindowLongPtr(hDlg, DWLP_USER); if (uMsg == WM_INITDIALOG) { PROPSHEETPAGE *pPropSheetPage = (PROPSHEETPAGE*)lParam; if (pPropSheetPage) { ptwp = (CTimeWarpProp*) pPropSheetPage->lParam; if (ptwp) { SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)ptwp); ptwp->_OnInit(hDlg); return 1; } } } else if (ptwp) { switch (uMsg) { case WM_DESTROY: SetWindowLongPtr(hDlg, DWLP_USER, 0); return 1; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDC_REVERT: ptwp->_OnRevert(); return 1; case IDC_VIEW: ptwp->_OnView(); return 1; case IDC_COPY: ptwp->_OnCopy(); return 1; } break; case WM_NOTIFY: { NMHDR *pnmh = (NMHDR*)lParam; switch (pnmh->code) { case NM_DBLCLK: if (IDC_LIST == pnmh->idFrom) { ptwp->_OnView(); } break; case LVN_ITEMCHANGED: if (IDC_LIST == pnmh->idFrom) { NMLISTVIEW *pnmlv = (NMLISTVIEW*)lParam; if (pnmlv->uChanged & LVIF_STATE) { ptwp->_UpdateButtons(); } } break; case PSN_TRANSLATEACCELERATOR: { MSG *pMsg = (MSG*)(((PSHNOTIFY*)lParam)->lParam); if (WM_KEYUP == pMsg->message && VK_F5 == pMsg->wParam) { ptwp->_OnRefresh(); } } break; case PSN_HELP: { SHELLEXECUTEINFOW sei; sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_DOENVSUBST; sei.hwnd = hDlg; sei.lpVerb = NULL; sei.lpFile = c_szChmPath; sei.lpParameters = NULL; sei.lpDirectory = NULL; sei.nShow = SW_SHOWNORMAL; ShellExecuteExW(&sei); } break; } } break; case WM_SIZE: ptwp->_OnSize(); break; case WM_HELP: /* F1 or title-bar help button */ WinHelpW((HWND)((LPHELPINFO) lParam)->hItemHandle, c_szHelpFile, HELP_WM_HELP, (DWORD_PTR)rgdwTimeWarpPropHelp); break; case WM_CONTEXTMENU: /* right mouse click */ WinHelpW((HWND)wParam, c_szHelpFile, HELP_CONTEXTMENU, (DWORD_PTR)rgdwTimeWarpPropHelp); break; } } return 0; } int CALLBACK BrowseCallback(HWND hDlg, UINT uMsg, LPARAM lParam, LPARAM pData) { if (BFFM_INITIALIZED == uMsg) { // Set the caption ("Copy Items") TCHAR szTemp[100]; if (LoadString(g_hInstance, IDS_BROWSE_TITLE_COPY, szTemp, ARRAYSIZE(szTemp))) { SetWindowText(hDlg, szTemp); } // Set the text of the Ok Button ("Copy") if (LoadString(g_hInstance, IDS_COPY, szTemp, ARRAYSIZE(szTemp))) // 0x1031 in shell32 { SendMessage(hDlg, BFFM_SETOKTEXT, 0, (LPARAM)szTemp); } // Set My Computer expanded PIDLIST_ABSOLUTE pidlMyComputer; HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidlMyComputer); if (SUCCEEDED(hr)) { SendMessage(hDlg, BFFM_SETEXPANDED, FALSE, (LPARAM)pidlMyComputer); SHILFree(pidlMyComputer); } // Set the default selected pidl SendMessage(hDlg, BFFM_SETSELECTION, FALSE, pData); } return 0; } // // Because the Name is the same for each entry in the listview, we have to // expose more info in accName to make this usable in accessibility scenarios, // e.g. to a screen reader. We override get_accName and concatenate accDescription // onto the name. // STDMETHODIMP CNameDescriptionAccessibleWrapper::get_accName(VARIANT varChild, BSTR* pstrName) { // Call the base class first in all cases. HRESULT hr = CAccessibleWrapper::get_accName(varChild, pstrName); // varChild.lVal specifies which sub-part of the component is being queried. // CHILDID_SELF (0) specifies the overall component - other values specify a child. if (SUCCEEDED(hr) && varChild.vt == VT_I4 && varChild.lVal != CHILDID_SELF) { BSTR strDescription = NULL; // Get accDescription and concatenate onto accName // // If anything fails, we return the result from above if (SUCCEEDED(CAccessibleWrapper::get_accDescription(varChild, &strDescription))) { LPWSTR pszNewName = NULL; if (FormatString(&pszNewName, g_hInstance, MAKEINTRESOURCE(IDS_ACCNAME_FORMAT), *pstrName, strDescription)) { BSTR strNewName = SysAllocString(pszNewName); if (strNewName) { SysFreeString(*pstrName); *pstrName = strNewName; } LocalFree(pszNewName); } SysFreeString(strDescription); } } return hr; } extern "C" LPCWSTR FindSnapshotPathSplit(LPCWSTR lpszPath); // timewarp.c typedef struct { BOOL bHasShadowCopy; DWORD dwCacheTime; ULONG cchPath; WCHAR szPath[1]; } SNAPCHECK_CACHE_ENTRY; // 5 minutes #define _CACHE_AGE_LIMIT (5*60*1000) CRITICAL_SECTION g_csSnapCheckCache; HDPA g_dpaSnapCheckCache = NULL; int CALLBACK _LocalFreeCallback(void *p, void*) { // OK to pass NULL to LocalFree LocalFree(p); return 1; } void InitSnapCheckCache(void) { InitializeCriticalSection(&g_csSnapCheckCache); } void DestroySnapCheckCache(void) { if (NULL != g_dpaSnapCheckCache) { DPA_DestroyCallback(g_dpaSnapCheckCache, _LocalFreeCallback, 0); } DeleteCriticalSection(&g_csSnapCheckCache); } static int CALLBACK _CompareServerEntries(void *p1, void *p2, LPARAM lParam) { int nResult; SNAPCHECK_CACHE_ENTRY *pEntry1 = (SNAPCHECK_CACHE_ENTRY*)p1; SNAPCHECK_CACHE_ENTRY *pEntry2 = (SNAPCHECK_CACHE_ENTRY*)p2; BOOL *pbExact = (BOOL*)lParam; ASSERT(NULL != pEntry1); ASSERT(NULL != pEntry2); ASSERT(NULL != pbExact); nResult = CompareString(LOCALE_SYSTEM_DEFAULT, SORT_STRINGSORT | NORM_IGNORECASE | NORM_STOP_ON_NULL, pEntry1->szPath, pEntry1->cchPath, pEntry2->szPath, pEntry2->cchPath) - CSTR_EQUAL; if (0 == nResult) { *pbExact = TRUE; } return nResult; } static void SnapCheck_CacheResult(LPCWSTR pszPath, LPCWSTR pszShadowPath, BOOL bHasShadowCopy) { LPWSTR pszServer = NULL; if (bHasShadowCopy) { // Use the shadow path instead ASSERT(NULL != pszShadowPath); pszPath = pszShadowPath; } if (SUCCEEDED(SHStrDup(pszPath, &pszServer))) { // FindSnapshotPathSplit hits the net, so try to avoid it. LPWSTR pszTail = bHasShadowCopy ? wcsstr(pszServer, SNAPSHOT_MARKER) : (LPWSTR)FindSnapshotPathSplit(pszServer); if (pszTail) { *pszTail = L'\0'; } EliminatePathPrefix(pszServer); PathRemoveBackslashW(pszServer); int cchServer = lstrlen(pszServer); SNAPCHECK_CACHE_ENTRY *pEntry = (SNAPCHECK_CACHE_ENTRY*)LocalAlloc(LPTR, sizeof(SNAPCHECK_CACHE_ENTRY) + sizeof(WCHAR)*cchServer); if (pEntry) { pEntry->bHasShadowCopy = bHasShadowCopy; pEntry->cchPath = cchServer; lstrcpynW(pEntry->szPath, pszServer, cchServer+1); EnterCriticalSection(&g_csSnapCheckCache); if (NULL == g_dpaSnapCheckCache) { // This ref is not balanced. This causes us to remain loaded // until the process terminates, so the cache isn't deleted // prematurely (i.e. if AlwaysUnloadDlls is set). DllAddRef(); g_dpaSnapCheckCache = DPA_Create(4); } if (NULL != g_dpaSnapCheckCache) { pEntry->dwCacheTime = GetTickCount(); BOOL bExact = FALSE; int iIndex = DPA_Search(g_dpaSnapCheckCache, pEntry, 0, _CompareServerEntries, (LPARAM)&bExact, DPAS_SORTED | DPAS_INSERTBEFORE); if (bExact) { // Found a duplicate. Replace it. SNAPCHECK_CACHE_ENTRY *pOldEntry = (SNAPCHECK_CACHE_ENTRY*)DPA_FastGetPtr(g_dpaSnapCheckCache, iIndex); DPA_SetPtr(g_dpaSnapCheckCache, iIndex, pEntry); LocalFree(pOldEntry); } else if (-1 == DPA_InsertPtr(g_dpaSnapCheckCache, iIndex, pEntry)) { LocalFree(pEntry); } } else { LocalFree(pEntry); } LeaveCriticalSection(&g_csSnapCheckCache); } LocalFree(pszServer); } } static int CALLBACK _SearchServerEntries(void *p1, void *p2, LPARAM lParam) { int nResult = 0; LPCWSTR pszFind = (LPCWSTR)p1; ULONG cchFind = (ULONG)lParam; SNAPCHECK_CACHE_ENTRY *pEntry = (SNAPCHECK_CACHE_ENTRY*)p2; ASSERT(NULL != pszFind); ASSERT(NULL != pEntry); // Compare the first pEntry->cchPath chars of both strings nResult = CompareString(LOCALE_SYSTEM_DEFAULT, SORT_STRINGSORT | NORM_IGNORECASE | NORM_STOP_ON_NULL, pszFind, pEntry->cchPath, pEntry->szPath, pEntry->cchPath) - CSTR_EQUAL; if (0 == nResult) { // // Check whether pszFind is longer than pEntry->szPath, but allow // extra path segments in pszFind. // // For example, if // pEntry->szPath = "\\server\share" // pszFind = "\\server\share2" // then we don't have a match. But if // pEntry->szPath = "\\server\share" // pszFind = "\\server\share\dir" // the we DO have a match. // // Also, at the root of a mapped drive, pEntry->szPath includes // a trailing backslash, so we may have this: // pEntry->szPath = "X:\" // pszFind = "X:\dir" // which we consider to be a match. // if (cchFind > pEntry->cchPath && pszFind[pEntry->cchPath] != L'\\' && (PathIsUNCW(pEntry->szPath) || !PathIsRootW(pEntry->szPath))) { ASSERT(pszFind[pEntry->cchPath] != L'\0'); // otherwise, cchFind == pEntry->cchPath and we don't get here nResult = 1; } } return nResult; } static BOOL SnapCheck_LookupResult(LPCWSTR pszPath, BOOL *pbHasShadowCopy) { BOOL bFound = FALSE; *pbHasShadowCopy = FALSE; if (NULL == g_dpaSnapCheckCache) return FALSE; EnterCriticalSection(&g_csSnapCheckCache); int iIndex = DPA_Search(g_dpaSnapCheckCache, (void*)pszPath, 0, _SearchServerEntries, lstrlenW(pszPath), DPAS_SORTED); if (-1 != iIndex) { // Found a match SNAPCHECK_CACHE_ENTRY *pEntry = (SNAPCHECK_CACHE_ENTRY*)DPA_FastGetPtr(g_dpaSnapCheckCache, iIndex); DWORD dwCurrentTime = GetTickCount(); if (dwCurrentTime > pEntry->dwCacheTime && dwCurrentTime - pEntry->dwCacheTime < _CACHE_AGE_LIMIT) { *pbHasShadowCopy = pEntry->bHasShadowCopy; bFound = TRUE; } else { // The entry has aged out DPA_DeletePtr(g_dpaSnapCheckCache, iIndex); LocalFree(pEntry); } } LeaveCriticalSection(&g_csSnapCheckCache); return bFound; }