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

#include "precomp.hxx"
#pragma hdrstop
#include <ccstock2.h> // DataObj_GetHIDA, IDA_ILClone, HIDA_ReleaseStgMedium
#include <winnlsp.h> // 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<CNameDescriptionAccessibleWrapper>(_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;
}