windows-nt/Source/XPSP1/NT/shell/ext/cscui/folder.cpp

4256 lines
112 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1999
//
// File: folder.cpp
//
//--------------------------------------------------------------------------
#include "pch.h"
#pragma hdrstop
#include <shguidp.h>
#include <shdguid.h>
#include <shlapip.h>
#include <shlobjp.h>
#include <shsemip.h>
#include "folder.h"
#include "resource.h"
#include "idldata.h"
#include "idlhelp.h"
#include "items.h"
#include "strings.h"
#include "msgbox.h"
#include "sharecnx.h"
#include "msg.h"
#include "security.h"
//
// This module contains several classes. Here's a summary list.
//
// COfflineFilesFolder - Implementation for IShellFolder
//
// COfflineDetails - Implementation for IShellDetails
//
// COfflineFilesViewCallback - Implementation for IShellFolderViewCB
//
// COfflineFilesDropTarget - Implementation for IDropTarget
//
// COfflineFilesViewEnum - Implementation for IEnumSFVViews
//
// CShellObjProxy<T> - Template class that encapsulates the attainment of a
// shell object and item ID list for a given OLID and interface
// type. Also ensures proper cleanup of the interface pointer
// and ID list.
//
// CFolderCache - A simple cache of a bound shell object pointer
// and item ID lists for associated OLIDs. Reduces the number
// of binds required in the shell namespace. A singleton instance
// is used for all cache accesses.
//
// CFolderDeleteHandler - Centralizes folder item deletion code.
//
// CFileTypeCache - Cache of file type descriptions. This reduces the number
// of calls to SHGetFileInfo.
//
//
// Columns
//
enum {
ICOL_NAME = 0,
ICOL_TYPE,
ICOL_SYNCSTATUS,
ICOL_PINSTATUS,
ICOL_ACCESS,
ICOL_SERVERSTATUS,
ICOL_LOCATION,
ICOL_SIZE,
ICOL_DATE,
ICOL_MAX
};
typedef struct
{
short int icol; // column index
short int ids; // Id of string for title
short int cchCol; // Number of characters wide to make column
short int iFmt; // The format of the column;
} COL_DATA;
const COL_DATA c_cols[] = {
{ICOL_NAME, IDS_COL_NAME, 20, LVCFMT_LEFT},
{ICOL_TYPE, IDS_COL_TYPE, 20, LVCFMT_LEFT},
{ICOL_SYNCSTATUS, IDS_COL_SYNCSTATUS, 18, LVCFMT_LEFT},
{ICOL_PINSTATUS, IDS_COL_PINSTATUS, 18, LVCFMT_LEFT},
{ICOL_ACCESS, IDS_COL_ACCESS, 18, LVCFMT_LEFT},
{ICOL_SERVERSTATUS,IDS_COL_SERVERSTATUS,18, LVCFMT_LEFT},
{ICOL_LOCATION, IDS_COL_LOCATION, 18, LVCFMT_LEFT},
{ICOL_SIZE, IDS_COL_SIZE, 16, LVCFMT_RIGHT},
{ICOL_DATE, IDS_COL_DATE, 20, LVCFMT_LEFT}
};
//
// This is a special GUID used by the folder's delete handler to obtain
// the IShellFolderViewCB pointer from the COfflineFilesFolder.
// The delete handler QI's for this "interface". If the folder knows
// about it (only the COfflineFilesFolder will) then it returns it's
// IShellFolderViewCB pointer. See COfflineFilesFolder::QueryInterface()
// and CFolderDeleteHandler::InvokeCommand for usage.
//
// {47862305-0417-11d3-8BED-00C04FA31A66}
static const GUID IID_OfflineFilesFolderViewCB =
{ 0x47862305, 0x417, 0x11d3, { 0x8b, 0xed, 0x0, 0xc0, 0x4f, 0xa3, 0x1a, 0x66 } };
//
// Private message used to enable/disable redraw of the listview
// through the MessageSFVCB method of the folder view callback.
// See CFolderDeleteHandler::DeleteFiles and
// COfflineFilesViewCallback::OnSFVMP_SetViewRedraw for usage.
//
const UINT SFVMP_SETVIEWREDRAW = 1234;
const UINT SFVMP_DELVIEWITEM = 1235;
#if defined(ALIGNMENT_MACHINE)
LONG
__inline
static
uaCompareFileTime(
IN FILETIME CONST UNALIGNED *UaFileTime1,
IN FILETIME CONST UNALIGNED *UaFileTime2
)
{
FILETIME fileTime1;
FILETIME fileTime2;
fileTime1 = *UaFileTime1;
fileTime2 = *UaFileTime2;
return CompareFileTime( &fileTime1, &fileTime2 );
}
#else
#define uaCompareFileTime CompareFileTime
#endif
HRESULT StringToStrRet(LPCTSTR pString, STRRET *pstrret)
{
#ifdef UNICODE
HRESULT hr = SHStrDup(pString, &pstrret->pOleStr);
if (SUCCEEDED(hr))
{
pstrret->uType = STRRET_WSTR;
}
return hr;
#else
pstrret->uType = STRRET_CSTR;
lstrcpyn(pstrret->cStr, pString, ARRAYSIZE(pstrret->cStr));
return S_OK;
#endif
}
//---------------------------------------------------------------------------
// Shell view details
//---------------------------------------------------------------------------
class COfflineDetails : public IShellDetails
{
public:
COfflineDetails(COfflineFilesFolder *pFav);
// *** IUnknown methods ***
STDMETHOD(QueryInterface) (THIS_ REFIID riid, void ** ppv);
STDMETHOD_(ULONG,AddRef) (THIS);
STDMETHOD_(ULONG,Release) (THIS);
// IshellDetails
STDMETHOD(GetDetailsOf)(LPCITEMIDLIST pidl, UINT iColumn, LPSHELLDETAILS pDetails);
STDMETHOD(ColumnClick)(UINT iColumn);
protected:
~COfflineDetails();
COfflineFilesFolder *_pfolder;
LONG _cRef;
};
//---------------------------------------------------------------------------
// Folder view callback
//---------------------------------------------------------------------------
class COfflineFilesViewCallback : public IShellFolderViewCB, IObjectWithSite
{
public:
COfflineFilesViewCallback(COfflineFilesFolder *pfolder);
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// IShellFolderViewCB
STDMETHOD(MessageSFVCB)(UINT uMsg, WPARAM wParam, LPARAM lParam);
// IObjectWithSite
STDMETHOD(SetSite)(IUnknown *punkSite);
STDMETHOD(GetSite)(REFIID riid, void **ppv);
private:
LONG _cRef;
COfflineFilesFolder *_pfolder;
IShellFolderView *_psfv;
HWND m_hwnd;
CRITICAL_SECTION m_cs; // Serialize change notify handling.
~COfflineFilesViewCallback();
DWORD GetChangeNotifyEvents(void) const
{ return (SHCNE_UPDATEITEM | SHCNE_UPDATEDIR | SHCNE_RENAMEITEM | SHCNE_DELETE); }
HRESULT OnSFVM_WindowCreated(HWND hwnd);
HRESULT OnSFVM_AddPropertyPages(DWORD pv, SFVM_PROPPAGE_DATA *ppagedata);
HRESULT OnSFVM_QueryFSNotify(SHChangeNotifyEntry *pfsne);
HRESULT OnSFVM_FSNotify(LPCITEMIDLIST *ppidl, LONG lEvent);
HRESULT OnSFVM_GetNotify(LPITEMIDLIST *ppidl, LONG *plEvents);
HRESULT OnSFVM_GetViews(SHELLVIEWID *pvid, IEnumSFVViews **ppev);
HRESULT OnSFVM_AlterDropEffect(DWORD *pdwEffect, IDataObject *pdtobj);
HRESULT OnSFVMP_SetViewRedraw(BOOL bRedraw);
HRESULT OnSFVMP_DelViewItem(LPCTSTR pszPath);
HRESULT UpdateDir(LPCTSTR pszDir);
HRESULT UpdateItem(LPCTSTR pszItem);
HRESULT UpdateItem(LPCTSTR pszPath, const WIN32_FIND_DATA& fd, DWORD dwStatus, DWORD dwPinCount, DWORD dwHintFlags);
HRESULT RemoveItem(LPCTSTR pszPath);
HRESULT RemoveItem(LPCOLID polid);
HRESULT RemoveItems(LPCTSTR pszDir);
HRESULT RenameItem(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidl);
UINT ItemIndexFromOLID(LPCOLID polid);
HRESULT FindOLID(LPCTSTR pszPath, LPCOLID *ppolid);
void Lock(void)
{ EnterCriticalSection(&m_cs); }
void Unlock(void)
{ LeaveCriticalSection(&m_cs); }
};
//---------------------------------------------------------------------------
// Drop target
//---------------------------------------------------------------------------
class COfflineFilesDropTarget : public IDropTarget
{
public:
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
// IDropTarget
STDMETHODIMP DragEnter(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
STDMETHODIMP DragLeave(void);
STDMETHODIMP Drop(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
static HRESULT CreateInstance(HWND hwnd, REFIID riid, void **ppv);
private:
COfflineFilesDropTarget(HWND hwnd);
~COfflineFilesDropTarget();
bool IsOurDataObject(IDataObject *pdtobj);
LONG m_cRef;
HWND m_hwnd;
LPCONTEXTMENU m_pcm;
bool m_bIsOurData;
};
//---------------------------------------------------------------------------
// View type enumerator
//---------------------------------------------------------------------------
class COfflineFilesViewEnum : public IEnumSFVViews
{
public:
// *** IUnknown methods ***
STDMETHOD(QueryInterface) (REFIID riid, void **ppv);
STDMETHOD_(ULONG,AddRef) (void);
STDMETHOD_(ULONG,Release) (void);
// *** IEnumSFVViews methods ***
STDMETHOD(Next)(ULONG celt, SFVVIEWSDATA **ppData, ULONG *pceltFetched);
STDMETHOD(Skip)(ULONG celt);
STDMETHOD(Reset)(void);
STDMETHOD(Clone)(IEnumSFVViews **ppenum);
static HRESULT CreateInstance(IEnumSFVViews **ppEnum);
protected:
COfflineFilesViewEnum(void);
~COfflineFilesViewEnum(void);
LONG m_cRef;
int m_iAddView;
};
//-------------------------------------------------------------------------
// Shell object proxy
//
// A simple template class to package up the attainment of a
// shell object pointer and item PIDL from our folder cache for a given OLID.
// The caller can then easily call the appropriate shell object function
// through operator ->(). The object automates the release of the shell
// object interface and freeing of the IDList. Caller must call Result()
// to verify validity of contents prior to invoking operator ->().
//
// Usage:
//
// CShellObjProxy<IShellFolder> pxy(IID_IShellFolder, polid);
// if (SUCCEEDED(hr = pxy.Result()))
// {
// hr = pxy->GetIconOf(pxy.ItemIDList(), gil, pnIcon);
// }
//
//-------------------------------------------------------------------------
template <class T>
class CShellObjProxy
{
public:
CShellObjProxy(REFIID riid, LPCOLID polid)
: m_hr(E_INVALIDARG),
m_pObj(NULL),
m_pidlFull(NULL),
m_pidlItem(NULL)
{
if (NULL != polid)
{
m_hr = CFolderCache::Singleton().GetItem(polid,
riid,
(void **)&m_pObj,
&m_pidlFull,
&m_pidlItem);
}
}
~CShellObjProxy(void)
{
if (NULL != m_pidlFull)
ILFree(m_pidlFull);
if (NULL != m_pObj)
m_pObj->Release();
}
HRESULT Result(void) const
{ return m_hr; }
T* operator -> () const
{ return m_pObj; }
LPCITEMIDLIST ItemIDList(void) const
{ return m_pidlItem; }
private:
HRESULT m_hr;
T *m_pObj;
LPITEMIDLIST m_pidlFull;
LPCITEMIDLIST m_pidlItem;
};
//-----------------------------------------------------------------------------------
// Folder cache
//
// The OfflineFiles folder IDList format (OLID) contains fully-qualified UNC paths.
// The folder may (most likely) contain OLIDs from multiple network shares.
// Therefore, when creating IDLists to hand off to the shell's filesystem
// implementations we create fully-qualified IDLists (an expensive operation).
// The folder cache is used to cache these IDLists to reduce the number of calls
// to SHBindToParent. This also speeds up filling of the listview as
// GetAttributesOf(), GetIconIndex() etc. are called many times as the view
// is opened.
//
// The implementation is a simple circular queue to handle aging of items.
// Only three public methods are exposed. GetItem() is used to
// retrieve the IShellFolder ptr and IDList associated with a particular OLID.
// If the item is not in the cache, the implementation obtains the shell folder
// ptr and IDList then caches them for later use. Clear() is used to clear the
// contents of the cache to reduce memory footprint when the Offline Files
// folder is no longer open.
//
// The entries in the queue utilize a handle-envelope idiom to hide memory
// management of the entries from the caching code. This way we can assign
// handle values without copying the actual use-counted entry. Once a use-count
// drops to 0 the actual entry is deleted.
//
// A singleton instance is enforced through a private ctor. Use the
// Singleton() method to obtain a reference to the singleton.
//
// Note that because of the shell's icon thread this cache must be thread-safe.
// A critical section is used for this.
//-----------------------------------------------------------------------------------
class CFolderCache
{
public:
~CFolderCache(void);
//
// Retrieve one item from the cache. Item is added if not in cache.
//
HRESULT GetItem(
LPCOLID polid,
REFIID riid,
void **ppv,
LPITEMIDLIST *ppidl,
LPCITEMIDLIST *ppidlChild);
//
// Clear the cache entry data.
//
void Clear(void);
//
// Return reference to the singleton instance.
//
static CFolderCache& Singleton(void);
private:
//
// Enforce singleton existence.
//
CFolderCache(void);
//
// Prevent copy.
//
CFolderCache(const CFolderCache& rhs);
CFolderCache& operator = (const CFolderCache& rhs);
LPOLID m_polid; // Key olid.
IShellFolder *m_psf; // Cached IShellFolder ptr.
LPITEMIDLIST m_pidl; // Cached shell pidl.
CRITICAL_SECTION m_cs; // For synchronizing cache access.
void Lock(void)
{ EnterCriticalSection(&m_cs); }
void Unlock(void)
{ LeaveCriticalSection(&m_cs); }
};
//----------------------------------------------------------------------------
// COfflineDetails
//----------------------------------------------------------------------------
STDMETHODIMP
COfflineDetails::GetDetailsOf(
LPCITEMIDLIST pidl,
UINT iColumn,
LPSHELLDETAILS pDetails
)
{
TCHAR szTemp[MAX_PATH];
HRESULT hres;
if (!pidl)
{
if (iColumn < ICOL_MAX)
{
pDetails->fmt = c_cols[iColumn].iFmt;
pDetails->cxChar = c_cols[iColumn].cchCol;
LoadString(g_hInstance, c_cols[iColumn].ids, szTemp, ARRAYSIZE(szTemp));
hres = StringToStrRet(szTemp, &pDetails->str);
}
else
{
pDetails->str.uType = STRRET_CSTR;
pDetails->str.cStr[0] = 0;
hres = E_NOTIMPL;
}
}
else
{
LPCOLID polid = _pfolder->_Validate(pidl);
if (polid)
{
hres = S_OK;
// Need to fill in the details
switch (iColumn)
{
case ICOL_TYPE:
_pfolder->_GetTypeString(polid, szTemp, ARRAYSIZE(szTemp));
break;
case ICOL_SYNCSTATUS:
_pfolder->_GetSyncStatusString(polid, szTemp, ARRAYSIZE(szTemp));
break;
case ICOL_PINSTATUS:
_pfolder->_GetPinStatusString(polid, szTemp, ARRAYSIZE(szTemp));
break;
case ICOL_ACCESS:
_pfolder->_GetAccessString(polid, szTemp, ARRAYSIZE(szTemp));
break;
case ICOL_SERVERSTATUS:
_pfolder->_GetServerStatusString(polid, szTemp, ARRAYSIZE(szTemp));
break;
case ICOL_LOCATION:
ualstrcpyn(szTemp, polid->szPath, ARRAYSIZE(szTemp));
break;
case ICOL_SIZE:
{
ULARGE_INTEGER ullSize = {polid->dwFileSizeLow, polid->dwFileSizeHigh};
StrFormatKBSize(ullSize.QuadPart, szTemp, ARRAYSIZE(szTemp));
break;
}
case ICOL_DATE:
SHFormatDateTime(&polid->ft, NULL, szTemp, ARRAYSIZE(szTemp));
break;
default:
hres = E_FAIL;
}
if (SUCCEEDED(hres))
hres = StringToStrRet(szTemp, &pDetails->str);
}
else
hres = E_INVALIDARG;
}
return hres;
}
STDMETHODIMP
COfflineDetails::ColumnClick(
UINT iColumn
)
{
return S_FALSE; // bounce this to the IShellFolderViewCB handler
}
//----------------------------------------------------------------------------
// CFolderCache
//----------------------------------------------------------------------------
//
// This is a very simple cache of one entry.
// Originally I implemented a multi-item cache. This of course had more
// overhead than a single-item cache. The problem is that the access patterns
// in the folder are such that cache hits occur consecutively for the
// same item as the view is filling. Rarely (or never) was there a hit
// for an item other than the most-recently-added item. Therefore, items
// 1 through n-1 were just taking up space. This is why I went back to using
// a single-item cache. [brianau - 5/27/99]
//
//
// Returns a reference to the global shell folder cache.
// Since the folder cache object is a function static it will not be created
// until this function is first called. That also means it will not be
// destroyed until the module is unloaded. That's why we have the Clear() method.
// The FolderViewCallback dtor clears the cache so that we don't have cached
// info laying around in memory while the Offline Files folder isn't open.
// The cache skeleton is very cheap so that's not a problem to leave in memory.
//
CFolderCache&
CFolderCache::Singleton(
void
)
{
static CFolderCache TheFolderCache;
return TheFolderCache;
}
CFolderCache::CFolderCache(
void
) : m_polid(NULL),
m_pidl(NULL),
m_psf(NULL)
{
InitializeCriticalSection(&m_cs);
}
CFolderCache::~CFolderCache(
void
)
{
Clear();
DeleteCriticalSection(&m_cs);
}
//
// Clear the cache by deleting the queue array and
// resetting the head/tail indexes. A subsequent call to
// GetItem() will re-initialize the queue.
//
void
CFolderCache::Clear(
void
)
{
Lock();
if (m_polid)
{
ILFree((LPITEMIDLIST)m_polid);
m_polid = NULL;
}
if (m_pidl)
{
ILFree(m_pidl);
m_pidl = NULL;
}
if (m_psf)
{
m_psf->Release();
m_psf = NULL;
}
Unlock();
}
//
// Retrieve an item from the cache. If not found, bind to
// and cache a new one.
//
HRESULT
CFolderCache::GetItem(
LPCOLID polid,
REFIID riid,
void **ppv,
LPITEMIDLIST *ppidlParent,
LPCITEMIDLIST *ppidlChild
)
{
TraceAssert(NULL != polid);
TraceAssert(NULL != ppv);
TraceAssert(NULL != ppidlParent);
TraceAssert(NULL != ppidlChild);
HRESULT hr = NOERROR;
*ppidlParent = NULL;
*ppidlChild = NULL;
*ppv = NULL;
Lock();
IShellFolder *psf;
LPCITEMIDLIST pidlChild;
LPITEMIDLIST pidl;
if (NULL == m_polid || !ILIsEqual((LPCITEMIDLIST)m_polid, (LPCITEMIDLIST)polid))
{
//
// Cache miss.
//
Clear();
hr = COfflineFilesFolder::OLID_Bind(polid,
IID_IShellFolder,
(void **)&psf,
(LPITEMIDLIST *)&pidl,
&pidlChild);
if (SUCCEEDED(hr))
{
//
// Cache the new item.
//
m_polid = (LPOLID)ILClone((LPCITEMIDLIST)polid);
if (NULL != m_polid)
{
m_pidl = pidl; // Take ownership of pidl from Bind
m_psf = psf; // Use ref count from Bind.
}
else
{
ILFree(pidl);
m_psf->Release();
hr = E_OUTOFMEMORY;
}
}
}
if (SUCCEEDED(hr))
{
//
// Cache hit or we just bound and cached a new item.
//
*ppidlParent = ILClone(m_pidl);
if (NULL != *ppidlParent)
{
*ppidlChild = ILFindLastID(*ppidlParent);
hr = m_psf->QueryInterface(riid, ppv);
}
}
Unlock();
return hr;
}
//----------------------------------------------------------------------------
// CFolderDeleteHandler
//----------------------------------------------------------------------------
CFolderDeleteHandler::CFolderDeleteHandler(
HWND hwndParent,
IDataObject *pdtobj,
IShellFolderViewCB *psfvcb
) : m_hwndParent(hwndParent),
m_pdtobj(pdtobj),
m_psfvcb(psfvcb)
{
TraceAssert(NULL != pdtobj);
if (NULL != m_pdtobj)
m_pdtobj->AddRef();
if (NULL != m_psfvcb)
m_psfvcb->AddRef();
}
CFolderDeleteHandler::~CFolderDeleteHandler(
void
)
{
if (NULL != m_pdtobj)
m_pdtobj->Release();
if (NULL != m_psfvcb)
m_psfvcb->Release();
}
//
// This function deletes files from the cache while also displaying
// standard shell progress UI.
//
HRESULT
CFolderDeleteHandler::DeleteFiles(
void
)
{
HRESULT hr = E_FAIL;
if (!ConfirmDeleteFiles(m_hwndParent))
return S_FALSE;
if (NULL != m_pdtobj)
{
//
// Retrieve the selection as an HDROP.
//
FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM medium;
hr = m_pdtobj->GetData(&fe, &medium);
if (SUCCEEDED(hr))
{
LPDROPFILES pDropFiles = (LPDROPFILES)GlobalLock(medium.hGlobal);
if (NULL != pDropFiles)
{
//
// Create the progress dialog.
//
IProgressDialog *ppd;
if (SUCCEEDED(CoCreateInstance(CLSID_ProgressDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_IProgressDialog,
(void **)&ppd)))
{
//
// Init and start the progress dialog.
//
TCHAR szCaption[80];
TCHAR szLine1[80];
LPTSTR pszFileList = (LPTSTR)((LPBYTE)pDropFiles + pDropFiles->pFiles);
LPTSTR pszFile = pszFileList;
int cFiles = 0;
int iFile = 0;
bool bCancelled = false;
bool bNoToAll = false;
//
// Count the number of files in the list.
//
while(TEXT('\0') != *pszFile && !bCancelled)
{
//
// Need to guard against deleting files that have offline
// changes but haven't been synchronized. User may want
// to delete these but we give them lot's of warning.
//
if (FileModifiedOffline(pszFile) &&
(bNoToAll || !ConfirmDeleteModifiedFile(m_hwndParent,
pszFile,
&bNoToAll,
&bCancelled)))
{
//
// "Remove" this file from the list by replacing the
// first char with a '*'. We'll use this as an indicator
// when scanning through the file list during the deletion
// phase below.
//
*pszFile = TEXT('*');
cFiles--;
}
while(*pszFile)
pszFile++;
pszFile++;
cFiles++;
}
if (!bCancelled)
{
LoadString(g_hInstance, IDS_APPLICATION, szCaption, ARRAYSIZE(szCaption));
LoadString(g_hInstance, IDS_DELFILEPROG_LINE1, szLine1, ARRAYSIZE(szLine1));
ppd->SetTitle(szCaption);
ppd->SetLine(1, szLine1, FALSE, NULL);
ppd->SetAnimation(g_hInstance, IDA_FILEDEL);
ppd->StartProgressDialog(m_hwndParent,
NULL,
PROGDLG_AUTOTIME | PROGDLG_MODAL,
NULL);
}
//
// Process the files in the list.
//
CShareCnxStatusCache CnxStatus;
BOOL bUserIsAdmin = IsCurrentUserAnAdminMember();
//
// Disable redraw on the view to avoid flicker.
//
m_psfvcb->MessageSFVCB(SFVMP_SETVIEWREDRAW, 0, 0);
pszFile = pszFileList;
while(TEXT('\0') != *pszFile && !bCancelled)
{
//
// If the file wasn't excluded from deletion above
// by replacing the first character with '*', delete it.
//
if (TEXT('*') != *pszFile)
{
DWORD dwErr = ERROR_ACCESS_DENIED;
ppd->SetLine(2, pszFile, FALSE, NULL);
if (bUserIsAdmin || !OthersHaveAccess(pszFile))
{
dwErr = CscDelete(pszFile);
if (ERROR_ACCESS_DENIED == dwErr)
{
//
// This is a little weird. CscDelete
// returns ERROR_ACCESS_DENIED if there's
// a handle open on the file. Set the
// code to ERROR_BUSY so we know to handle
// this as a special case below.
//
dwErr = ERROR_BUSY;
}
}
if (ERROR_SUCCESS == dwErr)
{
//
// File was deleted.
//
if (S_OK == CnxStatus.IsOpenConnectionPathUNC(pszFile))
{
//
// Post a shell chg "update" notify if there's
// an open connection to the path. Deleting
// something from the cache will remove the
// "pinned" icon overlay in shell filesystem folders.
//
ShellChangeNotify(pszFile, NULL, iFile == cFiles, SHCNE_UPDATEITEM);
}
m_psfvcb->MessageSFVCB(SFVMP_DELVIEWITEM, 0, (LPARAM)pszFile);
}
else
{
//
// Error deleting file.
//
HWND hwndProgress = GetProgressDialogWindow(ppd);
INT iUserResponse = IDOK;
if (ERROR_BUSY == dwErr)
{
//
// Special handling for ERROR_BUSY.
//
iUserResponse = CscMessageBox(hwndProgress ? hwndProgress : m_hwndParent,
MB_OKCANCEL | MB_ICONERROR,
g_hInstance,
IDS_FMT_ERR_DELFROMCACHE_BUSY,
pszFile);
}
else
{
//
// Hit an error deleting file. Display message and
// give user a chance to cancel operation.
//
iUserResponse = CscMessageBox(hwndProgress ? hwndProgress : m_hwndParent,
MB_OKCANCEL | MB_ICONERROR,
Win32Error(dwErr),
g_hInstance,
IDS_FMT_DELFILES_ERROR,
pszFile);
}
bCancelled = bCancelled || IDCANCEL == iUserResponse;
}
ppd->SetProgress(iFile++, cFiles);
bCancelled = bCancelled || ppd->HasUserCancelled();
}
while(*pszFile)
pszFile++;
pszFile++;
}
//
// Clean up the progress dialog.
//
ppd->StopProgressDialog();
ppd->Release();
m_psfvcb->MessageSFVCB(SFVMP_SETVIEWREDRAW, 0, 1);
}
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
}
}
return hr;
}
//
// Inform user they're deleting only the offline copy of the
// selected file(s) and that the file(s) will no longer be
// available offline once they're deleted. The dialog also
// provides a "don't bug me again" checkbox. This setting
// is saved per-user in the registry.
//
// Returns:
//
// true = User pressed [OK] or had checked "don't show me
// this again" at some time in the past.
// false = User cancelled operation.
//
bool
CFolderDeleteHandler::ConfirmDeleteFiles(
HWND hwndParent
)
{
//
// See if user has already seen this dialog and checked
// the "don't bug me again" checkbox".
//
DWORD dwType = REG_DWORD;
DWORD cbData = sizeof(DWORD);
DWORD bNoShow = 0;
SHGetValue(HKEY_CURRENT_USER,
c_szCSCKey,
c_szConfirmDelShown,
&dwType,
&bNoShow,
&cbData);
return bNoShow || IDOK == DialogBox(g_hInstance,
MAKEINTRESOURCE(IDD_CONFIRM_DELETE),
hwndParent,
ConfirmDeleteFilesDlgProc);
}
INT_PTR
CFolderDeleteHandler::ConfirmDeleteFilesDlgProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch(uMsg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
{
UINT idCmd = LOWORD(wParam);
switch(LOWORD(idCmd))
{
case IDOK:
{
//
// Save the "Don't bug me" value if the checkbox is
// checked. If it's not checked, no need to take up
// reg space with a 0 value.
//
if (BST_CHECKED == IsDlgButtonChecked(hwnd, IDC_CBX_CONFIRMDEL_NOSHOW))
{
DWORD dwNoShow = 1;
SHSetValue(HKEY_CURRENT_USER,
c_szCSCKey,
c_szConfirmDelShown,
REG_DWORD,
&dwNoShow,
sizeof(dwNoShow));
}
EndDialog(hwnd, IDOK);
break;
}
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
default:
break;
}
}
break;
}
return FALSE;
}
//
// Inform user that the file they're about to delete has been
// modified offline and the changes haven't been synchronized.
// Ask if they still want to delete it.
// The choices are Yes, No, NoToAll, Cancel.
//
//
// Arguments:
//
// hwndParent - Dialog parent.
//
// pszFile - Address of filename string to embed in
// dialog text. Passed to dialog proc in the
// LPARAM of DialogBoxParam.
//
// pbNoToAll - On return, indicates if the user pressed
// the "No To All" button.
//
// pbCancel - On return, indicates if the user pressed
// the "Cancel" button.
// Returns:
//
// true = Delete it.
// false = Don't delete it.
//
bool
CFolderDeleteHandler::ConfirmDeleteModifiedFile(
HWND hwndParent,
LPCTSTR pszFile,
bool *pbNoToAll,
bool *pbCancel
)
{
TraceAssert(NULL != pszFile);
TraceAssert(NULL != pbNoToAll);
TraceAssert(NULL != pbCancel);
INT_PTR iResult = DialogBoxParam(g_hInstance,
MAKEINTRESOURCE(IDD_CONFIRM_DELETEMOD),
hwndParent,
ConfirmDeleteModifiedFileDlgProc,
(LPARAM)pszFile);
bool bResult = false;
*pbNoToAll = false;
*pbCancel = false;
switch(iResult)
{
case IDYES:
bResult = true;
break;
case IDCANCEL:
*pbCancel = true;
break;
case IDIGNORE:
*pbNoToAll = true;
break;
case IDNO:
default:
break;
}
return bResult;
}
INT_PTR
CFolderDeleteHandler::ConfirmDeleteModifiedFileDlgProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
//
// lParam is the address of the filename string to be
// embedded in the dialog text. If the path is too long
// to fit in the text control, shorten it with an embedded
// ellipsis.
//
LPTSTR pszPath = NULL;
if (LocalAllocString(&pszPath, (LPCTSTR)lParam))
{
LPTSTR pszText = NULL;
RECT rc;
GetWindowRect(GetDlgItem(hwnd, IDC_TXT_CONFIRM_DELETEMOD), &rc);
PathCompactPath(NULL, pszPath, rc.right - rc.left);
FormatStringID(&pszText,
g_hInstance,
IDS_CONFIRM_DELETEMOD,
pszPath);
if (NULL != pszText)
{
SetWindowText(GetDlgItem(hwnd, IDC_TXT_CONFIRM_DELETEMOD), pszText);
LocalFree(pszText);
}
LocalFreeString(&pszPath);
}
return TRUE;
}
case WM_COMMAND:
EndDialog(hwnd, LOWORD(wParam));
break;
default:
break;
}
return FALSE;
}
//
// Determine if a particular file has been modified offline.
//
bool
CFolderDeleteHandler::FileModifiedOffline(
LPCTSTR pszFile
)
{
TraceAssert(NULL != pszFile);
DWORD dwStatus = 0;
CSCQueryFileStatus(pszFile, &dwStatus, NULL, NULL);
return 0 != (FLAG_CSCUI_COPY_STATUS_LOCALLY_DIRTY & dwStatus);
}
//
// Determine if a particular file can be access by another
// user other than guest.
//
bool
CFolderDeleteHandler::OthersHaveAccess(
LPCTSTR pszFile
)
{
TraceAssert(NULL != pszFile);
DWORD dwStatus = 0;
CSCQueryFileStatus(pszFile, &dwStatus, NULL, NULL);
return CscAccessOther(dwStatus);
}
//----------------------------------------------------------------------------
// COfflineFilesFolder
//----------------------------------------------------------------------------
COfflineFilesFolder::COfflineFilesFolder(
void
) : _cRef(1),
_psfvcb(NULL), // Non ref-counted interface ptr.
m_FileTypeCache(101) // Bucket count should be prime.
{
DllAddRef();
_pidl = NULL;
}
COfflineFilesFolder::~COfflineFilesFolder(
void
)
{
if (_pidl)
ILFree(_pidl);
DllRelease();
}
// class factory constructor
STDAPI
COfflineFilesFolder_CreateInstance(
REFIID riid,
void **ppv
)
{
HRESULT hr;
COfflineFilesFolder* polff = new COfflineFilesFolder();
if (polff)
{
hr = polff->QueryInterface(riid, ppv);
polff->Release();
}
else
{
*ppv = NULL;
hr = E_OUTOFMEMORY;
}
return hr;
}
LPCOLID
COfflineFilesFolder::_Validate(
LPCITEMIDLIST pidl
)
{
LPCOLID polid = (LPCOLID)pidl;
if (polid && (polid->cbFixed == sizeof(*polid)) && (polid->uSig == OLID_SIG))
return polid;
return NULL;
}
//
// External version of _Validate but returns only T/F.
//
bool
COfflineFilesFolder::ValidateIDList(
LPCITEMIDLIST pidl
)
{
return NULL != _Validate(pidl);
}
STDMETHODIMP
COfflineFilesFolder::QueryInterface(
REFIID riid,
void **ppv
)
{
static const QITAB qit[] = {
QITABENT(COfflineFilesFolder, IShellFolder),
QITABENT(COfflineFilesFolder, IPersistFolder2),
QITABENTMULTI(COfflineFilesFolder, IPersistFolder, IPersistFolder2),
QITABENTMULTI(COfflineFilesFolder, IPersist, IPersistFolder2),
QITABENT(COfflineFilesFolder, IShellIcon),
QITABENT(COfflineFilesFolder, IShellIconOverlay),
{ 0 },
};
HRESULT hr = QISearch(this, qit, riid, ppv);
if (FAILED(hr))
{
//
// OK, this is a little slimy. The "delete handler" needs to
// get at the folder's IShellFolderViewCB interface so it can
// update the view following a deletion (remember, we're only deleting
// from the cache so no meaningful FS notification will occur).
// We define this secret IID that only our folder knows about. This
// way the delete handler can safely QI any IShellFolder interface
// and only our folder will respond with a view CB pointer.
// [brianau - 5/5/99]
//
if (riid == IID_OfflineFilesFolderViewCB && NULL != _psfvcb)
{
_psfvcb->AddRef();
*ppv = (void **)_psfvcb;
hr = NOERROR;
}
}
return hr;
}
STDMETHODIMP_ (ULONG)
COfflineFilesFolder::AddRef(
void
)
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_ (ULONG)
COfflineFilesFolder::Release(
void
)
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
// IPersist methods
STDMETHODIMP
COfflineFilesFolder::GetClassID(
CLSID *pclsid
)
{
*pclsid = CLSID_OfflineFilesFolder;
return S_OK;
}
HRESULT
COfflineFilesFolder::Initialize(
LPCITEMIDLIST pidl
)
{
if (_pidl)
ILFree(_pidl);
_pidl = ILClone(pidl);
return _pidl ? S_OK : E_OUTOFMEMORY;
}
HRESULT
COfflineFilesFolder::GetCurFolder(
LPITEMIDLIST *ppidl
)
{
if (_pidl)
{
*ppidl = ILClone(_pidl);
return *ppidl ? NOERROR : E_OUTOFMEMORY;
}
*ppidl = NULL;
return S_FALSE; // success but empty
}
STDMETHODIMP
COfflineFilesFolder::ParseDisplayName(
HWND hwnd,
LPBC pbc,
LPOLESTR pDisplayName,
ULONG* pchEaten,
LPITEMIDLIST* ppidl,
ULONG *pdwAttributes
)
{
return E_NOTIMPL;
}
STDMETHODIMP
COfflineFilesFolder::EnumObjects(
HWND hwnd,
DWORD grfFlags,
IEnumIDList **ppenum
)
{
*ppenum = NULL;
HRESULT hr = E_FAIL;
COfflineFilesEnum *penum = new COfflineFilesEnum(grfFlags, this);
if (penum)
{
if (penum->IsValid())
hr = penum->QueryInterface(IID_IEnumIDList, (void **)ppenum);
penum->Release();
}
else
hr = E_OUTOFMEMORY;
return hr;
}
STDMETHODIMP
COfflineFilesFolder::BindToObject(
LPCITEMIDLIST pidl,
LPBC pbc,
REFIID riid,
void **ppv
)
{
return E_NOTIMPL;
}
STDMETHODIMP
COfflineFilesFolder::BindToStorage(
LPCITEMIDLIST pidl,
LPBC pbc,
REFIID riid,
void **ppv
)
{
return E_NOTIMPL;
}
void
COfflineFilesFolder::_GetSyncStatusString(
LPCOLID polid,
LPTSTR pszStatus,
UINT cchStatus
)
{
//
// Translate a file status into a stale state code.
// Note that the stale state codes are the same values as their
// corresponding string resource IDs. Order of this array is
// important. The first match is the message that is displayed.
// In the case of multiple bits being set, we want to display a
// message for the most "serious" reason.
//
static const struct
{
DWORD dwStatusMask;
UINT idMsg;
} rgStaleInfo[] = {
{ FLAG_CSC_COPY_STATUS_SUSPECT, IDS_STALEREASON_SUSPECT },
{ FLAG_CSC_COPY_STATUS_ORPHAN, IDS_STALEREASON_ORPHAN },
{ FLAG_CSC_COPY_STATUS_STALE, IDS_STALEREASON_STALE },
{ FLAG_CSC_COPY_STATUS_LOCALLY_CREATED, IDS_STALEREASON_LOCALLY_CREATED },
{ FLAG_CSC_COPY_STATUS_DATA_LOCALLY_MODIFIED, IDS_STALEREASON_LOCALLY_MODDATA },
{ FLAG_CSC_COPY_STATUS_TIME_LOCALLY_MODIFIED, IDS_STALEREASON_LOCALLY_MODTIME },
{ FLAG_CSC_COPY_STATUS_ATTRIB_LOCALLY_MODIFIED, IDS_STALEREASON_LOCALLY_MODATTR },
{ FLAG_CSC_COPY_STATUS_SPARSE, IDS_STALEREASON_SPARSE }
};
int idStatusText = IDS_STALEREASON_NOTSTALE; // Default is "not stale".
for (int i = 0; i < ARRAYSIZE(rgStaleInfo); i++)
{
if (0 != (rgStaleInfo[i].dwStatusMask & polid->dwStatus))
{
idStatusText = rgStaleInfo[i].idMsg;
break;
}
}
LoadString(g_hInstance, idStatusText, pszStatus, cchStatus);
}
void
COfflineFilesFolder::_GetPinStatusString(
LPCOLID polid,
LPTSTR pszStatus,
UINT cchStatus
)
{
LoadString(g_hInstance,
(FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN) & polid->dwHintFlags ? IDS_FILE_PINNED : IDS_FILE_NOTPINNED,
pszStatus,
cchStatus);
}
void
COfflineFilesFolder::_GetServerStatusString(
LPCOLID polid,
LPTSTR pszStatus,
UINT cchStatus
)
{
//
// Only two possible status strings: "Online" and "Offline".
//
UINT idText = IDS_SHARE_STATUS_ONLINE;
if (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & polid->dwServerStatus)
idText = IDS_SHARE_STATUS_OFFLINE;
LoadString(g_hInstance, idText, pszStatus, cchStatus);
}
void
COfflineFilesFolder::_GetTypeString(
LPCOLID polid,
LPTSTR pszType,
UINT cchType
)
{
PCTSTR pszName;
//
// We utilize a local cache of type name information to reduce
// the number of calls to SHGetFileInfo. This speeds things up
// tremendously. The shell does something similar in DefView.
// Note that the filetype cache is a member of COfflineFilesFolder
// so that it lives only while the folder is active. The alternative
// would be to make a local static object here in this function.
// The problem with that is that once created the cache would remain
// in memory until our DLL is unloaded; which in explorer.exe is NEVER.
//
TSTR_ALIGNED_STACK_COPY( &pszName,
polid->szPath + polid->cchNameOfs );
m_FileTypeCache.GetTypeName(pszName,
polid->dwFileAttributes,
pszType,
cchType);
}
void
COfflineFilesFolder::_GetAccessString(
LPCOLID polid,
LPTSTR pszAccess,
UINT cchAccess
)
{
//
// Three strings containing the replacement text for the rgFmts[i] template.
// Note that the index value corresponds directly with the access values
// obtained from the OLID's dwStatus member. This makes the translation from
// OLID access info to text string very fast. These are small enough to
// cache. Caching saves us three LoadStrings each time.
//
// Index String resource (english)
// -------- ---------------------- ---------
// 0 IDS_ACCESS_READ "R"
// 1 IDS_ACCESS_WRITE "W"
// 2 IDS_ACCESS_READWRITE "R/W"
//
static TCHAR rgszAccess[3][4] = {0};
//
// This table lists the "mask" and "shift count" used to retrieve the access
// information from the OLID dwStatus value.
//
static const struct
{
DWORD dwMask;
DWORD dwShift;
} rgAccess[] = {{ FLAG_CSC_USER_ACCESS_MASK, FLAG_CSC_USER_ACCESS_SHIFT_COUNT },
{ FLAG_CSC_GUEST_ACCESS_MASK, FLAG_CSC_GUEST_ACCESS_SHIFT_COUNT },
{ FLAG_CSC_OTHER_ACCESS_MASK, FLAG_CSC_OTHER_ACCESS_SHIFT_COUNT }};
//
// These IDs specify which format string to use for a given
// item access value in the OLID's dwStatus member.
// The index into this array is calculated below from the access bits
// set for this OLID. Note that these are "message" formats defined
// in msg.mc and not resource strings. This way we eliminate the
// need for a LoadString and do everything with FormatMessage.
//
// iFmt (see below)
static const UINT rgFmts[] = { 0, // 0x0000
MSG_FMT_ACCESS_USER, // 0x0001
MSG_FMT_ACCESS_GUEST, // 0x0002
MSG_FMT_ACCESS_USERGUEST, // 0x0003
MSG_FMT_ACCESS_OTHER, // 0x0004
MSG_FMT_ACCESS_USEROTHER, // 0x0005
MSG_FMT_ACCESS_GUESTOTHER, // 0x0006
MSG_FMT_ACCESS_USERGUESTOTHER }; // 0x0007
const DWORD dwAccess = polid->dwStatus & FLAG_CSC_ACCESS_MASK;
int i;
if (TEXT('\0') == rgszAccess[0][0])
{
//
// First-time init for strings used in access text.
// This stuff happens only once.
//
const UINT rgidStr[] = { IDS_ACCESS_READ,
IDS_ACCESS_WRITE,
IDS_ACCESS_READWRITE };
//
// Load up the "R", "W", "R/W" strings.
//
for (i = 0; i < ARRAYSIZE(rgidStr); i++)
{
TraceAssert(i < ARRAYSIZE(rgszAccess));
LoadString(g_hInstance, rgidStr[i], rgszAccess[i], ARRAYSIZE(rgszAccess[i]));
}
}
//
// Build an index into rgFmts[] based on the access bits set on the olid.
//
int iFmt = 0;
if (FLAG_CSC_USER_ACCESS_MASK & dwAccess)
iFmt |= 0x0001;
if (FLAG_CSC_GUEST_ACCESS_MASK & dwAccess)
iFmt |= 0x0002;
if (FLAG_CSC_OTHER_ACCESS_MASK & dwAccess)
iFmt |= 0x0004;
*pszAccess = TEXT('\0');
if (0 != iFmt)
{
//
// Fill in the argument array passed to FormatMessage.
// Each of the elements will contain the address of one element in the
// rgszAccess[] string array.
//
LPCTSTR rgpszArgs[ARRAYSIZE(rgszAccess)] = {0};
int iArg = 0;
for (i = 0; i < ARRAYSIZE(rgpszArgs); i++)
{
int a = dwAccess & rgAccess[i].dwMask;
if (0 != a)
{
rgpszArgs[iArg++] = &rgszAccess[(a >> rgAccess[i].dwShift) - 1][0];
}
}
//
// Finally, format the message text.
//
FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
g_hInstance,
rgFmts[iFmt],
0,
pszAccess,
cchAccess,
(va_list *)rgpszArgs);
}
}
STDMETHODIMP
COfflineFilesFolder::CompareIDs(
LPARAM lParam,
LPCITEMIDLIST pidl1,
LPCITEMIDLIST pidl2
)
{
HRESULT hres;
LPCOLID polid1 = _Validate(pidl1);
LPCOLID polid2 = _Validate(pidl2);
if (polid1 && polid2)
{
TCHAR szStr1[MAX_PATH], szStr2[MAX_PATH];
switch (lParam & SHCIDS_COLUMNMASK)
{
case ICOL_NAME:
hres = ResultFromShort(ualstrcmpi(polid1->szPath + polid1->cchNameOfs,
polid2->szPath + polid2->cchNameOfs));
if (0 == hres)
{
//
// Since we present a "flat" view of the CSC cache,
// we can't compare only by name. We have to include
// path for items with the same name. This is because the
// shell uses column 0 as the unique identifying column for
// an ID.
//
hres = ResultFromShort(ualstrcmpi(polid1->szPath, polid2->szPath));
}
break;
case ICOL_TYPE:
_GetTypeString(polid1, szStr1, ARRAYSIZE(szStr1));
_GetTypeString(polid2, szStr2, ARRAYSIZE(szStr2));
hres = ResultFromShort(lstrcmpi(szStr1, szStr2));
break;
case ICOL_SYNCSTATUS:
_GetSyncStatusString(polid1, szStr1, ARRAYSIZE(szStr1));
_GetSyncStatusString(polid2, szStr2, ARRAYSIZE(szStr2));
hres = ResultFromShort(lstrcmpi(szStr1, szStr2));
break;
case ICOL_PINSTATUS:
_GetPinStatusString(polid1, szStr1, ARRAYSIZE(szStr1));
_GetPinStatusString(polid2, szStr2, ARRAYSIZE(szStr2));
hres = ResultFromShort(lstrcmpi(szStr1, szStr2));
break;
case ICOL_ACCESS:
_GetAccessString(polid1, szStr1, ARRAYSIZE(szStr1));
_GetAccessString(polid2, szStr2, ARRAYSIZE(szStr2));
hres = ResultFromShort(lstrcmpi(szStr1, szStr2));
break;
case ICOL_SERVERSTATUS:
_GetServerStatusString(polid1, szStr1, ARRAYSIZE(szStr1));
_GetServerStatusString(polid2, szStr2, ARRAYSIZE(szStr2));
hres = ResultFromShort(lstrcmpi(szStr1, szStr2));
break;
case ICOL_LOCATION:
hres = ResultFromShort(ualstrcmpi(polid1->szPath, polid2->szPath));
break;
case ICOL_SIZE:
if (polid1->dwFileSizeLow > polid2->dwFileSizeLow)
hres = ResultFromShort(1);
else if (polid1->dwFileSizeLow < polid2->dwFileSizeLow)
hres = ResultFromShort(-1);
else
hres = ResultFromShort(0);
break;
case ICOL_DATE:
hres = ResultFromShort(uaCompareFileTime(&polid1->ft, &polid2->ft));
break;
}
if (hres == S_OK && (lParam & SHCIDS_ALLFIELDS))
{
hres = CompareIDs(ICOL_PINSTATUS, pidl1, pidl2);
if (hres == S_OK)
{
hres = CompareIDs(ICOL_SYNCSTATUS, pidl1, pidl2);
if (hres == S_OK)
{
hres = CompareIDs(ICOL_SIZE, pidl1, pidl2);
if (hres == S_OK)
{
hres = CompareIDs(ICOL_DATE, pidl1, pidl2);
}
}
}
}
}
else
hres = E_INVALIDARG;
return hres;
}
STDMETHODIMP
COfflineFilesFolder::CreateViewObject(
HWND hwnd,
REFIID riid,
void **ppv
)
{
HRESULT hres;
if (IsEqualIID(riid, IID_IShellView))
{
COfflineFilesViewCallback *pViewCB = new COfflineFilesViewCallback(this);
if (pViewCB)
{
SFV_CREATE sSFV;
sSFV.cbSize = sizeof(sSFV);
sSFV.psvOuter = NULL;
sSFV.pshf = this;
sSFV.psfvcb = pViewCB;
hres = SHCreateShellFolderView(&sSFV, (IShellView**)ppv);
pViewCB->Release();
if (SUCCEEDED(hres))
{
//
// Save the view callback pointer so we can use it in our context menu
// handler for view notifications. Note we don't take a ref count as that
// would create a ref count cycle. The view will live as long as the
// folder does.
//
_psfvcb = pViewCB;
}
}
else
hres = E_OUTOFMEMORY;
}
else if (IsEqualIID(riid, IID_IShellDetails))
{
COfflineDetails *pDetails = new COfflineDetails(this);
if (pDetails)
{
*ppv = (IShellDetails *)pDetails;
hres = S_OK;
}
else
hres = E_OUTOFMEMORY;
}
else if (IsEqualIID(riid, IID_IDropTarget))
{
hres = COfflineFilesDropTarget::CreateInstance(hwnd, riid, ppv);
}
else if (IsEqualIID(riid, IID_IContextMenu))
{
IContextMenu *pcmCSCUI;
hres = CreateOfflineFilesContextMenu(NULL, riid, (void **)ppv);
}
else
{
*ppv = NULL;
hres = E_NOINTERFACE;
}
return hres;
}
STDMETHODIMP
COfflineFilesFolder::GetAttributesOf(
UINT cidl,
LPCITEMIDLIST* apidl,
ULONG *rgfInOut
)
{
HRESULT hr = NOERROR;
IShellFolder *psf = NULL;
ULONG ulAttrRequested = *rgfInOut;
*rgfInOut = (ULONG)-1;
for (UINT i = 0; i < cidl && SUCCEEDED(hr); i++)
{
CShellObjProxy<IShellFolder> pxy(IID_IShellFolder, (LPCOLID)*apidl++);
if (SUCCEEDED(hr = pxy.Result()))
{
ULONG ulThis = ulAttrRequested;
LPCITEMIDLIST pidlItem = pxy.ItemIDList();
hr = pxy->GetAttributesOf(1, &pidlItem, &ulThis);
if (SUCCEEDED(hr))
{
//
// Build up the intersection of attributes for all items
// in the IDList. Note that we don't allow move.
//
*rgfInOut &= (ulThis & ~SFGAO_CANMOVE);
}
}
}
return hr;
}
HRESULT
COfflineFilesFolder::GetAssociations(
LPCOLID polid,
void **ppvQueryAssociations
)
{
TraceAssert(NULL != polid);
TraceAssert(NULL != ppvQueryAssociations);
HRESULT hr = NOERROR;
*ppvQueryAssociations = NULL;
CCoInit coinit;
if (SUCCEEDED(hr = coinit.Result()))
{
CShellObjProxy<IShellFolder> pxy(IID_IShellFolder, polid);
if (SUCCEEDED(hr = pxy.Result()))
{
LPCITEMIDLIST pidlItem = pxy.ItemIDList();
hr = pxy->GetUIObjectOf(NULL, 1, &pidlItem, IID_IQueryAssociations, NULL, ppvQueryAssociations);
if (FAILED(hr))
{
// this means that the folder doesnt support
// the IQueryAssociations. so we will
// just check to see if this is a folder
ULONG rgfAttrs = SFGAO_FOLDER | SFGAO_BROWSABLE;
IQueryAssociations *pqa;
if (SUCCEEDED(pxy->GetAttributesOf(1, &pidlItem, &rgfAttrs))
&& (rgfAttrs & SFGAO_FOLDER | SFGAO_BROWSABLE)
&& (SUCCEEDED(AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void **)&pqa))))
{
hr = pqa->Init(0, L"Folder", NULL, NULL);
if (SUCCEEDED(hr))
*ppvQueryAssociations = (void *)pqa;
else
pqa->Release();
}
}
}
}
return hr;
}
BOOL
COfflineFilesFolder::GetClassKey(
LPCOLID polid,
HKEY *phkeyProgID,
HKEY *phkeyBaseID
)
{
TraceAssert(NULL != polid);
BOOL bRet = FALSE;
IQueryAssociations *pqa;
if (phkeyProgID)
*phkeyProgID = NULL;
if (phkeyBaseID)
*phkeyBaseID = NULL;
if (SUCCEEDED(GetAssociations(polid, (void **)&pqa)))
{
if (phkeyProgID)
pqa->GetKey(ASSOCF_IGNOREBASECLASS, ASSOCKEY_CLASS, NULL, phkeyProgID);
if (phkeyBaseID)
pqa->GetKey(0, ASSOCKEY_BASECLASS, NULL, phkeyBaseID);
pqa->Release();
bRet = TRUE;
}
return bRet;
}
HRESULT
COfflineFilesFolder::ContextMenuCB(
IShellFolder *psf,
HWND hwndOwner,
IDataObject *pdtobj,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
HRESULT hr = NOERROR;
switch(uMsg)
{
case DFM_MERGECONTEXTMENU:
//
// Return NOERROR.
// This causes the shell to add the default verbs
// (i.e. Open, Print etc) to the menu.
//
break;
case DFM_INVOKECOMMAND:
switch(wParam)
{
case DFM_CMD_DELETE:
{
IShellFolderViewCB *psfvcb = NULL;
if (SUCCEEDED(psf->QueryInterface(IID_OfflineFilesFolderViewCB, (void **)&psfvcb)))
{
CFolderDeleteHandler handler(hwndOwner, pdtobj, psfvcb);
handler.DeleteFiles();
psfvcb->Release();
}
break;
}
case DFM_CMD_COPY:
SetPreferredDropEffect(pdtobj, DROPEFFECT_COPY);
hr = S_FALSE;
break;
case DFM_CMD_PROPERTIES:
SHMultiFileProperties(pdtobj, 0);
break;
default:
hr = S_FALSE; // Execute default code.
break;
}
break;
default:
hr = E_NOTIMPL;
break;
}
return hr;
}
/*
// Used for dumping out interface requests. Uncomment if you want to use it.
//
//
LPCTSTR IIDToStr(REFIID riid, LPTSTR pszDest, UINT cchDest)
{
struct
{
const IID *piid;
LPCTSTR s;
} rgMap[] = { { &IID_IDataObject, TEXT("IID_IDataObject") },
{ &IID_IUnknown, TEXT("IID_IUnknown") },
{ &IID_IContextMenu, TEXT("IID_IContextMenu") },
{ &IID_IExtractIconA, TEXT("IID_IExtractIconA") },
{ &IID_IExtractIconW, TEXT("IID_IExtractIconW") },
{ &IID_IExtractImage, TEXT("IID_IExtractImage") },
{ &IID_IPersistFolder2, TEXT("IID_IPersistFolder2") },
{ &IID_IQueryInfo, TEXT("IID_IQueryInfo") },
{ &IID_IDropTarget, TEXT("IID_IDropTarget") },
{ &IID_IQueryAssociations, TEXT("IID_IQueryAssociations") }
};
StringFromGUID2(riid, pszDest, cchDest);
for (int i = 0; i < ARRAYSIZE(rgMap); i++)
{
if (riid == *(rgMap[i].piid))
{
lstrcpyn(pszDest, rgMap[i].s, cchDest);
break;
}
}
return pszDest;
}
*/
STDMETHODIMP
COfflineFilesFolder::GetUIObjectOf(
HWND hwnd,
UINT cidl,
LPCITEMIDLIST *ppidl,
REFIID riid,
UINT* prgfReserved,
void **ppv
)
{
HRESULT hr;
if (IID_IDataObject == riid)
{
LPITEMIDLIST pidlOfflineFiles;
hr = COfflineFilesFolder::CreateIDList(&pidlOfflineFiles);
if (SUCCEEDED(hr))
{
hr = COfflineItemsData::CreateInstance((IDataObject **)ppv,
pidlOfflineFiles,
cidl,
ppidl,
hwnd);
if (SUCCEEDED(hr))
{
SetPreferredDropEffect((IDataObject *)*ppv, DROPEFFECT_COPY);
}
ILFree(pidlOfflineFiles);
}
}
else if (riid == IID_IContextMenu)
{
HKEY hkeyBaseProgID = NULL;
HKEY hkeyProgID = NULL;
HKEY hkeyAllFileSys = NULL;
//
// Get the hkeyProgID and hkeyBaseProgID from the first item.
//
GetClassKey((LPCOLID)*ppidl, &hkeyProgID, &hkeyBaseProgID);
//
// Pick up "Send To..."
//
RegOpenKeyEx(HKEY_CLASSES_ROOT,
TEXT("AllFilesystemObjects"),
0,
KEY_READ,
&hkeyAllFileSys);
LPITEMIDLIST pidlOfflineFilesFolder;
hr = COfflineFilesFolder::CreateIDList(&pidlOfflineFilesFolder);
if (SUCCEEDED(hr))
{
HKEY rgClassKeys[] = { hkeyProgID, hkeyBaseProgID, hkeyAllFileSys };
hr = CDefFolderMenu_Create2(pidlOfflineFilesFolder,
hwnd,
cidl,
ppidl,
this,
COfflineFilesFolder::ContextMenuCB,
ARRAYSIZE(rgClassKeys),
rgClassKeys,
(IContextMenu **)ppv);
ILFree(pidlOfflineFilesFolder);
}
if (NULL != hkeyBaseProgID)
RegCloseKey(hkeyBaseProgID);
if (NULL != hkeyProgID)
RegCloseKey(hkeyProgID);
if (NULL != hkeyAllFileSys)
RegCloseKey(hkeyAllFileSys);
}
else if (1 == cidl)
{
CShellObjProxy<IShellFolder> pxy(IID_IShellFolder, (LPCOLID)*ppidl);
if (SUCCEEDED(hr = pxy.Result()))
{
//
// Forward single-item selection to the filesystem implementation.
//
LPCITEMIDLIST pidlItem = pxy.ItemIDList();
hr = pxy->GetUIObjectOf(hwnd, 1, &pidlItem, riid, prgfReserved, ppv);
}
}
else if (0 == cidl)
{
hr = E_INVALIDARG;
}
else
{
*ppv = NULL;
hr = E_FAIL;
}
return hr;
}
STDMETHODIMP
COfflineFilesFolder::GetDisplayNameOf(
LPCITEMIDLIST pidl,
DWORD uFlags,
STRRET *pName
)
{
TraceAssert(NULL != pidl);
HRESULT hres = E_INVALIDARG;
LPCOLID polid = _Validate(pidl);
if (polid)
{
if (uFlags & SHGDN_FORPARSING)
{
TCHAR szPath[MAX_PATH];
OLID_GetFullPath(polid, szPath, ARRAYSIZE(szPath));
hres = StringToStrRet(szPath, pName);
}
else
{
CShellObjProxy<IShellFolder> pxy(IID_IShellFolder, polid);
if (SUCCEEDED(hres = pxy.Result()))
{
hres = pxy->GetDisplayNameOf(pxy.ItemIDList(), uFlags, pName);
}
}
}
return hres;
}
STDMETHODIMP
COfflineFilesFolder::SetNameOf(
HWND hwnd,
LPCITEMIDLIST pidl,
LPCOLESTR pName,
DWORD uFlags,
LPITEMIDLIST *ppidlOut
)
{
HRESULT hr;
CShellObjProxy<IShellFolder> pxy(IID_IShellFolder, _Validate(pidl));
if (SUCCEEDED(hr = pxy.Result()))
{
hr = pxy->SetNameOf(hwnd, pxy.ItemIDList(), pName, uFlags, ppidlOut);
}
return hr;
}
//
// Forward IShellIcon methods to parent filesystem folder.
//
HRESULT
COfflineFilesFolder::GetIconOf(
LPCITEMIDLIST pidl,
UINT gil,
int *pnIcon
)
{
TraceAssert(NULL != pidl);
HRESULT hr;
CShellObjProxy<IShellIcon> pxy(IID_IShellIcon, _Validate(pidl));
if (SUCCEEDED(hr = pxy.Result()))
{
hr = pxy->GetIconOf(pxy.ItemIDList(), gil, pnIcon);
}
return hr;
}
//
// Defer IShellIconOverlay methods to parent filesystem folder.
//
HRESULT
COfflineFilesFolder::GetOverlayIndex(
LPCITEMIDLIST pidl,
int *pIndex
)
{
TraceAssert(NULL != pidl);
HRESULT hr;
CShellObjProxy<IShellIconOverlay> pxy(IID_IShellIconOverlay, _Validate(pidl));
if (SUCCEEDED(hr = pxy.Result()))
{
hr = pxy->GetOverlayIndex(pxy.ItemIDList(), pIndex);
}
return hr;
}
//
// Defer IShellIconOverlay methods to parent filesystem folder.
//
HRESULT
COfflineFilesFolder::GetOverlayIconIndex(
LPCITEMIDLIST pidl,
int * pIconIndex
)
{
TraceAssert(NULL != pidl);
HRESULT hr;
CShellObjProxy<IShellIconOverlay> pxy(IID_IShellIconOverlay, _Validate(pidl));
if (SUCCEEDED(hr = pxy.Result()))
{
hr = pxy->GetOverlayIconIndex(pxy.ItemIDList(), pIconIndex);
}
return hr;
}
//
// Static member function for creating and opening the offline files folder.
//
INT
COfflineFilesFolder::Open( // [static]
void
)
{
INT iReturn = 0;
if (CConfig::GetSingleton().NoCacheViewer())
{
CscMessageBox(NULL,
MB_OK | MB_ICONINFORMATION,
g_hInstance,
IDS_ERR_POLICY_NOVIEWCACHE);
iReturn = -1;
}
else
{
SHELLEXECUTEINFO shei = { 0 };
shei.cbSize = sizeof(shei);
shei.fMask = SEE_MASK_IDLIST | SEE_MASK_INVOKEIDLIST;
shei.nShow = SW_SHOWNORMAL;
if (SUCCEEDED(COfflineFilesFolder::CreateIDList((LPITEMIDLIST *)(&shei.lpIDList))))
{
ShellExecuteEx(&shei);
ILFree((LPITEMIDLIST)(shei.lpIDList));
}
}
return iReturn;
}
//
// Static member function for creating the folder's IDList.
//
HRESULT
COfflineFilesFolder::CreateIDList( // [static]
LPITEMIDLIST *ppidl
)
{
TraceAssert(NULL != ppidl);
IShellFolder *psf;
HRESULT hr = SHGetDesktopFolder(&psf);
if (SUCCEEDED(hr))
{
IBindCtx *pbc;
hr = CreateBindCtx(0, &pbc);
if (SUCCEEDED(hr))
{
BIND_OPTS bo;
memset(&bo, 0, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.grfFlags = BIND_JUSTTESTEXISTENCE;
bo.grfMode = STGM_CREATE;
pbc->SetBindOptions(&bo);
WCHAR wszPath[80] = L"::";
StringFromGUID2(CLSID_OfflineFilesFolder,
&wszPath[2],
sizeof(wszPath) - (2 * sizeof(WCHAR)));
hr = psf->ParseDisplayName(NULL, pbc, wszPath, NULL, ppidl, NULL);
pbc->Release();
}
psf->Release();
}
return hr;
}
//
// Static function for creating a link to the folder on the desktop.
//
HRESULT
COfflineFilesFolder::CreateLinkOnDesktop( // [static]
HWND hwndParent
)
{
IShellLink* psl;
CCoInit coinit;
HRESULT hr = coinit.Result();
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void **)&psl);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl = NULL;
hr = COfflineFilesFolder::CreateIDList(&pidl);
if (SUCCEEDED(hr))
{
hr = psl->SetIDList(pidl);
if (SUCCEEDED(hr))
{
TCHAR szLinkTitle[80] = { 0 };
LoadString(g_hInstance, IDS_FOLDER_LINK_NAME, szLinkTitle, ARRAYSIZE(szLinkTitle));
psl->SetDescription(szLinkTitle);
IPersistFile* ppf;
hr = psl->QueryInterface(IID_IPersistFile, (void **)&ppf);
if (SUCCEEDED(hr))
{
TCHAR szLinkPath[MAX_PATH];
hr = SHGetSpecialFolderPath(hwndParent, szLinkPath, CSIDL_DESKTOPDIRECTORY, FALSE) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
TCHAR szLinkFileName[80];
LoadStringW(g_hInstance, IDS_FOLDER_LINK_NAME, szLinkFileName, ARRAYSIZE(szLinkFileName));
lstrcat(szLinkFileName, TEXT(".LNK"));
PathAppend(szLinkPath, szLinkFileName);
hr = ppf->Save(szLinkPath, TRUE);
if (SUCCEEDED(hr))
{
//
// Record that we've created a folder shortcut on the
// desktop. This is used to minimize the number of
// times we look for the shortcut on the desktop.
// DeleteOfflineFilesFolderLink_PerfSensitive() will look
// for this value to avoid unnecessary scans of the desktop
// when looking for our LINK file.
//
DWORD dwValue = 1;
DWORD cbValue = sizeof(dwValue);
SHSetValue(HKEY_CURRENT_USER,
REGSTR_KEY_OFFLINEFILES,
REGSTR_VAL_FOLDERSHORTCUTCREATED,
REG_DWORD,
&dwValue,
cbValue);
}
}
ppf->Release();
}
}
ILFree(pidl);
}
psl->Release();
}
}
return hr;
}
//
// Static function for determining if there's a link to the offline files
// folder sitting on the user's desktop.
//
HRESULT
COfflineFilesFolder::IsLinkOnDesktop( // [static]
HWND hwndParent,
LPTSTR pszPathOut,
UINT cchPathOut
)
{
TCHAR szDesktop[MAX_PATH];
HRESULT hr = SHGetSpecialFolderPath(hwndParent, szDesktop, CSIDL_DESKTOPDIRECTORY, FALSE) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
hr = S_FALSE; // Assume not found.
TCHAR szPath[MAX_PATH];
PathCombine(szPath, szDesktop, TEXT("*.LNK"));
WIN32_FIND_DATA fd;
HANDLE hFind = FindFirstFile(szPath, &fd);
if (INVALID_HANDLE_VALUE != hFind)
{
do
{
PathRemoveFileSpec(szPath);
PathAppend(szPath, fd.cFileName);
hr = IsOurLink(szPath);
if (S_OK == hr)
{
if (NULL != pszPathOut)
{
lstrcpyn(pszPathOut, szPath, cchPathOut);
}
break;
}
}
while(FindNextFile(hFind, &fd));
FindClose(hFind);
}
}
return hr;
}
//
// Given a link file path, determine if it's a link to the
// offline files folder.
//
HRESULT
COfflineFilesFolder::IsOurLink( // [static]
LPCTSTR pszFile
)
{
IShellLink *psl;
CCoInit coinit;
HRESULT hr = coinit.Result();
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void **)&psl);
if (SUCCEEDED(hr))
{
IPersistFile *ppf;
hr = psl->QueryInterface(IID_IPersistFile, (void **)&ppf);
if (SUCCEEDED(hr))
{
hr = ppf->Load(pszFile, STGM_DIRECT);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlLink;
hr = psl->GetIDList(&pidlLink);
if (SUCCEEDED(hr))
{
hr = COfflineFilesFolder::IdentifyIDList(pidlLink);
ILFree(pidlLink);
}
}
ppf->Release();
}
psl->Release();
}
}
return hr;
}
//
// Determines if a given IDList is the IDList of the
// offline files folder.
//
// Returns:
//
// S_OK = It's our IDList.
// S_FALSE = It's not our IDList.
//
HRESULT
COfflineFilesFolder::IdentifyIDList( // [static]
LPCITEMIDLIST pidl
)
{
IShellFolder *psf;
HRESULT hr = SHGetDesktopFolder(&psf);
if (SUCCEEDED(hr))
{
IBindCtx *pbc;
hr = CreateBindCtx(0, &pbc);
if (SUCCEEDED(hr))
{
STRRET strret;
BIND_OPTS bo;
memset(&bo, 0, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.grfFlags = BIND_JUSTTESTEXISTENCE;
bo.grfMode = STGM_CREATE;
pbc->SetBindOptions(&bo);
hr = psf->GetDisplayNameOf(pidl,
SHGDN_FORPARSING,
&strret);
if (SUCCEEDED(hr))
{
TCHAR szIDList[80];
TCHAR szPath[80] = TEXT("::");
StrRetToBuf(&strret, pidl, szIDList, ARRAYSIZE(szIDList));
StringFromGUID2(CLSID_OfflineFilesFolder,
&szPath[2],
sizeof(szPath) - (2 * sizeof(TCHAR)));
if (0 == lstrcmpi(szIDList, szPath))
hr = S_OK;
else
hr = S_FALSE;
}
pbc->Release();
}
psf->Release();
}
return hr;
}
HRESULT
COfflineFilesFolder::GetFolder( // [static]
IShellFolder **ppsf
)
{
TraceAssert(NULL != ppsf);
*ppsf = NULL;
IShellFolder *psfDesktop;
HRESULT hr = SHGetDesktopFolder(&psfDesktop);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlOfflineFiles;
hr = COfflineFilesFolder::CreateIDList(&pidlOfflineFiles);
if (SUCCEEDED(hr))
{
hr = psfDesktop->BindToObject(pidlOfflineFiles, NULL, IID_IShellFolder, (void **)ppsf);
ILFree(pidlOfflineFiles);
}
psfDesktop->Release();
}
return hr;
}
//
// Generate a new OLID from a UNC path.
//
HRESULT
COfflineFilesFolder::OLID_CreateFromUNCPath( // [static]
LPCTSTR pszPath,
const WIN32_FIND_DATA *pfd,
DWORD dwStatus,
DWORD dwPinCount,
DWORD dwHintFlags,
DWORD dwServerStatus,
LPOLID *ppolid
)
{
HRESULT hr = E_OUTOFMEMORY;
int cchPath = lstrlen(pszPath) + 1;
int cbIDL = sizeof(OLID) + (cchPath * sizeof(TCHAR)) + sizeof(WORD); // NULL terminator WORD
WIN32_FIND_DATA fd;
if (NULL == pfd)
{
//
// Caller didn't provide a finddata block. Use a default one
// with all zeros.
//
ZeroMemory(&fd, sizeof(fd));
pfd = &fd;
}
*ppolid = NULL;
LPITEMIDLIST pidl = (LPITEMIDLIST)SHAlloc(cbIDL);
if (NULL != pidl)
{
OLID *polid = (OLID *)pidl;
memset(pidl, 0, cbIDL);
polid->cb = (USHORT)(cbIDL - sizeof(WORD));
polid->uSig = OLID_SIG;
polid->cbFixed = sizeof(OLID);
polid->cchNameOfs = (DWORD)(PathFindFileName(pszPath) - pszPath);
polid->dwStatus = dwStatus;
polid->dwPinCount = dwPinCount;
polid->dwHintFlags = dwHintFlags;
polid->dwServerStatus = dwServerStatus;
polid->dwFileAttributes = pfd->dwFileAttributes;
polid->dwFileSizeLow = pfd->nFileSizeLow;
polid->dwFileSizeHigh = pfd->nFileSizeHigh;
polid->ft = pfd->ftLastWriteTime;
lstrcpy(polid->szPath, pszPath);
OLID_SplitPathAndName(polid);
*ppolid = polid;
hr = NOERROR;
}
return hr;
}
void
COfflineFilesFolder::OLID_GetWin32FindData( // [static]
LPCOLID polid,
WIN32_FIND_DATA *pfd
)
{
TraceAssert(NULL != polid);
TraceAssert(NULL != pfd);
ZeroMemory(pfd, sizeof(*pfd));
pfd->dwFileAttributes = polid->dwFileAttributes;
pfd->nFileSizeLow = polid->dwFileSizeLow;
pfd->nFileSizeHigh = polid->dwFileSizeHigh;
pfd->ftLastWriteTime = polid->ft;
OLID_GetFileName(polid, pfd->cFileName, ARRAYSIZE(pfd->cFileName));
}
//
// Retrieve the full path (including filename) from an OLID.
//
LPCTSTR
COfflineFilesFolder::OLID_GetFullPath( // [static]
LPCOLID polid,
LPTSTR pszPath,
UINT cchPath
)
{
PCTSTR pszInPath;
PCTSTR pszInName;
TraceAssert(NULL != polid);
TraceAssert(NULL != pszPath);
TSTR_ALIGNED_STACK_COPY( &pszInPath, polid->szPath );
TSTR_ALIGNED_STACK_COPY( &pszInName,
polid->szPath + polid->cchNameOfs );
PathCombine(pszPath, pszInPath, pszInName);
return pszPath;
}
//
// Retrieve only the path portion of the OLID.
//
LPCTSTR
COfflineFilesFolder::OLID_GetPath( // [static]
LPCOLID polid,
LPTSTR pszPath,
UINT cchPath
)
{
TraceAssert(NULL != polid);
TraceAssert(NULL != pszPath);
ualstrcpyn(pszPath, polid->szPath, cchPath);
return pszPath;
}
//
// Retrieve only the filename portion of the OLID.
//
LPCTSTR
COfflineFilesFolder::OLID_GetFileName( // [static]
LPCOLID polid,
LPTSTR pszName,
UINT cchName
)
{
TraceAssert(NULL != polid);
TraceAssert(NULL != pszName);
ualstrcpyn(pszName, polid->szPath + polid->cchNameOfs, cchName);
return pszName;
}
//
// Restore the backslash separating the path from the filename
// in the OLID.
//
void
COfflineFilesFolder::OLID_CombinePathAndName( // [static]
LPOLID polid
)
{
TraceAssert(NULL != polid);
TraceAssert(COfflineFilesFolder::ValidateIDList((LPCITEMIDLIST)polid));
if (0 < polid->cchNameOfs)
polid->szPath[polid->cchNameOfs - 1] = TEXT('\\');
}
//
// Remove the backslash separating the path from the filename
// in the OLID.
//
void
COfflineFilesFolder::OLID_SplitPathAndName( // [static]
LPOLID polid
)
{
TraceAssert(NULL != polid);
TraceAssert(COfflineFilesFolder::ValidateIDList((LPCITEMIDLIST)polid));
if (0 < polid->cchNameOfs)
polid->szPath[polid->cchNameOfs - 1] = TEXT('\0');
}
//
// Given an OLID this function creates a fully-qualified simple
// IDList for use by the shell. The returned IDList is relative to the
// desktop folder.
//
HRESULT
COfflineFilesFolder::OLID_CreateSimpleIDList( // [static]
LPCOLID polid,
LPITEMIDLIST *ppidlOut
)
{
TraceAssert(NULL != polid);
TraceAssert(NULL != ppidlOut);
TraceAssert(COfflineFilesFolder::ValidateIDList((LPCITEMIDLIST)polid));
WIN32_FIND_DATA fd;
TCHAR szFullPath[MAX_PATH];
OLID_GetWin32FindData(polid, &fd);
OLID_GetFullPath(polid, szFullPath, ARRAYSIZE(szFullPath));
return SHSimpleIDListFromFindData(szFullPath, &fd, ppidlOut);
}
HRESULT
COfflineFilesFolder::OLID_Bind( // [static]
LPCOLID polid,
REFIID riid,
void **ppv,
LPITEMIDLIST *ppidlFull,
LPCITEMIDLIST *ppidlItem
)
{
*ppidlFull = NULL;
*ppidlItem = NULL;
HRESULT hr = OLID_CreateSimpleIDList(polid, ppidlFull);
if (SUCCEEDED(hr))
{
hr = ::BindToIDListParent((LPCITEMIDLIST)*ppidlFull, riid, ppv, ppidlItem);
}
return hr;
}
//-----------------------------------------------------------------------------
// COfflineFilesDropTarget
//-----------------------------------------------------------------------------
COfflineFilesDropTarget::COfflineFilesDropTarget(
HWND hwnd
) : m_cRef(1),
m_hwnd(hwnd),
m_pcm(NULL),
m_bIsOurData(false)
{
}
COfflineFilesDropTarget::~COfflineFilesDropTarget(
void
)
{
DoRelease(m_pcm);
}
HRESULT
COfflineFilesDropTarget::QueryInterface(
REFIID riid,
void **ppv
)
{
static const QITAB qit[] = {
QITABENT(COfflineFilesDropTarget, IDropTarget),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG
COfflineFilesDropTarget::AddRef(
void
)
{
return InterlockedIncrement(&m_cRef);
}
ULONG
COfflineFilesDropTarget::Release(
void
)
{
if (InterlockedDecrement(&m_cRef))
return m_cRef;
delete this;
return 0;
}
HRESULT
COfflineFilesDropTarget::DragEnter(
IDataObject *pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD *pdwEffect
)
{
HRESULT hr;
*pdwEffect = DROPEFFECT_NONE;
// The context menu handler has logic to check whether
// the selected files are cacheable, etc. It only adds
// verbs to the context menu when it makes sense to do so.
// We can make use of this by calling QueryContextMenu
// here and seeing whether anything is added to the menu.
DoRelease(m_pcm);
if (!(m_bIsOurData = IsOurDataObject(pDataObject)))
{
hr = CreateOfflineFilesContextMenu(pDataObject, IID_IContextMenu, (void **)&m_pcm);
if (SUCCEEDED(hr))
{
HMENU hmenu = CreateMenu();
if (hmenu)
{
hr = m_pcm->QueryContextMenu(hmenu, 0, 0, 100, 0);
DestroyMenu(hmenu);
}
else
hr = E_OUTOFMEMORY;
// Did the context menu add anything?
if (FAILED(hr) || ResultFromShort(0) == hr)
{
// No, release m_pcm and set it to NULL
DoRelease(m_pcm);
}
else
{
// Yes
*pdwEffect |= DROPEFFECT_COPY;
}
}
}
return NOERROR;
}
HRESULT
COfflineFilesDropTarget::DragOver(
DWORD grfKeyState,
POINTL pt,
DWORD *pdwEffect
)
{
*pdwEffect = DROPEFFECT_NONE;
if (m_pcm && !m_bIsOurData)
*pdwEffect = DROPEFFECT_COPY;
return NOERROR;
}
HRESULT
COfflineFilesDropTarget::DragLeave(
void
)
{
DoRelease(m_pcm);
return NOERROR;
}
HRESULT
COfflineFilesDropTarget::Drop(
IDataObject *pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD *pdwEffect
)
{
HRESULT hr = E_FAIL;
*pdwEffect = DROPEFFECT_NONE;
if (m_pcm && !m_bIsOurData)
{
CMINVOKECOMMANDINFO cmi;
ZeroMemory(&cmi, sizeof(cmi));
cmi.cbSize = sizeof(cmi);
cmi.hwnd = m_hwnd;
cmi.lpVerb = STR_PIN_VERB;
cmi.nShow = SW_SHOWNORMAL;
hr = m_pcm->InvokeCommand(&cmi);
if (SUCCEEDED(hr))
{
*pdwEffect = DROPEFFECT_COPY;
}
}
DoRelease(m_pcm);
return hr;
}
HRESULT
COfflineFilesDropTarget::CreateInstance(
HWND hwnd,
REFIID riid,
void **ppv
)
{
HRESULT hr = E_NOINTERFACE;
*ppv = NULL;
COfflineFilesDropTarget* pdt = new COfflineFilesDropTarget(hwnd);
if (NULL != pdt)
{
hr = pdt->QueryInterface(riid, ppv);
pdt->Release();
}
else
hr = E_OUTOFMEMORY;
return hr;
}
//
// If the source of the data is the Offline Files folder the data object
// will support the "Data Source Clsid" clipboard format and the CLSID
// will be CLSID_OfflineFilesFolder.
// Checking for this is how we keep from dropping our own data on ourselves.
//
bool
COfflineFilesDropTarget::IsOurDataObject(
IDataObject *pdtobj
)
{
TraceAssert(NULL != pdtobj);
bool bIsOurData = false;
CLIPFORMAT cfSrcClsid = (CLIPFORMAT)RegisterClipboardFormat(c_szCFDataSrcClsid);
FORMATETC fe = { cfSrcClsid, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM medium;
HRESULT hr = pdtobj->GetData(&fe, &medium);
if (SUCCEEDED(hr))
{
const CLSID *pclsid = (const CLSID *)GlobalLock(medium.hGlobal);
if (pclsid)
{
bIsOurData = boolify(IsEqualCLSID(CLSID_OfflineFilesFolder, *pclsid));
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
}
return bIsOurData;
}
//-----------------------------------------------------------------------------
// COfflineFilesViewCallback
//-----------------------------------------------------------------------------
COfflineFilesViewCallback::COfflineFilesViewCallback(
COfflineFilesFolder *pfolder
) : _cRef(1)
{
m_hwnd = NULL;
_psfv = NULL;
_pfolder = pfolder;
_pfolder->AddRef();
InitializeCriticalSection(&m_cs);
}
COfflineFilesViewCallback::~COfflineFilesViewCallback(
void
)
{
_pfolder->Release();
if (_psfv)
_psfv->Release();
//
// Since the folder cache is global we don't want it taking up space while the
// Offline Folders view isn't active. Clear it when the view callback is
// destroyed.
//
CFolderCache::Singleton().Clear();
DeleteCriticalSection(&m_cs);
}
STDMETHODIMP
COfflineFilesViewCallback::QueryInterface(
REFIID riid,
void **ppv
)
{
static const QITAB qit[] = {
QITABENT(COfflineFilesViewCallback, IShellFolderViewCB), // IID_IShellFolderViewCB
QITABENT(COfflineFilesViewCallback, IObjectWithSite), // IID_IObjectWithSite
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_ (ULONG)
COfflineFilesViewCallback::AddRef(
void
)
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_ (ULONG)
COfflineFilesViewCallback::Release(
void
)
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
HRESULT
COfflineFilesViewCallback::SetSite(
IUnknown *punkSite
)
{
if (_psfv)
{
_psfv->Release();
_psfv = NULL;
}
if (punkSite)
punkSite->QueryInterface(IID_IShellFolderView, (void **)&_psfv);
return S_OK;
}
HRESULT
COfflineFilesViewCallback::GetSite(
REFIID riid,
void **ppv
)
{
if (_psfv)
return _psfv->QueryInterface(riid, ppv);
*ppv = NULL;
return E_FAIL;
}
STDMETHODIMP
COfflineFilesViewCallback::MessageSFVCB(
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
HRESULT hres = S_OK;
switch (uMsg)
{
case SFVM_COLUMNCLICK:
if (_psfv)
return _psfv->Rearrange((int)wParam);
break;
case SFVM_WINDOWCREATED:
OnSFVM_WindowCreated((HWND)wParam);
break;
case SFVM_ADDPROPERTYPAGES:
OnSFVM_AddPropertyPages((DWORD)wParam, (SFVM_PROPPAGE_DATA *)lParam);
break;
case SFVM_GETHELPTOPIC:
StrCpyW(((SFVM_HELPTOPIC_DATA *)lParam)->wszHelpFile, L"offlinefolders.chm > windefault");
break;
case SFVM_QUERYFSNOTIFY:
hres = OnSFVM_QueryFSNotify((SHChangeNotifyEntry *)lParam);
break;
case SFVM_GETNOTIFY:
hres = OnSFVM_GetNotify((LPITEMIDLIST *)wParam, (LONG *)lParam);
break;
case SFVM_FSNOTIFY:
hres = OnSFVM_FSNotify((LPCITEMIDLIST *)wParam, (LONG)lParam);
break;
case SFVM_GETVIEWS:
hres = OnSFVM_GetViews((SHELLVIEWID *)wParam, (IEnumSFVViews **)lParam);
break;
case SFVM_ALTERDROPEFFECT:
hres = OnSFVM_AlterDropEffect((DWORD *)wParam, (IDataObject *)lParam);
break;
case SFVMP_SETVIEWREDRAW:
hres = OnSFVMP_SetViewRedraw(lParam != FALSE);
break;
case SFVMP_DELVIEWITEM:
hres = OnSFVMP_DelViewItem((LPCTSTR)lParam);
break;
default:
hres = E_NOTIMPL;
}
return hres;
}
HRESULT
COfflineFilesViewCallback::OnSFVM_WindowCreated(
HWND hwnd
)
{
m_hwnd = hwnd;
return NOERROR;
}
HRESULT
COfflineFilesViewCallback::OnSFVM_AddPropertyPages(
DWORD pv,
SFVM_PROPPAGE_DATA *ppagedata
)
{
const CLSID *c_rgFilePages[] = {
&CLSID_FileTypes,
&CLSID_OfflineFilesOptions
};
IShellPropSheetExt * pspse;
HRESULT hr;
for (int i = 0; i < ARRAYSIZE(c_rgFilePages); i++)
{
hr = SHCoCreateInstance(NULL,
c_rgFilePages[i],
NULL,
IID_IShellPropSheetExt,
(void **)&pspse);
if (SUCCEEDED(hr))
{
pspse->AddPages(ppagedata->pfn, ppagedata->lParam);
pspse->Release();
}
}
return S_OK;
}
HRESULT
COfflineFilesViewCallback::OnSFVM_GetViews(
SHELLVIEWID *pvid,
IEnumSFVViews **ppev
)
{
//
// Offline files folder prefers details view.
//
*pvid = VID_Details;
return COfflineFilesViewEnum::CreateInstance(ppev);
}
HRESULT
COfflineFilesViewCallback::OnSFVM_GetNotify(
LPITEMIDLIST *ppidl,
LONG *plEvents
)
{
*ppidl = NULL;
*plEvents = GetChangeNotifyEvents();
return NOERROR;
}
HRESULT
COfflineFilesViewCallback::OnSFVM_QueryFSNotify(
SHChangeNotifyEntry *pfsne
)
{
//
// Register to receive global events
//
pfsne->pidl = NULL;
pfsne->fRecursive = TRUE;
return NOERROR;
}
HRESULT
COfflineFilesViewCallback::OnSFVMP_SetViewRedraw(
BOOL bRedraw
)
{
if (_psfv)
_psfv->SetRedraw(bRedraw);
return NOERROR;
}
HRESULT
COfflineFilesViewCallback::OnSFVMP_DelViewItem(
LPCTSTR pszPath
)
{
Lock();
HRESULT hr = RemoveItem(pszPath);
Unlock();
return hr;
}
//
// This is called immediately before the shell calls DoDragDrop().
// It let's us turn off "move" after all of the other drop effect
// modifications have taken place.
//
HRESULT
COfflineFilesViewCallback::OnSFVM_AlterDropEffect(
DWORD *pdwEffect,
IDataObject *pdtobj // unused.
)
{
*pdwEffect &= ~DROPEFFECT_MOVE; // Disable move.
return NOERROR;
}
//
// Handler for shell change notifications.
//
// We handle SHCNE_UPDATEITEM, SHCNE_UPDATEDIR, SHCNE_DELETE
// and SHCNE_RENAMEITEM
//
HRESULT
COfflineFilesViewCallback::OnSFVM_FSNotify(
LPCITEMIDLIST *ppidl,
LONG lEvent
)
{
HRESULT hr = NOERROR;
if (GetChangeNotifyEvents() & lEvent)
{
Lock();
if (SHCNE_RENAMEITEM & lEvent)
{
hr = RenameItem(*ppidl, *(ppidl + 1));
}
else
{
//
// Convert the full pidl to a UNC path.
//
TCHAR szPath[MAX_PATH];
if (SHGetPathFromIDList(*ppidl, szPath))
{
if (SHCNE_UPDATEDIR & lEvent)
hr = UpdateDir(szPath);
else if (SHCNE_UPDATEITEM & lEvent)
hr = UpdateItem(szPath);
else if (SHCNE_DELETE & lEvent)
hr = RemoveItem(szPath);
}
}
Unlock();
}
return hr;
}
//
// Handler for SHCNE_RENAMEITEM notifications.
//
HRESULT
COfflineFilesViewCallback::RenameItem(
LPCITEMIDLIST pidlOld,
LPCITEMIDLIST pidl
)
{
TraceAssert(NULL != pidlOld);
TraceAssert(NULL != pidl);
//
// Get the full path for the original pidl.
//
TCHAR szPath[MAX_PATH];
HRESULT hr = NOERROR;
if (SHGetPathFromIDList(pidlOld, szPath))
{
//
// Find the original OLID in the listview.
//
LPCOLID polid = NULL;
hr = FindOLID(szPath, &polid);
if (SUCCEEDED(hr))
{
//
// Get the full path for the new renamed pidl.
//
if (SHGetPathFromIDList(pidl, szPath))
{
//
// Create a new OLID for the newly renamed pidl.
//
LPOLID polidNew;
WIN32_FIND_DATA fd;
ZeroMemory(&fd, sizeof(fd));
fd.nFileSizeHigh = polid->dwFileSizeHigh;
fd.nFileSizeLow = polid->dwFileSizeLow;
fd.ftLastWriteTime = polid->ft;
fd.dwFileAttributes = polid->dwFileAttributes;
hr = COfflineFilesFolder::OLID_CreateFromUNCPath(szPath,
&fd,
polid->dwStatus,
polid->dwPinCount,
polid->dwHintFlags,
polid->dwServerStatus,
&polidNew);
if (SUCCEEDED(hr))
{
UINT iItem;
//
// Replace the old olid in the view with the new olid.
// DefView will free the old one if successful.
//
hr = _psfv->UpdateObject((LPITEMIDLIST)polid,
(LPITEMIDLIST)polidNew,
&iItem);
if (FAILED(hr))
{
//
// View wouldn't accept the new OLID so free it.
//
ILFree((LPITEMIDLIST)polidNew);
}
}
}
}
}
return hr;
}
//
// Locates an OLID in the view and returns the address of the
// OLID. The returned pointer is to a const object so the caller
// should not call ILFree on it.
//
HRESULT
COfflineFilesViewCallback::FindOLID(
LPCTSTR pszPath,
LPCOLID *ppolid
)
{
TraceAssert(NULL != pszPath);
TraceAssert(NULL != ppolid);
//
// Create one of our OLIDs from the UNC path to use as a search key.
//
LPOLID polid = NULL;
HRESULT hr = COfflineFilesFolder::OLID_CreateFromUNCPath(pszPath, NULL, 0, 0, 0, 0, &polid);
if (SUCCEEDED(hr))
{
//
// Lock so that index returned by IndexItemFromOLID() is
// still valid in call to GetObject().
//
Lock();
//
// Get our item's index in the listview.
//
UINT iItem = ItemIndexFromOLID(polid);
if ((UINT)-1 != iItem)
hr = _psfv->GetObject((LPITEMIDLIST *)ppolid, iItem);
else
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
Unlock();
ILFree((LPITEMIDLIST)polid);
}
return hr;
}
//
// Handler for SHCNE_UPDATEDIR notifications.
//
// Enumerates each immediate child of the directory and performs
// an update.
//
HRESULT
COfflineFilesViewCallback::UpdateDir(
LPCTSTR pszPath
)
{
TraceAssert(NULL != pszPath);
HRESULT hr = NOERROR;
//
// First remove all items from the listview that are immediate children
// of this directory. This in effect causes a refresh.
//
RemoveItems(pszPath);
//
// Now scan the CSC cache for all items in this directory and update/add
// to the listview as appropriate.
//
WIN32_FIND_DATA fd;
FILETIME ft;
DWORD dwHintFlags;
DWORD dwPinCount;
DWORD dwStatus;
CCscFindHandle hFind = CacheFindFirst(pszPath, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft);
if (hFind.IsValid())
{
TCHAR szPath[MAX_PATH];
do
{
if (0 == (FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes))
{
if (NULL != PathCombine(szPath, pszPath, fd.cFileName))
UpdateItem(szPath, fd, dwStatus, dwPinCount, dwHintFlags);
}
}
while(CacheFindNext(hFind, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft));
}
else
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
//
// Given a directory path, remove all immediate children from the listview.
//
HRESULT
COfflineFilesViewCallback::RemoveItems(
LPCTSTR pszDir
)
{
TraceAssert(NULL != pszDir);
UINT cItems;
if (SUCCEEDED(_psfv->GetObjectCount(&cItems)))
{
LPCOLID polid;
for (UINT i = 0; i < cItems; i++)
{
if (SUCCEEDED(_psfv->GetObject((LPITEMIDLIST *)&polid, i)))
{
if (0 == ualstrcmpi(pszDir, polid->szPath))
{
//
// This item is from the "pszDir" directory.
// Remove it from the listview.
//
RemoveItem(polid);
//
// Adjust item count and loop variable for deleted
// item.
//
cItems--;
i--;
}
}
}
}
return NOERROR;
}
//
// Given an OLID, remove an item from the view.
//
HRESULT
COfflineFilesViewCallback::RemoveItem(
LPCOLID polid
)
{
TraceAssert(NULL != polid);
HRESULT hr = E_FAIL;
UINT iItem = ItemIndexFromOLID(polid);
if ((UINT)-1 != iItem)
{
//
// File is in the listview. Remove it.
//
hr = _psfv->RemoveObject((LPITEMIDLIST)polid, &iItem);
}
return hr;
}
//
// Give a UNC path, remove an item from the view.
//
HRESULT
COfflineFilesViewCallback::RemoveItem(
LPCTSTR pszPath
)
{
TraceAssert(NULL != pszPath);
LPOLID polid = NULL;
HRESULT hr = COfflineFilesFolder::OLID_CreateFromUNCPath(pszPath, NULL, 0, 0, 0, 0, &polid);
if (SUCCEEDED(hr))
{
hr = RemoveItem(polid);
ILFree((LPITEMIDLIST)polid);
}
return hr;
}
//
// Handler for SHCNE_UPDATEITEM notifications.
//
// Updates a single item in the viewer. If the item no longer
// exists in the cache, it is removed from the view.
//
HRESULT
COfflineFilesViewCallback::UpdateItem(
LPCTSTR pszPath
)
{
TraceAssert(NULL != pszPath);
HRESULT hr = NOERROR;
DWORD dwAttr = ::GetFileAttributes(pszPath);
if (DWORD(-1) != dwAttr)
{
if (0 == (FILE_ATTRIBUTE_DIRECTORY & dwAttr))
{
DWORD dwHintFlags = 0;
DWORD dwPinCount = 0;
DWORD dwStatus = 0;
WIN32_FIND_DATA fd;
FILETIME ft;
CCscFindHandle hFind = CacheFindFirst(pszPath, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft);
if (hFind.IsValid())
{
hr = UpdateItem(pszPath, fd, dwStatus, dwPinCount, dwHintFlags);
}
else
{
hr = RemoveItem(pszPath);
}
}
}
return hr;
}
//
// Update a single item in the cache. This instance of UpdateItem()
// is called once we have information on the item from the CSC cache.
// If an item doesn't already exist in the viewer, it is added.
// If an item does exist, it is updated with the new CSC info.
//
// This function assumes the item is NOT a directory.
//
HRESULT
COfflineFilesViewCallback::UpdateItem(
LPCTSTR pszPath,
const WIN32_FIND_DATA& fd,
DWORD dwStatus,
DWORD dwPinCount,
DWORD dwHintFlags
)
{
TraceAssert(NULL != pszPath);
TraceAssert(0 == (FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes));
HRESULT hr = NOERROR;
UINT iItem = (UINT)-1;
//
// Now create one of our OLIDs from the UNC path.
//
LPOLID polid = NULL;
hr = COfflineFilesFolder::OLID_CreateFromUNCPath(pszPath, NULL, 0, 0, 0, 0, &polid);
if (SUCCEEDED(hr))
{
//
// Get our item's index in the listview.
//
LPCITEMIDLIST pidlOld = NULL;
//
// Lock so that index returned by ItemIndexFromOLID() is
// still valid in call to GetObject().
//
Lock();
iItem = ItemIndexFromOLID(polid);
if ((UINT)-1 != iItem)
{
//
// Won't be using this olid. We'll be cloning the one from the
// listview.
//
ILFree((LPITEMIDLIST)polid);
polid = NULL;
//
// Item is in the view. Get the existing OLID and clone it.
// IMPORTANT: We DON'T call ILFree on pidlOld. Despite the
// argument to GetObject being non-const, it's
// really returning a pointer to a const object.
// In actuality, it's the address of the listview
// item's LPARAM.
//
hr = _psfv->GetObject((LPITEMIDLIST *)&pidlOld, iItem);
if (SUCCEEDED(hr))
{
polid = (LPOLID)ILClone(pidlOld);
if (NULL == polid)
{
hr = E_OUTOFMEMORY;
}
}
}
Unlock();
if (NULL != polid)
{
//
// polid either points to the new partial OLID we created
// with OLID_CreateFromUNCPath() or a clone of the existing
// OLID in the listview. Fill/update the file and
// CSC information.
//
polid->dwFileSizeHigh = fd.nFileSizeHigh;
polid->dwFileSizeLow = fd.nFileSizeLow;
polid->ft = fd.ftLastWriteTime;
polid->dwFileAttributes = fd.dwFileAttributes;
polid->dwStatus = dwStatus;
polid->dwHintFlags = dwHintFlags;
polid->dwPinCount = dwPinCount;
if ((UINT)-1 != iItem)
{
//
// Replace the old olid in the view with the new olid.
// DefView will free the old one if successful.
//
hr = _psfv->UpdateObject((LPITEMIDLIST)pidlOld,
(LPITEMIDLIST)polid,
&iItem);
}
else
{
//
// Add the new olid to the view.
//
hr = _psfv->AddObject((LPITEMIDLIST)polid, &iItem);
}
if (SUCCEEDED(hr))
{
//
// Added new OLID to the listview. Null out the local
// ptr so we don't free the IDList later.
//
polid = NULL;
}
}
if (NULL != polid)
ILFree((LPITEMIDLIST)polid);
}
return hr;
}
//
// Retrieve the listview index for a give OLID.
// Returns: Index of item or -1 if not found.
//
UINT
COfflineFilesViewCallback::ItemIndexFromOLID(
LPCOLID polid
)
{
TraceAssert(NULL != polid);
UINT iItem = (UINT)-1;
UINT cItems;
//
// Lock so that list remains consistent while we locate the item.
//
Lock();
if (SUCCEEDED(_psfv->GetObjectCount(&cItems)))
{
for (UINT i = 0; i < cItems; i++)
{
LPCITEMIDLIST pidl;
if (SUCCEEDED(_psfv->GetObject((LPITEMIDLIST *)&pidl, i)))
{
//
// Do name comparison first since it is least likely to find a match.
//
if (S_OK == _pfolder->CompareIDs(ICOL_NAME, pidl, (LPCITEMIDLIST)polid) &&
S_OK == _pfolder->CompareIDs(ICOL_LOCATION, pidl, (LPCITEMIDLIST)polid))
{
iItem = i;
break;
}
}
}
}
Unlock();
return (UINT)iItem;
}
//-----------------------------------------------------------------------------
// COfflineFilesViewEnum
//-----------------------------------------------------------------------------
COfflineFilesViewEnum::COfflineFilesViewEnum(
void
)
: m_cRef(1),
m_iAddView(0)
{
}
COfflineFilesViewEnum::~COfflineFilesViewEnum(
void
)
{
}
HRESULT
COfflineFilesViewEnum::CreateInstance(
IEnumSFVViews **ppenum
)
{
HRESULT hr = E_OUTOFMEMORY;
COfflineFilesViewEnum *pEnum = new COfflineFilesViewEnum;
if (NULL != pEnum)
{
hr = pEnum->QueryInterface(IID_IEnumSFVViews, (void **)ppenum);
}
return hr;
}
STDMETHODIMP
COfflineFilesViewEnum::QueryInterface (
REFIID riid,
void **ppv
)
{
static const QITAB qit[] = {
QITABENT(COfflineFilesViewEnum, IEnumSFVViews),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG)
COfflineFilesViewEnum::AddRef(
void
)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG)
COfflineFilesViewEnum::Release(
void
)
{
if (InterlockedDecrement(&m_cRef))
return m_cRef;
delete this;
return 0;
}
STDMETHODIMP
COfflineFilesViewEnum::Next(
ULONG celt,
SFVVIEWSDATA **ppData,
ULONG *pceltFetched
)
{
HRESULT hr = S_FALSE;
ULONG celtFetched = 0;
if (!celt || !ppData || (celt > 1 && !pceltFetched))
{
return E_INVALIDARG;
}
if (0 == m_iAddView)
{
//
// All we add is Thumbnail view.
//
ppData[0] = (SFVVIEWSDATA *) SHAlloc(sizeof(SFVVIEWSDATA));
if (ppData[0])
{
ppData[0]->idView = CLSID_ThumbnailViewExt;
ppData[0]->idExtShellView = CLSID_ThumbnailViewExt;
ppData[0]->dwFlags = SFVF_TREATASNORMAL | SFVF_NOWEBVIEWFOLDERCONTENTS;
ppData[0]->lParam = 0x00000011;
ppData[0]->wszMoniker[0] = 0;
celtFetched++;
m_iAddView++;
hr = S_OK;
}
else
hr = E_OUTOFMEMORY;
}
if ( pceltFetched )
{
*pceltFetched = celtFetched;
}
return hr;
}
STDMETHODIMP
COfflineFilesViewEnum::Skip(
ULONG celt
)
{
if (celt && !m_iAddView)
{
m_iAddView++;
celt--;
}
return (celt ? S_FALSE : S_OK );
}
STDMETHODIMP COfflineFilesViewEnum::Reset(
void
)
{
m_iAddView = 0;
return NOERROR;
}
STDMETHODIMP
COfflineFilesViewEnum::Clone(
IEnumSFVViews **ppenum
)
{
return CreateInstance(ppenum);
}
//-----------------------------------------------------------------------------
// COfflineDetails
//-----------------------------------------------------------------------------
COfflineDetails::COfflineDetails(
COfflineFilesFolder *pfolder
) : _cRef (1)
{
_pfolder = pfolder;
_pfolder->AddRef();
}
COfflineDetails::~COfflineDetails()
{
if (_pfolder)
_pfolder->Release();
}
STDMETHODIMP
COfflineDetails::QueryInterface(
REFIID riid,
void **ppv
)
{
static const QITAB qit[] = {
QITABENT(COfflineDetails, IShellDetails),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG)
COfflineDetails::AddRef(
void
)
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG)
COfflineDetails::Release(
void
)
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
//-----------------------------------------------------------------------------
// CFileTypeCache
//
// Implements a simple hash table for storing file type strings keyed on
// file extension.
//
//-----------------------------------------------------------------------------
CFileTypeCache::CFileTypeCache(
int cBuckets
) : m_cBuckets(cBuckets),
m_prgBuckets(NULL)
{
InitializeCriticalSection(&m_cs);
}
CFileTypeCache::~CFileTypeCache(
void
)
{
Lock();
if (NULL != m_prgBuckets)
{
for (int i = 0; i < m_cBuckets; i++)
{
while(NULL != m_prgBuckets[i])
{
CEntry *pDelThis = m_prgBuckets[i];
m_prgBuckets[i] = m_prgBuckets[i]->Next();
delete pDelThis;
}
}
delete[] m_prgBuckets;
m_prgBuckets = NULL;
}
Unlock();
DeleteCriticalSection(&m_cs);
}
CFileTypeCache::CEntry *
CFileTypeCache::Lookup(
LPCTSTR pszExt
)
{
if (NULL != m_prgBuckets)
{
for (CEntry *pEntry = m_prgBuckets[Hash(pszExt)]; pEntry; pEntry = pEntry->Next())
{
if (0 == pEntry->CompareExt(pszExt))
return pEntry;
}
}
return NULL;
}
HRESULT
CFileTypeCache::Add(
LPCTSTR pszExt,
LPCTSTR pszTypeName
)
{
HRESULT hr = E_OUTOFMEMORY;
if (NULL != m_prgBuckets)
{
CEntry *pNewEntry = new CEntry(pszExt, pszTypeName);
if (NULL != pNewEntry && pNewEntry->IsValid())
{
//
// Link new entry at the head of the bucket's linked list.
//
int iHash = Hash(pszExt);
pNewEntry->SetNext(m_prgBuckets[iHash]);
m_prgBuckets[iHash] = pNewEntry;
hr = NOERROR;
}
else
{
delete pNewEntry;
}
}
return hr;
}
HRESULT
CFileTypeCache::GetTypeName(
LPCTSTR pszPath, // Can be full path or only "filename.ext".
DWORD dwFileAttributes,
LPTSTR pszDest,
int cchDest
)
{
HRESULT hr = S_OK;
Lock();
if (NULL == m_prgBuckets)
{
//
// Create hash bucket array on-demand. This way it's not
// created until someone asks for something from the cache.
// Simple "creation" of the cache object is therefore cheap.
//
m_prgBuckets = new CEntry* [m_cBuckets];
if (NULL != m_prgBuckets)
{
ZeroMemory(m_prgBuckets, sizeof(m_prgBuckets[0]) * m_cBuckets);
}
else
{
hr = E_OUTOFMEMORY;
}
}
if (SUCCEEDED(hr))
{
SHFILEINFO sfi;
LPCTSTR pszTypeName = NULL;
LPCTSTR pszExt = ::PathFindExtension(pszPath);
//
// Note that Lookup will gracefully fail if the hash bucket array
// creation failed. In that case we'll get the info from
// SHGetFileInfo and return it directly to the caller. This means
// that failure to create the cache is not fatal. It just means we
// don't cache any data.
//
CEntry *pEntry = Lookup(pszExt);
if (NULL != pEntry)
{
// Cache hit.
pszTypeName = pEntry->TypeName();
}
if (NULL == pszTypeName)
{
// Cache miss.
if (SHGetFileInfo(::PathFindFileName(pszPath),
dwFileAttributes,
&sfi,
sizeof(sfi),
SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES))
{
//
// Add new entry to cache. We're not concerned if the
// addition fails. It just means we'll get a cache miss
// on this item next time and repeat the SHGetFileInfo call.
//
pszTypeName = sfi.szTypeName;
Add(pszExt, sfi.szTypeName);
}
}
if (NULL != pszTypeName)
{
lstrcpyn(pszDest, pszTypeName, cchDest);
hr = S_OK;
}
else
{
hr = E_FAIL;
}
}
Unlock();
return hr;
}
int
CFileTypeCache::Hash(
LPCTSTR pszExt
)
{
int iSum = 0;
while(*pszExt)
iSum += int(*pszExt++);
return iSum % m_cBuckets;
}
CFileTypeCache::CEntry::CEntry(
LPCTSTR pszExt,
LPCTSTR pszTypeName
) : m_pNext(NULL)
{
m_pszExt = StrDup(pszExt);
m_pszTypeName = StrDup(pszTypeName);
}
CFileTypeCache::CEntry::~CEntry(
void
)
{
if (NULL != m_pszExt)
{
LocalFree(m_pszExt);
}
if (NULL != m_pszTypeName)
{
LocalFree(m_pszTypeName);
}
}
//
// This function creates our standard offline-files context menu.
// This is the one used by the shell that inserts the
// "Make Available Offline" and "Synchronize" items.
//
HRESULT
CreateOfflineFilesContextMenu(
IDataObject *pdtobj,
REFIID riid,
void **ppv
)
{
TraceAssert(NULL != ppv);
HRESULT hr = E_OUTOFMEMORY;
*ppv = NULL;
CCscShellExt *pse = new CCscShellExt;
if (NULL != pse)
{
IShellExtInit *psei;
hr = pse->QueryInterface(IID_IShellExtInit, (void **)&psei);
pse->Release();
if (SUCCEEDED(hr))
{
if (NULL != pdtobj)
hr = psei->Initialize(NULL, pdtobj, NULL);
if (SUCCEEDED(hr))
{
hr = psei->QueryInterface(riid, ppv);
}
psei->Release();
}
}
return hr;
}