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.
4255 lines
117 KiB
4255 lines
117 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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 "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)
|
|
{
|
|
HRESULT hr = SHStrDup(pString, &pstrret->pOleStr);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pstrret->uType = STRRET_WSTR;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// 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
|
|
)
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
// 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))
|
|
{
|
|
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))
|
|
{
|
|
StringCchCopy(pszDest, cchDest, rgMap[i].s);
|
|
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];
|
|
hres = OLID_GetFullPath(polid, szPath, ARRAYSIZE(szPath));
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
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 };
|
|
if (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];
|
|
if (LoadStringW(g_hInstance, IDS_FOLDER_LINK_NAME, szLinkFileName, ARRAYSIZE(szLinkFileName)))
|
|
{
|
|
hr = StringCchCat(szLinkFileName, ARRAYSIZE(szLinkFileName), TEXT(".LNK"));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
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 szPath[MAX_PATH];
|
|
HRESULT hr = SHGetSpecialFolderPath(hwndParent, szPath, CSIDL_DESKTOPDIRECTORY, FALSE) ? S_OK : E_FAIL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = S_FALSE; // Assume not found.
|
|
if (PathAppend(szPath, TEXT("*.LNK")))
|
|
{
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hFind = FindFirstFile(szPath, &fd);
|
|
if (INVALID_HANDLE_VALUE != hFind)
|
|
{
|
|
do
|
|
{
|
|
if (!PathRemoveFileSpec(szPath) ||
|
|
!PathAppend(szPath, fd.cFileName))
|
|
{
|
|
// We can't be sure of the buffer contents anymore,
|
|
// so continuing could give bogus results.
|
|
break;
|
|
}
|
|
|
|
hr = IsOurLink(szPath);
|
|
if (S_OK == hr)
|
|
{
|
|
if (NULL != pszPathOut)
|
|
{
|
|
// We found the file using a MAX_PATH buffer and
|
|
// it's unlikely that callers are using smaller
|
|
// buffers, so don't worry about truncation.
|
|
StringCchCopy(pszPathOut, cchPathOut, szPath);
|
|
}
|
|
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;
|
|
|
|
OLID *polid = (OLID *)SHAlloc(cbIDL);
|
|
if (NULL != polid)
|
|
{
|
|
ZeroMemory(polid, 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;
|
|
hr = StringCchCopy(polid->szPath, cchPath, pszPath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Split the name from the path
|
|
if (0 < polid->cchNameOfs)
|
|
polid->szPath[polid->cchNameOfs - 1] = TEXT('\0');
|
|
*ppolid = polid;
|
|
}
|
|
else
|
|
{
|
|
SHFree(polid);
|
|
}
|
|
}
|
|
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.
|
|
//
|
|
HRESULT
|
|
COfflineFilesFolder::OLID_GetFullPath( // [static]
|
|
LPCOLID polid,
|
|
LPTSTR pszPath,
|
|
UINT cchPath
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
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 );
|
|
|
|
if (!PathCombine(pszPath, pszInPath, pszInName))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
|
|
//
|
|
// 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));
|
|
|
|
TCHAR szFullPath[MAX_PATH];
|
|
|
|
HRESULT hr = OLID_GetFullPath(polid, szFullPath, ARRAYSIZE(szFullPath));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WIN32_FIND_DATA fd;
|
|
OLID_GetWin32FindData(polid, &fd);
|
|
hr = SHSimpleIDListFromFindData(szFullPath, &fd, ppidlOut);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
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 = ::SHBindToIDListParent((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
|
|
)
|
|
{
|
|
ASSERT( 0 != m_cRef );
|
|
ULONG cRef = InterlockedDecrement(&m_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
|
|
|
|
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
|
|
)
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
|
|
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:
|
|
{
|
|
SFVM_HELPTOPIC_DATA *phtd = (SFVM_HELPTOPIC_DATA*)lParam;
|
|
hres = StringCchCopy(phtd->wszHelpFile, ARRAYSIZE(phtd->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
|
|
)
|
|
{
|
|
ASSERT( 0 != m_cRef );
|
|
ULONG cRef = InterlockedDecrement(&m_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
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
|
|
)
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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)
|
|
{
|
|
hr = StringCchCopy(pszDest, cchDest, pszTypeName);
|
|
}
|
|
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;
|
|
}
|
|
|