1354 lines
41 KiB
C++
1354 lines
41 KiB
C++
|
|
|
|
// Microsoft Windows
|
|
|
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
|
|
|
// File: shellex.cpp
|
|
|
|
|
|
|
|
/*
|
|
/ Title;
|
|
/ shellex.cpp
|
|
|
|
/ Authors;
|
|
/ Jeff Saathoff (jeffreys)
|
|
|
|
/ Notes;
|
|
/ Context Menu and Property Sheet shell extensions
|
|
*/
|
|
#include "pch.h"
|
|
#include "options.h" // ..\viewer\options.h
|
|
#include "firstpin.h"
|
|
#include "uihooks.h" // Self-host notifications
|
|
#include "msgbox.h"
|
|
|
|
#define CSC_PROP_NO_CSC 0x00000001L
|
|
#define CSC_PROP_MULTISEL 0x00000002L
|
|
#define CSC_PROP_PINNED 0x00000004L
|
|
#define CSC_PROP_SYNCABLE 0x00000008L
|
|
#define CSC_PROP_ADMIN_PINNED 0x00000010L
|
|
#define CSC_PROP_INHERIT_PIN 0x00000020L
|
|
#define CSC_PROP_DCON_MODE 0x00000040L
|
|
|
|
// Thread data for unpinning files
|
|
typedef struct _CSC_UNPIN_DATA
|
|
{
|
|
CscFilenameList* pNamelist;
|
|
DWORD dwUpdateFlags;
|
|
HWND hwndOwner;
|
|
} CSC_UNPIN_DATA, * PCSC_UNPIN_DATA;
|
|
|
|
|
|
|
|
// Shell extension object implementation //
|
|
|
|
|
|
|
|
HRESULT WINAPI
|
|
CCscShellExt::CreateInstance(REFIID riid, LPVOID* ppv)
|
|
{
|
|
HRESULT hr;
|
|
CCscShellExt* pThis;
|
|
|
|
pThis = new CCscShellExt;
|
|
if (pThis) {
|
|
hr = pThis->QueryInterface(riid, ppv);
|
|
pThis->Release(); // release initial ref
|
|
} else
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shell extension object implementation (IUnknown) //
|
|
|
|
|
|
|
|
STDMETHODIMP CCscShellExt::QueryInterface(REFIID riid, void** ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CCscShellExt, IShellExtInit),
|
|
QITABENT(CCscShellExt, IContextMenu),
|
|
QITABENT(CCscShellExt, IShellIconOverlayIdentifier),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CCscShellExt::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CCscShellExt::Release()
|
|
{
|
|
if (InterlockedDecrement(&m_cRef))
|
|
return m_cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// Shell extension object implementation (IShellExtInit) //
|
|
|
|
|
|
|
|
|
|
STDMETHODIMP CCscShellExt::Initialize(LPCITEMIDLIST /*pidlFolder*/,
|
|
LPDATAOBJECT lpdobj,
|
|
HKEY /*hKeyProgID*/)
|
|
{
|
|
DoRelease(m_lpdobj);
|
|
|
|
m_lpdobj = lpdobj;
|
|
|
|
if (m_lpdobj)
|
|
m_lpdobj->AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shell extension object implementation (IContextMenu) //
|
|
|
|
|
|
|
|
|
|
|
|
// FUNCTION: IContextMenu::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
|
|
|
|
// PURPOSE: Called by the shell just before the context menu is displayed.
|
|
// This is where you add your specific menu items.
|
|
|
|
// PARAMETERS:
|
|
// hMenu - Handle to the context menu
|
|
// iMenu - Index of where to begin inserting menu items
|
|
// idCmdFirst - Lowest value for new menu ID's
|
|
// idCmtLast - Highest value for new menu ID's
|
|
// uFlags - Specifies the context of the menu event
|
|
|
|
// RETURN VALUE:
|
|
// HRESULT signifying success or failure.
|
|
|
|
// COMMENTS:
|
|
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::QueryContextMenu(HMENU hMenu,
|
|
UINT iMenu,
|
|
UINT idCmdFirst,
|
|
UINT idCmdLast,
|
|
UINT uFlags)
|
|
{
|
|
HRESULT hr = ResultFromShort(0);
|
|
UINT idCmd = idCmdFirst;
|
|
TCHAR szMenu[MAX_PATH];
|
|
MENUITEMINFO mii;
|
|
CConfig& config = CConfig::GetSingleton();
|
|
|
|
if ((uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY)) || !m_lpdobj)
|
|
return hr;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::QueryContextMenu");
|
|
TraceAssert(IsCSCEnabled());
|
|
|
|
|
|
// Check the pin status and CSC-ability of the current selection
|
|
|
|
m_dwUIStatus = 0;
|
|
if (FAILED(CheckFileStatus(m_lpdobj, &m_dwUIStatus)))
|
|
m_dwUIStatus = CSC_PROP_NO_CSC;
|
|
|
|
if (m_dwUIStatus & CSC_PROP_NO_CSC)
|
|
TraceLeaveResult(hr);
|
|
|
|
|
|
// Add a menu separator
|
|
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_TYPE;
|
|
mii.fType = MFT_SEPARATOR;
|
|
|
|
InsertMenuItem(hMenu, iMenu++, TRUE, &mii);
|
|
|
|
if (!config.NoMakeAvailableOffline()) {
|
|
|
|
// Add the "Make Available Offline" menu item
|
|
|
|
LoadString(g_hInstance, IDS_MENU_PIN, szMenu, ARRAYSIZE(szMenu));
|
|
|
|
mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
|
|
mii.fType = MFT_STRING;
|
|
mii.fState = MFS_ENABLED;
|
|
if (m_dwUIStatus & (CSC_PROP_ADMIN_PINNED | CSC_PROP_PINNED)) {
|
|
mii.fState = MFS_CHECKED;
|
|
if (m_dwUIStatus & (CSC_PROP_ADMIN_PINNED | CSC_PROP_INHERIT_PIN))
|
|
mii.fState |= MFS_DISABLED;
|
|
}
|
|
mii.wID = idCmd++;
|
|
mii.dwTypeData = szMenu;
|
|
|
|
InsertMenuItem(hMenu, iMenu++, TRUE, &mii);
|
|
}
|
|
|
|
if (m_dwUIStatus & (CSC_PROP_SYNCABLE | CSC_PROP_PINNED | CSC_PROP_ADMIN_PINNED)) {
|
|
|
|
// Add the "Synchronize" menu item
|
|
|
|
LoadString(g_hInstance, IDS_MENU_SYNCHRONIZE, szMenu, ARRAYSIZE(szMenu));
|
|
|
|
mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
|
|
mii.fType = MFT_STRING;
|
|
mii.fState = MFS_ENABLED;
|
|
mii.wID = idCmd++;
|
|
mii.dwTypeData = szMenu;
|
|
|
|
InsertMenuItem(hMenu, iMenu++, TRUE, &mii);
|
|
}
|
|
|
|
|
|
// Return the number of menu items we added.
|
|
|
|
hr = ResultFromShort(idCmd - idCmdFirst);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
// FUNCTION: IContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO)
|
|
|
|
// PURPOSE: Called by the shell after the user has selected on of the
|
|
// menu items that was added in QueryContextMenu().
|
|
|
|
// PARAMETERS:
|
|
// lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
|
|
|
|
// RETURN VALUE:
|
|
// HRESULT signifying success or failure.
|
|
|
|
// COMMENTS:
|
|
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UINT iCmd = 0;
|
|
CscFilenameList* pfnl = NULL; // Namelist object.
|
|
BOOL fPin;
|
|
BOOL bSubFolders = FALSE;
|
|
DWORD dwUpdateFlags = 0;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::InvokeCommand");
|
|
TraceAssert(IsCSCEnabled());
|
|
TraceAssert(!(m_dwUIStatus & CSC_PROP_NO_CSC));
|
|
|
|
if (HIWORD(lpcmi->lpVerb)) {
|
|
if (!lstrcmpiA(lpcmi->lpVerb, STR_PIN_VERB)) {
|
|
iCmd = 0;
|
|
m_dwUIStatus &= ~CSC_PROP_PINNED;
|
|
} else if (!lstrcmpiA(lpcmi->lpVerb, STR_UNPIN_VERB)) {
|
|
iCmd = 0;
|
|
m_dwUIStatus |= CSC_PROP_PINNED;
|
|
} else if (!lstrcmpiA(lpcmi->lpVerb, STR_SYNC_VERB)) {
|
|
iCmd = 1;
|
|
} else {
|
|
Trace((TEXT("Unknown command \"%S\""), lpcmi->lpVerb));
|
|
ExitGracefully(hr, E_INVALIDARG, "Invalid command");
|
|
}
|
|
} else {
|
|
iCmd = LOWORD(lpcmi->lpVerb);
|
|
|
|
// If we didn't add the "Make Available Offline" verb, adjust the index
|
|
if (CConfig::GetSingleton().NoMakeAvailableOffline())
|
|
iCmd++;
|
|
}
|
|
if (iCmd >= 2)
|
|
ExitGracefully(hr, E_INVALIDARG, "Invalid command");
|
|
|
|
pfnl = new CscFilenameList;
|
|
if (!pfnl)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create CscFilenameList object");
|
|
|
|
hr = BuildFileList(m_lpdobj,
|
|
lpcmi->hwnd,
|
|
pfnl,
|
|
&bSubFolders);
|
|
FailGracefully(hr, "Unable to build file list");
|
|
|
|
switch (iCmd) {
|
|
case 0: // "Make available offline" menu choice - Pin files
|
|
if (!FirstPinWizardCompleted()) {
|
|
|
|
// User has never seen the "first pin" wizard.
|
|
|
|
if (S_FALSE == ShowFirstPinWizard(lpcmi->hwnd)) {
|
|
|
|
// User cancelled wizard. Abort pinning operation.
|
|
|
|
ExitGracefully(hr, S_OK, "User cancelled first-pin wizard");
|
|
}
|
|
}
|
|
fPin = !(m_dwUIStatus & CSC_PROP_PINNED);
|
|
if (!fPin && (m_dwUIStatus & CSC_PROP_DCON_MODE)) {
|
|
// Unpin while disconnected causes things to disappear.
|
|
// Warn the user.
|
|
if (IDCANCEL == CscMessageBox(lpcmi->hwnd,
|
|
MB_OKCANCEL | MB_ICONWARNING,
|
|
g_hInstance,
|
|
IDS_CONFIRM_UNPIN_OFFLINE)) {
|
|
ExitGracefully(hr, E_FAIL, "User cancelled disconnected unpin operation");
|
|
}
|
|
}
|
|
// If there is a directory in the list AND we're pinning AND
|
|
// the "AlwaysPinSubFolders" policy is NOT set, ask the user
|
|
// whether to go deep or not.
|
|
// If the policy IS set we automatically do a recursive pin.
|
|
if (bSubFolders && (!fPin || !CConfig::GetSingleton().AlwaysPinSubFolders())) {
|
|
switch (DialogBox(g_hInstance,
|
|
MAKEINTRESOURCE(fPin ? IDD_CONFIRM_PIN : IDD_CONFIRM_UNPIN),
|
|
lpcmi->hwnd,
|
|
_ConfirmPinDlgProc)) {
|
|
case IDYES:
|
|
// nothing
|
|
break;
|
|
case IDNO:
|
|
bSubFolders = FALSE; // no subfolders
|
|
break;
|
|
case IDCANCEL:
|
|
ExitGracefully(hr, E_FAIL, "User cancelled (un)pin operation");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bSubFolders)
|
|
dwUpdateFlags |= CSC_UPDATE_PIN_RECURSE;
|
|
|
|
if (fPin) {
|
|
// Self-host notification callback
|
|
CSCUI_NOTIFYHOOK((CSCH_Pin, TEXT("Pinning %1!d! selected items"), pfnl->GetFileCount()));
|
|
|
|
// Set the flags for pin + quick sync
|
|
dwUpdateFlags |= CSC_UPDATE_SELECTION | CSC_UPDATE_STARTNOW
|
|
| CSC_UPDATE_PINFILES | CSC_UPDATE_FILL_QUICK;
|
|
} else {
|
|
HANDLE hThread;
|
|
DWORD dwThreadID;
|
|
PCSC_UNPIN_DATA pUnpinData = (PCSC_UNPIN_DATA)LocalAlloc(LPTR, SIZEOF(CSC_UNPIN_DATA));
|
|
|
|
// Self-host notification callback
|
|
CSCUI_NOTIFYHOOK((CSCH_Unpin, TEXT("Unpinning %1!d! selected items"), pfnl->GetFileCount()));
|
|
|
|
|
|
// No sync is required to unpin files, so let's do it in this
|
|
// process rather than starting SyncMgr. However, let's do
|
|
// it in the background in case there's a lot to unpin.
|
|
|
|
if (pUnpinData) {
|
|
pUnpinData->pNamelist = pfnl;
|
|
pUnpinData->dwUpdateFlags = dwUpdateFlags;
|
|
pUnpinData->hwndOwner = lpcmi->hwnd;
|
|
hThread = CreateThread(NULL, 0, _UnpinFilesThread, pUnpinData, 0, &dwThreadID);
|
|
if (hThread) {
|
|
// The thread will delete pUnpinData and pUnpinData->pNamelist
|
|
pfnl = NULL;
|
|
|
|
// We give the async thread a little time to complete, during which we
|
|
// put up the busy cursor. This is solely to let the user see that
|
|
// some work is being done...
|
|
HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
WaitForSingleObject(hThread, 750);
|
|
CloseHandle(hThread);
|
|
SetCursor(hCur);
|
|
} else {
|
|
LocalFree(pUnpinData);
|
|
}
|
|
}
|
|
|
|
// Clear the flags to prevent sync below
|
|
dwUpdateFlags = 0;
|
|
}
|
|
break;
|
|
|
|
case 1: // Synchronize
|
|
// Set the flags for a full sync
|
|
dwUpdateFlags = CSC_UPDATE_SELECTION | CSC_UPDATE_STARTNOW
|
|
| CSC_UPDATE_REINT | CSC_UPDATE_FILL_ALL
|
|
| CSC_UPDATE_SHOWUI_ALWAYS | CSC_UPDATE_NOTIFY_DONE;
|
|
break;
|
|
}
|
|
|
|
|
|
// Update the files we are pinning or synchronizing.
|
|
// Setting the "ignore access" flag will cause us to ignore the
|
|
// user/guest/other access info and sync all selected files. We want
|
|
// this behavior as the operation was initiated by a user's explicit
|
|
// selection of files/folders in explorer.
|
|
|
|
if (dwUpdateFlags && pfnl->GetFileCount()) {
|
|
if (!::IsSyncInProgress()) {
|
|
hr = CscUpdateCache(dwUpdateFlags | CSC_UPDATE_IGNORE_ACCESS, pfnl);
|
|
} else {
|
|
|
|
// A sync is in progress. Tell user why they can't currently
|
|
// pin or sync.
|
|
|
|
const UINT rgidsMsg[] = {IDS_CANTPIN_SYNCINPROGRESS,
|
|
IDS_CANTSYNC_SYNCINPROGRESS};
|
|
|
|
CscMessageBox(lpcmi->hwnd,
|
|
MB_OK | MB_ICONINFORMATION,
|
|
g_hInstance,
|
|
rgidsMsg[iCmd]);
|
|
}
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
delete pfnl;
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
// FUNCTION: IContextMenu::GetCommandString(UINT, UINT, UINT, LPSTR, UINT)
|
|
|
|
// PURPOSE: Called by the shell after the user has selected on of the
|
|
// menu items that was added in QueryContextMenu().
|
|
|
|
// PARAMETERS:
|
|
// lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
|
|
|
|
// RETURN VALUE:
|
|
// HRESULT signifying success or failure.
|
|
|
|
// COMMENTS:
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::GetCommandString(UINT_PTR iCmd,
|
|
UINT uFlags,
|
|
LPUINT /*reserved*/,
|
|
LPSTR pszString,
|
|
UINT cchMax)
|
|
{
|
|
HRESULT hr = E_UNEXPECTED;
|
|
|
|
if (uFlags == GCS_VALIDATE)
|
|
hr = S_FALSE;
|
|
|
|
if (iCmd > 1)
|
|
return hr;
|
|
|
|
hr = S_OK;
|
|
|
|
if (uFlags == GCS_HELPTEXT) {
|
|
LoadString(g_hInstance, iCmd ? IDS_HELP_UPDATE_SEL : IDS_HELP_PIN, (LPTSTR)pszString, cchMax);
|
|
} else if (uFlags == GCS_VERB) {
|
|
lstrcpyn((LPTSTR)pszString, iCmd ? TEXT(STR_SYNC_VERB) : ((m_dwUIStatus & CSC_PROP_PINNED) ? TEXT(STR_UNPIN_VERB) : TEXT(STR_PIN_VERB)), cchMax);
|
|
} else if (uFlags != GCS_VALIDATE) {
|
|
// Must be some other flag that we don't handle
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shell extension object implementation (IShellIconOverlayIdentifier) //
|
|
|
|
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::IsMemberOf(LPCWSTR pwszPath,
|
|
DWORD dwAttrib)
|
|
{
|
|
HRESULT hr = S_FALSE; // assume not pinned
|
|
DWORD dwHintFlags;
|
|
DWORD dwErr;
|
|
LPTSTR pszUNC = NULL;
|
|
LPTSTR pszSlash;
|
|
|
|
USES_CONVERSION;
|
|
|
|
|
|
// Make sure we have a UNC path
|
|
|
|
GetRemotePath(W2CT(pwszPath), &pszUNC);
|
|
if (!pszUNC)
|
|
return S_FALSE;
|
|
|
|
|
|
// Ask CSC if this is a pinned file
|
|
|
|
dwHintFlags = 0;
|
|
if (CSCQueryFileStatus(pszUNC, NULL, NULL, &dwHintFlags)) {
|
|
if (dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN))
|
|
hr = S_OK;
|
|
} else {
|
|
dwErr = GetLastError();
|
|
if (ERROR_FILE_NOT_FOUND != dwErr) {
|
|
|
|
// BUGBUG: Need to check for 0 to accomodate GetLastError
|
|
// returning 0 on CSCQueryFileStatus failure.
|
|
// I'll talk to Shishir about getting this fixed.
|
|
// [brianau - 5/13/99]
|
|
|
|
if (0 == dwErr)
|
|
dwErr = ERROR_GEN_FAILURE;
|
|
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
|
|
DWORD dwAttribTest = FILE_ATTRIBUTE_ENCRYPTED;
|
|
if (!CConfig::GetSingleton().AlwaysPinSubFolders())
|
|
dwAttribTest |= FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
if (S_FALSE == hr && !(dwAttrib & dwAttribTest)) {
|
|
|
|
// If we get here, then either CSCQueryFileStatus succeeded but the file
|
|
// isn't pinned, or the file isn't in the cache (ERROR_FILE_NOT_FOUND).
|
|
|
|
// Check whether the parent folder has the pin-inherit-user or
|
|
// admin-pin flag and pin this file if necessary.
|
|
|
|
// Note that we don't pin encrypted files here.
|
|
// Also note that pinning of folder is policy-dependent. The default
|
|
// behavior is to NOT pin folders (only files). If the
|
|
// "AlwaysPinSubFolders" policy is set, we will pin folders.
|
|
|
|
pszSlash = PathFindFileName(pszUNC);
|
|
if (pszSlash && pszUNC != pszSlash) {
|
|
--pszSlash;
|
|
*pszSlash = TEXT('\0'); // truncate the path
|
|
|
|
// Check the parent status
|
|
if (CSCQueryFileStatus(pszUNC, NULL, NULL, &dwHintFlags) &&
|
|
(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN))) {
|
|
// The parent is pinned, so pin this file with the same flags
|
|
|
|
if (dwHintFlags & FLAG_CSC_HINT_PIN_ADMIN)
|
|
dwHintFlags |= FLAG_CSC_HINT_COMMAND_ALTER_PIN_COUNT;
|
|
else
|
|
dwHintFlags |= FLAG_CSC_HINT_PIN_INHERIT_USER;
|
|
|
|
// Restore the rest of the path
|
|
*pszSlash = TEXT('\\');
|
|
|
|
|
|
// To avoid a nasty race condition between purging and auto-pinning we need
|
|
// to disable auto-pinning when a purge is in progress. The race condition
|
|
// can occur if a shell folder for the files being purged is open. We purge
|
|
// a file and send out a change notify. The shell updates the icon overlay
|
|
// and calls our overlay handler to remove the overlay. Our handler notices
|
|
// that the parent folder is pinned so we re-pin the file which places it
|
|
// back in the cache. Ugh... [brianau - 11/01/99]
|
|
|
|
// p.s.: Note that this check calls WaitForSingleObject so we only
|
|
// do it AFTER we're sure that we want to pin the file. We don't
|
|
// want to do the "wait" and THEN decide the file should not be
|
|
// pinned because it's not a UNC path or it's a directory.
|
|
|
|
if (!IsPurgeInProgress()) {
|
|
if (CSCPinFile(pszUNC, dwHintFlags, NULL, NULL, NULL))
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LocalFreeString(&pszUNC);
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::GetOverlayInfo(LPWSTR pwszIconFile,
|
|
int cchMax,
|
|
int* pIndex,
|
|
DWORD* pdwFlags)
|
|
{
|
|
|
|
if (cchMax < (lstrlen(c_szDllName) + 1)) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
lstrcpyn(pwszIconFile, c_szDllName, cchMax);
|
|
#else
|
|
MultiByteToWideChar(CP_ACP, 0, c_szDllName, -1, pwszIconFile, cchMax);
|
|
#endif
|
|
|
|
// BUGBUG Using the ID doesn't work on Win95. We're currently not shipping
|
|
// on Win95, but if that changes, we may need to fix this. The overlay icon
|
|
// is deliberately early in the DLL (right after the CSC icon) so the index
|
|
// should always be one, but using the ID is still preferable.
|
|
#ifdef WINNT
|
|
// Use positive #'s for indexes, negative for ID's
|
|
* pIndex = -IDI_PIN_OVERLAY;
|
|
#else
|
|
* pIndex = 1;
|
|
#endif
|
|
* pdwFlags = (ISIOI_ICONFILE | ISIOI_ICONINDEX);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::GetPriority(int* pIPriority)
|
|
{
|
|
*pIPriority = 1;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
|
|
// CCscShellExt implementation //
|
|
|
|
|
|
|
|
BOOL
|
|
CCscShellExt::ShareIsCacheable(LPCTSTR pszUNC, PBOOL pbShareConnected, PDWORD pdwShareStatus)
|
|
{
|
|
TCHAR szShare[MAX_PATH];
|
|
DWORD dwShareStatus = 0;
|
|
|
|
*pbShareConnected = FALSE;
|
|
|
|
lstrcpyn(szShare, pszUNC, ARRAYSIZE(szShare));
|
|
PathStripToRoot(szShare);
|
|
|
|
if (!CSCQueryFileStatus(szShare, &dwShareStatus, NULL, NULL)) {
|
|
if (ConnectShare(szShare)) {
|
|
*pbShareConnected = TRUE;
|
|
if (!CSCQueryFileStatus(szShare, &dwShareStatus, NULL, NULL))
|
|
dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
|
|
} else
|
|
dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
|
|
}
|
|
|
|
if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_NO_CACHING)
|
|
return FALSE;
|
|
|
|
if (pdwShareStatus)
|
|
*pdwShareStatus = dwShareStatus;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
IsSameServer(LPCTSTR pszUNC, LPCTSTR pszServer)
|
|
{
|
|
ULONG nLen;
|
|
LPTSTR pszSlash;
|
|
|
|
pszUNC += 2; // Skip leading backslashes
|
|
|
|
pszSlash = StrChr(pszUNC, TEXT('\\'));
|
|
if (pszSlash)
|
|
nLen = (ULONG)(pszSlash - pszUNC);
|
|
else
|
|
nLen = lstrlen(pszUNC);
|
|
|
|
return (CSTR_EQUAL == CompareString(LOCALE_SYSTEM_DEFAULT,
|
|
NORM_IGNORECASE,
|
|
pszUNC,
|
|
nLen,
|
|
pszServer,
|
|
-1));
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::CheckOneFileStatus(LPCTSTR pszItem,
|
|
DWORD dwAttr, // SFGAO_* flags
|
|
BOOL bShareChecked,
|
|
LPDWORD pdwStatus) // CSC_PROP_* flags
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL bShareConnected = FALSE;
|
|
DWORD dwHintFlags = 0;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::CheckOneFileStatus");
|
|
TraceAssert(pszItem && *pszItem);
|
|
TraceAssert(pdwStatus);
|
|
|
|
if (!PathIsUNC(pszItem))
|
|
ExitGracefully(hr, HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME), "Not a network path");
|
|
|
|
// If server is local machine, fail. Don't allow someone to
|
|
// cache a local path via a net share.
|
|
if (IsSameServer(pszItem, m_szLocalMachine))
|
|
ExitGracefully(hr, HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME), "Locally redirected path");
|
|
|
|
// Check whether the share is cacheable
|
|
if (!bShareChecked) {
|
|
DWORD dwShareStatus = 0;
|
|
|
|
if (!ShareIsCacheable(pszItem, &bShareConnected, &dwShareStatus))
|
|
ExitGracefully(hr, E_FAIL, "Share not cacheable");
|
|
|
|
if (dwShareStatus & FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP)
|
|
*pdwStatus |= CSC_PROP_DCON_MODE;
|
|
}
|
|
|
|
// Check the file status
|
|
if (!CSCQueryFileStatus(pszItem, NULL, NULL, &dwHintFlags)) {
|
|
DWORD dwErr = GetLastError();
|
|
if (dwErr != ERROR_FILE_NOT_FOUND) {
|
|
if (NO_ERROR == dwErr)
|
|
dwErr = ERROR_GEN_FAILURE;
|
|
ExitGracefully(hr, HRESULT_FROM_WIN32(dwErr), "CSCQueryFileStatus failed");
|
|
}
|
|
} else {
|
|
if (dwAttr & SFGAO_FOLDER) {
|
|
// CSCQueryFileStatus succeeded, so this folder is in the cache.
|
|
// Enable the sync menu.
|
|
if (PathIsRoot(pszItem)) {
|
|
// Special note for "\\server\share" items: CSCQueryFileStatus
|
|
// can succeed even if nothing on the share is cached. Only
|
|
// enable CSC_PROP_SYNCABLE if something on this share is cached.
|
|
CSCSHARESTATS shareStats;
|
|
CSCGETSTATSINFO si = {SSEF_NONE, // No exclusions
|
|
SSUF_TOTAL, // Interested in total only.
|
|
false, // No access info reqd (faster).
|
|
false};
|
|
|
|
_GetShareStatisticsForUser(pszItem, &si, &shareStats);
|
|
if (shareStats.cTotal)
|
|
*pdwStatus |= CSC_PROP_SYNCABLE;
|
|
} else {
|
|
*pdwStatus |= CSC_PROP_SYNCABLE;
|
|
}
|
|
}
|
|
|
|
const bool bPinSubFolders = CConfig::GetSingleton().AlwaysPinSubFolders();
|
|
if (!(*pdwStatus & CSC_PROP_INHERIT_PIN) &&
|
|
(!(dwAttr & SFGAO_FOLDER) || bPinSubFolders)) {
|
|
TCHAR szParent[MAX_PATH];
|
|
DWORD dwParentHints = 0;
|
|
|
|
// It's a file OR it's a folder and the "AlwaysPinSubFolders"
|
|
// policy is set.. Check whether the parent is pinned.
|
|
lstrcpyn(szParent, pszItem, ARRAYSIZE(szParent));
|
|
PathRemoveFileSpec(szParent);
|
|
if (CSCQueryFileStatus(szParent, NULL, NULL, &dwParentHints)
|
|
&& (dwParentHints & FLAG_CSC_HINT_PIN_USER)) {
|
|
*pdwStatus |= CSC_PROP_INHERIT_PIN;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If it's not pinned, turn off pinned flag
|
|
if (0 == (dwHintFlags & FLAG_CSC_HINT_PIN_USER))
|
|
*pdwStatus &= ~CSC_PROP_PINNED;
|
|
|
|
// If it's not admin pinned, turn off admin pinned flag
|
|
if (0 == (dwHintFlags & FLAG_CSC_HINT_PIN_ADMIN))
|
|
*pdwStatus &= ~CSC_PROP_ADMIN_PINNED;
|
|
|
|
exit_gracefully:
|
|
|
|
if (bShareConnected)
|
|
DisconnectShare(pszItem);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
BOOL
|
|
_PathIsUNCServer(LPCTSTR pszPath)
|
|
{
|
|
int i;
|
|
|
|
if (!pszPath)
|
|
return FALSE;
|
|
|
|
for (i = 0; *pszPath; pszPath++) {
|
|
if (pszPath[0] == TEXT('\\') && pszPath[1]) // don't count a trailing slash
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return (i == 2);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::CheckFileStatus(LPDATAOBJECT pdobj,
|
|
LPDWORD pdwStatus) // CSC_PROP_* flags
|
|
{
|
|
HRESULT hr;
|
|
DWORD dwStatus;
|
|
UINT cItems, i;
|
|
BOOL bShareConnected = FALSE;
|
|
BOOL bShareOK = FALSE;
|
|
LPCTSTR pszItem;
|
|
LPTSTR pszUNC = NULL;
|
|
CIDArray ida;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::CheckFileStatus");
|
|
TraceAssert(pdobj != NULL);
|
|
TraceAssert(IsCSCEnabled());
|
|
|
|
if (pdwStatus != NULL)
|
|
*pdwStatus = 0;
|
|
|
|
// Assume that everything is both user and system pinned. If anything
|
|
// is not pinned, clear the appropriate flag and treat the entire
|
|
// selection as non-pinned.
|
|
dwStatus = CSC_PROP_PINNED | CSC_PROP_ADMIN_PINNED;
|
|
|
|
hr = ida.Initialize(pdobj);
|
|
FailGracefully(hr, "Can't get ID List format from data object");
|
|
|
|
cItems = ida.Count();
|
|
if (cItems < 1)
|
|
ExitGracefully(hr, E_FAIL, "No items");
|
|
|
|
if (cItems > 1)
|
|
dwStatus |= CSC_PROP_MULTISEL;
|
|
|
|
// Check the parent path
|
|
pszItem = ida.GetItemPath(0);
|
|
if (PathIsUNC(pszItem) && !_PathIsUNCServer(pszItem)) {
|
|
DWORD dwShareStatus = 0;
|
|
|
|
if (!ShareIsCacheable(pszItem, &bShareConnected, &dwShareStatus))
|
|
ExitGracefully(hr, E_FAIL, "Share not cacheable");
|
|
|
|
if (dwShareStatus & FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP)
|
|
dwStatus |= CSC_PROP_DCON_MODE;
|
|
|
|
// No need to check share status again inside CheckOneFileStatus
|
|
bShareOK = TRUE;
|
|
}
|
|
|
|
// Loop over each selected item
|
|
for (i = 1; i <= cItems; i++) {
|
|
// Get the attributes
|
|
DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_FOLDER;
|
|
hr = ida.GetItemAttributes(i, &dwAttr);
|
|
FailGracefully(hr, "Unable to get item attributes");
|
|
|
|
if (!(dwAttr & SFGAO_FILESYSTEM))
|
|
ExitGracefully(hr, E_FAIL, "Not a filesystem object");
|
|
|
|
// Get the path
|
|
pszItem = ida.GetItemPath(i);
|
|
if (!pszItem)
|
|
ExitGracefully(hr, E_FAIL, "Unable to get item path");
|
|
|
|
// Is it a shortcut?
|
|
if (dwAttr & SFGAO_LINK) {
|
|
LPTSTR pszTarget = NULL;
|
|
|
|
// Check the target
|
|
GetLinkTarget(pszItem, NULL, &pszTarget);
|
|
if (pszTarget) {
|
|
hr = CheckOneFileStatus(pszTarget, 0, FALSE, &dwStatus);
|
|
LocalFreeString(&pszTarget);
|
|
|
|
if (SUCCEEDED(hr)
|
|
&& !PathIsUNC(pszItem)) {
|
|
// The link is local, but the target is remote, so don't
|
|
// bother checking status of the link itself. Just go
|
|
// with the target status and move on to the next item.
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the parent is "My Computer", then the item path will look
|
|
// like "X:\" here. Also, if the path is local (in which case we're
|
|
// only interested in link targets), then the path will be a drive
|
|
// letter path. In all cases, continue only if we can get a UNC path.
|
|
if (!PathIsUNC(pszItem)) {
|
|
GetRemotePath(pszItem, &pszUNC);
|
|
if (!pszUNC || !PathIsUNC(pszUNC))
|
|
ExitGracefully(hr, E_FAIL, "Not a net path");
|
|
|
|
// Switch to the UNC path
|
|
pszItem = pszUNC;
|
|
}
|
|
|
|
hr = CheckOneFileStatus(pszItem, dwAttr, bShareOK, &dwStatus);
|
|
FailGracefully(hr, "File not cacheable");
|
|
|
|
LocalFreeString(&pszUNC);
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
if (bShareConnected)
|
|
DisconnectShare(ida.GetItemPath(0));
|
|
|
|
LocalFreeString(&pszUNC);
|
|
|
|
if (SUCCEEDED(hr) && pdwStatus != NULL)
|
|
*pdwStatus = dwStatus;
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
// Determines if a folder has subfolders.
|
|
// Returns:
|
|
// S_OK = Has subfolders.
|
|
// S_FALSE = No subfolders.
|
|
// E_OUTOFMEMORY = Insufficient memory.
|
|
|
|
HRESULT
|
|
CCscShellExt::FolderHasSubFolders(
|
|
LPCTSTR pszPath,
|
|
CscFilenameList* pfnl
|
|
)
|
|
{
|
|
if (NULL == pszPath || TEXT('\0') == *pszPath)
|
|
return E_INVALIDARG;
|
|
|
|
HRESULT hr = S_FALSE;
|
|
const TCHAR szWildcard[] = TEXT("*.*");
|
|
UINT cchFolder = lstrlen(pszPath) + 1; // +1 for '\\'
|
|
LPTSTR pszTemp = (LPTSTR)LocalAlloc(LPTR, (cchFolder + MAX_PATH + 1) * sizeof(TCHAR));
|
|
if (NULL != pszTemp) {
|
|
PathCombine(pszTemp, pszPath, szWildcard);
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hFind = FindFirstFile(pszTemp, &fd);
|
|
if (INVALID_HANDLE_VALUE != hFind) {
|
|
do {
|
|
if ((FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes) && !PathIsDotOrDotDot(fd.cFileName)) {
|
|
if (IsHiddenSystem(fd.dwFileAttributes)) {
|
|
// This subfolder is "super hidden". Build the full path
|
|
// and silently add it to the file list, but don't set the
|
|
// result to S_OK (we don't want superhidden subfolders to
|
|
// cause prompts).
|
|
lstrcpy(&pszTemp[cchFolder], fd.cFileName);
|
|
pfnl->AddFile(pszTemp, true);
|
|
} else
|
|
hr = S_OK; // don't break, there may be superhidden folders
|
|
}
|
|
} while (FindNextFile(hFind, &fd));
|
|
FindClose(hFind);
|
|
} else {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
LocalFree(pszTemp);
|
|
} else {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::BuildFileList(LPDATAOBJECT pdobj,
|
|
HWND hwndOwner,
|
|
CscFilenameList* pfnl,
|
|
LPBOOL pbSubFolders)
|
|
{
|
|
HRESULT hr;
|
|
DWORD dwErr;
|
|
UINT cItems, i;
|
|
LPCTSTR pszItem;
|
|
LPTSTR pszUNC = NULL;
|
|
CIDArray ida;
|
|
BOOL bDirectory;
|
|
HCURSOR hCur;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::BuildFileList");
|
|
TraceAssert(pdobj != NULL);
|
|
TraceAssert(pfnl != NULL);
|
|
|
|
hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
hr = ida.Initialize(pdobj);
|
|
FailGracefully(hr, "Can't get ID List format from data object");
|
|
|
|
cItems = ida.Count();
|
|
if (cItems < 1)
|
|
ExitGracefully(hr, E_FAIL, "No items");
|
|
|
|
// Loop over each selected item
|
|
for (i = 1; i <= cItems; i++) {
|
|
// Get the attributes
|
|
DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_FOLDER;
|
|
hr = ida.GetItemAttributes(i, &dwAttr);
|
|
FailGracefully(hr, "Unable to get item attributes");
|
|
|
|
if (!(dwAttr & SFGAO_FILESYSTEM))
|
|
continue;
|
|
|
|
// Get the path
|
|
pszItem = ida.GetItemPath(i);
|
|
if (!pszItem)
|
|
ExitGracefully(hr, E_FAIL, "Unable to get item path");
|
|
|
|
// Is it a shortcut?
|
|
if (dwAttr & SFGAO_LINK) {
|
|
LPTSTR pszTarget = NULL;
|
|
|
|
// Check the target
|
|
GetLinkTarget(pszItem, hwndOwner, &pszTarget);
|
|
if (pszTarget) {
|
|
// Add the target to the file list
|
|
if (!pfnl->FileExists(pszTarget, false))
|
|
pfnl->AddFile(pszTarget, false);
|
|
|
|
LocalFreeString(&pszTarget);
|
|
}
|
|
}
|
|
|
|
// If the path is local or the parent is "My Computer", then the item
|
|
// path will be a drive letter path. In all cases, add the file to
|
|
// the list only if we can get a UNC path.
|
|
if (!PathIsUNC(pszItem)) {
|
|
GetRemotePath(pszItem, &pszUNC);
|
|
if (!pszUNC || !PathIsUNC(pszUNC))
|
|
continue;
|
|
|
|
// Switch to the UNC path
|
|
pszItem = pszUNC;
|
|
}
|
|
|
|
bDirectory = (dwAttr & SFGAO_FOLDER);
|
|
|
|
if (pbSubFolders && bDirectory && !*pbSubFolders)
|
|
*pbSubFolders = (S_OK == FolderHasSubFolders(pszItem, pfnl));
|
|
|
|
// Add the item to the file list
|
|
pfnl->AddFile(pszItem, !!bDirectory);
|
|
|
|
// If it's an html file, look for a directory of the same name
|
|
// and add it to the file list if necessary.
|
|
|
|
// BUGBUG we're supposed to look for a localized version of "Files"
|
|
// tacked on to the root name. For example, given "foo.htm" we
|
|
// should look for a directory named "foo Files" where the "Files"
|
|
// part comes from a list of localized strings provided by Office.
|
|
// This is a stupid way to go.
|
|
|
|
if (!bDirectory && PathIsHTMLFile(pszItem)) {
|
|
// Truncate the path
|
|
LPTSTR pszExtn = PathFindExtension(pszItem);
|
|
if (pszExtn)
|
|
*pszExtn = NULL;
|
|
|
|
// Check for existence
|
|
dwAttr = GetFileAttributes(pszItem);
|
|
|
|
if ((DWORD)-1 != dwAttr && (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
|
|
pfnl->AddFile(pszItem, true);
|
|
}
|
|
|
|
LocalFreeString(&pszUNC);
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
LocalFreeString(&pszUNC);
|
|
|
|
SetCursor(hCur);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
BOOL
|
|
CCscShellExt::ConnectShare(LPCTSTR pszUNC)
|
|
{
|
|
NETRESOURCE nr;
|
|
DWORD dwResult;
|
|
DWORD dwErr;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::ConnectShare");
|
|
TraceAssert(pszUNC && *pszUNC);
|
|
|
|
nr.dwType = RESOURCETYPE_DISK;
|
|
nr.lpLocalName = NULL;
|
|
nr.lpRemoteName = (LPTSTR)pszUNC;
|
|
nr.lpProvider = NULL;
|
|
|
|
dwErr = WNetUseConnection(NULL, &nr, NULL, NULL, 0, NULL, NULL, &dwResult);
|
|
|
|
Trace((TEXT("Connecting %s (%d)"), pszUNC, dwErr));
|
|
|
|
TraceLeaveValue(NOERROR == dwErr);
|
|
}
|
|
|
|
|
|
BOOL
|
|
CCscShellExt::DisconnectShare(LPCTSTR pszUNC)
|
|
{
|
|
DWORD dwErr;
|
|
TCHAR szShare[MAX_PATH];
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::DisconnectShare");
|
|
TraceAssert(pszUNC && *pszUNC);
|
|
|
|
lstrcpyn(szShare, pszUNC, ARRAYSIZE(szShare));
|
|
PathStripToRoot(szShare);
|
|
|
|
dwErr = WNetCancelConnection2(szShare, 0, FALSE);
|
|
|
|
Trace((TEXT("Disconnecting %s (%d)"), pszUNC, dwErr));
|
|
|
|
TraceLeaveValue(NOERROR == dwErr);
|
|
}
|
|
|
|
|
|
DWORD WINAPI
|
|
CCscShellExt::_UnpinFilesThread(LPVOID pvThreadData)
|
|
{
|
|
HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
|
|
PCSC_UNPIN_DATA pUnpinData = reinterpret_cast<PCSC_UNPIN_DATA>(pvThreadData);
|
|
|
|
if (pUnpinData) {
|
|
CscUnpinFileList(pUnpinData->pNamelist,
|
|
(pUnpinData->dwUpdateFlags & CSC_UPDATE_PIN_RECURSE),
|
|
NULL, NULL, 0);
|
|
delete pUnpinData->pNamelist;
|
|
LocalFree(pUnpinData);
|
|
}
|
|
|
|
FreeLibraryAndExitThread(hInstThisDll, 0);
|
|
return 0;
|
|
}
|
|
|
|
|
|
INT_PTR CALLBACK
|
|
CCscShellExt::_ConfirmPinDlgProc(HWND hDlg,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
INT_PTR bResult = TRUE;
|
|
switch (uMsg) {
|
|
case WM_INITDIALOG:
|
|
CheckRadioButton(hDlg, IDC_PIN_NO_RECURSE, IDC_PIN_RECURSE, IDC_PIN_RECURSE);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam)) {
|
|
case IDCANCEL:
|
|
EndDialog(hDlg, IDCANCEL);
|
|
break;
|
|
|
|
case IDOK:
|
|
// Return IDYES to indicate that the operation should be recursive.
|
|
// Return IDNO to indicate no recursion.
|
|
EndDialog(hDlg, BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_PIN_RECURSE) ? IDYES : IDNO);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
bResult = FALSE; // message not handled
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
|
|
// Support for recursively unpinning a tree with progress updates
|
|
|
|
typedef struct _UNPIN_FILES_DATA
|
|
{
|
|
BOOL bSubfolders;
|
|
PFN_UNPINPROGRESSPROC pfnProgressCB;
|
|
LPARAM lpContext;
|
|
} UNPIN_FILES_DATA, * PUNPIN_FILES_DATA;
|
|
|
|
DWORD WINAPI
|
|
_UnpinCallback(LPCTSTR pszItem,
|
|
ENUM_REASON eReason,
|
|
DWORD /*dwStatus*/,
|
|
DWORD dwHintFlags,
|
|
DWORD dwPinCount,
|
|
LPWIN32_FIND_DATA pFind32,
|
|
LPARAM lpContext)
|
|
{
|
|
PUNPIN_FILES_DATA pufd = reinterpret_cast<PUNPIN_FILES_DATA>(lpContext);
|
|
|
|
// Skip folders if we aren't recursing
|
|
if (eReason == ENUM_REASON_FOLDER_BEGIN && !pufd->bSubfolders)
|
|
return CSCPROC_RETURN_SKIP;
|
|
|
|
// Update progress
|
|
if (pufd->pfnProgressCB) {
|
|
DWORD dwResult = (*pufd->pfnProgressCB)(pszItem, pufd->lpContext);
|
|
if (CSCPROC_RETURN_CONTINUE != dwResult)
|
|
return dwResult;
|
|
}
|
|
|
|
// Unpin the item if it's pinned. For folders,
|
|
// do this before recursing.
|
|
if ((eReason == ENUM_REASON_FILE || eReason == ENUM_REASON_FOLDER_BEGIN)
|
|
&& (dwHintFlags & FLAG_CSC_HINT_PIN_USER)) {
|
|
CSCUnpinFile(pszItem,
|
|
FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER,
|
|
NULL,
|
|
NULL,
|
|
&dwHintFlags);
|
|
|
|
ShellChangeNotify(pszItem, pFind32, FALSE);
|
|
}
|
|
|
|
// Delete items that are no longer pinned. For folders,
|
|
// do this after recursing.
|
|
if (eReason == ENUM_REASON_FILE || eReason == ENUM_REASON_FOLDER_END) {
|
|
if (!dwHintFlags && !dwPinCount)
|
|
CscDelete(pszItem);
|
|
|
|
// If this is a folder, send a separate UpdateDir notification.
|
|
// It's possible that the folder isn't empty yet (might contain
|
|
// a pinned folder for example) so the CscDelete may fail. In
|
|
// such a case, we want any views of this folder to update.
|
|
// (The above notification is an UpdateItem, which only removes
|
|
// the pinned icon overlay.)
|
|
if (eReason == ENUM_REASON_FOLDER_END)
|
|
ShellChangeNotify(pszItem, pFind32, TRUE, SHCNE_UPDATEDIR);
|
|
}
|
|
|
|
return CSCPROC_RETURN_CONTINUE;
|
|
}
|
|
|
|
DWORD
|
|
_UnpinOneShare(CscFilenameList* pfnl,
|
|
CscFilenameList::HSHARE hShare,
|
|
PUNPIN_FILES_DATA pufd)
|
|
{
|
|
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
|
|
LPCTSTR pszFile;
|
|
LPCTSTR pszShare = pfnl->GetShareName(hShare);
|
|
CscFilenameList::FileIter fi = pfnl->CreateFileIterator(hShare);
|
|
|
|
// Iterate over the filenames associated with the share.
|
|
while (pszFile = fi.Next()) {
|
|
TCHAR szFullPath[MAX_PATH];
|
|
TCHAR szRelativePath[MAX_PATH];
|
|
WIN32_FIND_DATA fd;
|
|
ULONG cchFile = lstrlen(pszFile) + 1; // include NULL
|
|
DWORD dwPinCount = 0;
|
|
DWORD dwHintFlags = 0;
|
|
|
|
ZeroMemory(&fd, sizeof(fd));
|
|
fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
// Directories have a trailing "\*"
|
|
if (StrChr(pszFile, TEXT('*'))) {
|
|
// It's a directory. Trim off the "\*"
|
|
cchFile -= 2;
|
|
fd.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
// When unpinning at the share level, pszFile points to "*"
|
|
// and cchFile is now zero.
|
|
}
|
|
|
|
szRelativePath[0] = TEXT('\0');
|
|
lstrcpyn(szRelativePath, pszFile, min(cchFile, ARRAYSIZE(szRelativePath)));
|
|
|
|
// Build the full path
|
|
PathCombine(szFullPath, pszShare, szRelativePath);
|
|
|
|
pszFile = PathFindFileName(szFullPath);
|
|
lstrcpyn(fd.cFileName, pszFile ? pszFile : szFullPath, ARRAYSIZE(fd.cFileName));
|
|
|
|
// Update progress
|
|
if (pufd->pfnProgressCB) {
|
|
dwResult = (*pufd->pfnProgressCB)(szFullPath, pufd->lpContext);
|
|
switch (dwResult) {
|
|
case CSCPROC_RETURN_SKIP:
|
|
continue;
|
|
case CSCPROC_RETURN_ABORT:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Unpin it
|
|
CSCUnpinFile(szFullPath,
|
|
FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER,
|
|
NULL,
|
|
&dwPinCount,
|
|
&dwHintFlags);
|
|
|
|
ShellChangeNotify(szFullPath, &fd, FALSE);
|
|
|
|
// If it's a directory, unpin its contents
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
_CSCEnumDatabase(szFullPath,
|
|
pufd->bSubfolders,
|
|
_UnpinCallback,
|
|
(LPARAM)pufd);
|
|
ShellChangeNotify(szFullPath, &fd, TRUE, SHCNE_UPDATEDIR);
|
|
}
|
|
|
|
// Is it still pinned?
|
|
if (!dwHintFlags && !dwPinCount) {
|
|
// Remove it from the cache (folders may still contain children
|
|
// so we expect this to fail sometimes).
|
|
CscDelete(szFullPath);
|
|
|
|
// Update the parent folder
|
|
PathRemoveFileSpec(szFullPath);
|
|
pszFile = PathFindFileName(szFullPath);
|
|
lstrcpyn(fd.cFileName, pszFile ? pszFile : szFullPath, ARRAYSIZE(fd.cFileName));
|
|
fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
ShellChangeNotify(szFullPath, &fd, TRUE, SHCNE_UPDATEDIR);
|
|
}
|
|
}
|
|
|
|
return dwResult;
|
|
}
|
|
|
|
void
|
|
CscUnpinFileList(CscFilenameList* pfnl,
|
|
BOOL bSubfolders,
|
|
LPCTSTR pszShare,
|
|
PFN_UNPINPROGRESSPROC pfnProgressCB,
|
|
LPARAM lpContext)
|
|
{
|
|
UNPIN_FILES_DATA ufd;
|
|
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
|
|
CscFilenameList::HSHARE hShare;
|
|
|
|
if (NULL == pfnl || !pfnl->IsValid() || 0 == pfnl->GetFileCount())
|
|
return;
|
|
|
|
ufd.bSubfolders = bSubfolders;
|
|
ufd.pfnProgressCB = pfnProgressCB;
|
|
ufd.lpContext = lpContext;
|
|
|
|
if (pszShare) // enumerate this share only
|
|
{
|
|
if (pfnl->GetShareHandle(pszShare, &hShare))
|
|
_UnpinOneShare(pfnl, hShare, &ufd);
|
|
} else // enumerate everything in the list
|
|
{
|
|
CscFilenameList::ShareIter si = pfnl->CreateShareIterator();
|
|
|
|
while (si.Next(&hShare) && dwResult != CSCPROC_RETURN_ABORT) {
|
|
dwResult = _UnpinOneShare(pfnl, hShare, &ufd);
|
|
}
|
|
}
|
|
|
|
// Flush the shell notify queue
|
|
ShellChangeNotify(NULL, TRUE);
|
|
}
|