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.
1539 lines
50 KiB
1539 lines
50 KiB
// 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 "msgbox.h"
|
|
#include "strings.h"
|
|
#include "nopin.h"
|
|
#include <ccstock.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
|
|
{
|
|
CscFilenameList *pNamelist;
|
|
DWORD dwUpdateFlags;
|
|
HWND hwndOwner;
|
|
BOOL bOffline;
|
|
} CSC_UNPIN_DATA;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Shell extension object implementation //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
STDAPI CCscShellExt::CreateInstance(REFIID riid, LPVOID *ppv)
|
|
{
|
|
HRESULT hr;
|
|
CCscShellExt *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()
|
|
{
|
|
ASSERT( 0 != m_cRef );
|
|
ULONG cRef = InterlockedDecrement(&m_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
// IShellExtInit
|
|
|
|
STDMETHODIMP CCscShellExt::Initialize(LPCITEMIDLIST /*pidlFolder*/, IDataObject *pdobj, HKEY /*hKeyProgID*/)
|
|
{
|
|
IUnknown_Set((IUnknown **)&m_lpdobj, pdobj);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// IContextMenu
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
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())
|
|
{
|
|
if (SUCCEEDED(hr = CanAllFilesBePinned(m_lpdobj)))
|
|
{
|
|
if (S_OK == hr)
|
|
{
|
|
mii.fState = MFS_ENABLED; // All files in selection can be pinned.
|
|
}
|
|
else
|
|
{
|
|
mii.fState = MFS_DISABLED; // 1+ files in selection cannot be pinned.
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
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)
|
|
{
|
|
// 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;
|
|
CSC_UNPIN_DATA *pUnpinData = (CSC_UNPIN_DATA *)LocalAlloc(LPTR, sizeof(*pUnpinData));
|
|
|
|
//
|
|
// 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;
|
|
pUnpinData->bOffline = !!(m_dwUIStatus & CSC_PROP_DCON_MODE);
|
|
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)
|
|
{
|
|
if (0 == LoadString(g_hInstance, iCmd ? IDS_HELP_UPDATE_SEL : IDS_HELP_PIN, (LPTSTR)pszString, cchMax))
|
|
{
|
|
hr = ResultFromLastError();
|
|
}
|
|
}
|
|
else if (uFlags == GCS_VERB)
|
|
{
|
|
hr = StringCchCopy((LPTSTR)pszString, cchMax, iCmd ? TEXT(STR_SYNC_VERB) : ((m_dwUIStatus & CSC_PROP_PINNED) ? TEXT(STR_UNPIN_VERB) : TEXT(STR_PIN_VERB)));
|
|
}
|
|
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;
|
|
|
|
//
|
|
// Make sure we have a UNC path
|
|
//
|
|
GetRemotePath(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)
|
|
{
|
|
//
|
|
// 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]
|
|
//
|
|
// Most of these were fixed for Windows 2000. If we
|
|
// hit the assertion below we should file a bug and
|
|
// get Shishir to fix it.
|
|
// [jeffreys - 1/24/2000]
|
|
//
|
|
if (0 == dwErr)
|
|
{
|
|
ASSERTMSG(FALSE, "CSCQueryFileStatus failed with error = 0");
|
|
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))
|
|
{
|
|
//
|
|
// Check to see if pinning is disallowed by system policy.
|
|
//
|
|
hr = m_NoPinList.IsPinAllowed(pszUNC);
|
|
if (S_OK == hr)
|
|
{
|
|
hr = S_FALSE; // Reset
|
|
//
|
|
// 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).
|
|
// Also, policy allows pinning of this file/folder.
|
|
//
|
|
// 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_USER)
|
|
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)
|
|
{
|
|
// Use positive #'s for indexes, negative for ID's (NT only)
|
|
*pIndex = -IDI_PIN_OVERLAY;
|
|
*pdwFlags = (ISIOI_ICONFILE | ISIOI_ICONINDEX);
|
|
return StringCchCopy(pwszIconFile, cchMax, c_szDllName);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CCscShellExt::GetPriority (int * pIPriority)
|
|
{
|
|
*pIPriority = 1;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// CCscShellExt implementation //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
BOOL
|
|
ShareIsCacheable(LPCTSTR pszUNC, BOOL bPathIsFile, LPTSTR *ppszConnectionName, PDWORD pdwShareStatus)
|
|
{
|
|
TCHAR szShare[MAX_PATH];
|
|
DWORD dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
|
|
|
|
*ppszConnectionName = NULL;
|
|
|
|
// CSCQueryFileStatus can fail for multiple reasons, one of which is that
|
|
// there is no database entry and no existing SMB connection to the share.
|
|
// To handle the no-connection part, we try to connect to the share and
|
|
// retry CSCQueryFileStatus.
|
|
//
|
|
// However, there may be a non-SMB connnection which the SMB RDR doesn't
|
|
// know about, so we have to check for a connection first. If there is a
|
|
// non-SMB connection and we connect again, we would end up disconnecting
|
|
// the pre-existing connection later, since we think we made the connection.
|
|
//
|
|
// If there is a non-SMB connection, then caching is not possible.
|
|
//
|
|
// Note that we can get here without a connection in at least 3 ways:
|
|
// 1. When exploring on \\server and the context menu is for \\server\share.
|
|
// 2. When checking a link target, which may live on a different server
|
|
// than what we're exploring.
|
|
// 3. When checking a folder which is a DFS junction (we need to connect
|
|
// to the 'child' share).
|
|
|
|
|
|
// Use a deep path to get correct results in DFS scenarios, but strip the
|
|
// filename if it's not a directory.
|
|
if (SUCCEEDED(StringCchCopy(szShare, ARRAYSIZE(szShare), pszUNC)))
|
|
{
|
|
if (bPathIsFile)
|
|
{
|
|
PathRemoveFileSpec(szShare);
|
|
}
|
|
|
|
// CSCQueryShareStatus is currently unable to return permissions in
|
|
// some cases (e.g. DFS), so don't use the permission parameters.
|
|
if (!CSCQueryShareStatus(szShare, &dwShareStatus, NULL, NULL, NULL, NULL))
|
|
{
|
|
if (!ShareIsConnected(szShare) && ConnectShare(szShare, ppszConnectionName))
|
|
{
|
|
if (!CSCQueryShareStatus(szShare, &dwShareStatus, NULL, NULL, NULL, NULL))
|
|
{
|
|
dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
|
|
|
|
// We're going to return FALSE; kill the connection
|
|
if (*ppszConnectionName)
|
|
{
|
|
WNetCancelConnection2(*ppszConnectionName, 0, FALSE);
|
|
LocalFreeString(ppszConnectionName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
|
|
}
|
|
}
|
|
}
|
|
|
|
*pdwShareStatus = dwShareStatus;
|
|
|
|
return !((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_NO_CACHING);
|
|
}
|
|
|
|
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;
|
|
LPTSTR pszConnectionName = NULL;
|
|
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
|
|
// To handle DFS correctly, we need to re-check share status for folders,
|
|
// since they may be DFS junction points and have different cache settings.
|
|
if (!bShareChecked || (dwAttr & SFGAO_FOLDER))
|
|
{
|
|
DWORD dwShareStatus = 0;
|
|
|
|
if (!ShareIsCacheable(pszItem, !(dwAttr & SFGAO_FOLDER), &pszConnectionName, &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.
|
|
if (SUCCEEDED(StringCchCopy(szParent, ARRAYSIZE(szParent), pszItem))
|
|
&& PathRemoveFileSpec(szParent)
|
|
&& 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 (pszConnectionName)
|
|
{
|
|
WNetCancelConnection2(pszConnectionName, 0, FALSE);
|
|
LocalFreeString(&pszConnectionName);
|
|
}
|
|
|
|
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(IDataObject *pdobj, DWORD *pdwStatus) // CSC_PROP_* flags
|
|
{
|
|
LPTSTR pszConnectionName = NULL;
|
|
UINT i;
|
|
BOOL bShareOK = FALSE;
|
|
TCHAR szItem[MAX_PATH];
|
|
CIDArray ida;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::CheckFileStatus");
|
|
TraceAssert(pdobj != NULL);
|
|
TraceAssert(IsCSCEnabled());
|
|
|
|
if (pdwStatus)
|
|
*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.
|
|
DWORD dwStatus = CSC_PROP_PINNED | CSC_PROP_ADMIN_PINNED;
|
|
|
|
HRESULT hr = ida.Initialize(pdobj);
|
|
FailGracefully(hr, "Can't get ID List format from data object");
|
|
|
|
if (ida.Count() > 1)
|
|
dwStatus |= CSC_PROP_MULTISEL;
|
|
|
|
// Check the parent path
|
|
hr = ida.GetFolderPath(szItem, ARRAYSIZE(szItem));
|
|
FailGracefully(hr, "No parent path");
|
|
|
|
if (ida.Count() > 1 && PathIsUNC(szItem) && !_PathIsUNCServer(szItem))
|
|
{
|
|
DWORD dwShareStatus = 0;
|
|
|
|
if (!ShareIsCacheable(szItem, FALSE, &pszConnectionName, &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 = 0; i < ida.Count(); i++)
|
|
{
|
|
// Get the attributes
|
|
DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_FOLDER;
|
|
hr = ida.GetItemPath(i, szItem, ARRAYSIZE(szItem), &dwAttr);
|
|
FailGracefully(hr, "Unable to get item attributes");
|
|
|
|
if (!(dwAttr & SFGAO_FILESYSTEM))
|
|
ExitGracefully(hr, E_FAIL, "Not a filesystem object");
|
|
|
|
// Is it a shortcut?
|
|
if (dwAttr & SFGAO_LINK)
|
|
{
|
|
LPTSTR pszTarget = NULL;
|
|
|
|
// Check the target
|
|
GetLinkTarget(szItem, &pszTarget);
|
|
if (pszTarget)
|
|
{
|
|
hr = CheckOneFileStatus(pszTarget, 0, FALSE, &dwStatus);
|
|
LocalFreeString(&pszTarget);
|
|
|
|
if (SUCCEEDED(hr) && !PathIsUNC(szItem))
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
hr = CheckOneFileStatus(szItem, dwAttr, bShareOK, &dwStatus);
|
|
FailGracefully(hr, "File not cacheable");
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
if (pszConnectionName)
|
|
{
|
|
WNetCancelConnection2(pszConnectionName, 0, FALSE);
|
|
LocalFreeString(&pszConnectionName);
|
|
}
|
|
|
|
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;
|
|
size_t cchBuffer = lstrlen(pszPath) + 1 + MAX_PATH;
|
|
LPTSTR pszTemp = (LPTSTR)LocalAlloc(LPTR, cchBuffer * sizeof(TCHAR));
|
|
if (NULL != pszTemp)
|
|
{
|
|
LPTSTR pszEnd;
|
|
|
|
// We allocated more than enough to hold pszPath + "\\*", so
|
|
// this should never fail.
|
|
StringCchCopyEx(pszTemp, cchBuffer, pszPath, &pszEnd, &cchBuffer, 0);
|
|
ASSERT(pszEnd > pszTemp && *(pszEnd-1) != TEXT('\\'));
|
|
|
|
StringCchCopy(pszEnd, cchBuffer, TEXT("\\*"));
|
|
pszEnd++; // move past '\\'
|
|
cchBuffer--;
|
|
|
|
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).
|
|
if (SUCCEEDED(StringCchCopy(pszEnd, cchBuffer, 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(IDataObject *pdobj, HWND hwndOwner,
|
|
CscFilenameList *pfnl, LPBOOL pbSubFolders)
|
|
{
|
|
UINT i;
|
|
TCHAR szItem[MAX_PATH];
|
|
CIDArray ida;
|
|
BOOL bDirectory;
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::BuildFileList");
|
|
TraceAssert(pdobj != NULL);
|
|
TraceAssert(pfnl != NULL);
|
|
|
|
HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
HRESULT hr = ida.Initialize(pdobj);
|
|
FailGracefully(hr, "Can't get ID List format from data object");
|
|
|
|
// Loop over each selected item
|
|
for (i = 0; i < ida.Count(); i++)
|
|
{
|
|
// Get the attributes
|
|
DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_FOLDER;
|
|
hr = ida.GetItemPath(i, szItem, ARRAYSIZE(szItem), &dwAttr);
|
|
FailGracefully(hr, "Unable to get item attributes");
|
|
|
|
if (!(dwAttr & SFGAO_FILESYSTEM))
|
|
continue;
|
|
|
|
// Is it a shortcut?
|
|
if (dwAttr & SFGAO_LINK)
|
|
{
|
|
LPTSTR pszTarget = NULL;
|
|
|
|
// Check the target
|
|
GetLinkTarget(szItem, &pszTarget);
|
|
if (pszTarget)
|
|
{
|
|
// Add the target to the file list
|
|
if (!pfnl->FileExists(pszTarget, false))
|
|
pfnl->AddFile(pszTarget, false);
|
|
|
|
LocalFreeString(&pszTarget);
|
|
}
|
|
}
|
|
|
|
bDirectory = (dwAttr & SFGAO_FOLDER);
|
|
|
|
if (pbSubFolders && bDirectory && !*pbSubFolders)
|
|
*pbSubFolders = (S_OK == FolderHasSubFolders(szItem, pfnl));
|
|
|
|
// Add the item to the file list
|
|
pfnl->AddFile(szItem, !!bDirectory);
|
|
|
|
// If it's an html file, look for a directory of the same name
|
|
// and add it to the file list if necessary.
|
|
//
|
|
// 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.
|
|
// We don't bother and just look for a directory named "foo".
|
|
//
|
|
if (!bDirectory && PathIsHTMLFile(szItem))
|
|
{
|
|
// Truncate the path
|
|
LPTSTR pszExtn = PathFindExtension(szItem);
|
|
if (pszExtn)
|
|
*pszExtn = NULL;
|
|
|
|
// Check for existence
|
|
dwAttr = GetFileAttributes(szItem);
|
|
|
|
if ((DWORD)-1 != dwAttr && (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
|
|
pfnl->AddFile(szItem, true);
|
|
}
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
SetCursor(hCur);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
#define _WNET_ENUM_BUFFER_SIZE 4000
|
|
|
|
BOOL
|
|
ShareIsConnected(LPCTSTR pszUNC)
|
|
{
|
|
HANDLE hEnum;
|
|
PVOID pBuffer;
|
|
BOOL fShareIsConnected = FALSE;
|
|
|
|
pBuffer = (PVOID)LocalAlloc(LMEM_FIXED, _WNET_ENUM_BUFFER_SIZE);
|
|
|
|
if (NULL != pBuffer)
|
|
{
|
|
//
|
|
// Enumerate all connected disk resources
|
|
//
|
|
if (NO_ERROR == WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, 0, NULL, &hEnum))
|
|
{
|
|
//
|
|
// Look at each connected share. If we find the share we're looking for,
|
|
// we know it's connected so we can quit looking.
|
|
//
|
|
while (!fShareIsConnected)
|
|
{
|
|
LPNETRESOURCE pnr;
|
|
DWORD cEnum = (DWORD)-1;
|
|
DWORD dwBufferSize = _WNET_ENUM_BUFFER_SIZE;
|
|
|
|
if (NO_ERROR != WNetEnumResource(hEnum, &cEnum, pBuffer, &dwBufferSize))
|
|
break;
|
|
|
|
for (pnr = (LPNETRESOURCE)pBuffer; cEnum > 0; cEnum--, pnr++)
|
|
{
|
|
if (NULL != pnr->lpRemoteName &&
|
|
0 == lstrcmpi(pnr->lpRemoteName, pszUNC))
|
|
{
|
|
// Found it
|
|
fShareIsConnected = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
WNetCloseEnum(hEnum);
|
|
}
|
|
LocalFree(pBuffer);
|
|
}
|
|
|
|
return fShareIsConnected;
|
|
}
|
|
|
|
|
|
BOOL
|
|
ConnectShare(LPCTSTR pszUNC, LPTSTR *ppszAccessName)
|
|
{
|
|
NETRESOURCE nr;
|
|
DWORD dwResult;
|
|
DWORD dwErr;
|
|
TCHAR szAccessName[MAX_PATH];
|
|
DWORD cchAccessName = ARRAYSIZE(szAccessName);
|
|
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::ConnectShare");
|
|
TraceAssert(pszUNC && *pszUNC);
|
|
|
|
nr.dwType = RESOURCETYPE_DISK;
|
|
nr.lpLocalName = NULL;
|
|
nr.lpRemoteName = (LPTSTR)pszUNC;
|
|
nr.lpProvider = NULL;
|
|
|
|
szAccessName[0] = TEXT('\0');
|
|
|
|
dwErr = WNetUseConnection(NULL,
|
|
&nr,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
szAccessName,
|
|
&cchAccessName,
|
|
&dwResult);
|
|
|
|
Trace((TEXT("Connecting %s (%d)"), pszUNC, dwErr));
|
|
|
|
if (ppszAccessName && NOERROR == dwErr)
|
|
{
|
|
LocalAllocString(ppszAccessName, szAccessName);
|
|
}
|
|
|
|
TraceLeaveValue(NOERROR == dwErr);
|
|
}
|
|
|
|
|
|
DWORD WINAPI
|
|
CCscShellExt::_UnpinFilesThread(LPVOID pvThreadData)
|
|
{
|
|
CSC_UNPIN_DATA *pUnpinData = reinterpret_cast<CSC_UNPIN_DATA *>(pvThreadData);
|
|
if (pUnpinData)
|
|
{
|
|
HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
|
|
if (hInstThisDll)
|
|
{
|
|
CscUnpinFileList(pUnpinData->pNamelist,
|
|
(pUnpinData->dwUpdateFlags & CSC_UPDATE_PIN_RECURSE),
|
|
pUnpinData->bOffline,
|
|
NULL, NULL, 0);
|
|
}
|
|
|
|
delete pUnpinData->pNamelist;
|
|
LocalFree(pUnpinData);
|
|
|
|
if (hInstThisDll)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
//
|
|
// Given an IDataObject ptr representing a selection of files from
|
|
// within the shell, this function determins if pinning of any of
|
|
// the files and directories is disallowed via system policy.
|
|
//
|
|
// Returns: S_OK - All files in data object can be pinned.
|
|
// S_FALSE - At least one file in data object cannot be pinned.
|
|
//
|
|
HRESULT
|
|
CCscShellExt::CanAllFilesBePinned(
|
|
IDataObject *pdtobj
|
|
)
|
|
{
|
|
TraceEnter(TRACE_SHELLEX, "CCscShellExt::CanAllFilesBePinned");
|
|
|
|
//
|
|
// Quick check to see if ANY pin restrictions are in place.
|
|
//
|
|
HRESULT hr = m_NoPinList.IsAnyPinDisallowed();
|
|
if (S_OK == hr)
|
|
{
|
|
//
|
|
// Yes, at least one restriction was read from registry.
|
|
//
|
|
CscFilenameList fnl;
|
|
hr = BuildFileList(m_lpdobj,
|
|
GetDesktopWindow(),
|
|
&fnl,
|
|
NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Iterate over all UNC paths in the data object
|
|
// until we either exhaust the list or find one for which
|
|
// pinning is disallowed.
|
|
//
|
|
CscFilenameList::ShareIter si = fnl.CreateShareIterator();
|
|
CscFilenameList::HSHARE hShare;
|
|
|
|
while(si.Next(&hShare))
|
|
{
|
|
TCHAR szUncPath[MAX_PATH];
|
|
|
|
hr = StringCchCopy(szUncPath, ARRAYSIZE(szUncPath), fnl.GetShareName(hShare));
|
|
if (FAILED(hr) || !PathAddBackslash(szUncPath))
|
|
{
|
|
TraceLeaveResult(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE));
|
|
}
|
|
|
|
const int cchShare = lstrlen(szUncPath);
|
|
|
|
CscFilenameList::FileIter fi = fnl.CreateFileIterator(hShare);
|
|
|
|
LPCTSTR pszFile;
|
|
while(NULL != (pszFile = fi.Next()))
|
|
{
|
|
//
|
|
// Assemble the full UNC path string.
|
|
// If the item is a directory, will need to truncate the trailing
|
|
// "\*" characters.
|
|
//
|
|
hr = StringCchCopy(szUncPath + cchShare, ARRAYSIZE(szUncPath) - cchShare, pszFile);
|
|
if (FAILED(hr))
|
|
{
|
|
TraceLeaveResult(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE));
|
|
}
|
|
LPTSTR pszEnd = szUncPath + lstrlen(szUncPath) - 1;
|
|
while(pszEnd > szUncPath && (TEXT('\\') == *pszEnd || TEXT('*') == *pszEnd))
|
|
{
|
|
*pszEnd-- = TEXT('\0');
|
|
}
|
|
if (S_FALSE == m_NoPinList.IsPinAllowed(szUncPath))
|
|
{
|
|
Trace((TEXT("Policy prevents pinning of \"%s\""), szUncPath));
|
|
TraceLeaveResult(S_FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TraceLeaveResult(SUCCEEDED(hr) ? S_OK : hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Support for recursively unpinning a tree with progress updates
|
|
//
|
|
typedef struct _UNPIN_FILES_DATA
|
|
{
|
|
BOOL bSubfolders;
|
|
BOOL bOffline;
|
|
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))
|
|
{
|
|
if (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)
|
|
{
|
|
if (NOERROR == CscDelete(pszItem) && pufd->bOffline)
|
|
{
|
|
// Removing from the cache while in offline mode means
|
|
// it's no longer available, so remove it from view.
|
|
ShellChangeNotify(pszItem,
|
|
pFind32,
|
|
FALSE,
|
|
(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? SHCNE_RMDIR : SHCNE_DELETE);
|
|
}
|
|
}
|
|
}
|
|
|
|
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];
|
|
WIN32_FIND_DATA fd;
|
|
DWORD dwPinCount = 0;
|
|
DWORD dwHintFlags = 0;
|
|
|
|
ZeroMemory(&fd, sizeof(fd));
|
|
fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
// Build the full path
|
|
if (!PathCombine(szFullPath, pszShare, pszFile))
|
|
{
|
|
// fail instead?
|
|
continue;
|
|
}
|
|
|
|
// Directories have a trailing "\*"
|
|
if (StrChr(pszFile, TEXT('*')))
|
|
{
|
|
// It's a directory. Trim off the "\*"
|
|
PathRemoveFileSpec(szFullPath);
|
|
fd.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
|
|
// 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
|
|
if (CSCUnpinFile(szFullPath,
|
|
FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER,
|
|
NULL,
|
|
&dwPinCount,
|
|
&dwHintFlags))
|
|
{
|
|
StringCchCopy(fd.cFileName, ARRAYSIZE(fd.cFileName), PathFindFileName(szFullPath));
|
|
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);
|
|
}
|
|
|
|
// Is it still pinned?
|
|
if (!dwHintFlags && !dwPinCount)
|
|
{
|
|
// Remove it from the cache (folders may still contain children
|
|
// so we expect this to fail sometimes).
|
|
if (NOERROR == CscDelete(szFullPath) && pufd->bOffline)
|
|
{
|
|
// Removing from the cache while in offline mode means
|
|
// it's no longer available, so remove it from view.
|
|
ShellChangeNotify(szFullPath,
|
|
&fd,
|
|
FALSE,
|
|
(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? SHCNE_RMDIR : SHCNE_DELETE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dwResult;
|
|
}
|
|
|
|
void
|
|
CscUnpinFileList(CscFilenameList *pfnl,
|
|
BOOL bSubfolders,
|
|
BOOL bOffline,
|
|
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.bOffline = bOffline;
|
|
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);
|
|
}
|