/////////////////////////////////////////////////////////////////////////////// // // 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 // /////////////////////////////////////////////////////////////////////////////// #ifdef WINNT #include #include #include #include #include #include #include #include #include #define INITGUID #include #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