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
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;
|
|
}
|
|
|
|
|