|
|
///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1995.
//
// FILE: SHCOMPUI.C
//
// DESCRIPTION:
//
// This module provides the code for supporting NTFS file compression
// in the NT Explorer user interface. There are two interfaces to the
// compression features.
//
// The first interface is a shell context menu extension that adds
// the options "Compress" and "Uncompress" to the context/file menu of
// an object that has registered the extension. This interface
// uses the standard shell extension protocol of QueryContextMenu,
// InvokeCommand etc. InvokeCommand calls ShellChangeCompressionAttribute()
// to do the actual compression/uncompression.
//
// The shell extension CLSID is {764BF0E1-F219-11ce-972D-00AA00A14F56}
// and is represented by the symbol CLSID_CompressMenuExt.
//
// The second interface is provided for handling compression
// requests through object property pages. Property page action
// code calls ShellChangeCompressionAttribute( ).
//
// This way, through either interface, compression appears the same
// to the user.
//
// Note that a good portion of the actual compression code was taken
// from the WinFile implementation. Some changes were made to
// eliminate redundant code and to produce the desired Explorer
// compression behavior.
//
// The comment string "WARNING" points out areas that are sensitive to maintenance activity.
//
// This module is applicable only to the NT version of the shell.
//
// REVISIONS:
//
// Date Description Programmer
// ---------- --------------------------------------------------- ----------
// 09/15/95 Initial creation. brianau
// 09/20/95 Incorporated changes from 1st code review. brianau
// 10/02/95 Added SCCA context structure. brianau
// Changed all __TEXT() macros to TEXT()
// 10/13/95 Removed function Int64ToString and moved it to brianau
// util.c.
// 02/22/96 Check for shift-key before adding context menu brianau
// items. No shift key, no items.
// Also added call to SHChangeNotify to notify shell
// that compression attribute changed on each file.
// 03/20/96 Added invalidation of drive type flags cache. brianau
// Replaced imported function declarations with
// #include <shellprv.h>
//
///////////////////////////////////////////////////////////////////////////////
#ifdef WINNT
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <shlobj.h>
#include <shellapi.h>
#include <shlobjp.h>
#define INITGUID
#include <initguid.h>
#include "resids.h" // SHCOMPUI Resource IDs.
#include "debug.h" // DbgOut and ASSERT.
#include "shcompui.h"
#define Assert(f)
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
//
// Debug message control.
//
//#define TRACE_SHEXT 1 // Un-comment for shell extension tracing.
//#define TRACE_DLL 1 // Un-comment for DLL load/unload tracing.
//#define TRACE_COMPRESSION 1 // Un-comment for compression code tracing.
//#define SIM_DISK_FULL 1 // Un-comment to test disk-full condition.
//
// Define context menu position offsets for each option.
//
#define MENUOFS_FIRST 0 // For index range checking.
#define MENUOFS_COMPRESS 0 // Command index for "Compress" menu opt..
#define MENUOFS_UNCOMPRESS 1 // Command index for "Uncompress" menu opt.
#define MENUOFS_LAST 1 // For index range checking.
//
// Return values for compression confirmation dialog.
//
#define COMPRESS_CANCELLED 0 // User pressed Cancel.
#define COMPRESS_SUBSNO 1 // User pressed OK without box checked.
#define COMPRESS_SUBSYES 2 // User pressed OK with box checked.
//
// Control values for DisplayCompressProgress( ) and
// DisplayUncompressProgress( )
//
#define PROGRESS_UPD_FILENAME 1
#define PROGRESS_UPD_DIRECTORY 2
#define PROGRESS_UPD_FILEANDDIR 3
#define PROGRESS_UPD_DIRCNT 4
#define PROGRESS_UPD_FILECNT 5
#define PROGRESS_UPD_COMPRESSEDSIZE 6
#define PROGRESS_UPD_FILESIZE 7
#define PROGRESS_UPD_PERCENTAGE 8
#define PROGRESS_UPD_FILENUMBERS 9
#define PROGRESS_UPD_FINAL 10
//
// Return values for CompressErrMessageBox routine.
//
#define RETRY_CREATE 1
#define RETRY_DEVIO 2
//
// Some text string and character constants.
// FEATURE: These should probably come from some locale info.
//
const TCHAR c_szSTAR[] = TEXT("*"); const TCHAR c_szDOT[] = TEXT("."); const TCHAR c_szDOTDOT[] = TEXT(".."); const TCHAR c_szNTLDR[] = TEXT("NTLDR"); const TCHAR c_szBACKSLASH[] = TEXT("\\");
#define CH_NULL TEXT('\0')
//
// String length constants.
//
#define MAX_DLGTITLE_LEN 128 // Max length of dialog title.
#define MAX_MESSAGE_LEN (_MAX_PATH * 3) // General dialog text message.
#define MAX_MENUITEM_LEN 40 // Max length of context menu item.
#define MAX_CMDVERB_LEN 40 // Max length of cmd verb.
typedef HRESULT (CALLBACK FAR * LPFNCREATEINSTANCE)(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID FAR* ppvObject);
static INT_PTR CALLBACK CompressSubsConfirmDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
static BOOL DoCompress(HWND hwndParent, LPTSTR DirectorySpec, LPTSTR FileSpec);
static BOOL DoUncompress(HWND hwndParent, LPTSTR DirectorySpec, LPTSTR FileSpec);
static int CompressErrMessageBox(HWND hwndActive, LPTSTR szFile, PHANDLE phFile);
static INT_PTR CALLBACK CompressErrDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
static BOOL OpenFileForCompress(PHANDLE phFile, LPTSTR szFile);
static DWORD FormatStringWithArgs(LPCTSTR pszFormat, LPTSTR pszBuffer, DWORD nSize, ...);
static DWORD LoadStringWithArgs(HINSTANCE hInstance, UINT uId, LPTSTR pszBuffer, DWORD nSize, ...);
static VOID CompressProgressYield(void);
static VOID DisplayUncompressProgress(int iType);
static void UncompressDiskFullError(HWND hwndParent, HANDLE hFile);
//
// Structure used to communicate with the compression confirmation dialog.
//
typedef struct { BOOL bCompress; // TRUE = compress, FALSE = uncompress
TCHAR szFileName[_MAX_PATH+1]; // File to be acted on.
} CompressionDesc;
//
// Macros for converting between interface and class pointers.
//
#define CMX_OFFSETOF(x) ((UINT_PTR)(&((CContextMenuExt *)0)->x))
#define PVOID2PCMX(pv,offset) ((CContextMenuExt *)(((LPBYTE)pv)-offset))
#define PCM2PCMX(pcmx) PVOID2PCMX(pcmx, CMX_OFFSETOF(ctm))
#define PSEI2PCMX(psei) PVOID2PCMX(psei, CMX_OFFSETOF(sei))
INT g_cRefThisDll = 0; // Reference count for this DLL.
HINSTANCE g_hmodThisDll = NULL; // Handle to the DLL.
HANDLE g_hProcessHeap = NULL; // Handle to the process heap.
HANDLE g_hdlgProgress = NULL; // Operation progress dialog.
TCHAR szMessage[MAX_MESSAGE_LEN+1]; // FEATURE: This need not be global.
TCHAR g_szByteCntFmt[10]; // Byte cnt disp fmt str ( "%1 bytes" ).
#define SZ_SEMAPHORE_NAME TEXT("SHCOMPUI_SEMAPHORE")
HANDLE g_hSemaphore = NULL; // Re-entrancy semaphore.
LPSCCA_CONTEXT g_pContext = NULL; // Ptr to current context structure.
INT g_iRecursionLevel = 0; // Used to control shell change notifications.
//
// Global variables to hold the User option information.
//
BOOL g_bDoSubdirectories = FALSE; // Include all subdirectories ?
BOOL g_bShowProgress = FALSE; // Show operation progress dialog ?
BOOL g_bIgnoreAllErrors = FALSE; // User wants to ignore all errors ?
BOOL g_bDiskFull = FALSE; // Is disk full on uncompression ?
//
// Global variables to hold compression statistics.
//
LONGLONG g_cTotalDirectories = 0; LONGLONG g_cTotalFiles = 0;
//
// Compression ratio statistics values.
//
unsigned _int64 g_iTotalFileSize = 0; unsigned _int64 g_iTotalCompressedSize = 0;
//
// "Current" file and directory names are global.
//
TCHAR g_szFile[_MAX_PATH + 1]; TCHAR g_szDirectory[_MAX_PATH + 1];
//
// Directory text control in progress dialog.
//
HDC g_hdcDirectoryTextCtrl = NULL; // Control handle.
DWORD g_cDirectoryTextCtrlWd = 0; // Width of control.
//
// Number format locale information.
//
NUMBERFMT g_NumberFormat; TCHAR g_szDecimalSep[5]; TCHAR g_szThousandSep[5];
//
// Context menu extension GUID generated with GUIDGEN.
//
// {764BF0E1-F219-11ce-972D-00AA00A14F56}
DEFINE_GUID(CLSID_CompressMenuExt, 0x764bf0e1, 0xf219, 0x11ce, 0x97, 0x2d, 0x0, 0xaa, 0x0, 0xa1, 0x4f, 0x56);
//
// Structure representing the class factory for this in-process server.
//
typedef struct { IClassFactory cf; // Pointer to class factory vtbl.
UINT cRef; // Interface reference counter.
LPFNCREATEINSTANCE pfnCI; // Pointer to instance generator function.
} CClassFactory;
//
// Structure representing the context menu extension.
//
typedef struct { IContextMenu ctm; // Pointer to context menu interface.
IShellExtInit sei; // Pointer to shell extension init interface.
UINT cRef; // Interface reference counter.
STGMEDIUM medium; // OLE data xfer storage medium descriptor.
INT cSelectedFiles; // Cnt of files selected. Must be signed.
BOOL bDriveSelected; // Are drives selected ?
LPDATAOBJECT pDataObj; // Saved pointer to Data Object.
} CContextMenuExt;
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: PathRemoveTheBackslash
//
// DESCRIPTION:
//
// Removes trailing backslash from path string if it exists.
// This code was cut directly from path.c in shell32.dll and
// renamed from PathRemoveBackslash to avoid linkage naming
// conflicts.
//
// in:
// lpszPath (A:\, C:\foo\, etc)
//
// out:
// lpszPath (A:\, C:\foo, etc)
//
// returns:
// ponter to NULL that replaced the backslash
// or the pointer to the last character if it isn't a backslash.
//
///////////////////////////////////////////////////////////////////////////////
LPTSTR WINAPI PathRemoveTheBackslash(LPTSTR lpszPath) { int len = lstrlen(lpszPath)-1; if (IsDBCSLeadByte(*((LPSTR)CharPrev(lpszPath,lpszPath+len+1)))) len--;
if (!PathIsRoot(lpszPath) && lpszPath[len] == TEXT('\\')) lpszPath[len] = TEXT('\0');
return lpszPath + len; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: FileAttribString
//
// DESCRIPTION:
//
// Formats a file's attribute word into a string of characters.
// Used for program tracing and debugging only.
//
// ARGUMENTS:
//
// dwAttrib
// Attribute bits to be decoded.
//
// pszDest
// Address of destination string.
// String must be at least 8 characters long.
//
// RETURNS:
//
// Pointer to destination string.
//
///////////////////////////////////////////////////////////////////////////////
#ifdef TRACE_COMPRESSION
static LPTSTR FileAttribString(DWORD dwAttrib, LPTSTR pszDest) { if (dwAttrib != (DWORD)-1) { wsprintf(pszDest, TEXT("%c%c%c%c%c%c%c"), dwAttrib & FILE_ATTRIBUTE_ARCHIVE ? TEXT('A') : TEXT('.'), dwAttrib & FILE_ATTRIBUTE_COMPRESSED ? TEXT('C') : TEXT('.'), dwAttrib & FILE_ATTRIBUTE_DIRECTORY ? TEXT('D') : TEXT('.'), dwAttrib & FILE_ATTRIBUTE_HIDDEN ? TEXT('H') : TEXT('.'), dwAttrib & FILE_ATTRIBUTE_NORMAL ? TEXT('N') : TEXT('.'), dwAttrib & FILE_ATTRIBUTE_READONLY ? TEXT('R') : TEXT('.'), dwAttrib & FILE_ATTRIBUTE_SYSTEM ? TEXT('S') : TEXT('.')); } else lstrcpy(pszDest, TEXT("INVALID"));
return pszDest; } #endif
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: ChgDllRefCnt
//
// DESCRIPTION:
//
// Adds reference count tracking to the incrementing and decrementing of the
// module reference count.
//
// ARGUMENTS:
//
// n
// Should be +1 or -1.
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
__inline void ChgDllRefCnt(INT n) { ASSERT(g_cRefThisDll >= 0 && g_cRefThisDll+(n) >= 0);
g_cRefThisDll += (n);
#ifdef TRACE_DLL
DbgOut(TEXT("SHCOMPUI: ChgDllRefCnt. Count = %d"), g_cRefThisDll); #endif
}
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_QueryInterface
//
// DESCRIPTION:
//
// Class factory support for IUnknown::QueryInterface( ).
// Queries class factory object for a specific interface.
//
// ARGUMENTS:
//
// pcf
// Pointer to class factory interface.
//
// riid
// Reference to ID of interface being requested.
//
// ppvOut
// Destination for address of vtable for requested interface.
//
// RETURNS:
//
// NOERROR = Success.
// E_NOINTERFACE = Requested interface not supported.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CClassFactory_QueryInterface(IClassFactory *pcf, REFIID riid, LPVOID *ppvOut) { CClassFactory *this = IToClass(CClassFactory, cf, pcf); HRESULT hResult = E_NOINTERFACE;
ASSERT(NULL != this); ASSERT(NULL != ppvOut);
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CClassFactory::QueryInterface")); #endif
*ppvOut = NULL;
if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown)) { (LPCLASSFACTORY)*ppvOut = &this->cf; this->cRef++; hResult = NOERROR; }
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_AddRef
//
// DESCRIPTION:
//
// Class factory support for IUnknown::AddRef( ).
// Increments object reference count.
//
// ARGUMENTS:
//
// pcf
// Pointer to class factory interface.
//
// RETURNS:
//
// Returns object reference count after it is incremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CClassFactory_AddRef(IClassFactory *pcf) { CClassFactory *this = IToClass(CClassFactory, cf, pcf);
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CClassFactory::AddRef")); #endif
ASSERT(NULL != this);
return ++this->cRef; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_Release
//
// DESCRIPTION:
//
// Class factory support for IUnknown::Release( ).
// Decrements object reference count.
// Deletes object from memory when reference count reaches 0.
//
// ARGUMENTS:
//
// pcf
// Pointer to class factory interface.
//
// RETURNS:
//
// Returns object reference count after it is incremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CClassFactory_Release(IClassFactory *pcf) { CClassFactory *this = IToClass(CClassFactory, cf, pcf); ULONG refCnt = 0;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CClassFactory::Release")); #endif
ASSERT(NULL != this);
if ((refCnt = --this->cRef) == 0) { LocalFree((HLOCAL)this); ChgDllRefCnt(-1); }
return refCnt; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_CreateInstance
//
// DESCRIPTION:
//
// Generates an instance of the class factory.
//
// ARGUMENTS:
//
// pcf
// Pointer to class factory interface.
//
// punkOuter
// Pointer to outer object's IUnknown interface. Only used when
// object aggregation is requested.
//
// riid
// Reference to requested interface ID.
//
// ppv
// Destination for address of requested interface.
//
// RETURNS:
//
//
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CClassFactory_CreateInstance(IClassFactory *pcf, IUnknown *punkOuter, REFIID riid, LPVOID *ppv) { CClassFactory *this = IToClass(CClassFactory, cf, pcf); HRESULT hResult = CLASS_E_NOAGGREGATION;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CClassFactory::CreateInstance")); #endif
ASSERT(NULL != this);
if (NULL == punkOuter) { hResult = this->pfnCI(punkOuter, riid, ppv); }
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_LockServer
//
// DESCRIPTION:
//
// Explicitly locks the server from unloading regardless of the module
// reference count.
// Not used for DLL servers. Therefore, this implementation is nul.
//
//
// ARGUMENTS:
// pcf
// Pointer to class factory object.
//
// fLock
// TRUE = Lock server.
// FALSE = Unlock server.
//
//
// RETURNS:
//
// E_NOTIMPL = Server locking not required for DLL server.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CClassFactory_LockServer(IClassFactory *pcf, BOOL fLock) { #ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CClassFactory::LockServer")); #endif
return E_NOTIMPL; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_QueryInterface
//
// DESCRIPTION:
//
// Context menu extension support for IUnknown::QueryInterface( ).
// Queries context menu extension object for a specific interface.
//
// ARGUMENTS:
//
// pctm
// Pointer to context menu interface.
//
// riid
// Reference to ID of interface being requested.
//
// ppvOut
// Destination for address of vtable for requested interface.
//
// RETURNS:
//
// NOERROR = Success.
// E_NOINTERFACE = Requested interface not supported.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_QueryInterface(IContextMenu *pctm, REFIID riid, LPVOID *ppvOut) { CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm); HRESULT hResult = NOERROR;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::QueryInterface")); #endif
ASSERT(NULL != this); ASSERT(NULL != ppvOut);
*ppvOut = NULL;
if (IsEqualIID(riid, &IID_IContextMenu) || IsEqualIID(riid, &IID_IUnknown)) { (IContextMenu *)*ppvOut = &this->ctm; this->cRef++; } else if (IsEqualIID(riid, &IID_IShellExtInit)) { (IShellExtInit *)*ppvOut = &this->sei; this->cRef++; } else hResult = E_NOINTERFACE;
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_AddRef
//
// DESCRIPTION:
//
// Context menu extension support for IUnknown::AddRef( ).
// Increments object reference count.
//
// ARGUMENTS:
//
// pctm
// Pointer to context menu interface.
//
// RETURNS:
//
// Returns object reference count after it is incremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CContextMenuExt_AddRef(IContextMenu *pctm) { CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::AddRef")); #endif
ASSERT(NULL != this);
return ++this->cRef; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_Cleanup
//
// DESCRIPTION:
//
// Does the cleanup associated with a previous IShellExtInit_Initialize call
//
// ARGUMENTS:
//
// this
// Pointer to context menu extension
//
// RETURNS:
//
// -nothing-
//
///////////////////////////////////////////////////////////////////////////////
void CContextMenu_Cleanup( CContextMenuExt *this ) { if (this->pDataObj) { this->pDataObj->lpVtbl->Release(this->pDataObj); } //
// Now release the stgmedium (FEATURE - Replace this with OLE's ReleaseStgMedium
//
if (this->medium.pUnkForRelease) { this->medium.pUnkForRelease->lpVtbl->Release(this->medium.pUnkForRelease); } else { switch(this->medium.tymed) { case TYMED_HGLOBAL: GlobalFree(this->medium.hGlobal); break;
case TYMED_ISTORAGE: // depends on pstm/pstg overlap in union
case TYMED_ISTREAM: this->medium.pstm->lpVtbl->Release(this->medium.pstm); break;
default: Assert(0); // unknown type
} } }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_Release
//
// DESCRIPTION:
//
// Context menu extension support for IUnknown::Release( ).
// Decrements object reference count.
// Deletes object from memory when reference count reaches 0.
//
// ARGUMENTS:
//
// pctm
// Pointer to context menu interface.
//
// RETURNS:
//
// Returns object reference count after it is decremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CContextMenuExt_Release(IContextMenu *pctm) { CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm); ULONG refCnt = 0;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::Release")); #endif
ASSERT(NULL != this);
if ((refCnt = --this->cRef) == 0) { CContextMenu_Cleanup(this); LocalFree((HLOCAL)this);
ChgDllRefCnt(-1); }
return refCnt; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_QueryContextMenu
//
// DESCRIPTION:
//
// Called by NT Shell when requesting menu option text and command numbers.
//
// ARGUMENTS:
//
// pctm
// Pointer to context menu interface.
//
// hMenu
// Handle to context menu to be modified.
//
// indexMenu
// Index where first menu item may be inserted.
//
// idCmdFirst
// Lower bound of available menu command IDs.
//
// idCmdLast
// Upper bound of available menu command IDs.
//
// uFlags
// Flag indicating context of function call.
//
// RETURNS:
//
// Returns the number of menu items added (excluding separators).
//
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_QueryContextMenu(IContextMenu *pctm, HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm); BOOL bDisableCompress = FALSE; BOOL bDisableUncompress = FALSE; BOOL bDirectorySelected = FALSE; BOOL bNonCompressibleVol = FALSE; DWORD dwAttribTalley = 0; INT i = 0; INT cMenuItemsAdded = 0; HCURSOR hCursor = NULL; DRAGINFO di; // Drag information.
LPTSTR pDragFileName = NULL; // Ptr into list of drag info names.
LPTSTR pNextName = NULL; // Lookahead pointer into name list.
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::QueryContextMenu")); #endif
ASSERT(NULL != this);
//
// Only allow addition of Compress/Uncompress when user is pressing
// shift key.
//
// cSelectedFiles will be -1 if user selected only a virtual object.
// i.e. Control Panel, Printer etc.
//
if (GetAsyncKeyState(VK_SHIFT) >= 0 || this->cSelectedFiles <= 0) return 0;
//
// Determine if we should hide both of the context menu items or
// disable one of them.
// If no selected device supports compression, HIDE both items.
// If a directory is selected, show both menu items.
// If a directory is not in the selected list, disable a menu item
// if all of the selected files have the same compression state.
// i.e.: All items compressed - disable the "Compress" item.
// A mix of compression states activates both items.
//
dwAttribTalley = 0; // Attribute talley mask.
//
// Attribute talley mask bits.
//
#define TALLEY_UNCOMPRESSED 0x0001 // Object is an uncompressed file.
#define TALLEY_COMPRESSED 0x0002 // Object is a compressed file.
#define TALLEY_DIRECTORY 0x0004 // Object is a directory.
#define TALLEY_NOCOMPSUPT 0x0008 // At least 1 file from FAT/HPFS drive.
//
// Display the hourglass cursor so that the user knows something is
// happening. This loop can take a long time on large selections.
//
if (hCursor = LoadCursor(NULL, IDC_WAIT)) hCursor = SetCursor(hCursor); ShowCursor(TRUE);
//
// Determine if the user has selected drives.
// Since you can't select a combination of drive(s) and folders/files,
// if there are ANY drives selected, the first one must be a drive.
//
{ TCHAR szFileName[_MAX_PATH + 1]; TCHAR szRootName[_MAX_PATH + 1];
DragQueryFile((HDROP)this->medium.hGlobal, 0, szFileName, ARRAYSIZE(szFileName)); lstrcpy(szRootName, szFileName); PathStripToRoot(szRootName); this->bDriveSelected = (lstrcmpi(szRootName, szFileName) == 0); }
//
// Get list of file names selected.
// The string contains nul-terminated names.
// The entire list is terminated with a double-nul.
//
// FEATURE: We need to write ANSI/UNICODE thunking layer for
// DragQueryInfo().
//
di.uSize = sizeof(DRAGINFO); DragQueryInfo((HDROP)this->medium.hGlobal, &di); pDragFileName = pNextName = di.lpFileList;
for (i = 0; i < this->cSelectedFiles; i++) { DWORD dwFlags = 0; DWORD dwAttrib = 0;
//
// Set bits in the attribute "talley" word. This signals the existence of
// each desired quantity for all selected files.
//
if ((dwAttrib = GetFileAttributes(pDragFileName)) != (DWORD)-1) { if ((dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) dwAttribTalley |= TALLEY_DIRECTORY;
if ((dwAttrib & FILE_ATTRIBUTE_COMPRESSED)) dwAttribTalley |= TALLEY_COMPRESSED; else dwAttribTalley |= TALLEY_UNCOMPRESSED; }
//
// Save pointer to next name.
//
pNextName += lstrlen(pNextName) + 1;
if (!(dwAttribTalley & TALLEY_NOCOMPSUPT)) { //
// Do we have at least one drive that DOES NOT support compression?
// If so, we don't show the menu items. This provides the INTERSECTION
// of capabilities for the selected files per the UI design guide.
// Note that if GetVolumeInformation fails for a drive, the drive is empty
// and is therefore uncompressible.
//
PathStripToRoot(pDragFileName); // GetVolumeInformation requires a trailing backslash. Append
// one if this is a UNC path.
if (PathIsUNC(pDragFileName)) { lstrcat(pDragFileName, c_szBACKSLASH); } if (GetVolumeInformation(pDragFileName, NULL, 0, NULL, NULL, &dwFlags, NULL, 0)) dwAttribTalley |= (dwFlags & FS_FILE_COMPRESSION) == 0 ? TALLEY_NOCOMPSUPT : 0; else dwAttribTalley |= TALLEY_NOCOMPSUPT; }
//
// If all of the flag bits are set, no need to check more files.
// We have all the info we need to properly configure the UI.
//
if ( (dwAttribTalley & ((DWORD)-1) ) == (TALLEY_DIRECTORY | TALLEY_COMPRESSED | TALLEY_UNCOMPRESSED | TALLEY_NOCOMPSUPT)) { break; }
//
// Advance name pointer to next name.
//
pDragFileName = pNextName; }
//
// Free the file name list we got through DragQueryInfo().
//
if (di.lpFileList) SHFree(di.lpFileList);
//
// Convert the settings of the talley flag bits to more meaningful names.
//
bNonCompressibleVol = (dwAttribTalley & TALLEY_NOCOMPSUPT) != 0; bDirectorySelected = (dwAttribTalley & TALLEY_DIRECTORY) != 0;
if (!bDirectorySelected) { bDisableUncompress = (dwAttribTalley & (TALLEY_COMPRESSED | TALLEY_UNCOMPRESSED)) == TALLEY_UNCOMPRESSED; bDisableCompress = (dwAttribTalley & (TALLEY_COMPRESSED | TALLEY_UNCOMPRESSED)) == TALLEY_COMPRESSED; }
switch(uFlags & 0x0F) // Upper 28 bits are reserved.
{ case CMF_EXPLORE: //
// Win32 SDK says we should only get this flag bit set when the user
// has selected an object in the left pane of the Explorer. This is a
// doc bug in the SDK verified by "satona". We get it via selection
// in either pane.
//
case CMF_NORMAL:
if (!bNonCompressibleVol) { INT cchLoaded = 0; TCHAR szMenuItem[MAX_MENUITEM_LEN + 1];
//
// Regarding item separators; can we always count on there being
// an item above and below our new items? If not, we're
// in danger of adding a separator at the top or bottom of
// the menu. BobDay says we'll always have something above
// and below us.
//
cchLoaded = LoadString(g_hmodThisDll, bDirectorySelected ? IDS_COMPRESS_MENUITEM_ELLIP : IDS_COMPRESS_MENUITEM, szMenuItem, ARRAYSIZE(szMenuItem)); ASSERT(cchLoaded > 0);
InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL); InsertMenu(hMenu, indexMenu++, MF_STRING | MF_BYPOSITION | (bDisableCompress ? MF_GRAYED : 0), idCmdFirst + MENUOFS_COMPRESS, szMenuItem); cMenuItemsAdded++; cchLoaded = LoadString(g_hmodThisDll, bDirectorySelected ? IDS_UNCOMPRESS_MENUITEM_ELLIP : IDS_UNCOMPRESS_MENUITEM, szMenuItem, ARRAYSIZE(szMenuItem)); ASSERT(cchLoaded > 0);
InsertMenu(hMenu, indexMenu++, MF_STRING | MF_BYPOSITION | (bDisableUncompress ? MF_GRAYED : 0), idCmdFirst + MENUOFS_UNCOMPRESS, szMenuItem); cMenuItemsAdded++; InsertMenu(hMenu, indexMenu, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);
} break;
case CMF_DEFAULTONLY: case CMF_VERBSONLY: //
// SDK Docs say Context Menu Extensions should ignore these.
//
break;
default: break; }
//
// Restore original cursor and return number of menu items added.
//
if (hCursor) SetCursor(hCursor); ShowCursor(FALSE);
return cMenuItemsAdded; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_InvokeCommand
//
// DESCRIPTION:
//
// Called by the shell whenever the user selects one of the registered
// items on the context menu.
//
// Or may be called programmatically with one of the following verb
// strings:
// "COMPRESS" to compress files.
// "UNCOMPRESS" to uncompress files.
//
// These verb names are case-sensitive and language-
// insensitive.
//
// ARGUMENTS:
//
// pctm
// Pointer to the context menu interface.
//
// pici
// Pointer to the command info structure associated with the selected
// menu command.
//
// RETURNS:
//
// NOERROR = Success
// OLEOBJ_E_INVALIDVERB = Invalid verb was specified in programatic
// invocation.
// E_FAIL = User aborted operation or an error occured during
// compression/uncompression. We should have more
// descriptive failure return info. The original
// implementation of compression in WinFile only
// returned TRUE/FALSE. In the interest of time,
// that "feature" was retained.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_InvokeCommand(IContextMenu *pctm, LPCMINVOKECOMMANDINFO pici) { HRESULT hResult = NOERROR; INT i = 0; CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm); BOOL bCompressing = FALSE; BOOL bShowUI = FALSE; HWND hwndParent = NULL;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::InvokeCommand")); #endif
ASSERT(NULL != pici);
//
// Caller can request that no UI be activated.
//
bShowUI = (pici->fMask & CMIC_MASK_FLAG_NO_UI) == 0;
//
// Parent all displayed dialogs with the handle passed in from the caller.
// Use the desktop as a default if one wasn't provided.
//
if ((hwndParent = pici->hwnd) == NULL) hwndParent = GetDesktopWindow();
if (HIWORD(pici->lpVerb) == 0) { //
// InvokeCommand was called through the context menu extension protocol.
//
bCompressing = LOWORD(pici->lpVerb) == MENUOFS_COMPRESS; } else { //
// InvokeCommand was called programatically.
// If lpVerb is "COMPRESS", compress the file(s).
// If lpVerb is "UNCOMPRESS", uncompress the file(s).
// Otherwise, do nothing and return OLE error code.
//
TCHAR szValidVerb[MAX_CMDVERB_LEN + 1]; TCHAR szVerb[MAX_CMDVERB_LEN + 1]; INT cchLoaded = 0;
//
// Convert verb string to unicode.
//
MultiByteToWideChar(CP_ACP, 0L, pici->lpVerb, -1, szVerb, ARRAYSIZE(szVerb));
bCompressing = FALSE;
cchLoaded = LoadString(g_hmodThisDll, IDS_COMPRESS_CMDVERB, szValidVerb, ARRAYSIZE(szValidVerb)); ASSERT(cchLoaded > 0);
if (!lstrcmp(szValidVerb, szVerb)) { bCompressing = TRUE; } else { cchLoaded = LoadString(g_hmodThisDll, IDS_UNCOMPRESS_CMDVERB, szValidVerb, ARRAYSIZE(szValidVerb)); ASSERT(cchLoaded > 0); if (!lstrcmp(szValidVerb, szVerb)) { bCompressing = FALSE; } else { //
// Verb isn't COMPRESS or UNCOMPRESS.
//
hResult = OLEOBJ_E_INVALIDVERB; } } }
//
// Do the compression/uncompression.
//
if (hResult == NOERROR) { SCCA_CONTEXT Context; // Compression context structure.
SCCA_CONTEXT_INIT(&Context); // Initialize context.
for (i = 0; i < this->cSelectedFiles; i++) { TCHAR szFileName[_MAX_PATH + 1];
//
// Get the name of the file to compress.
//
DragQueryFile((HDROP)this->medium.hGlobal, i, szFileName, ARRAYSIZE(szFileName));
//
// Compress/uncompress file. Return value of FALSE indicates either an error
// occured or the user cancelled the operation. In either case, don't process
// any more files.
//
if (!ShellChangeCompressionAttribute(hwndParent, szFileName, &Context, bCompressing, bShowUI)) { hResult = E_FAIL; break; } } }
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_GetCommandString
//
// DESCRIPTION:
//
// Called by the shell when it wants a text string associated with a menu
// item. Status bar text for example or a menu command verb.
//
// ARGUMENTS:
//
// pctm
// Pointer to the context menu interface.
//
// idCmd
// Integer identifier of the command in question. The number is the
// offset of the menu item in the set of added menu items, based 0.
//
// uFlags
// Indicates what type of service the shell is requesting.
//
// pwReserved
// Unused.
//
// pszName
// Destination for menu item character string. Provided by shell.
//
// cchMax
// Max size of destination buffer. Provided by shell.
//
// RETURNS:
//
// NOERROR = Success.
// E_FAIL = Requested menu idCmd not recognized.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_GetCommandString(IContextMenu *pctm, UINT_PTR idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax) { CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm); HRESULT hResult = NOERROR;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::GetCommandString idCmd = %d"), idCmd); #endif
ASSERT(NULL != this); ASSERT(NULL != pszName);
//
// Start with destination buffer blank.
//
if (cchMax > 0) pszName[0] = TEXT('\0');
if (idCmd >= MENUOFS_FIRST && idCmd <= MENUOFS_LAST) { DWORD *pStrIdArray = NULL; BOOL bUnicode = FALSE; DWORD dwCmdVerbIds[] = { IDS_COMPRESS_CMDVERB, IDS_UNCOMPRESS_CMDVERB }; DWORD dwSbarTextIds[] = { IDS_COMPRESS_SBARTEXT, IDS_UNCOMPRESS_SBARTEXT }; DWORD dwSbarTextMultIds[] = { IDS_COMPRESS_SBARTEXT_M, IDS_UNCOMPRESS_SBARTEXT_M }; DWORD dwSbarDrvTextIds[] = { IDS_COMPRESS_SBARTEXT_DRV, IDS_UNCOMPRESS_SBARTEXT_DRV }; DWORD dwSbarDrvTextMultIds[] = { IDS_COMPRESS_SBARTEXT_DRV_M, IDS_UNCOMPRESS_SBARTEXT_DRV_M };
switch(uFlags) { //
// Provide help text for menu item.
//
case GCS_HELPTEXTW: case GCS_HELPTEXTA: //
// If drives selected, use "Drive" strings. Otherwise, use "File"
// strings. Also address multiplicity.
//
if (this->cSelectedFiles == 1) pStrIdArray = this->bDriveSelected ? dwSbarDrvTextIds : dwSbarTextIds; else pStrIdArray = this->bDriveSelected ? dwSbarDrvTextMultIds : dwSbarTextMultIds;
bUnicode = uFlags == GCS_HELPTEXTW; break;
//
// Provide command verb recognized by InvokeCommand( ).
//
case GCS_VERBW: case GCS_VERBA: pStrIdArray = dwCmdVerbIds; bUnicode = uFlags == GCS_VERBW; break;
//
// Validate that the menu cmd exists.
//
case GCS_VALIDATE: hResult = NOERROR; break;
default: break; }
//
// If we've identified what array to get the string resource ID from, load
// the ANSI or UNICODE version of the string related to the command id.
//
if (NULL != pStrIdArray) { INT cchLoaded = 0;
if (bUnicode) cchLoaded = LoadStringW(g_hmodThisDll, *(pStrIdArray + idCmd), (LPWSTR)pszName, cchMax); else cchLoaded = LoadStringA(g_hmodThisDll, *(pStrIdArray + idCmd), pszName, cchMax);
ASSERT(cchLoaded > 0); } } else hResult = E_FAIL;
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CShellExtInit_QueryInterface
//
// DESCRIPTION:
//
// Called by the shell to obtain an interface from the shell extension.
//
// ARGUMENTS:
//
// psei
// Pointer to the shell extension interface.
//
// riid
// Reference to the requested interface ID.
//
// ppvOut
// Address of destination for resulting interface pointer.
//
//
// RETURNS:
//
// NOERROR
// E_NOINTERFACE
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CShellExtInit_QueryInterface(IShellExtInit *psei, REFIID riid, LPVOID *ppvOut) { CContextMenuExt *this = PSEI2PCMX(psei);
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CShellExtInit::QueryInterface")); #endif
ASSERT(NULL != this); ASSERT(NULL != ppvOut);
return CContextMenuExt_QueryInterface(&this->ctm, riid, ppvOut); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CShellExtInit_AddRef
//
// DESCRIPTION:
//
// Increments the reference count for the shell extension init
// interface.
//
// ARGUMENTS:
//
// psei
// Pointer to the shell extension interface.
//
// RETURNS:
//
// New reference counter value.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CShellExtInit_AddRef(IShellExtInit *psei) { CContextMenuExt *this = PSEI2PCMX(psei);
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CShellExtInit::AddRef")); #endif
ASSERT(NULL != this);
return CContextMenuExt_AddRef(&this->ctm); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CShellExtInit_Release
//
// DESCRIPTION:
//
// Decrements the reference count for the shell extension init
// interface. When count reaches 0, the interface object is
// deleted.
//
// ARGUMENTS:
//
// psei
// Pointer to the shell extension interface.
//
// RETURNS:
//
// New reference counter value.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CShellExtInit_Release(IShellExtInit *psei) { CContextMenuExt *this = PSEI2PCMX(psei); ULONG refCnt = 0;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CShellExtInit::Release")); #endif
ASSERT(NULL != this);
return CContextMenuExt_Release(&this->ctm); }
///////////////////////////////////////////////////////////////////////////////
//
// CShellExtInit::Initialize
//
// Called by the shell to initialize the shell extension.
//
// Arguments:
//
// psei
// Pointer to the IShellExtInit interface.
//
// pidlFolder
// Pointer to item ID list of parent folder.
//
// lpdobj
// Pointer to data object containing selected file names.
//
// hkeyProgID
// Registry class of the file object that has focus.
//
// RETURNS:
//
// S_OK
// E_FAIL
// E_INVALIDARG
// E_UNEXPECTED
// E_OUTOFMEMORY
// DV_E_LINDEX
// DV_E_FORMATETC
// DV_E_TYMED
// DV_E_DVASPECT
// OLE_E_NOTRUNNING
// STG_E_MEDIUMFULL
//
///////////////////////////////////////////////////////////////////////////////
static STDMETHODIMP CShellExtInit_Initialize(IShellExtInit *psei, LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj, HKEY hkeyProgID) { CContextMenuExt *this = PSEI2PCMX(psei); FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; HRESULT hResult = NOERROR;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CShellExtInit::Initialize")); #endif
ASSERT(NULL != this);
//
// According to Win32 SDK, Initialize can be called more than once.
//
CContextMenu_Cleanup(this);
if (NULL != lpdobj) { this->pDataObj = lpdobj; lpdobj->lpVtbl->AddRef(lpdobj);
//
// If we are allowed to get data from the data object, save the medium
// descriptor in our context menu extension object. We'll use it to
// iterate through the names of the selected files in
// CContextMenuExt::InvokeCommand( ).
//
hResult = lpdobj->lpVtbl->GetData(lpdobj, &fe, &this->medium); if (NOERROR == hResult) this->cSelectedFiles = DragQueryFile((HDROP)this->medium.hGlobal, (DWORD)-1, NULL, 0); else this->cSelectedFiles = 0; } else hResult = E_FAIL;
return hResult; }
///////////////////////////////////////////////////////////////////////////////
// CLASS VTABLES
///////////////////////////////////////////////////////////////////////////////
#pragma data_seg(".text")
//
// Create the class factory vtbl in a read-only segment.
//
IClassFactoryVtbl c_vtblCClassFactory = { CClassFactory_QueryInterface, CClassFactory_AddRef, CClassFactory_Release, CClassFactory_CreateInstance, CClassFactory_LockServer };
//
// Create the context menu extension vtbl in a read-only segment.
//
IContextMenuVtbl c_vtblContextMenuExt = { CContextMenuExt_QueryInterface, CContextMenuExt_AddRef, CContextMenuExt_Release, CContextMenuExt_QueryContextMenu, CContextMenuExt_InvokeCommand, CContextMenuExt_GetCommandString };
//
// Create the shell extension initialization vtbl in a read-only segment.
//
IShellExtInitVtbl c_vtblShellExtInit = { CShellExtInit_QueryInterface, CShellExtInit_AddRef, CShellExtInit_Release, CShellExtInit_Initialize };
#pragma data_seg()
///////////////////////////////////////////////////////////////////////////////
//
// CContextMenuExt_CreateInstance
//
// Context menu extension instance generator. Creates a context menu extension
// object and returns a pointer to the requested interface.
//
// Arguments:
//
// punkOuter
// Not used for objects that don't support aggregation. We don't support
// aggregation so we don't use it.
//
// riid
// Reference to the requested interface ID.
//
// ppvOut
// Address of the destination for the interface pointer.
//
// RETURNS:
//
// NOERROR = Success.
// E_OUTOFMEMORY = Can't allocate extension object.
// E_NOINTERFACE = Interface not supported.
// CLASS_E_NOAGGREGATION = Aggregation not supported.
//
///////////////////////////////////////////////////////////////////////////////
static HRESULT CContextMenuExt_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut) { CContextMenuExt *pcmx = NULL; HRESULT hResult = NOERROR;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CContextMenuExt::CreateInstance")); #endif
ASSERT(NULL != ppvOut);
*ppvOut = NULL;
if (NULL == punkOuter) { pcmx = (CContextMenuExt *)LocalAlloc(LPTR, sizeof(CContextMenuExt)); if (NULL != pcmx) { pcmx->ctm.lpVtbl = &c_vtblContextMenuExt; // Context menu ext vtable ptr.
pcmx->sei.lpVtbl = &c_vtblShellExtInit; // Shell extention int vtable ptr.
pcmx->cRef = 0; // Initialize reference counter.
pcmx->medium.tymed = TYMED_NULL; // Not yet initialized by shell.
pcmx->medium.hGlobal = (HGLOBAL)NULL; // Not yet initialized by shell.
pcmx->medium.pUnkForRelease = NULL; // Not yet initialized by shell.
pcmx->cSelectedFiles = 0; // Not yet initialized by shell.
pcmx->bDriveSelected = FALSE; // No drives selected yet.
pcmx->pDataObj = NULL; hResult = c_vtblContextMenuExt.QueryInterface(&pcmx->ctm, riid, ppvOut); ChgDllRefCnt(+1); }
else hResult = E_OUTOFMEMORY; } else hResult = CLASS_E_NOAGGREGATION; // Extension doesn't support aggregation.
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// CreateClassObject
//
// Creates a class factory object returning a pointer to its IUnknown interface.
//
// Arguments:
//
// riid
// Reference to interface on class factory object.
//
// pfcnCI
// Pointer to instance creation function.
// In this application, this is CContextMenuExt_CreateInstance.
//
// ppvOut
// Destination for pointer to class factory interface (Vtable).
//
// RETURNS:
//
// NOERROR = Success.
// E_NOINTERFACE = Interface not supported.
// E_OUTOFMEMORY = Can't create class factory object.
//
///////////////////////////////////////////////////////////////////////////////
STDAPI CreateClassObject(REFIID riid, LPFNCREATEINSTANCE pfnCI, LPVOID *ppvOut) { HRESULT hResult = NOERROR;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: CreateClassObject")); #endif
ASSERT(NULL != ppvOut); ASSERT(NULL != pfnCI);
*ppvOut = NULL; // Initialize pointer transfer buffer.
if (IsEqualIID(riid, &IID_IClassFactory)) { //
// Allocate the class factory structure.
//
CClassFactory *pcf = (CClassFactory *)LocalAlloc(LPTR, sizeof(CClassFactory)); if (NULL != pcf) { pcf->cf.lpVtbl = &c_vtblCClassFactory; // Assign ptr to vtbl.
pcf->cRef++; // Increment interface ref count.
pcf->pfnCI = pfnCI; // Assign ptr instance creation proc.
(IClassFactory *)*ppvOut = &pcf->cf; // Return ptr to vtbl (interface).
ChgDllRefCnt(+1); } else hResult = E_OUTOFMEMORY; // Cannot get or use shell memory allocator interface.
} else hResult = E_NOINTERFACE; // Cannot produce requested interface.
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DllGetClassObject
//
// DESCRIPTION:
//
// Called by NT Shell to retrieve the interface to the Class factory.
//
// ARGUMENTS:
//
// rclsid
// Reference to class ID that identifies the type of object that the
// class factory will be asked to create.
//
// riid
// Reference to interface ID on the class factory object.
//
// ppvOut
// Destination location for class factory object pointer after instantiation.
//
// RETURNS:
//
// NOERROR = Success.
// E_OUTOFMEMORY = Can't create class factory object.
// E_NOINTERFACE = Interface not supported.
// CLASS_E_CLASSNOTAVAILABLE = Context menu extension not available.
//
///////////////////////////////////////////////////////////////////////////////
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut) { HRESULT hResult = CLASS_E_CLASSNOTAVAILABLE;
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: DllGetClassObject")); #endif
ASSERT(NULL != ppvOut);
*ppvOut = NULL;
//
// Call the extension-provided creation function corresponding
// to the class ID of the requested extension.
//
if (IsEqualIID(rclsid, &CLSID_CompressMenuExt )) { hResult = CreateClassObject(riid, (LPFNCREATEINSTANCE)CContextMenuExt_CreateInstance, ppvOut); }
#ifdef DBG
if (hResult != NOERROR) DbgOut(TEXT("SHCOMPUI: Context menu extension [CLSID_CompressMenuExt] creation failed.")); #endif
return hResult; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DllCanUnloadNow
//
// DESCRIPTION:
//
// Called by NT to determine if DLL can be unloaded.
//
// ARGUMENTS:
//
// None.
//
// RETURNS:
//
// S_FALSE = Can't unload.
// S_OK = OK to unload.
//
///////////////////////////////////////////////////////////////////////////////
STDAPI DllCanUnloadNow(void) {
#ifdef TRACE_SHEXT
DbgOut(TEXT("SHCOMPUI: DllCanUnloadNow. Dll reference count = %d"), g_cRefThisDll); #endif
ASSERT(g_cRefThisDll >= 0);
//
// I test for <= 0 so that the DLL can be unloaded even if the ref
// count drops below 0 (reference count error). The preceding
// ASSERT( ) catches this during development. This means that
// the ref counter has to be signed.
//
return ResultFromScode((g_cRefThisDll <= 0) ? S_OK : S_FALSE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DllMain
//
// DESCRIPTION:
//
// Dll entry point.
//
///////////////////////////////////////////////////////////////////////////////
int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { TCHAR szLocaleInfo[20];
switch(dwReason) { case DLL_PROCESS_ATTACH: #ifdef TRACE_DLL
DbgOut(TEXT("SHCOMPUI: DLL_PROCESS_ATTACH")); #endif
g_hmodThisDll = hInstance; g_hProcessHeap = GetProcessHeap();
DisableThreadLibraryCalls(hInstance);
//
// Create/Open semaphore to prevent re-entrancy.
//
g_hSemaphore = CreateSemaphore(NULL, 1, 1, SZ_SEMAPHORE_NAME); if (GetLastError() == ERROR_ALREADY_EXISTS) g_hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SZ_SEMAPHORE_NAME);
if (NULL == g_hSemaphore) return FALSE; // Can't create/open semaphore object.
//
// Prepare number format info for current locale.
// Used in progress dialog display of 64-bit integers.
//
g_NumberFormat.NumDigits = 0; // This is locale-insensitive.
g_NumberFormat.LeadingZero = 0; // So is this.
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, szLocaleInfo, ARRAYSIZE(szLocaleInfo)); g_NumberFormat.Grouping = StrToLong(szLocaleInfo);
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, g_szDecimalSep, ARRAYSIZE(g_szDecimalSep)); g_NumberFormat.lpDecimalSep = g_szDecimalSep;
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, g_szThousandSep, ARRAYSIZE(g_szThousandSep)); g_NumberFormat.lpThousandSep = g_szThousandSep;
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, szLocaleInfo, ARRAYSIZE(szLocaleInfo)); g_NumberFormat.NegativeOrder = StrToLong(szLocaleInfo);
break;
case DLL_PROCESS_DETACH: #ifdef TRACE_DLL
DbgOut(TEXT("SHCOMPUI: DLL_PROCESS_DETACH")); #endif
CloseHandle(g_hSemaphore); break;
case DLL_THREAD_ATTACH: #ifdef TRACE_DLL
DbgOut(TEXT("SHCOMPUI: DLL_THREAD_ATTACH")); #endif
break;
case DLL_THREAD_DETACH: #ifdef TRACE_DLL
DbgOut(TEXT("SHCOMPUI: DLL_THREAD_DETACH")); #endif
break;
default: break; } return TRUE; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: FormatStringWithArgs
//
// DESCRIPTION:
//
// Formats a text string with variable arguments.
//
// ARGUMENTS:
//
// pszFormat
// Address of format text string using %1,%2 etc. format specifiers.
//
// pszBuffer
// Address of destination buffer.
//
// nSize
// Number of characters in destination buffer.
//
// ...
// Variable length list of replacement parameters.
//
// RETURNS:
//
// Returns number of characters copied to buffer.
// 0 = Error. GetLastError() if you're interested in why.
//
///////////////////////////////////////////////////////////////////////////////
static DWORD FormatStringWithArgs(LPCTSTR pszFormat, LPTSTR pszBuffer, DWORD nSize, ...) { DWORD dwCharCount = 0; va_list args;
ASSERT(NULL != pszBuffer); ASSERT(NULL != pszFormat);
//
// Format the resource string by replacing parameters if present.
//
va_start(args, nSize); dwCharCount = FormatMessage(FORMAT_MESSAGE_FROM_STRING, pszFormat, 0, 0, pszBuffer, nSize, &args); va_end(args);
return dwCharCount; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: LoadStringWithArgs
//
// DESCRIPTION:
//
// Formats a resource string with variable arguments.
//
// ARGUMENTS:
//
// hInstance
// Instance handle for module containing resource string.
//
// uId
// Resource string ID. String may contain embedded formatting characters
// for replaceable parameters (i.e. "Delete file %1 ?")
//
// szBuffer
// Destination buffer.
//
// nSize
// Number of characters in destination buffer.
//
// ...
// Variable length list of replacement parameters.
//
// RETURNS:
//
// Returns number of characters copied to buffer.
// 0 = Error. GetLastError() if you're interested in why.
// Function does set last error to E_OUTOFMEMORY on LocalAlloc fail.
//
///////////////////////////////////////////////////////////////////////////////
static DWORD LoadStringWithArgs(HINSTANCE hInstance, UINT uId, LPTSTR pszBuffer, DWORD nSize, ...) { DWORD dwCharCount = 0; LPTSTR pszFormat = NULL; va_list args;
ASSERT(NULL != pszBuffer);
//
// Allocate a buffer for the resource string.
//
if ((pszFormat = LocalAlloc(LMEM_FIXED, nSize * sizeof(TCHAR))) != NULL) { //
// Load the resource string from the specified module.
//
if (LoadString(hInstance, uId, pszFormat, nSize) != 0) { va_start(args, nSize); dwCharCount = FormatMessage(FORMAT_MESSAGE_FROM_STRING, pszFormat, 0, 0, pszBuffer, nSize, &args); va_end(args); } LocalFree(pszFormat); } else SetLastError((DWORD)E_OUTOFMEMORY);
return dwCharCount; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CenterWindowInParent
//
// DESCRIPTION:
//
// Positions a window centered in its parent.
// This function was taken from WinFile.
//
// ARGUMENTS:
//
// hwnd
// Handle of window to be centered.
//
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static VOID CenterWindowInParent(HWND hwnd) { RECT rect; RECT rectParent; HWND hwndParent; LONG Style;
//
// Get window rect.
//
GetWindowRect(hwnd, &rect);
//
// Get parent rect.
//
Style = GetWindowLong(hwnd, GWL_STYLE); if ((Style & WS_CHILD) == 0) { hwndParent = GetDesktopWindow(); } else { hwndParent = GetParent(hwnd); if (hwndParent == NULL) { hwndParent = GetDesktopWindow(); } } GetWindowRect(hwndParent, &rectParent);
//
// Center the child in the parent.
//
rect.left = rectParent.left + (((rectParent.right - rectParent.left) - (rect.right - rect.left)) >> 1); rect.top = rectParent.top + (((rectParent.bottom - rectParent.top) - (rect.bottom - rect.top)) >> 1);
//
// Move the child into position.
//
SetWindowPos( hwnd, NULL, rect.left, rect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
SetForegroundWindow(hwnd); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressSubsConfirmDlgProc
//
// DESCRIPTION:
//
// Message proc for compression UI confirmation dialog. The dialog is
// displayed whenever a selected directory is about to be compressed or
// uncompressed. The dialog includes a message stating that all files
// in the selected directory are about to be compressed/uncompressed.
// Included is a checkbox that may be checked to approve compression/
// uncompression of all sub-folders.
//
// ARGUMENTS:
//
// wParam
// Unused.
//
// lParam
// Address of a "Compression Descriptor" (type CompressionDesc).
// The descriptor contains the name of the file to be compresssed/
// uncompressed along with a flag value indicating which operation
// is being performed.
//
//
// RETURNS:
//
// 0 = User pressed Cancel button. Don't compress this folder.
// (COMPRESS_CANCELLED)
// 1 = User pressed OK button. Sub-folder checkbox is unchecked.
// (COMPRESS_SUBSNO)
// 2 = User pressed OK button. Sub-folder checkbox is checked.
// (COMPRESS_SUBSYES)
//
///////////////////////////////////////////////////////////////////////////////
static INT_PTR CALLBACK CompressSubsConfirmDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { //
// Dialog message string resource IDs. Array indexes map directly to values
// of lParam.
//
UINT uDlgTextIds[] = { IDS_UNCOMPRESS_CONFIRMATION, IDS_COMPRESS_CONFIRMATION }; UINT uCbxTextIds[] = { IDS_UNCOMPRESS_ALSO, IDS_COMPRESS_ALSO }; UINT uActionTextIds[] = { IDS_UNCOMPRESS_ACTION, IDS_COMPRESS_ACTION };
TCHAR szDlgText[40 + _MAX_PATH]; // Dialog text resource string buffer.
CompressionDesc *cd = (CompressionDesc *)lParam; UINT cchLoaded = 0;
switch(uMsg) { case WM_INITDIALOG: ASSERT(NULL != (void *)lParam);
//
// Initialize the "Compress/Uncompress all files in..." message.
//
LoadStringWithArgs(g_hmodThisDll, uDlgTextIds[cd->bCompress], szDlgText, ARRAYSIZE(szDlgText), cd->szFileName); SetDlgItemText(hDlg, IDC_COMPRESS_CONFIRM_TEXT, szDlgText);
//
// Initialize the "This action compresses..." message.
//
cchLoaded = LoadString(g_hmodThisDll, uActionTextIds[cd->bCompress], szDlgText, ARRAYSIZE(szDlgText)); ASSERT(cchLoaded > 0); SetDlgItemText(hDlg, IDC_COMPRESS_ACTION_TEXT, szDlgText);
//
// Initialize the "also compress/uncompress subfolders" checkbox message.
//
cchLoaded = LoadString(g_hmodThisDll, uCbxTextIds[cd->bCompress], szDlgText, ARRAYSIZE(szDlgText)); ASSERT(cchLoaded > 0);
SetDlgItemText(hDlg, IDC_COMPRESS_SUBFOLDERS, szDlgText);
return TRUE;
case WM_COMMAND: //
// Handle user button selections.
//
switch(wParam) { case IDOK: EndDialog(hDlg, Button_GetCheck(GetDlgItem(hDlg, IDC_COMPRESS_SUBFOLDERS)) ? COMPRESS_SUBSYES : COMPRESS_SUBSNO); return TRUE;
case IDCANCEL: g_pContext->uCompletionReason = SCCA_REASON_USERCANCEL; EndDialog(hDlg, COMPRESS_CANCELLED);
//
// Fall through to return TRUE.
//
case IDC_COMPRESS_SUBFOLDERS: //
// Do nothing when the checkbox is selected.
//
return TRUE; } }
return FALSE; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressProgressYield
//
// DESCRIPTION:
//
// Allow other messages including Dialog messages for Modeless dialog to be
// processed while we are Compressing and Uncompressing files. This message
// loop is similar to "wfYield" in treectl.c except that it allows for the
// processing of Modeless dialog messages also (specifically for the Progress
// Dialogs).
//
// Since the file/directory Compression/Uncompression is done on a single
// thread (in order to keep it synchronous with the existing Set Attributes
// processing) we need to provide a mechanism that will allow a user to
// Cancel out of the operation and also allow window messages, like WM_PAINT,
// to be processed by other Window Procedures.
//
// Taken from WinFile. Removed MDI-related processing.
//
// ARGUMENTS:
//
// None.
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static VOID CompressProgressYield(void) { MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (!g_hdlgProgress || !IsDialogMessage(g_hdlgProgress, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DisplayUncompressProgress
//
// DESCRIPTION:
//
// Update the progress of uncompressing files.
//
// This routine uses the global variables to update the Dialog box items
// which display the progress through the uncompression process. The global
// variables are updated by individual routines. An ordinal value is sent
// to this routine which determines which dialog box item to update.
// Taken from WinFile.
//
//
// ARGUMENTS:
//
// iType
// Control value to determine what is to be updated.
// Value names are self-descriptive.
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static VOID DisplayUncompressProgress(int iType) { TCHAR szNum[30];
if (!g_bShowProgress) { return; }
switch (iType) { case ( PROGRESS_UPD_FILEANDDIR ) : case ( PROGRESS_UPD_FILENAME ) : { SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_FILE, g_szFile); if (iType != PROGRESS_UPD_FILEANDDIR) { break; }
// else...fall thru
} case ( PROGRESS_UPD_DIRECTORY ) : { RECT rect; //
// Preprocess the directory name to shorten it to fit
// into the alloted space.
//
GetWindowRect(GetDlgItem(g_hdlgProgress, IDC_UNCOMPRESS_DIR), &rect); DrawTextEx(g_hdcDirectoryTextCtrl, g_szDirectory, lstrlen(g_szDirectory), &rect, DT_MODIFYSTRING | DT_PATH_ELLIPSIS, NULL); SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_DIR, g_szDirectory);
break; } case ( PROGRESS_UPD_DIRCNT ) : { AddCommas((DWORD)g_cTotalDirectories, szNum); SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_DIRCNT, szNum);
break; } case ( PROGRESS_UPD_FILENUMBERS ) : case ( PROGRESS_UPD_FILECNT ) : { AddCommas((DWORD)g_cTotalFiles, szNum); SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_FILECNT, szNum);
break; } }
CompressProgressYield(); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: UncompressProgDlg
//
// DESCRIPTION:
//
// Display progress information.
// Taken from WinFile.
//
// NOTE: This is a modeless dialog and must be terminated with DestroyWindow
// and NOT EndDialog
//
// ARGUMENTS:
//
// Standard dialog proc args.
//
// RETURNS:
//
// TRUE = Message handled.
// FALSE = Message not handled.
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR APIENTRY UncompressProgDlg( HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam) { TCHAR szTemp[120]; RECT rect;
switch (nMsg) { case ( WM_INITDIALOG ) : { CenterWindowInParent(hDlg);
g_hdlgProgress = hDlg;
//
// Clear Dialog items.
//
szTemp[0] = TEXT('\0');
SetDlgItemText(hDlg, IDC_UNCOMPRESS_FILE, szTemp); SetDlgItemText(hDlg, IDC_UNCOMPRESS_DIR, szTemp); SetDlgItemText(hDlg, IDC_UNCOMPRESS_DIRCNT, szTemp); SetDlgItemText(hDlg, IDC_UNCOMPRESS_FILECNT, szTemp);
g_hdcDirectoryTextCtrl = GetDC(GetDlgItem(hDlg, IDC_UNCOMPRESS_DIR)); GetClientRect(GetDlgItem(hDlg, IDC_UNCOMPRESS_DIR), &rect); g_cDirectoryTextCtrlWd = rect.right;
EnableWindow(hDlg, TRUE); break; } case ( WM_COMMAND ) : { switch (LOWORD(wParam)) { case ( IDOK ) : case ( IDCANCEL ) : { if (LOWORD(wParam) == IDCANCEL) g_pContext->uCompletionReason = SCCA_REASON_USERCANCEL;
if (g_hdcDirectoryTextCtrl) { ReleaseDC(GetDlgItem(hDlg, IDC_UNCOMPRESS_DIR), g_hdcDirectoryTextCtrl); g_hdcDirectoryTextCtrl = NULL; } DestroyWindow(hDlg); g_hdlgProgress = NULL; break; } default : { return (FALSE); } } break; } default : { return (FALSE); } }
return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DisplayCompressProgress
//
// DESCRIPTION:
//
// Update the progress of compressing files.
//
// This routine uses the global variables to update the Dialog box items
// which display the progress through the compression process. The global
// variables are updated by individual routines. An ordinal value is sent
// to this routine which determines which dialog box item to update.
// Taken from WinFile.
//
//
// ARGUMENTS:
//
// iType
// Control value to determine what is to be updated.
// Value names are self-descriptive.
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
void DisplayCompressProgress(int iType) { TCHAR szTemp[120]; TCHAR szNum[30]; unsigned _int64 Percentage;
if (!g_bShowProgress) { return; }
switch (iType) { case ( PROGRESS_UPD_FILEANDDIR ) : case ( PROGRESS_UPD_FILENAME ) : { SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_FILE, g_szFile); if (iType != PROGRESS_UPD_FILEANDDIR) { break; }
// else...fall thru
} case ( PROGRESS_UPD_DIRECTORY ) : { RECT rect; //
// Preprocess the directory name to shorten it to fit
// into the alloted space.
//
GetWindowRect(GetDlgItem(g_hdlgProgress, IDC_COMPRESS_DIR), &rect); DrawTextEx(g_hdcDirectoryTextCtrl, g_szDirectory, lstrlen(g_szDirectory), &rect, DT_MODIFYSTRING | DT_PATH_ELLIPSIS, NULL); SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_DIR, g_szDirectory);
break; } case ( PROGRESS_UPD_DIRCNT ) : { AddCommas((DWORD)g_cTotalDirectories, szNum); SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_DIRCNT, szNum);
break; } case ( PROGRESS_UPD_FILENUMBERS ) : case ( PROGRESS_UPD_FILECNT ) : { AddCommas((DWORD)g_cTotalFiles, szNum); SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_FILECNT, szNum); if (iType != PROGRESS_UPD_FILENUMBERS) { break; }
// else...fall thru
} case ( PROGRESS_UPD_COMPRESSEDSIZE ) : { Int64ToString(g_iTotalCompressedSize, szTemp, ARRAYSIZE(szTemp), TRUE, &g_NumberFormat, NUMFMT_ALL); FormatStringWithArgs(g_szByteCntFmt, szNum, ARRAYSIZE(szNum), szTemp); SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_CSIZE, szNum); if (iType != PROGRESS_UPD_FILENUMBERS) { break; }
// else...fall thru
} case ( PROGRESS_UPD_FILESIZE ) : { Int64ToString(g_iTotalFileSize, szTemp, ARRAYSIZE(szTemp), TRUE, &g_NumberFormat, NUMFMT_ALL); FormatStringWithArgs(g_szByteCntFmt, szNum, ARRAYSIZE(szNum), szTemp); SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_USIZE, szNum); if (iType != PROGRESS_UPD_FILENUMBERS) { break; }
// else...fall thru
}
case ( PROGRESS_UPD_PERCENTAGE ) : { if (g_iTotalFileSize != 0) { //
// Percentage = 100 - ((CompressSize * 100) / FileSize)
//
Percentage = (g_iTotalCompressedSize * 100) / g_iTotalFileSize;
if (Percentage > 100) { Percentage = 100; } else Percentage = 100 - Percentage; } else { Percentage = 0; } //
// Note that percentage string is not formatted.
// i.e. no commas or decimal places.
//
Int64ToString(Percentage, szTemp, ARRAYSIZE(szTemp), FALSE, NULL, 0); wsprintf(szNum, TEXT("%s%%"), szTemp); SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_RATIO, szNum);
break; } }
CompressProgressYield(); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressProgDlg
//
// DESCRIPTION:
//
// Display progress information.
// Taken from WinFile.
//
// NOTE: This is a modeless dialog and must be terminated with DestroyWindow
// and NOT EndDialog
//
// ARGUMENTS:
//
// Standard dialog proc args.
//
// RETURNS:
//
// TRUE = Message handled.
// FALSE = Message not handled.
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR APIENTRY CompressProgDlg( HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam) { TCHAR szTemp[120]; RECT rect;
switch (nMsg) { case ( WM_INITDIALOG ) : { CenterWindowInParent(hDlg);
g_hdlgProgress = hDlg;
//
// Clear Dialog items.
//
szTemp[0] = TEXT('\0');
SetDlgItemText(hDlg, IDC_COMPRESS_FILE, szTemp); SetDlgItemText(hDlg, IDC_COMPRESS_DIR, szTemp); SetDlgItemText(hDlg, IDC_COMPRESS_DIRCNT, szTemp); SetDlgItemText(hDlg, IDC_COMPRESS_FILECNT, szTemp); SetDlgItemText(hDlg, IDC_COMPRESS_CSIZE, szTemp); SetDlgItemText(hDlg, IDC_COMPRESS_USIZE, szTemp); SetDlgItemText(hDlg, IDC_COMPRESS_RATIO, szTemp);
g_hdcDirectoryTextCtrl = GetDC(GetDlgItem(hDlg, IDC_COMPRESS_DIR)); GetClientRect(GetDlgItem(hDlg, IDC_COMPRESS_DIR), &rect); g_cDirectoryTextCtrlWd = rect.right;
//
// Set Dialog message text.
//
LoadString(g_hmodThisDll, IDS_COMPRESS_DIR, szTemp, ARRAYSIZE(szTemp)); EnableWindow(hDlg, TRUE);
break; } case ( WM_COMMAND ) : { switch (LOWORD(wParam)) { case ( IDOK ) : case ( IDCANCEL ) : { if (LOWORD(wParam) == IDCANCEL) g_pContext->uCompletionReason = SCCA_REASON_USERCANCEL;
if (g_hdcDirectoryTextCtrl) { ReleaseDC(GetDlgItem(hDlg, IDC_COMPRESS_DIR), g_hdcDirectoryTextCtrl); g_hdcDirectoryTextCtrl = NULL; } DestroyWindow(hDlg); g_hdlgProgress = NULL;
break; } default : { return (FALSE); } } break; } default : { return (FALSE); } }
return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: NotifyShellOfAttribChange
//
// DESCRIPTION:
//
// If the item is visible or is a directory, tell the shell that it's
// attributes have changed. This will cause the shell to update any
// compression-related display characteristics.
//
// ARGUMENTS:
//
// pszPath
// Fully-qualified path to file/directory that changed.
//
// bIsDirectory
// If TRUE, shell is notified.
// If FALSE, shell is notified only if global recursion level counter
// is 1. This ensures that we don't send unnecessary notifications to
// the shell for items that definitely are not visible in the shell view.
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
void NotifyShellOfAttribChange(LPTSTR pszPath, BOOL bIsDirectory) { //
// Only notify shell if item is visible.
// All items handled at recursion level 1 are visible by default.
// Subdirectories must always be updated because they may be visible
// the Explorer tree view.
//
if (1 == g_iRecursionLevel || bIsDirectory) { if (PathIsRoot(pszPath)) { //
// Invalidate the drive type flags cache for this drive.
// Cache will be updated with new information on next call to
// RealDriveTypeFlags( ).
//
InvalidateDriveType(PathGetDriveNumber(pszPath)); } else { PathRemoveTheBackslash(pszPath); } SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, pszPath, NULL);
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: Shell notified. %s"), pszPath); #endif
} }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: ShellChangeCompressionAttribute
//
// DESCRIPTION:
//
// Main entry point for file compression/uncompression.
//
// ARGUMENTS:
//
// hwndParent
// Handle to window for parenting dialogs.
//
// szFileSpec
// Fully-qualified path of file to be compressed/uncompressed.
//
// pContext
// Address of a context structure as defined in shcompui.h
// This structure is created by the caller so that we can maintain
// information about the compression process across a series of
// calls to this function. In particular, the user can press the
// "Ignore All Errors" button and we must remember this during
// subsequent calls. The structure also maintains error count
// information and completion status. This may be used to supplement
// the TRUE/FALSE return mechanism to further discriminate a FALSE
// return value.
//
// bCompressing
// Control variable. TRUE = compress file. FALSE = uncompress file.
//
// bShowUI
// Control of message box and dialog displays.
// TRUE = Show all dialogs and messages.
// FALSE = Hide all dialogs and messages. Used when compression is desired
// without any user interactiion.
//
// ***********************************************************
// NOTE
//
// The bShowUI argument has been introduced to support
// programmatic invocation of shell compression in cases
// where a UI display is not wanted. The functionality
// of preventing UI display is not complete at this time.
// To meet the SUR beta deadline, this parameter is ignored.
//
// ***********************************************************
//
// RETURNS:
//
// TRUE = Operation successful. Continue if iterating
// through set of files/directories.
// FALSE = User aborted compression/uncompression or error occurred.
// Stop if iterating through set of files/directories.
// Query the context structure for additional completion information.
//
///////////////////////////////////////////////////////////////////////////////
BOOL ShellChangeCompressionAttribute( HWND hwndParent, LPTSTR szNameSpec, LPSCCA_CONTEXT pContext, BOOL bCompressing, BOOL bShowUI) { TCHAR szTitle[MAX_DLGTITLE_LEN+1]; TCHAR szTemp[MAX_MESSAGE_LEN+1]; TCHAR szFilespec[_MAX_PATH+1]; BOOL bCompressionAttrChange; BOOL bIsDir = FALSE; BOOL bRet = TRUE; HCURSOR hCursor = NULL; DWORD dwAttribs = 0; DWORD dwNewAttribs = 0; DWORD dwFlags = 0;
ASSERT(hwndParent != NULL); ASSERT(szNameSpec != NULL);
//
// Make sure we're not in the middle of another compression operation.
// If so, put up an error box warning the user that they need to wait
// to do another compression operation.
//
if (WaitForSingleObject(g_hSemaphore, 0L) == WAIT_TIMEOUT) { //
// REARCHITECT: Shouldn't assume the UI is being displayed on behalf of
// Explorer. The code should be modified so that the app
// name is passed in through ShellChangeCompressionAtttribute.
//
LoadString(g_hmodThisDll, IDS_APP_NAME, szTitle, ARRAYSIZE(szTitle)); LoadString(g_hmodThisDll, IDS_MULTI_COMPRESS_ERR, szMessage, ARRAYSIZE(szMessage)); MessageBox(hwndParent, szMessage, szTitle, MB_OK | MB_ICONEXCLAMATION);
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: Re-entrancy attempted and denied")); #endif
return (TRUE); }
//
// Give the context structure "file" scope.
//
g_pContext = pContext;
//
// Reset recursion level counter.
//
g_iRecursionLevel = 0;
//
// Make sure the volume supports File Compression.
//
lstrcpy(szTemp, szNameSpec); PathStripToRoot(szTemp); // GetVolumeInformation requires a trailing backslash. Append
// one if this is a UNC path.
if (PathIsUNC(szTemp)) { lstrcat(szTemp, c_szBACKSLASH); } if (!GetVolumeInformation (szTemp, NULL, 0L, NULL, NULL, &dwFlags, NULL, 0L) || !(dwFlags & FS_FILE_COMPRESSION)) { //
// The volume does not support file compression, so just
// quit out. Do not return FALSE, since that will not
// allow any other attributes to be changed.
//
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: Volume %s doesn't support compression."), szTemp); #endif
ReleaseSemaphore(g_hSemaphore, 1, NULL); return (TRUE); }
//
// Show the hour glass cursor.
//
if (hCursor = LoadCursor(NULL, IDC_WAIT)) { hCursor = SetCursor(hCursor); } ShowCursor(TRUE);
//
// Get the file attributes (current and new).
// On error, don't change the attribute.
//
if ((dwAttribs = GetFileAttributes(szNameSpec)) != (DWORD)-1) { if (bCompressing) dwNewAttribs = dwAttribs | FILE_ATTRIBUTE_COMPRESSED; else dwNewAttribs = dwAttribs & ~FILE_ATTRIBUTE_COMPRESSED; }
//
// Determine if ATTR_COMPRESSED is changing state.
//
bCompressionAttrChange = ( (dwAttribs & FILE_ATTRIBUTE_COMPRESSED) != (dwNewAttribs & FILE_ATTRIBUTE_COMPRESSED) );
#ifdef TRACE_COMPRESSION
{ TCHAR szAttribStr[40]; TCHAR szNewAttribStr[40]; DbgOut(TEXT("SHCOMPUI: File = \"%-30s\" Attr = [%s] New = [%s]"), szNameSpec, FileAttribString(dwAttribs, szAttribStr), FileAttribString(dwNewAttribs, szNewAttribStr)); } #endif
g_bShowProgress = FALSE; g_bIgnoreAllErrors = g_pContext->bIgnoreAllErrors; g_bDiskFull = FALSE; g_pContext->cErrors = 0; // Clear "current" error counter.
//
// If the Compression attribute changed or if we're dealing with
// a directory, perform action.
//
bIsDir = PathIsDirectory(szNameSpec); if (bCompressionAttrChange || bIsDir) { INT cchLoaded = 0;
//
// Reset globals before progress display.
//
g_cTotalDirectories = 0; g_cTotalFiles = 0; g_iTotalFileSize = 0; g_iTotalCompressedSize = 0; g_szFile[0] = CH_NULL; g_szDirectory[0] = CH_NULL;
cchLoaded = LoadString(g_hmodThisDll, IDS_BYTECNT_FMT, g_szByteCntFmt, ARRAYSIZE(g_szByteCntFmt)); ASSERT(cchLoaded > 0);
if (bIsDir) { BOOL bIgnoreAll = FALSE; UINT_PTR uDlgResult = 0; CompressionDesc compdesc = { bCompressing, TEXT("") };
lstrcpy(compdesc.szFileName, szNameSpec); uDlgResult = DialogBoxParam(g_hmodThisDll, MAKEINTRESOURCE(DLG_COMPRESS_CONFIRMATION), hwndParent, CompressSubsConfirmDlgProc, (LPARAM)&compdesc);
lstrcpy(szFilespec, c_szSTAR); g_bShowProgress = TRUE; switch(uDlgResult) { case COMPRESS_SUBSYES: g_bDoSubdirectories = TRUE; break;
case COMPRESS_SUBSNO: g_bDoSubdirectories = FALSE; break;
case COMPRESS_CANCELLED: bRet = FALSE; goto CancelCompress; break;
default: ASSERT(0); break; }
if (g_bShowProgress) { g_hdlgProgress = CreateDialog( g_hmodThisDll, MAKEINTRESOURCE(bCompressing ? DLG_COMPRESS_PROGRESS : DLG_UNCOMPRESS_PROGRESS), hwndParent, (bCompressing ? CompressProgDlg : UncompressProgDlg));
ShowWindow(g_hdlgProgress, SW_SHOW); } PathAddBackslash(szNameSpec); lstrcpy(szTemp, szNameSpec);
bRet = bCompressing ? DoCompress(hwndParent, szNameSpec, szFilespec) : DoUncompress(hwndParent, szNameSpec, szFilespec);
//
// Set attribute on Directory if last call was successful.
//
if (bRet) { szFilespec[0] = TEXT('\0'); g_bDoSubdirectories = FALSE; lstrcpy(szNameSpec, szTemp); bRet = bCompressing ? DoCompress(hwndParent, szNameSpec, szFilespec) : DoUncompress(hwndParent, szNameSpec, szFilespec); }
//
// If the progress dialog was being displayed, destroy it.
//
if (g_hdlgProgress) { if (g_hdcDirectoryTextCtrl) { ReleaseDC( GetDlgItem(g_hdlgProgress, (bCompressing ? IDC_COMPRESS_DIR : IDC_UNCOMPRESS_DIR)), g_hdcDirectoryTextCtrl); g_hdcDirectoryTextCtrl = NULL; } DestroyWindow(g_hdlgProgress); g_hdlgProgress = NULL; } } else { //
// Compress single file.
//
g_bDoSubdirectories = FALSE; lstrcpy(szFilespec, szNameSpec);
PathStripPath(szFilespec); PathRemoveFileSpec(szNameSpec);
PathAddBackslash(szNameSpec);
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("Compress/Uncompress single file %s%s"),szNameSpec,szFilespec); #endif
bRet = bCompressing ? DoCompress(hwndParent, szNameSpec, szFilespec) : DoUncompress(hwndParent, szNameSpec, szFilespec); } }
CancelCompress:
//
// Reset the cursor.
//
if (hCursor) { SetCursor(hCursor); } ShowCursor(FALSE);
//
// Tell the shell that the free space on this volume has changed.
//
lstrcpy(szTemp, szNameSpec); PathStripToRoot(szTemp); if (PathIsUNC(szTemp)) { lstrcat(szTemp, c_szBACKSLASH); } SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, szTemp, NULL);
//
// Return the appropriate value.
//
g_pContext->bIgnoreAllErrors = g_bIgnoreAllErrors; ReleaseSemaphore(g_hSemaphore, 1, NULL); return (bRet); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressFile
//
// DESCRIPTION:
//
// Compress a single file.
// Originally take from WinFile.
//
// ARGUMENTS:
//
// Handle
// Handle to file to be compressed.
//
// FileSpec
// Full file specification.
// Required to obtain compressed file size.
// FindData->cFileName[] doesn't include the path.
//
// FindData
// Pointer to file search context data. Contains file name.
//
// RETURNS:
//
// TRUE = Success.
// FALSE = Device IO error during compression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL CompressFile( HANDLE Handle, LPTSTR FileSpec, PWIN32_FIND_DATA FindData) { USHORT State; ULONG Length; LARGE_INTEGER TempLarge;
//
// Print out the file name and then do the Ioctl to compress the
// file. When we are done we'll print the okay message.
//
lstrcpy(g_szFile, FindData->cFileName); DisplayCompressProgress(PROGRESS_UPD_FILENAME);
State = 1;
if (!DeviceIoControl( Handle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE )) { g_pContext->cErrors++; g_pContext->cCummErrors++;
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: CompressFile, DeviceIoControl failed with error 0x%08X"), GetLastError()); #endif
return (FALSE); }
//
// Gather statistics (File size, compressed size and count).
//
TempLarge.LowPart = FindData->nFileSizeLow; TempLarge.HighPart = FindData->nFileSizeHigh; g_iTotalFileSize += TempLarge.QuadPart;
TempLarge.LowPart = GetCompressedFileSize(FileSpec, &(TempLarge.HighPart)); g_iTotalCompressedSize += TempLarge.QuadPart;
g_cTotalFiles++;
DisplayCompressProgress(PROGRESS_UPD_FILENUMBERS);
return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DoCompress
//
// DESCRIPTION:
//
// Compress a directory and its subdirectories if necessary.
// Originally take from WinFile.
//
// ARGUMENTS:
//
// hwndParent
// Window handle for parenting dialogs and message boxes.
//
// DirectorySpec
// Fully-qualified directory specification with backslash appended.
//
// FileSpec
// File name with directory path removed.
//
// RETURNS:
//
// TRUE = Success.
// FALSE = Device IO error or user aborted compression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DoCompress( HWND hwndParent, LPTSTR DirectorySpec, LPTSTR FileSpec) { LPTSTR DirectorySpecEnd; HANDLE FileHandle = INVALID_HANDLE_VALUE; USHORT State; ULONG Length; HANDLE FindHandle; WIN32_FIND_DATA FindData; int MBRet; TCHAR szTitle[128];
g_iRecursionLevel++;
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: DoCompress with %s%s"), DirectorySpec, FileSpec); DbgOut(TEXT(" Recursion Level: %d -> %d"), g_iRecursionLevel-1, g_iRecursionLevel); #endif
//
// If the file spec is null, then set the compression bit for
// the directory spec and get out.
//
lstrcpy(g_szDirectory, DirectorySpec); g_szFile[0] = CH_NULL; DisplayCompressProgress(PROGRESS_UPD_FILEANDDIR);
if (lstrlen(FileSpec) == 0) { DoCompressRetryCreate:
if (!OpenFileForCompress(&FileHandle, DirectorySpec)) { #ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: DoCompress, OpenFileForCompress failed.")); #endif
goto DoCompressError; }
DoCompressRetryDevIo:
State = 1; if (!DeviceIoControl( FileHandle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE )) { g_pContext->cErrors++; g_pContext->cCummErrors++;
DoCompressError:
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: DoCompress, DeviceIoControl failed with error 0x%08X"), GetLastError()); #endif
if (!g_bIgnoreAllErrors) { MBRet = CompressErrMessageBox( hwndParent, DirectorySpec, &FileHandle ); if (MBRet == RETRY_CREATE) { goto DoCompressRetryCreate; } else if (MBRet == RETRY_DEVIO) { goto DoCompressRetryDevIo; } else if (MBRet == IDABORT) { //
// Return error.
// File handle was closed by CompressErrMessageBox( ).
//
g_pContext->uCompletionReason = SCCA_REASON_USERABORT; g_iRecursionLevel--; return (FALSE); } //
// Else (MBRet == IDIGNORE)
// Continue on as if the error did not occur.
//
} } if (INVALID_HANDLE_VALUE != FileHandle) { CloseHandle(FileHandle); FileHandle = INVALID_HANDLE_VALUE; }
g_cTotalDirectories++; g_cTotalFiles++;
DisplayCompressProgress(PROGRESS_UPD_DIRCNT); DisplayCompressProgress(PROGRESS_UPD_FILECNT);
NotifyShellOfAttribChange(DirectorySpec, TRUE);
g_iRecursionLevel--; return (TRUE); }
//
// Get a pointer to the end of the directory spec, so that we can
// keep appending names to the end of it.
//
DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);
//
// List the directory that is being compressed and display
// its current compress attribute.
//
g_cTotalDirectories++; DisplayCompressProgress(PROGRESS_UPD_DIRCNT);
//
// For every file in the directory that matches the file spec,
// open the file and compress it.
//
// Setup the template for findfirst/findnext.
//
lstrcpy(DirectorySpecEnd, FileSpec);
if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE) { do { DWORD dwAttrib = FindData.dwFileAttributes;
//
// Make sure the user hasn't hit cancel.
//
if (g_bShowProgress && !g_hdlgProgress) { g_iRecursionLevel--; FindClose(FindHandle); return FALSE; }
//
// Skip over the . and .. entries.
//
if ( !lstrcmp(FindData.cFileName, c_szDOT) || !lstrcmp(FindData.cFileName, c_szDOTDOT) ) { continue; } else if ((DirectorySpecEnd == (DirectorySpec + 3)) && !lstrcmpi(FindData.cFileName, c_szNTLDR)) { //
// Do not allow \NTLDR to be compressed.
// Put up OK message box and then continue.
//
lstrcpy(DirectorySpecEnd, FindData.cFileName); LoadString(g_hmodThisDll, IDS_NTLDR_COMPRESS_ERR, szTitle, ARRAYSIZE(szTitle)); wsprintf(szMessage, szTitle, DirectorySpec); LoadString(g_hmodThisDll, IDS_APP_NAME, szTitle, ARRAYSIZE(szTitle)); MessageBox(g_hdlgProgress ? g_hdlgProgress : hwndParent, szMessage, szTitle, MB_OK | MB_ICONEXCLAMATION);
continue; } else { //
// Append the found file to the directory spec and
// open the file.
//
lstrcpy(DirectorySpecEnd, FindData.cFileName);
if ((dwAttrib & FILE_ATTRIBUTE_COMPRESSED) || (!g_bDoSubdirectories && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY))) { //
// File is a directory or is already compressed.
// So just skip it.
//
continue; }
CompressFileRetryCreate:
if (!OpenFileForCompress(&FileHandle, DirectorySpec)) { #ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: DoCompress, OpenFileForCompress failed."), GetLastError()); #endif
goto CompressFileError; }
CompressFileRetryDevIo:
//
// Compress the file.
//
if (!CompressFile(FileHandle, DirectorySpec, &FindData)) { CompressFileError:
if (!g_bIgnoreAllErrors) { MBRet = CompressErrMessageBox( hwndParent, DirectorySpec, &FileHandle ); if (MBRet == RETRY_CREATE) { goto CompressFileRetryCreate; } else if (MBRet == RETRY_DEVIO) { goto CompressFileRetryDevIo; } else if (MBRet == IDABORT) { //
// Return error.
// File handle was closed by CompressErrMessageBox( ).
//
g_pContext->uCompletionReason = SCCA_REASON_USERABORT; g_iRecursionLevel--; FindClose(FindHandle); return (FALSE); } //
// Else (MBRet == IDIGNORE)
// Continue on as if the error did not occur.
//
} } if (INVALID_HANDLE_VALUE != FileHandle) { CloseHandle(FileHandle); FileHandle = INVALID_HANDLE_VALUE; } }
NotifyShellOfAttribChange(DirectorySpec, (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
CompressProgressYield();
} while (FindNextFile(FindHandle, &FindData));
FindClose(FindHandle); }
//
// If we are to do subdirectores, then look for every subdirectory
// and recursively call ourselves to list the subdirectory.
//
if (g_bDoSubdirectories) { //
// Setup findfirst/findnext to search the entire directory.
//
lstrcpy(DirectorySpecEnd, c_szSTAR);
if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE) { do { //
// Skip over the . and .. entries, otherwise recurse.
//
if ( !lstrcmp(FindData.cFileName, c_szDOT) || !lstrcmp(FindData.cFileName, c_szDOTDOT) ) { continue; } else { //
// If the entry is for a directory, then tack
// on the subdirectory name to the directory spec
// and recurse.
//
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { lstrcpy(DirectorySpecEnd, FindData.cFileName); lstrcat(DirectorySpecEnd, c_szBACKSLASH);
if (!DoCompress(hwndParent, DirectorySpec, FileSpec)) { g_iRecursionLevel--; FindClose(FindHandle); return (FALSE || g_bIgnoreAllErrors); } } }
} while (FindNextFile(FindHandle, &FindData));
FindClose(FindHandle); } }
g_iRecursionLevel--; return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: UncompressFile
//
// DESCRIPTION:
//
// Uncompress a single file.
// Originally take from WinFile.
//
// ARGUMENTS:
//
// hwndParent
// Handle to window for parenting any dialogs.
//
// hFile
// Handle to file to be compressed.
//
// FindData
// Pointer to file search context data. Contains file name.
//
// RETURNS:
//
// TRUE = Success.
// FALSE = Device IO error during compression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL UncompressFile(HWND hwndParent, HANDLE hFile, PWIN32_FIND_DATA FindData) { USHORT State; ULONG Length;
//
// Print out the file name and then do the Ioctl to uncompress the
// file. When we are done we'll print the okay message.
//
lstrcpy(g_szFile, FindData->cFileName); DisplayUncompressProgress(PROGRESS_UPD_FILENAME);
State = 0;
if (!DeviceIoControl( hFile, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE ) #ifdef SIM_DISK_FULL
|| TRUE #endif
) { g_pContext->cErrors++; g_pContext->cCummErrors++;
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: UncompressFile, DeviceIoControl failed with error 0x%08X"), GetLastError()); #endif
#ifdef SIM_DISK_FULL
SetLastError((DWORD)STATUS_DISK_FULL); #endif
if (GetLastError() == STATUS_DISK_FULL) { UncompressDiskFullError(hwndParent, hFile); g_bDiskFull = TRUE; }
return (FALSE); }
//
// Increment the running total.
//
g_cTotalFiles++; DisplayUncompressProgress(PROGRESS_UPD_FILENUMBERS);
return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DoUncompress
//
// DESCRIPTION:
//
// Uncompress a directory and its subdirectories if necessary.
// Originally take from WinFile.
//
// ARGUMENTS:
//
// hwndParent
// Window handle for parenting dialogs and message boxes.
//
// DirectorySpec
// Fully-qualified directory specification with backslash appended.
//
// FileSpec
// File name with directory path removed.
//
// RETURNS:
//
// TRUE = Success.
// FALSE = Device IO error or user aborted uncompression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DoUncompress( HWND hwndParent, LPTSTR DirectorySpec, LPTSTR FileSpec) { LPTSTR DirectorySpecEnd; HANDLE FileHandle = INVALID_HANDLE_VALUE; USHORT State; ULONG Length; HANDLE FindHandle; WIN32_FIND_DATA FindData; int MBRet;
g_iRecursionLevel++;
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: DoUncompress with %s%s"), DirectorySpec, FileSpec); DbgOut(TEXT(" Recursion Level: %d -> %d"), g_iRecursionLevel-1, g_iRecursionLevel); #endif
//
// If the file spec is null, then clear the compression bit for
// the directory spec and get out.
//
lstrcpy(g_szDirectory, DirectorySpec); g_szFile[0] = CH_NULL; DisplayUncompressProgress(PROGRESS_UPD_FILEANDDIR);
if (lstrlen(FileSpec) == 0) { DoUncompressRetryCreate:
if (!OpenFileForCompress(&FileHandle, DirectorySpec)) { #ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: UncompressDirecory, OpenFileForCompress failed.")); #endif
goto DoUncompressError; }
DoUncompressRetryDevIo:
State = 0; if (!DeviceIoControl( FileHandle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE ) #ifdef SIM_DISK_FULL
|| TRUE #endif
) { g_pContext->cErrors++; g_pContext->cCummErrors++;
DoUncompressError:
//
// Handle disk-full error.
//
#ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: DoUncompress, DeviceIoControl failed with error 0x%08X"), GetLastError()); #endif
#ifdef SIM_DISK_FULL
SetLastError((DWORD)STATUS_DISK_FULL); #endif
if (GetLastError() == STATUS_DISK_FULL) { UncompressDiskFullError(hwndParent, FileHandle); g_bDiskFull = TRUE; CloseHandle(FileHandle); g_iRecursionLevel--; return FALSE; }
if (!g_bIgnoreAllErrors) { MBRet = CompressErrMessageBox( hwndParent, DirectorySpec, &FileHandle ); if (MBRet == RETRY_CREATE) { goto DoUncompressRetryCreate; } else if (MBRet == RETRY_DEVIO) { goto DoUncompressRetryDevIo; } else if (MBRet == IDABORT) { //
// Return error.
// File handle was closed by CompressErrMessageBox.
//
g_pContext->uCompletionReason = SCCA_REASON_USERABORT; g_iRecursionLevel--; return (FALSE); } //
// Else (MBRet == IDIGNORE)
// Continue on as if the error did not occur.
//
} } if (INVALID_HANDLE_VALUE != FileHandle) { CloseHandle(FileHandle); FileHandle = INVALID_HANDLE_VALUE; }
g_cTotalDirectories++; g_cTotalFiles++;
DisplayUncompressProgress(PROGRESS_UPD_DIRCNT); DisplayUncompressProgress(PROGRESS_UPD_FILECNT);
NotifyShellOfAttribChange(DirectorySpec, TRUE);
g_iRecursionLevel--; return (TRUE); }
//
// Get a pointer to the end of the directory spec, so that we can
// keep appending names to the end of it.
//
DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);
g_cTotalDirectories++; DisplayUncompressProgress(PROGRESS_UPD_DIRCNT);
//
// For every file in the directory that matches the file spec,
// open the file and uncompress it.
//
// Setup the template for findfirst/findnext.
//
lstrcpy(DirectorySpecEnd, FileSpec);
if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE) { do { DWORD dwAttrib = FindData.dwFileAttributes;
//
// Make sure the user hasn't hit cancel.
//
if (g_bShowProgress && !g_hdlgProgress) { g_iRecursionLevel--; FindClose(FindHandle); return FALSE; }
//
// Skip over the . and .. entries.
//
if ( !lstrcmp(FindData.cFileName, c_szDOT) || !lstrcmp(FindData.cFileName, c_szDOTDOT) ) { continue; } else { //
// Append the found file to the directory spec and
// open the file.
//
lstrcpy(DirectorySpecEnd, FindData.cFileName);
if (!(dwAttrib & FILE_ATTRIBUTE_COMPRESSED) || (!g_bDoSubdirectories && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY))) { //
// File is a directory or already uncompressed.
// So skip it.
//
continue; }
UncompressFileRetryCreate:
if (!OpenFileForCompress(&FileHandle, DirectorySpec)) { #ifdef TRACE_COMPRESSION
DbgOut(TEXT("SHCOMPUI: OpenFileForCompress failed.")); #endif
goto UncompressFileError; }
UncompressFileRetryDevIo:
//
// Uncompress the file.
//
if (!UncompressFile(hwndParent, FileHandle, &FindData)) { UncompressFileError:
//
// If disk is full, UncompressFile( ) already handled the error.
// Don't handle it again. Just return.
//
if (g_bDiskFull) { g_pContext->uCompletionReason = SCCA_REASON_DISKFULL; CloseHandle(FileHandle); FindClose(FindHandle); g_iRecursionLevel--; return FALSE; }
if (!g_bIgnoreAllErrors) { MBRet = CompressErrMessageBox( hwndParent, DirectorySpec, &FileHandle ); if (MBRet == RETRY_CREATE) { goto UncompressFileRetryCreate; } else if (MBRet == RETRY_DEVIO) { goto UncompressFileRetryDevIo; } else if (MBRet == IDABORT) { //
// Return error.
// File handle was closed by CompressErrMessageBox.
//
g_pContext->uCompletionReason = SCCA_REASON_USERABORT; g_iRecursionLevel--; FindClose(FindHandle); return (FALSE); } //
// Else (MBRet == IDIGNORE)
// Continue on as if the error did not occur.
//
} } if (INVALID_HANDLE_VALUE != FileHandle) { CloseHandle(FileHandle); FileHandle = INVALID_HANDLE_VALUE; } }
NotifyShellOfAttribChange(DirectorySpec, (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
CompressProgressYield();
} while (FindNextFile(FindHandle, &FindData));
FindClose(FindHandle); }
//
// If we are to do subdirectores, then look for every subdirectory
// and recursively call ourselves to list the subdirectory.
//
if (g_bDoSubdirectories) { //
// Setup findfirst/findnext to search the entire directory.
//
lstrcpy(DirectorySpecEnd, c_szSTAR);
if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE) { do { //
// Skip over the . and .. entries, otherwise recurse.
//
if ( !lstrcmp(FindData.cFileName, c_szDOT) || !lstrcmp(FindData.cFileName, c_szDOTDOT) ) { continue; } else { //
// If the entry is for a directory, then tack
// on the subdirectory name to the directory spec
// and recurse.
//
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { lstrcpy(DirectorySpecEnd, FindData.cFileName); lstrcat(DirectorySpecEnd, c_szBACKSLASH);
if (!DoUncompress(hwndParent, DirectorySpec, FileSpec)) { g_iRecursionLevel--; FindClose(FindHandle); return (FALSE || g_bIgnoreAllErrors); } } }
} while (FindNextFile(FindHandle, &FindData));
FindClose(FindHandle); } }
g_iRecursionLevel--; return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressErrMessageBox
//
// DESCRIPTION:
//
// Puts up the error message box when a file cannot be compressed or
// uncompressed. It also returns the user preference.
//
// NOTE: The file handle is closed if the abort or ignore option is
// chosen by the user.
//
// ARGUMENTS:
//
//
// RETURNS:
//
// RETRY_CREATE = User selected "Retry"
// RETRY_DEVIO = User selected "Retry"
// IDABORT
// IDIGNORE
// IDC_COMPRESS_IGNOREALL
//
///////////////////////////////////////////////////////////////////////////////
int CompressErrMessageBox( HWND hwndActive, LPTSTR szFile, PHANDLE phFile) { int rc;
//
// Put up the error message box - ABORT, RETRY, IGNORE, IGNORE ALL.
//
rc = (int)DialogBoxParam( g_hmodThisDll, (LPTSTR) MAKEINTRESOURCE(DLG_COMPRESS_ERROR), g_hdlgProgress ? g_hdlgProgress : hwndActive, CompressErrDialogProc, (LPARAM)szFile );
//
// Return the user preference.
//
if (rc == IDRETRY) { if (*phFile == INVALID_HANDLE_VALUE) { return (RETRY_CREATE); } else { return (RETRY_DEVIO); } } else { //
// IDABORT or IDIGNORE or IDC_COMPRESS_IGNOREALL
//
// Close the file handle and return the message box result.
//
if (*phFile != INVALID_HANDLE_VALUE) { CloseHandle(*phFile); *phFile = INVALID_HANDLE_VALUE; }
return (rc); } }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressErrDialogProc
//
// DESCRIPTION:
//
// Puts up a dialog to allow the user to Abort, Retry, Ignore, or
// Ignore All when an error occurs during compression.
//
//
// Taken from WinFile source wffile.c. Modified control resource IDs to
// be consistent with other Explorer ID naming conventions.
//
// ARGUMENTS:
//
// Standard Dialog Proc args.
//
// RETURNS:
//
// Standard Dialog Proc return values..
//
// Returns through EndDialog( ):
//
// IDABORT
// IDRETRY
// IDIGNORE
// IDC_COMPRESS_IGNOREALL
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR CALLBACK CompressErrDialogProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { WORD IdControl = TRUE; TCHAR szTitle[MAX_DLGTITLE_LEN + 1];
switch (uMsg) { case ( WM_INITDIALOG ) : { //
// Modify very long path names so that they fit into the message box.
// They are formatted as "c:\dir1\dir2\dir3\...\dir8\filename.ext"
// DrawTextEx isn't drawing on anything. Only it's formatting capabilities are
// being used. The DT_CALCRECT flag prevents drawing.
//
HDC hDC = GetDC(hDlg); LONG iBaseUnits = GetDialogBaseUnits(); RECT rc; TCHAR szFilePath[MAX_PATH]; // Local copy of path name string.
const int MAX_PATH_DISPLAY_WD = 50; // Max characters to display in path name.
const int MAX_PATH_DISPLAY_HT = 1; // Path name is 1 character high.
rc.left = 0; rc.top = 0; rc.right = MAX_PATH_DISPLAY_WD * LOWORD(iBaseUnits); rc.bottom = MAX_PATH_DISPLAY_HT * HIWORD(iBaseUnits);
lstrcpyn(szFilePath, (LPCTSTR)lParam, ARRAYSIZE(szFilePath)); DrawTextEx(hDC, szFilePath, ARRAYSIZE(szFilePath), &rc, DT_CALCRECT | DT_PATH_ELLIPSIS | DT_MODIFYSTRING, NULL); ReleaseDC(hDlg, hDC);
//
// Set the dialog message text.
//
LoadString( g_hmodThisDll, IDS_COMPRESS_ATTRIB_ERR, szTitle, ARRAYSIZE(szTitle) );
wsprintf(szMessage, szTitle, szFilePath); SetDlgItemText(hDlg, IDC_COMPRESS_ERRTEXT, szMessage); EnableWindow (hDlg, TRUE);
break; } case ( WM_COMMAND ) : { IdControl = GET_WM_COMMAND_ID(wParam, lParam); switch (IdControl) { case ( IDC_COMPRESS_IGNOREALL ) : { g_bIgnoreAllErrors = TRUE;
// fall thru...
} case ( IDABORT ) : case ( IDRETRY ) : case ( IDIGNORE ) : { EndDialog(hDlg, IdControl); break; } default : { return (FALSE); } } break; } default : { return (FALSE); } } return (IdControl); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: OpenFileForCompress
//
// DESCRIPTION:
//
// Opens the file for compression. It handles the case where a READONLY
// file is trying to be compressed or uncompressed. Since read only files
// cannot be opened for WRITE_DATA, it temporarily resets the file to NOT
// be READONLY in order to open the file, and then sets it back once the
// file has been compressed.
//
// Taken from WinFile module wffile.c without change. Originally from
// G. Kimura's compact.c.
//
// ARGUMENTS:
//
// phFile
// Address of file handle variable for handle of open file if
// successful.
//
// szFile
// Name string of file to be opened.
//
// RETURNS:
//
// TRUE = File successfully opened. Handle in *phFile.
// FALSE = File couldn't be opened. *phFile == INVALID_HANDLE_VALUE
//
///////////////////////////////////////////////////////////////////////////////
BOOL OpenFileForCompress( PHANDLE phFile, LPTSTR szFile) { HANDLE hAttr; BY_HANDLE_FILE_INFORMATION fi;
//
// Try to open the file - READ_DATA | WRITE_DATA.
//
if ((*phFile = CreateFile( szFile, FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, NULL )) != INVALID_HANDLE_VALUE) { //
// Successfully opened the file.
//
return (TRUE); }
if (GetLastError() != ERROR_ACCESS_DENIED) { return (FALSE); }
//
// Try to open the file - READ_ATTRIBUTES | WRITE_ATTRIBUTES.
//
if ((hAttr = CreateFile( szFile, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, NULL )) == INVALID_HANDLE_VALUE) { return (FALSE); }
//
// See if the READONLY attribute is set.
//
if ( (!GetFileInformationByHandle(hAttr, &fi)) || (!(fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) ) { //
// If the file could not be open for some reason other than that
// the readonly attribute was set, then fail.
//
CloseHandle(hAttr); return (FALSE); }
//
// Turn OFF the READONLY attribute.
//
fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; if (!SetFileAttributes(szFile, fi.dwFileAttributes)) { CloseHandle(hAttr); return (FALSE); }
//
// Try again to open the file - READ_DATA | WRITE_DATA.
//
*phFile = CreateFile( szFile, FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, NULL );
//
// Close the file handle opened for READ_ATTRIBUTE | WRITE_ATTRIBUTE.
//
CloseHandle(hAttr);
//
// Make sure the open succeeded. If it still couldn't be opened with
// the readonly attribute turned off, then fail.
//
if (*phFile == INVALID_HANDLE_VALUE) { return (FALSE); }
//
// Turn the READONLY attribute back ON.
//
fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY; if (!SetFileAttributes(szFile, fi.dwFileAttributes)) { CloseHandle(*phFile); *phFile = INVALID_HANDLE_VALUE; return (FALSE); }
//
// Return success. A valid file handle is in *phFile.
//
return (TRUE); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: UncompressDiskFullError
//
// DESCRIPTION:
//
// To be called when disk space is exhausted during uncompression.
// The function displays a message box with an OK button.
// NTFS leaves a file partially compressed when DeviceIoControl( )
// returns STATUS_DISK_FULL in GetLastError( ). It also leaves the
// compressed attribute as "compressed".
// Once the user acknowledges the message, we attempt to re-compress the
// file so that the entire file is compressed and matches its
// attribute setting.
//
// ARGUMENTS:
//
// hFile
// Handle to file being uncompressed at the time of the error.
//
// RETURNS:
//
// Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static void UncompressDiskFullError(HWND hwndParent, HANDLE hFile) { TCHAR szTitle[MAX_DLGTITLE_LEN + 1]; USHORT State = 1; // 1 = Compress.
ULONG Length = 0;
LoadString(g_hmodThisDll, IDS_APP_NAME, szTitle, ARRAYSIZE(szTitle)); LoadString(g_hmodThisDll, IDS_UNCOMPRESS_DISKFULL, szMessage, ARRAYSIZE(szMessage));
MessageBox(hwndParent, szMessage, szTitle, MB_OK | MB_ICONSTOP);
//
// Try to compress the file.
// Don't worry about errors on the compression attempt.
// At worst case, the file is left partially compressed with the attribute
// set as "compressed". According to the NTFS people, this is not
// harmful and the file is still usable. Besides, there isn't much else
// we could do at this point.
//
DeviceIoControl( hFile, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE );
}
#endif // ifdef WINNT
|