windows-nt/Source/XPSP1/NT/shell/osshell/shcompui/shcompui.c
2020-09-26 16:20:57 +08:00

4289 lines
124 KiB
C

///////////////////////////////////////////////////////////////////////////////
//
// 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