///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1995.
//
//  FILE: SHCOMPUI.C
//
//  DESCRIPTION:
//
//    This module provides the code for supporting NTFS file compression
//    in the NT Explorer user interface.  There are two interfaces to the
//    compression features.
//
//    The first interface is a shell context menu extension that adds
//    the options "Compress" and "Uncompress" to the context/file menu of
//    an object that has registered the extension.  This interface
//    uses the standard shell extension protocol of QueryContextMenu,
//    InvokeCommand etc.  InvokeCommand calls ShellChangeCompressionAttribute()
//    to do the actual compression/uncompression.
//
//    The shell extension CLSID is {764BF0E1-F219-11ce-972D-00AA00A14F56}
//    and is represented by the symbol CLSID_CompressMenuExt.
//
//    The second interface is provided for handling compression
//    requests through object property pages.  Property page action
//    code calls ShellChangeCompressionAttribute( ).
//
//    This way, through either interface, compression appears the same
//    to the user.
//
//    Note that a good portion of the actual compression code was taken
//    from the WinFile implementation.  Some changes were made to
//    eliminate redundant code and to produce the desired Explorer
//    compression behavior.
//
//    The comment string "WARNING" points out areas that are sensitive to maintenance activity.
//
//    This module is applicable only to the NT version of the shell.
//
//    REVISIONS:
//
//    Date       Description                                         Programmer
//    ---------- --------------------------------------------------- ----------
//    09/15/95   Initial creation.                                   brianau
//    09/20/95   Incorporated changes from 1st code review.          brianau
//    10/02/95   Added SCCA context structure.                       brianau
//               Changed all __TEXT() macros to TEXT()
//    10/13/95   Removed function Int64ToString and moved it to      brianau
//               util.c.
//    02/22/96   Check for shift-key before adding context menu      brianau
//               items.  No shift key, no items.
//               Also added call to SHChangeNotify to notify shell
//               that compression attribute changed on each file.
//    03/20/96   Added invalidation of drive type flags cache.       brianau
//               Replaced imported function declarations with
//               #include <shellprv.h>
//
///////////////////////////////////////////////////////////////////////////////
#ifdef WINNT
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <shlobj.h>
#include <shellapi.h>
#include <shlobjp.h>
#define INITGUID
#include <initguid.h>
#include "resids.h"     // SHCOMPUI Resource IDs.
#include "debug.h"      // DbgOut and ASSERT.
#include "shcompui.h"

#define Assert(f)
#define ARRAYSIZE(a)    (sizeof(a)/sizeof(a[0]))

//
// Debug message control.
//
//#define TRACE_SHEXT 1        // Un-comment for shell extension tracing.
//#define TRACE_DLL   1        // Un-comment for DLL load/unload tracing.
//#define TRACE_COMPRESSION  1 // Un-comment for compression code tracing.
//#define SIM_DISK_FULL 1      // Un-comment to test disk-full condition.

//
// Define context menu position offsets for each option.
//
#define MENUOFS_FIRST      0     // For index range checking.
#define MENUOFS_COMPRESS   0     // Command index for "Compress" menu opt..
#define MENUOFS_UNCOMPRESS 1     // Command index for "Uncompress" menu opt.
#define MENUOFS_LAST       1     // For index range checking.

//
// Return values for compression confirmation dialog.
//
#define COMPRESS_CANCELLED 0     // User pressed Cancel.
#define COMPRESS_SUBSNO    1     // User pressed OK without box checked.
#define COMPRESS_SUBSYES   2     // User pressed OK with box checked.

//
//  Control values for DisplayCompressProgress( ) and
//  DisplayUncompressProgress( )
//
#define PROGRESS_UPD_FILENAME            1
#define PROGRESS_UPD_DIRECTORY           2
#define PROGRESS_UPD_FILEANDDIR          3
#define PROGRESS_UPD_DIRCNT              4
#define PROGRESS_UPD_FILECNT             5
#define PROGRESS_UPD_COMPRESSEDSIZE      6
#define PROGRESS_UPD_FILESIZE            7
#define PROGRESS_UPD_PERCENTAGE          8
#define PROGRESS_UPD_FILENUMBERS         9
#define PROGRESS_UPD_FINAL              10

//
//  Return values for CompressErrMessageBox routine.
//
#define RETRY_CREATE     1
#define RETRY_DEVIO      2

//
// Some text string and character constants.
// FEATURE: These should probably come from some locale info.
//
const TCHAR c_szSTAR[]      = TEXT("*");
const TCHAR c_szDOT[]       = TEXT(".");
const TCHAR c_szDOTDOT[]    = TEXT("..");
const TCHAR c_szNTLDR[]     = TEXT("NTLDR");
const TCHAR c_szBACKSLASH[] = TEXT("\\");

#define CH_NULL       TEXT('\0')


//
// String length constants.
//
#define MAX_DLGTITLE_LEN    128             // Max length of dialog title.
#define MAX_MESSAGE_LEN     (_MAX_PATH * 3) // General dialog text message.
#define MAX_MENUITEM_LEN     40             // Max length of context menu item.
#define MAX_CMDVERB_LEN      40             // Max length of cmd verb.

typedef HRESULT (CALLBACK FAR * LPFNCREATEINSTANCE)(LPUNKNOWN pUnkOuter,
        REFIID riid, LPVOID FAR* ppvObject);


static INT_PTR CALLBACK CompressSubsConfirmDlgProc(HWND hDlg, UINT uMsg,
                                WPARAM wParam, LPARAM lParam);

static BOOL DoCompress(HWND hwndParent,
                                LPTSTR DirectorySpec, LPTSTR FileSpec);

static BOOL DoUncompress(HWND hwndParent,
                                LPTSTR DirectorySpec, LPTSTR FileSpec);

static int  CompressErrMessageBox(HWND hwndActive,
                                LPTSTR szFile, PHANDLE phFile);

static INT_PTR CALLBACK CompressErrDialogProc(HWND hDlg, UINT uMsg,
                                WPARAM wParam, LPARAM lParam);

static BOOL OpenFileForCompress(PHANDLE phFile, LPTSTR szFile);

static DWORD FormatStringWithArgs(LPCTSTR pszFormat, LPTSTR pszBuffer,
                                                       DWORD nSize, ...);

static DWORD LoadStringWithArgs(HINSTANCE hInstance, UINT uId,
                                LPTSTR pszBuffer, DWORD nSize, ...);

static VOID CompressProgressYield(void);

static VOID DisplayUncompressProgress(int iType);

static void UncompressDiskFullError(HWND hwndParent, HANDLE hFile);


//
// Structure used to communicate with the compression confirmation dialog.
//
typedef struct {
   BOOL  bCompress;               // TRUE = compress, FALSE = uncompress
   TCHAR szFileName[_MAX_PATH+1]; // File to be acted on.
} CompressionDesc;


//
// Macros for converting between interface and class pointers.
//
#define CMX_OFFSETOF(x)          ((UINT_PTR)(&((CContextMenuExt *)0)->x))
#define PVOID2PCMX(pv,offset)    ((CContextMenuExt *)(((LPBYTE)pv)-offset))
#define PCM2PCMX(pcmx)           PVOID2PCMX(pcmx, CMX_OFFSETOF(ctm))
#define PSEI2PCMX(psei)          PVOID2PCMX(psei, CMX_OFFSETOF(sei))

INT g_cRefThisDll        = 0;       // Reference count for this DLL.
HINSTANCE g_hmodThisDll  = NULL;    // Handle to the DLL.
HANDLE g_hProcessHeap    = NULL;    // Handle to the process heap.
HANDLE g_hdlgProgress    = NULL;    // Operation progress dialog.

TCHAR szMessage[MAX_MESSAGE_LEN+1]; // FEATURE:  This need not be global.
TCHAR g_szByteCntFmt[10];           // Byte cnt disp fmt str ( "%1 bytes" ).

#define SZ_SEMAPHORE_NAME   TEXT("SHCOMPUI_SEMAPHORE")
HANDLE g_hSemaphore       = NULL;    // Re-entrancy semaphore.

LPSCCA_CONTEXT g_pContext = NULL;   // Ptr to current context structure.

INT g_iRecursionLevel = 0;          // Used to control shell change notifications.

//
//  Global variables to hold the User option information.
//
BOOL g_bDoSubdirectories = FALSE;   // Include all subdirectories ?
BOOL g_bShowProgress     = FALSE;   // Show operation progress dialog ?
BOOL g_bIgnoreAllErrors  = FALSE;   // User wants to ignore all errors ?
BOOL g_bDiskFull         = FALSE;   // Is disk full on uncompression ?

//
//  Global variables to hold compression statistics.
//
LONGLONG g_cTotalDirectories       = 0;
LONGLONG g_cTotalFiles             = 0;

//
// Compression ratio statistics values.
//
unsigned _int64 g_iTotalFileSize       = 0;
unsigned _int64 g_iTotalCompressedSize = 0;

//
// "Current" file and directory names are global.
//
TCHAR  g_szFile[_MAX_PATH + 1];
TCHAR  g_szDirectory[_MAX_PATH + 1];

//
// Directory text control in progress dialog.
//
HDC   g_hdcDirectoryTextCtrl = NULL; // Control handle.
DWORD g_cDirectoryTextCtrlWd = 0;    // Width of control.

//
// Number format locale information.
//
NUMBERFMT g_NumberFormat;
TCHAR g_szDecimalSep[5];
TCHAR g_szThousandSep[5];


//
// Context menu extension GUID generated with GUIDGEN.
//
// {764BF0E1-F219-11ce-972D-00AA00A14F56}
DEFINE_GUID(CLSID_CompressMenuExt,
0x764bf0e1, 0xf219, 0x11ce, 0x97, 0x2d, 0x0, 0xaa, 0x0, 0xa1, 0x4f, 0x56);

//
// Structure representing the class factory for this in-process server.
//
typedef struct
{
   IClassFactory        cf;       // Pointer to class factory vtbl.
   UINT                 cRef;     // Interface reference counter.
   LPFNCREATEINSTANCE   pfnCI;    // Pointer to instance generator function.
} CClassFactory;

//
// Structure representing the context menu extension.
//
typedef struct
{
   IContextMenu   ctm;            // Pointer to context menu interface.
   IShellExtInit  sei;            // Pointer to shell extension init interface.
   UINT           cRef;           // Interface reference counter.
   STGMEDIUM      medium;         // OLE data xfer storage medium descriptor.
   INT            cSelectedFiles; // Cnt of files selected. Must be signed.
   BOOL           bDriveSelected; // Are drives selected ?
   LPDATAOBJECT   pDataObj;       // Saved pointer to Data Object.
} CContextMenuExt;


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: PathRemoveTheBackslash
//
// DESCRIPTION:
//
//    Removes trailing backslash from path string if it exists.
//    This code was cut directly from path.c in shell32.dll and
//    renamed from PathRemoveBackslash to avoid linkage naming
//    conflicts.
//
// in:
//  lpszPath    (A:\, C:\foo\, etc)
//
// out:
//  lpszPath    (A:\, C:\foo, etc)
//
// returns:
//  ponter to NULL that replaced the backslash
//  or the pointer to the last character if it isn't a backslash.
//
///////////////////////////////////////////////////////////////////////////////
LPTSTR WINAPI PathRemoveTheBackslash(LPTSTR lpszPath)
{
  int len = lstrlen(lpszPath)-1;
  if (IsDBCSLeadByte(*((LPSTR)CharPrev(lpszPath,lpszPath+len+1))))
      len--;

  if (!PathIsRoot(lpszPath) && lpszPath[len] == TEXT('\\'))
      lpszPath[len] = TEXT('\0');

  return lpszPath + len;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: FileAttribString
//
// DESCRIPTION:
//
//    Formats a file's attribute word into a string of characters.
//    Used for program tracing and debugging only.
//
// ARGUMENTS:
//
//    dwAttrib
//       Attribute bits to be decoded.
//
//    pszDest
//       Address of destination string.
//       String must be at least 8 characters long.
//
// RETURNS:
//
//    Pointer to destination string.
//
///////////////////////////////////////////////////////////////////////////////
#ifdef TRACE_COMPRESSION
static LPTSTR FileAttribString(DWORD dwAttrib, LPTSTR pszDest)
{
   if (dwAttrib != (DWORD)-1)
   {
      wsprintf(pszDest, TEXT("%c%c%c%c%c%c%c"),
                  dwAttrib & FILE_ATTRIBUTE_ARCHIVE    ? TEXT('A') : TEXT('.'),
                  dwAttrib & FILE_ATTRIBUTE_COMPRESSED ? TEXT('C') : TEXT('.'),
                  dwAttrib & FILE_ATTRIBUTE_DIRECTORY  ? TEXT('D') : TEXT('.'),
                  dwAttrib & FILE_ATTRIBUTE_HIDDEN     ? TEXT('H') : TEXT('.'),
                  dwAttrib & FILE_ATTRIBUTE_NORMAL     ? TEXT('N') : TEXT('.'),
                  dwAttrib & FILE_ATTRIBUTE_READONLY   ? TEXT('R') : TEXT('.'),
                  dwAttrib & FILE_ATTRIBUTE_SYSTEM     ? TEXT('S') : TEXT('.'));
   }
   else
     lstrcpy(pszDest, TEXT("INVALID"));

   return pszDest;
}
#endif


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: ChgDllRefCnt
//
// DESCRIPTION:
//
//    Adds reference count tracking to the incrementing and decrementing of the
//    module reference count.
//
// ARGUMENTS:
//
//    n
//       Should be +1 or -1.
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
__inline void ChgDllRefCnt(INT n)
{
   ASSERT(g_cRefThisDll >= 0 && g_cRefThisDll+(n) >= 0);

   g_cRefThisDll += (n);

#ifdef TRACE_DLL
   DbgOut(TEXT("SHCOMPUI: ChgDllRefCnt. Count = %d"), g_cRefThisDll);
#endif
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_QueryInterface
//
// DESCRIPTION:
//
//    Class factory support for IUnknown::QueryInterface( ).
//    Queries class factory object for a specific interface.
//
// ARGUMENTS:
//
//    pcf
//       Pointer to class factory interface.
//
//    riid
//       Reference to ID of interface being requested.
//
//    ppvOut
//       Destination for address of vtable for requested interface.
//
// RETURNS:
//
//    NOERROR        = Success.
//    E_NOINTERFACE  = Requested interface not supported.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CClassFactory_QueryInterface(IClassFactory *pcf, REFIID riid,
                                                              LPVOID *ppvOut)
{
   CClassFactory *this = IToClass(CClassFactory, cf, pcf);
   HRESULT hResult     = E_NOINTERFACE;

   ASSERT(NULL != this);
   ASSERT(NULL != ppvOut);

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CClassFactory::QueryInterface"));
#endif

   *ppvOut = NULL;

   if (IsEqualIID(riid, &IID_IClassFactory) ||
       IsEqualIID(riid, &IID_IUnknown))
   {
      (LPCLASSFACTORY)*ppvOut = &this->cf;
      this->cRef++;
      hResult = NOERROR;
   }

   return hResult;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_AddRef
//
// DESCRIPTION:
//
//    Class factory support for IUnknown::AddRef( ).
//    Increments object reference count.
//
// ARGUMENTS:
//
//    pcf
//       Pointer to class factory interface.
//
// RETURNS:
//
//    Returns object reference count after it is incremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CClassFactory_AddRef(IClassFactory *pcf)
{
   CClassFactory *this = IToClass(CClassFactory, cf, pcf);

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CClassFactory::AddRef"));
#endif

   ASSERT(NULL != this);

   return ++this->cRef;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_Release
//
// DESCRIPTION:
//
//    Class factory support for IUnknown::Release( ).
//    Decrements object reference count.
//    Deletes object from memory when reference count reaches 0.
//
// ARGUMENTS:
//
//    pcf
//       Pointer to class factory interface.
//
// RETURNS:
//
//    Returns object reference count after it is incremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CClassFactory_Release(IClassFactory *pcf)
{
   CClassFactory *this = IToClass(CClassFactory, cf, pcf);
   ULONG refCnt = 0;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CClassFactory::Release"));
#endif

   ASSERT(NULL != this);

   if ((refCnt = --this->cRef) == 0)
   {
      LocalFree((HLOCAL)this);
      ChgDllRefCnt(-1);
   }

   return refCnt;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_CreateInstance
//
// DESCRIPTION:
//
//    Generates an instance of the class factory.
//
// ARGUMENTS:
//
//    pcf
//       Pointer to class factory interface.
//
//    punkOuter
//       Pointer to outer object's IUnknown interface.  Only used when
//       object aggregation is requested.
//
//    riid
//       Reference to requested interface ID.
//
//    ppv
//       Destination for address of requested interface.
//
// RETURNS:
//
//
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CClassFactory_CreateInstance(IClassFactory *pcf, IUnknown *punkOuter,
                                           REFIID riid, LPVOID *ppv)
{
   CClassFactory *this = IToClass(CClassFactory, cf, pcf);
   HRESULT hResult     = CLASS_E_NOAGGREGATION;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CClassFactory::CreateInstance"));
#endif

   ASSERT(NULL != this);

   if (NULL == punkOuter)
   {
      hResult = this->pfnCI(punkOuter, riid, ppv);
   }

   return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CClassFactory_LockServer
//
// DESCRIPTION:
//
//    Explicitly locks the server from unloading regardless of the module
//    reference count.
//    Not used for DLL servers.  Therefore, this implementation is nul.
//
//
// ARGUMENTS:
//    pcf
//       Pointer to class factory object.
//
//    fLock
//       TRUE  = Lock server.
//       FALSE = Unlock server.
//
//
// RETURNS:
//
//    E_NOTIMPL  = Server locking not required for DLL server.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CClassFactory_LockServer(IClassFactory *pcf, BOOL fLock)
{
#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CClassFactory::LockServer"));
#endif

   return E_NOTIMPL;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_QueryInterface
//
// DESCRIPTION:
//
//    Context menu extension support for IUnknown::QueryInterface( ).
//    Queries context menu extension object for a specific interface.
//
// ARGUMENTS:
//
//    pctm
//       Pointer to context menu interface.
//
//    riid
//       Reference to ID of interface being requested.
//
//    ppvOut
//       Destination for address of vtable for requested interface.
//
// RETURNS:
//
//    NOERROR        = Success.
//    E_NOINTERFACE  = Requested interface not supported.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_QueryInterface(IContextMenu *pctm, REFIID riid,
                                                                LPVOID *ppvOut)
{
   CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);
   HRESULT hResult       = NOERROR;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::QueryInterface"));
#endif

   ASSERT(NULL != this);
   ASSERT(NULL != ppvOut);

   *ppvOut = NULL;

   if (IsEqualIID(riid, &IID_IContextMenu) || IsEqualIID(riid, &IID_IUnknown))
   {
      (IContextMenu *)*ppvOut = &this->ctm;
      this->cRef++;
   }
   else if (IsEqualIID(riid, &IID_IShellExtInit))
   {
      (IShellExtInit *)*ppvOut = &this->sei;
      this->cRef++;
   }
   else
      hResult = E_NOINTERFACE;

   return hResult;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_AddRef
//
// DESCRIPTION:
//
//    Context menu extension support for IUnknown::AddRef( ).
//    Increments object reference count.
//
// ARGUMENTS:
//
//    pctm
//       Pointer to context menu interface.
//
// RETURNS:
//
//    Returns object reference count after it is incremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CContextMenuExt_AddRef(IContextMenu *pctm)
{
   CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::AddRef"));
#endif

   ASSERT(NULL != this);

   return ++this->cRef;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_Cleanup
//
// DESCRIPTION:
//
//    Does the cleanup associated with a previous IShellExtInit_Initialize call
//
// ARGUMENTS:
//
//    this
//       Pointer to context menu extension
//
// RETURNS:
//
//    -nothing-
//
///////////////////////////////////////////////////////////////////////////////
void CContextMenu_Cleanup( CContextMenuExt *this )
{
      if (this->pDataObj)
      {
         this->pDataObj->lpVtbl->Release(this->pDataObj);
      }
      //
      // Now release the stgmedium (FEATURE - Replace this with OLE's ReleaseStgMedium
      //
      if (this->medium.pUnkForRelease)
      {
         this->medium.pUnkForRelease->lpVtbl->Release(this->medium.pUnkForRelease);
      }
      else
      {
          switch(this->medium.tymed)
          {
             case TYMED_HGLOBAL:
                GlobalFree(this->medium.hGlobal);
                break;

             case TYMED_ISTORAGE: // depends on pstm/pstg overlap in union
             case TYMED_ISTREAM:
                this->medium.pstm->lpVtbl->Release(this->medium.pstm);
                break;

             default:
                Assert(0);  // unknown type
          }
      }
}

///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_Release
//
// DESCRIPTION:
//
//    Context menu extension support for IUnknown::Release( ).
//    Decrements object reference count.
//    Deletes object from memory when reference count reaches 0.
//
// ARGUMENTS:
//
//    pctm
//       Pointer to context menu interface.
//
// RETURNS:
//
//    Returns object reference count after it is decremented.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CContextMenuExt_Release(IContextMenu *pctm)
{
   CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);
   ULONG refCnt = 0;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::Release"));
#endif

   ASSERT(NULL != this);

   if ((refCnt = --this->cRef) == 0)
   {
      CContextMenu_Cleanup(this);
      LocalFree((HLOCAL)this);

      ChgDllRefCnt(-1);
   }

   return refCnt;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_QueryContextMenu
//
// DESCRIPTION:
//
//    Called by NT Shell when requesting menu option text and command numbers.
//
// ARGUMENTS:
//
//    pctm
//       Pointer to context menu interface.
//
//    hMenu
//       Handle to context menu to be modified.
//
//    indexMenu
//       Index where first menu item may be inserted.
//
//    idCmdFirst
//       Lower bound of available menu command IDs.
//
//    idCmdLast
//       Upper bound of available menu command IDs.
//
//    uFlags
//       Flag indicating context of function call.
//
// RETURNS:
//
//    Returns the number of menu items added (excluding separators).
//
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_QueryContextMenu(IContextMenu *pctm, HMENU hMenu,
                 UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
   CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);
   BOOL bDisableCompress        = FALSE;
   BOOL bDisableUncompress      = FALSE;
   BOOL bDirectorySelected      = FALSE;
   BOOL bNonCompressibleVol     = FALSE;
   DWORD dwAttribTalley         = 0;
   INT i                        = 0;
   INT cMenuItemsAdded          = 0;
   HCURSOR hCursor              = NULL;
   DRAGINFO di;                      // Drag information.
   LPTSTR pDragFileName = NULL;      // Ptr into list of drag info names.
   LPTSTR pNextName = NULL;          // Lookahead pointer into name list.

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::QueryContextMenu"));
#endif

   ASSERT(NULL != this);


   //
   // Only allow addition of Compress/Uncompress when user is pressing
   // shift key.
   //
   // cSelectedFiles will be -1 if user selected only a virtual object.
   // i.e. Control Panel, Printer etc.
   //
   if (GetAsyncKeyState(VK_SHIFT) >= 0 || this->cSelectedFiles <= 0)
      return 0;

   //
   // Determine if we should hide both of the context menu items or
   // disable one of them.
   // If no selected device supports compression, HIDE both items.
   // If a directory is selected, show both menu items.
   // If a directory is not in the selected list, disable a menu item
   // if all of the selected files have the same compression state.
   // i.e.:  All items compressed - disable the "Compress" item.
   // A mix of compression states activates both items.
   //

   dwAttribTalley = 0;   // Attribute talley mask.

   //
   // Attribute talley mask bits.
   //
#define TALLEY_UNCOMPRESSED 0x0001  // Object is an uncompressed file.
#define TALLEY_COMPRESSED   0x0002  // Object is a compressed file.
#define TALLEY_DIRECTORY    0x0004  // Object is a directory.
#define TALLEY_NOCOMPSUPT   0x0008  // At least 1 file from FAT/HPFS drive.

   //
   // Display the hourglass cursor so that the user knows something is
   // happening.  This loop can take a long time on large selections.
   //
   if (hCursor = LoadCursor(NULL, IDC_WAIT))
      hCursor = SetCursor(hCursor);
   ShowCursor(TRUE);

   //
   // Determine if the user has selected drives.
   // Since you can't select a combination of drive(s) and folders/files,
   // if there are ANY drives selected, the first one must be a drive.
   //
   {
      TCHAR szFileName[_MAX_PATH + 1];
      TCHAR szRootName[_MAX_PATH + 1];

      DragQueryFile((HDROP)this->medium.hGlobal, 0, szFileName,
                                                    ARRAYSIZE(szFileName));
      lstrcpy(szRootName, szFileName);
      PathStripToRoot(szRootName);
      this->bDriveSelected = (lstrcmpi(szRootName, szFileName) == 0);
   }

   //
   // Get list of file names selected.
   // The string contains nul-terminated names.
   // The entire list is terminated with a double-nul.
   //
   // FEATURE: We need to write ANSI/UNICODE thunking layer for
   //         DragQueryInfo().
   //
   di.uSize = sizeof(DRAGINFO);
   DragQueryInfo((HDROP)this->medium.hGlobal, &di);
   pDragFileName = pNextName = di.lpFileList;

   for (i = 0; i < this->cSelectedFiles; i++)
   {
      DWORD dwFlags      = 0;
      DWORD dwAttrib     = 0;

      //
      // Set bits in the attribute "talley" word.  This signals the existence of
      // each desired quantity for all selected files.
      //
      if ((dwAttrib = GetFileAttributes(pDragFileName)) != (DWORD)-1)
      {
         if ((dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
            dwAttribTalley |= TALLEY_DIRECTORY;

         if ((dwAttrib & FILE_ATTRIBUTE_COMPRESSED))
            dwAttribTalley |= TALLEY_COMPRESSED;
         else
            dwAttribTalley |= TALLEY_UNCOMPRESSED;
      }

      //
      // Save pointer to next name.
      //
      pNextName += lstrlen(pNextName) + 1;

      if (!(dwAttribTalley & TALLEY_NOCOMPSUPT))
      {
         //
         // Do we have at least one drive that DOES NOT support compression?
         // If so, we don't show the menu items.  This provides the INTERSECTION
         // of capabilities for the selected files per the UI design guide.
         // Note that if GetVolumeInformation fails for a drive, the drive is empty
         // and is therefore uncompressible.
         //
         PathStripToRoot(pDragFileName);
         // GetVolumeInformation requires a trailing backslash.  Append
         // one if this is a UNC path.
         if (PathIsUNC(pDragFileName))
         {
             lstrcat(pDragFileName, c_szBACKSLASH);
         }
         if (GetVolumeInformation(pDragFileName, NULL, 0, NULL, NULL, &dwFlags, NULL, 0))
            dwAttribTalley |= (dwFlags & FS_FILE_COMPRESSION) == 0 ?
                                                TALLEY_NOCOMPSUPT : 0;
         else
            dwAttribTalley |= TALLEY_NOCOMPSUPT;
      }

      //
      // If all of the flag bits are set, no need to check more files.
      // We have all the info we need to properly configure the UI.
      //
      if ( (dwAttribTalley & ((DWORD)-1) ) == (TALLEY_DIRECTORY    |
                                               TALLEY_COMPRESSED   |
                                               TALLEY_UNCOMPRESSED |
                                               TALLEY_NOCOMPSUPT))
      {
         break;
      }

      //
      // Advance name pointer to next name.
      //
      pDragFileName = pNextName;
   }

   //
   // Free the file name list we got through DragQueryInfo().
   //
   if (di.lpFileList)
      SHFree(di.lpFileList);

   //
   // Convert the settings of the talley flag bits to more meaningful names.
   //
   bNonCompressibleVol = (dwAttribTalley & TALLEY_NOCOMPSUPT) != 0;
   bDirectorySelected  = (dwAttribTalley & TALLEY_DIRECTORY) != 0;

   if (!bDirectorySelected)
   {
      bDisableUncompress = (dwAttribTalley &
            (TALLEY_COMPRESSED | TALLEY_UNCOMPRESSED)) == TALLEY_UNCOMPRESSED;
      bDisableCompress   = (dwAttribTalley &
            (TALLEY_COMPRESSED | TALLEY_UNCOMPRESSED)) == TALLEY_COMPRESSED;
   }

   switch(uFlags & 0x0F)  // Upper 28 bits are reserved.
   {
      case CMF_EXPLORE:
         //
         // Win32 SDK says we should only get this flag bit set when the user
         // has selected an object in the left pane of the Explorer. This is a
         // doc bug in the SDK verified by "satona".  We get it via selection
         // in either pane.
         //

      case CMF_NORMAL:

         if (!bNonCompressibleVol)
         {
            INT cchLoaded = 0;
            TCHAR szMenuItem[MAX_MENUITEM_LEN + 1];

            //
            // Regarding item separators; can we always count on there being
            //         an item above and below our new items?  If not, we're
            //         in danger of adding a separator at the top or bottom of
            //         the menu.  BobDay says we'll always have something above
            //         and below us.
            //
            cchLoaded = LoadString(g_hmodThisDll,
                                   bDirectorySelected ? IDS_COMPRESS_MENUITEM_ELLIP :
                                                        IDS_COMPRESS_MENUITEM,
                                   szMenuItem, ARRAYSIZE(szMenuItem));
            ASSERT(cchLoaded > 0);

            InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);
            InsertMenu(hMenu, indexMenu++, MF_STRING | MF_BYPOSITION | (bDisableCompress ? MF_GRAYED : 0),
                                           idCmdFirst + MENUOFS_COMPRESS, szMenuItem);
            cMenuItemsAdded++;
            cchLoaded = LoadString(g_hmodThisDll,
                                   bDirectorySelected ? IDS_UNCOMPRESS_MENUITEM_ELLIP :
                                                        IDS_UNCOMPRESS_MENUITEM,
                                   szMenuItem, ARRAYSIZE(szMenuItem));
            ASSERT(cchLoaded > 0);

            InsertMenu(hMenu, indexMenu++, MF_STRING | MF_BYPOSITION | (bDisableUncompress ? MF_GRAYED : 0),
                                           idCmdFirst + MENUOFS_UNCOMPRESS, szMenuItem);
            cMenuItemsAdded++;
            InsertMenu(hMenu, indexMenu,   MF_SEPARATOR | MF_BYPOSITION, 0, NULL);

         }
         break;

      case CMF_DEFAULTONLY:
      case CMF_VERBSONLY:
         //
         // SDK Docs say Context Menu Extensions should ignore these.
         //
         break;

      default:
         break;
   }

   //
   // Restore original cursor and return number of menu items added.
   //
   if (hCursor)
      SetCursor(hCursor);
   ShowCursor(FALSE);

   return cMenuItemsAdded;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_InvokeCommand
//
// DESCRIPTION:
//
//    Called by the shell whenever the user selects one of the registered
//    items on the context menu.
//
//    Or may be called programmatically with one of the following verb
//    strings:
//               "COMPRESS"    to compress files.
//               "UNCOMPRESS"  to uncompress files.
//
//               These verb names are case-sensitive and language-
//               insensitive.
//
// ARGUMENTS:
//
//    pctm
//       Pointer to the context menu interface.
//
//    pici
//       Pointer to the command info structure associated with the selected
//       menu command.
//
// RETURNS:
//
//    NOERROR              = Success
//    OLEOBJ_E_INVALIDVERB = Invalid verb was specified in programatic
//                           invocation.
//    E_FAIL               = User aborted operation or an error occured during
//                           compression/uncompression.  We should have more
//                           descriptive failure return info.  The original
//                           implementation of compression in WinFile only
//                           returned TRUE/FALSE.  In the interest of time,
//                           that "feature" was retained.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_InvokeCommand(IContextMenu *pctm,
                                           LPCMINVOKECOMMANDINFO pici)
{
   HRESULT hResult   = NOERROR;
   INT i = 0;
   CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);
   BOOL bCompressing = FALSE;
   BOOL bShowUI      = FALSE;
   HWND hwndParent   = NULL;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::InvokeCommand"));
#endif

   ASSERT(NULL != pici);

   //
   // Caller can request that no UI be activated.
   //
   bShowUI = (pici->fMask & CMIC_MASK_FLAG_NO_UI) == 0;

   //
   // Parent all displayed dialogs with the handle passed in from the caller.
   // Use the desktop as a default if one wasn't provided.
   //
   if ((hwndParent = pici->hwnd) == NULL)
      hwndParent = GetDesktopWindow();

   if (HIWORD(pici->lpVerb) == 0)
   {
      //
      // InvokeCommand was called through the context menu extension protocol.
      //
      bCompressing = LOWORD(pici->lpVerb) == MENUOFS_COMPRESS;
   }
   else
   {
      //
      // InvokeCommand was called programatically.
      // If lpVerb is "COMPRESS", compress the file(s).
      // If lpVerb is "UNCOMPRESS", uncompress the file(s).
      // Otherwise, do nothing and return OLE error code.
      //
      TCHAR szValidVerb[MAX_CMDVERB_LEN + 1];
      TCHAR szVerb[MAX_CMDVERB_LEN + 1];
      INT cchLoaded = 0;

      //
      // Convert verb string to unicode.
      //
      MultiByteToWideChar(CP_ACP, 0L, pici->lpVerb, -1, szVerb, ARRAYSIZE(szVerb));

      bCompressing = FALSE;

      cchLoaded = LoadString(g_hmodThisDll, IDS_COMPRESS_CMDVERB, szValidVerb,
                                                        ARRAYSIZE(szValidVerb));
      ASSERT(cchLoaded > 0);

      if (!lstrcmp(szValidVerb, szVerb))
      {
         bCompressing = TRUE;
      }
      else
      {
         cchLoaded = LoadString(g_hmodThisDll, IDS_UNCOMPRESS_CMDVERB, szValidVerb,
                                                            ARRAYSIZE(szValidVerb));
         ASSERT(cchLoaded > 0);
         if (!lstrcmp(szValidVerb, szVerb))
         {
            bCompressing = FALSE;
         }
         else
         {
            //
            // Verb isn't COMPRESS or UNCOMPRESS.
            //
            hResult = OLEOBJ_E_INVALIDVERB;
         }
      }
   }

   //
   // Do the compression/uncompression.
   //
   if (hResult == NOERROR)
   {
      SCCA_CONTEXT Context;        // Compression context structure.

      SCCA_CONTEXT_INIT(&Context); // Initialize context.

      for (i = 0; i < this->cSelectedFiles; i++)
      {
         TCHAR szFileName[_MAX_PATH + 1];

         //
         // Get the name of the file to compress.
         //
         DragQueryFile((HDROP)this->medium.hGlobal, i, szFileName, ARRAYSIZE(szFileName));

         //
         // Compress/uncompress file.  Return value of FALSE indicates either an error
         // occured or the user cancelled the operation.  In either case, don't process
         // any more files.
         //
         if (!ShellChangeCompressionAttribute(hwndParent, szFileName, &Context,
                                              bCompressing, bShowUI))
         {
            hResult = E_FAIL;
            break;
         }
      }
   }

   return hResult;
}



///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CContextMenuExt_GetCommandString
//
// DESCRIPTION:
//
//    Called by the shell when it wants a text string associated with a menu
//    item.  Status bar text for example or a menu command verb.
//
// ARGUMENTS:
//
//    pctm
//       Pointer to the context menu interface.
//
//    idCmd
//       Integer identifier of the command in question.  The number is the
//       offset of the menu item in the set of added menu items, based 0.
//
//    uFlags
//       Indicates what type of service the shell is requesting.
//
//    pwReserved
//       Unused.
//
//    pszName
//       Destination for menu item character string.  Provided by shell.
//
//    cchMax
//       Max size of destination buffer.  Provided by shell.
//
// RETURNS:
//
//    NOERROR = Success.
//    E_FAIL  = Requested menu idCmd not recognized.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CContextMenuExt_GetCommandString(IContextMenu *pctm, UINT_PTR idCmd,
                                              UINT uFlags, UINT *pwReserved, LPSTR pszName,
                                              UINT cchMax)
{
   CContextMenuExt *this = IToClass(CContextMenuExt, ctm, pctm);
   HRESULT hResult       = NOERROR;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::GetCommandString idCmd = %d"), idCmd);
#endif

   ASSERT(NULL != this);
   ASSERT(NULL != pszName);

   //
   // Start with destination buffer blank.
   //
   if (cchMax > 0)
      pszName[0] = TEXT('\0');


   if (idCmd >= MENUOFS_FIRST && idCmd <= MENUOFS_LAST)
   {
      DWORD *pStrIdArray = NULL;
      BOOL bUnicode      = FALSE;
      DWORD dwCmdVerbIds[]         = { IDS_COMPRESS_CMDVERB,
                                       IDS_UNCOMPRESS_CMDVERB };
      DWORD dwSbarTextIds[]        = { IDS_COMPRESS_SBARTEXT,
                                       IDS_UNCOMPRESS_SBARTEXT };
      DWORD dwSbarTextMultIds[]    = { IDS_COMPRESS_SBARTEXT_M,
                                       IDS_UNCOMPRESS_SBARTEXT_M };
      DWORD dwSbarDrvTextIds[]     = { IDS_COMPRESS_SBARTEXT_DRV,
                                       IDS_UNCOMPRESS_SBARTEXT_DRV };
      DWORD dwSbarDrvTextMultIds[] = { IDS_COMPRESS_SBARTEXT_DRV_M,
                                       IDS_UNCOMPRESS_SBARTEXT_DRV_M };

      switch(uFlags)
      {
         //
         // Provide help text for menu item.
         //
         case GCS_HELPTEXTW:
         case GCS_HELPTEXTA:
            //
            // If drives selected, use "Drive" strings. Otherwise, use "File"
            // strings. Also address multiplicity.
            //
            if (this->cSelectedFiles == 1)
               pStrIdArray = this->bDriveSelected ? dwSbarDrvTextIds : dwSbarTextIds;
            else
               pStrIdArray = this->bDriveSelected ? dwSbarDrvTextMultIds : dwSbarTextMultIds;

            bUnicode    = uFlags == GCS_HELPTEXTW;
            break;

         //
         // Provide command verb recognized by InvokeCommand( ).
         //
         case GCS_VERBW:
         case GCS_VERBA:
            pStrIdArray = dwCmdVerbIds;
            bUnicode    = uFlags == GCS_VERBW;
            break;

         //
         // Validate that the menu cmd exists.
         //
         case GCS_VALIDATE:
            hResult = NOERROR;
            break;

         default:
            break;
      }

      //
      // If we've identified what array to get the string resource ID from, load
      // the ANSI or UNICODE version of the string related to the command id.
      //
      if (NULL != pStrIdArray)
      {
         INT cchLoaded = 0;

         if (bUnicode)
            cchLoaded = LoadStringW(g_hmodThisDll, *(pStrIdArray + idCmd), (LPWSTR)pszName, cchMax);
         else
            cchLoaded = LoadStringA(g_hmodThisDll, *(pStrIdArray + idCmd), pszName, cchMax);

         ASSERT(cchLoaded > 0);
      }
   }
   else
      hResult = E_FAIL;

   return hResult;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CShellExtInit_QueryInterface
//
// DESCRIPTION:
//
//    Called by the shell to obtain an interface from the shell extension.
//
// ARGUMENTS:
//
//    psei
//       Pointer to the shell extension interface.
//
//    riid
//       Reference to the requested interface ID.
//
//    ppvOut
//       Address of destination for resulting interface pointer.
//
//
// RETURNS:
//
//    NOERROR
//    E_NOINTERFACE
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CShellExtInit_QueryInterface(IShellExtInit *psei, REFIID riid,
                                                               LPVOID *ppvOut)
{
   CContextMenuExt *this = PSEI2PCMX(psei);

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CShellExtInit::QueryInterface"));
#endif

   ASSERT(NULL != this);
   ASSERT(NULL != ppvOut);

   return CContextMenuExt_QueryInterface(&this->ctm, riid, ppvOut);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CShellExtInit_AddRef
//
// DESCRIPTION:
//
//    Increments the reference count for the shell extension init
//    interface.
//
// ARGUMENTS:
//
//    psei
//       Pointer to the shell extension interface.
//
// RETURNS:
//
//    New reference counter value.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CShellExtInit_AddRef(IShellExtInit *psei)
{
   CContextMenuExt *this = PSEI2PCMX(psei);

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CShellExtInit::AddRef"));
#endif

   ASSERT(NULL != this);

   return CContextMenuExt_AddRef(&this->ctm);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CShellExtInit_Release
//
// DESCRIPTION:
//
//    Decrements the reference count for the shell extension init
//    interface.  When count reaches 0, the interface object is
//    deleted.
//
// ARGUMENTS:
//
//    psei
//       Pointer to the shell extension interface.
//
// RETURNS:
//
//    New reference counter value.
//
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CShellExtInit_Release(IShellExtInit *psei)
{
   CContextMenuExt *this = PSEI2PCMX(psei);
   ULONG refCnt          = 0;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CShellExtInit::Release"));
#endif

   ASSERT(NULL != this);

   return CContextMenuExt_Release(&this->ctm);
}


///////////////////////////////////////////////////////////////////////////////
//
// CShellExtInit::Initialize
//
// Called by the shell to initialize the shell extension.
//
// Arguments:
//
//    psei
//       Pointer to the IShellExtInit interface.
//
//    pidlFolder
//       Pointer to item ID list of parent folder.
//
//    lpdobj
//       Pointer to data object containing selected file names.
//
//    hkeyProgID
//       Registry class of the file object that has focus.
//
// RETURNS:
//
//    S_OK
//    E_FAIL
//    E_INVALIDARG
//    E_UNEXPECTED
//    E_OUTOFMEMORY
//    DV_E_LINDEX
//    DV_E_FORMATETC
//    DV_E_TYMED
//    DV_E_DVASPECT
//    OLE_E_NOTRUNNING
//    STG_E_MEDIUMFULL
//
///////////////////////////////////////////////////////////////////////////////
static STDMETHODIMP CShellExtInit_Initialize(IShellExtInit *psei,
                 LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj, HKEY hkeyProgID)
{
   CContextMenuExt *this = PSEI2PCMX(psei);
   FORMATETC fe          = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
   HRESULT hResult       = NOERROR;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CShellExtInit::Initialize"));
#endif

   ASSERT(NULL != this);

   //
   // According to Win32 SDK, Initialize can be called more than once.
   //
   CContextMenu_Cleanup(this);

   if (NULL != lpdobj)
   {
      this->pDataObj = lpdobj;
      lpdobj->lpVtbl->AddRef(lpdobj);

      //
      // If we are allowed to get data from the data object, save the medium
      // descriptor in our context menu extension object.  We'll use it to
      // iterate through the names of the selected files in
      // CContextMenuExt::InvokeCommand( ).
      //
      hResult = lpdobj->lpVtbl->GetData(lpdobj, &fe, &this->medium);
      if (NOERROR == hResult)
         this->cSelectedFiles = DragQueryFile((HDROP)this->medium.hGlobal, (DWORD)-1, NULL, 0);
      else
         this->cSelectedFiles = 0;
   }
   else
      hResult = E_FAIL;

   return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//                              CLASS VTABLES
///////////////////////////////////////////////////////////////////////////////
#pragma data_seg(".text")

//
// Create the class factory vtbl in a read-only segment.
//
IClassFactoryVtbl c_vtblCClassFactory = {
   CClassFactory_QueryInterface,
   CClassFactory_AddRef,
   CClassFactory_Release,
   CClassFactory_CreateInstance,
   CClassFactory_LockServer
};

//
// Create the context menu extension vtbl in a read-only segment.
//
IContextMenuVtbl c_vtblContextMenuExt = {
   CContextMenuExt_QueryInterface,
   CContextMenuExt_AddRef,
   CContextMenuExt_Release,
   CContextMenuExt_QueryContextMenu,
   CContextMenuExt_InvokeCommand,
   CContextMenuExt_GetCommandString
};

//
// Create the shell extension initialization vtbl in a read-only segment.
//
IShellExtInitVtbl c_vtblShellExtInit = {
   CShellExtInit_QueryInterface,
   CShellExtInit_AddRef,
   CShellExtInit_Release,
   CShellExtInit_Initialize
};

#pragma data_seg()


///////////////////////////////////////////////////////////////////////////////
//
// CContextMenuExt_CreateInstance
//
// Context menu extension instance generator.  Creates a context menu extension
// object and returns a pointer to the requested interface.
//
// Arguments:
//
//    punkOuter
//       Not used for objects that don't support aggregation.  We don't support
//       aggregation so we don't use it.
//
//    riid
//       Reference to the requested interface ID.
//
//    ppvOut
//       Address of the destination for the interface pointer.
//
// RETURNS:
//
//    NOERROR                = Success.
//    E_OUTOFMEMORY          = Can't allocate extension object.
//    E_NOINTERFACE          = Interface not supported.
//    CLASS_E_NOAGGREGATION  = Aggregation not supported.
//
///////////////////////////////////////////////////////////////////////////////
static HRESULT CContextMenuExt_CreateInstance(IUnknown *punkOuter,
                                              REFIID riid, void **ppvOut)
{
   CContextMenuExt *pcmx = NULL;
   HRESULT hResult       = NOERROR;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CContextMenuExt::CreateInstance"));
#endif

   ASSERT(NULL != ppvOut);

   *ppvOut = NULL;

   if (NULL == punkOuter)
   {
      pcmx = (CContextMenuExt *)LocalAlloc(LPTR, sizeof(CContextMenuExt));
      if (NULL != pcmx)
      {
         pcmx->ctm.lpVtbl = &c_vtblContextMenuExt;  // Context menu ext vtable ptr.
         pcmx->sei.lpVtbl = &c_vtblShellExtInit;    // Shell extention int vtable ptr.
         pcmx->cRef = 0;                            // Initialize reference counter.
         pcmx->medium.tymed = TYMED_NULL;           // Not yet initialized by shell.
         pcmx->medium.hGlobal = (HGLOBAL)NULL;      // Not yet initialized by shell.
         pcmx->medium.pUnkForRelease = NULL;        // Not yet initialized by shell.
         pcmx->cSelectedFiles = 0;                  // Not yet initialized by shell.
         pcmx->bDriveSelected = FALSE;              // No drives selected yet.
         pcmx->pDataObj = NULL;
         hResult = c_vtblContextMenuExt.QueryInterface(&pcmx->ctm, riid, ppvOut);
         ChgDllRefCnt(+1);
      }

      else
         hResult = E_OUTOFMEMORY;
   }
   else
      hResult = CLASS_E_NOAGGREGATION;  // Extension doesn't support aggregation.

   return hResult;
}

///////////////////////////////////////////////////////////////////////////////
//
// CreateClassObject
//
// Creates a class factory object returning a pointer to its IUnknown interface.
//
// Arguments:
//
//    riid
//       Reference to interface on class factory object.
//
//    pfcnCI
//       Pointer to instance creation function.
//       In this application, this is CContextMenuExt_CreateInstance.
//
//    ppvOut
//       Destination for pointer to class factory interface (Vtable).
//
// RETURNS:
//
//    NOERROR        = Success.
//    E_NOINTERFACE  = Interface not supported.
//    E_OUTOFMEMORY  = Can't create class factory object.
//
///////////////////////////////////////////////////////////////////////////////
STDAPI CreateClassObject(REFIID riid, LPFNCREATEINSTANCE pfnCI, LPVOID *ppvOut)
{
   HRESULT hResult = NOERROR;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: CreateClassObject"));
#endif

   ASSERT(NULL != ppvOut);
   ASSERT(NULL != pfnCI);

   *ppvOut = NULL;                 // Initialize pointer transfer buffer.

   if (IsEqualIID(riid, &IID_IClassFactory))
   {
      //
      // Allocate the class factory structure.
      //
      CClassFactory *pcf = (CClassFactory *)LocalAlloc(LPTR, sizeof(CClassFactory));
      if (NULL != pcf)
      {
         pcf->cf.lpVtbl = &c_vtblCClassFactory;  // Assign ptr to vtbl.
         pcf->cRef++;                            // Increment interface ref count.
         pcf->pfnCI = pfnCI;                     // Assign ptr instance creation proc.
         (IClassFactory *)*ppvOut = &pcf->cf;    // Return ptr to vtbl (interface).
         ChgDllRefCnt(+1);
      }
      else
         hResult = E_OUTOFMEMORY;    // Cannot get or use shell memory allocator interface.
   }
   else
      hResult = E_NOINTERFACE;      // Cannot produce requested interface.

return hResult;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DllGetClassObject
//
// DESCRIPTION:
//
//    Called by NT Shell to retrieve the interface to the Class factory.
//
// ARGUMENTS:
//
//    rclsid
//       Reference to class ID that identifies the type of object that the
//       class factory will be asked to create.
//
//    riid
//       Reference to interface ID on the class factory object.
//
//    ppvOut
//       Destination location for class factory object pointer after instantiation.
//
// RETURNS:
//
//    NOERROR                   = Success.
//    E_OUTOFMEMORY             = Can't create class factory object.
//    E_NOINTERFACE             = Interface not supported.
//    CLASS_E_CLASSNOTAVAILABLE = Context menu extension not available.
//
///////////////////////////////////////////////////////////////////////////////
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
{
   HRESULT hResult = CLASS_E_CLASSNOTAVAILABLE;

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: DllGetClassObject"));
#endif

   ASSERT(NULL != ppvOut);

   *ppvOut = NULL;

   //
   // Call the extension-provided creation function corresponding
   // to the class ID of the requested extension.
   //
   if (IsEqualIID(rclsid, &CLSID_CompressMenuExt ))
   {
      hResult = CreateClassObject(riid,
                (LPFNCREATEINSTANCE)CContextMenuExt_CreateInstance, ppvOut);
   }

#ifdef DBG
   if (hResult != NOERROR)
      DbgOut(TEXT("SHCOMPUI: Context menu extension [CLSID_CompressMenuExt] creation failed."));
#endif

   return hResult;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DllCanUnloadNow
//
// DESCRIPTION:
//
//    Called by NT to determine if DLL can be unloaded.
//
// ARGUMENTS:
//
//    None.
//
// RETURNS:
//
//    S_FALSE   = Can't unload.
//    S_OK      = OK to unload.
//
///////////////////////////////////////////////////////////////////////////////
STDAPI DllCanUnloadNow(void)
{

#ifdef TRACE_SHEXT
   DbgOut(TEXT("SHCOMPUI: DllCanUnloadNow.  Dll reference count = %d"), g_cRefThisDll);
#endif

   ASSERT(g_cRefThisDll >= 0);

   //
   // I test for <= 0 so that the DLL can be unloaded even if the ref
   // count drops below 0 (reference count error).  The preceding
   // ASSERT( ) catches this during development.  This means that
   // the ref counter has to be signed.
   //
   return ResultFromScode((g_cRefThisDll <= 0) ? S_OK : S_FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DllMain
//
// DESCRIPTION:
//
//    Dll entry point.
//
///////////////////////////////////////////////////////////////////////////////
int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   TCHAR szLocaleInfo[20];

   switch(dwReason)
   {
      case DLL_PROCESS_ATTACH:
#ifdef TRACE_DLL
         DbgOut(TEXT("SHCOMPUI: DLL_PROCESS_ATTACH"));
#endif
         g_hmodThisDll  = hInstance;
         g_hProcessHeap = GetProcessHeap();

         DisableThreadLibraryCalls(hInstance);

         //
         // Create/Open semaphore to prevent re-entrancy.
         //
         g_hSemaphore = CreateSemaphore(NULL, 1, 1, SZ_SEMAPHORE_NAME);
         if (GetLastError() == ERROR_ALREADY_EXISTS)
            g_hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SZ_SEMAPHORE_NAME);

         if (NULL == g_hSemaphore)
            return FALSE;       // Can't create/open semaphore object.

         //
         // Prepare number format info for current locale.
         // Used in progress dialog display of 64-bit integers.
         //
         g_NumberFormat.NumDigits    = 0;  // This is locale-insensitive.
         g_NumberFormat.LeadingZero	 = 0;  // So is this.

         GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, szLocaleInfo, ARRAYSIZE(szLocaleInfo));
         g_NumberFormat.Grouping       = StrToLong(szLocaleInfo);

         GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, g_szDecimalSep, ARRAYSIZE(g_szDecimalSep));
         g_NumberFormat.lpDecimalSep   = g_szDecimalSep;

         GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, g_szThousandSep, ARRAYSIZE(g_szThousandSep));
         g_NumberFormat.lpThousandSep  = g_szThousandSep;

         GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, szLocaleInfo, ARRAYSIZE(szLocaleInfo));
         g_NumberFormat.NegativeOrder  = StrToLong(szLocaleInfo);

         break;

      case DLL_PROCESS_DETACH:
#ifdef TRACE_DLL
         DbgOut(TEXT("SHCOMPUI: DLL_PROCESS_DETACH"));
#endif
         CloseHandle(g_hSemaphore);
         break;

      case DLL_THREAD_ATTACH:
#ifdef TRACE_DLL
         DbgOut(TEXT("SHCOMPUI: DLL_THREAD_ATTACH"));
#endif
         break;

      case DLL_THREAD_DETACH:
#ifdef TRACE_DLL
         DbgOut(TEXT("SHCOMPUI: DLL_THREAD_DETACH"));
#endif
         break;

      default:
         break;
   }
   return TRUE;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: FormatStringWithArgs
//
// DESCRIPTION:
//
//    Formats a text string with variable arguments.
//
// ARGUMENTS:
//
//    pszFormat
//       Address of format text string using %1,%2 etc. format specifiers.
//
//    pszBuffer
//       Address of destination buffer.
//
//    nSize
//       Number of characters in destination buffer.
//
//    ...
//       Variable length list of replacement parameters.
//
// RETURNS:
//
//    Returns number of characters copied to buffer.
//    0 = Error.  GetLastError() if you're interested in why.
//
///////////////////////////////////////////////////////////////////////////////
static DWORD FormatStringWithArgs(LPCTSTR pszFormat, LPTSTR pszBuffer,
                                                       DWORD nSize, ...)
{
   DWORD dwCharCount   = 0;
   va_list args;

   ASSERT(NULL != pszBuffer);
   ASSERT(NULL != pszFormat);

   //
   // Format the resource string by replacing parameters if present.
   //
   va_start(args, nSize);
   dwCharCount = FormatMessage(FORMAT_MESSAGE_FROM_STRING,
                          pszFormat,
                          0,
                          0,
                          pszBuffer,
                          nSize,
                          &args);
   va_end(args);

   return dwCharCount;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: LoadStringWithArgs
//
// DESCRIPTION:
//
//    Formats a resource string with variable arguments.
//
// ARGUMENTS:
//
//    hInstance
//       Instance handle for module containing resource string.
//
//    uId
//       Resource string ID.  String may contain embedded formatting characters
//       for replaceable parameters (i.e. "Delete file %1 ?")
//
//    szBuffer
//       Destination buffer.
//
//    nSize
//       Number of characters in destination buffer.
//
//    ...
//       Variable length list of replacement parameters.
//
// RETURNS:
//
//    Returns number of characters copied to buffer.
//    0 = Error.  GetLastError() if you're interested in why.
//    Function does set last error to E_OUTOFMEMORY on LocalAlloc fail.
//
///////////////////////////////////////////////////////////////////////////////
static DWORD LoadStringWithArgs(HINSTANCE hInstance, UINT uId,
                                           LPTSTR pszBuffer, DWORD nSize, ...)
{
   DWORD dwCharCount = 0;
   LPTSTR pszFormat  = NULL;
   va_list args;

   ASSERT(NULL != pszBuffer);

   //
   // Allocate a buffer for the resource string.
   //
   if ((pszFormat = LocalAlloc(LMEM_FIXED, nSize * sizeof(TCHAR))) != NULL)
   {
      //
      // Load the resource string from the specified module.
      //
      if (LoadString(hInstance, uId, pszFormat, nSize) != 0)
      {
         va_start(args, nSize);
         dwCharCount = FormatMessage(FORMAT_MESSAGE_FROM_STRING,
                                pszFormat,
                                0,
                                0,
                                pszBuffer,
                                nSize,
                                &args);
         va_end(args);
      }
      LocalFree(pszFormat);
   }
   else
      SetLastError((DWORD)E_OUTOFMEMORY);

   return dwCharCount;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CenterWindowInParent
//
// DESCRIPTION:
//
//    Positions a window centered in its parent.
//    This function was taken from WinFile.
//
// ARGUMENTS:
//
//    hwnd
//       Handle of window to be centered.
//
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static VOID CenterWindowInParent(HWND hwnd)
{
    RECT    rect;
    RECT    rectParent;
    HWND    hwndParent;
    LONG    Style;

    //
    //  Get window rect.
    //
    GetWindowRect(hwnd, &rect);

    //
    //  Get parent rect.
    //
    Style = GetWindowLong(hwnd, GWL_STYLE);
    if ((Style & WS_CHILD) == 0)
    {
        hwndParent = GetDesktopWindow();
    }
    else
    {
        hwndParent = GetParent(hwnd);
        if (hwndParent == NULL)
        {
            hwndParent = GetDesktopWindow();
        }
    }
    GetWindowRect(hwndParent, &rectParent);

    //
    //  Center the child in the parent.
    //
    rect.left = rectParent.left + (((rectParent.right - rectParent.left) - (rect.right - rect.left)) >> 1);
    rect.top  = rectParent.top +  (((rectParent.bottom - rectParent.top) - (rect.bottom - rect.top)) >> 1);

    //
    //  Move the child into position.
    //
    SetWindowPos( hwnd,
                  NULL,
                  rect.left,
                  rect.top,
                  0,
                  0,
                  SWP_NOSIZE | SWP_NOZORDER );

    SetForegroundWindow(hwnd);
}



///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressSubsConfirmDlgProc
//
// DESCRIPTION:
//
//    Message proc for compression UI confirmation dialog.  The dialog is
//    displayed whenever a selected directory is about to be compressed or
//    uncompressed.  The dialog includes a message stating that all files
//    in the selected directory are about to be compressed/uncompressed.
//    Included is a checkbox that may be checked to approve compression/
//    uncompression of all sub-folders.
//
// ARGUMENTS:
//
//    wParam
//       Unused.
//
//    lParam
//       Address of a "Compression Descriptor" (type CompressionDesc).
//       The descriptor contains the name of the file to be compresssed/
//       uncompressed along with a flag value indicating which operation
//       is being performed.
//
//
// RETURNS:
//
//    0  = User pressed Cancel button.  Don't compress this folder.
//          (COMPRESS_CANCELLED)
//    1  = User pressed OK button.  Sub-folder checkbox is unchecked.
//          (COMPRESS_SUBSNO)
//    2  = User pressed OK button.  Sub-folder checkbox is checked.
//          (COMPRESS_SUBSYES)
//
///////////////////////////////////////////////////////////////////////////////
static INT_PTR CALLBACK CompressSubsConfirmDlgProc(HWND hDlg, UINT uMsg,
                                                   WPARAM wParam, LPARAM lParam)
{
   //
   // Dialog message string resource IDs.  Array indexes map directly to values
   // of lParam.
   //
   UINT uDlgTextIds[] = { IDS_UNCOMPRESS_CONFIRMATION, IDS_COMPRESS_CONFIRMATION };
   UINT uCbxTextIds[] = { IDS_UNCOMPRESS_ALSO,         IDS_COMPRESS_ALSO         };
   UINT uActionTextIds[] = { IDS_UNCOMPRESS_ACTION,    IDS_COMPRESS_ACTION       };

   TCHAR szDlgText[40 + _MAX_PATH];         // Dialog text resource string buffer.
   CompressionDesc *cd = (CompressionDesc *)lParam;
   UINT cchLoaded = 0;

   switch(uMsg)
   {
      case WM_INITDIALOG:
         ASSERT(NULL != (void *)lParam);

         //
         // Initialize the "Compress/Uncompress all files in..." message.
         //
         LoadStringWithArgs(g_hmodThisDll, uDlgTextIds[cd->bCompress], szDlgText,
                                ARRAYSIZE(szDlgText), cd->szFileName);
         SetDlgItemText(hDlg, IDC_COMPRESS_CONFIRM_TEXT, szDlgText);

         //
         // Initialize the "This action compresses..." message.
         //
         cchLoaded = LoadString(g_hmodThisDll, uActionTextIds[cd->bCompress],
                                              szDlgText, ARRAYSIZE(szDlgText));
         ASSERT(cchLoaded > 0);
         SetDlgItemText(hDlg, IDC_COMPRESS_ACTION_TEXT, szDlgText);

         //
         // Initialize the "also compress/uncompress subfolders" checkbox message.
         //
         cchLoaded = LoadString(g_hmodThisDll, uCbxTextIds[cd->bCompress], szDlgText,
                                                                 ARRAYSIZE(szDlgText));
         ASSERT(cchLoaded > 0);

         SetDlgItemText(hDlg, IDC_COMPRESS_SUBFOLDERS,  szDlgText);

         return TRUE;

      case WM_COMMAND:
         //
         // Handle user button selections.
         //
         switch(wParam)
         {
            case IDOK:
               EndDialog(hDlg, Button_GetCheck(GetDlgItem(hDlg, IDC_COMPRESS_SUBFOLDERS)) ?
                                                                COMPRESS_SUBSYES :
                                                                COMPRESS_SUBSNO);
               return TRUE;

            case IDCANCEL:
               g_pContext->uCompletionReason = SCCA_REASON_USERCANCEL;
               EndDialog(hDlg, COMPRESS_CANCELLED);

               //
               // Fall through to return TRUE.
               //

            case IDC_COMPRESS_SUBFOLDERS:
               //
               // Do nothing when the checkbox is selected.
               //
               return TRUE;
         }
      }

      return FALSE;
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressProgressYield
//
// DESCRIPTION:
//
//   Allow other messages including Dialog messages for Modeless dialog to be
//   processed while we are Compressing and Uncompressing files.  This message
//   loop is similar to "wfYield" in treectl.c except that it allows for the
//   processing of Modeless dialog messages also (specifically for the Progress
//   Dialogs).
//
//   Since the file/directory Compression/Uncompression is done on a single
//   thread (in order to keep it synchronous with the existing Set Attributes
//   processing) we need to provide a mechanism that will allow a user to
//   Cancel out of the operation and also allow window messages, like WM_PAINT,
//   to be processed by other Window Procedures.
//
//   Taken from WinFile.  Removed MDI-related processing.
//
// ARGUMENTS:
//
//    None.
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static VOID CompressProgressYield(void)
{
    MSG msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (!g_hdlgProgress || !IsDialogMessage(g_hdlgProgress, &msg))
        {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DisplayUncompressProgress
//
// DESCRIPTION:
//
//  Update the progress of uncompressing files.
//
//   This routine uses the global variables to update the Dialog box items
//   which display the progress through the uncompression process.  The global
//   variables are updated by individual routines.  An ordinal value is sent
//   to this routine which determines which dialog box item to update.
//   Taken from WinFile.
//
//
// ARGUMENTS:
//
//    iType
//       Control value to determine what is to be updated.
//       Value names are self-descriptive.
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static VOID DisplayUncompressProgress(int iType)
{
    TCHAR szNum[30];

    if (!g_bShowProgress)
    {
        return;
    }

    switch (iType)
    {
        case ( PROGRESS_UPD_FILEANDDIR ) :
        case ( PROGRESS_UPD_FILENAME ) :
        {
            SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_FILE, g_szFile);
            if (iType != PROGRESS_UPD_FILEANDDIR)
            {
                break;
            }

            // else...fall thru
        }
        case ( PROGRESS_UPD_DIRECTORY ) :
        {
            RECT rect;
            //
            //  Preprocess the directory name to shorten it to fit
            //  into the alloted space.
            //
            GetWindowRect(GetDlgItem(g_hdlgProgress, IDC_UNCOMPRESS_DIR), &rect);
            DrawTextEx(g_hdcDirectoryTextCtrl, g_szDirectory, lstrlen(g_szDirectory), &rect, DT_MODIFYSTRING | DT_PATH_ELLIPSIS, NULL);
            SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_DIR, g_szDirectory);

            break;
        }
        case ( PROGRESS_UPD_DIRCNT ) :
        {
            AddCommas((DWORD)g_cTotalDirectories, szNum);
            SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_DIRCNT, szNum);

            break;
        }
        case ( PROGRESS_UPD_FILENUMBERS ) :
        case ( PROGRESS_UPD_FILECNT ) :
        {
            AddCommas((DWORD)g_cTotalFiles, szNum);
            SetDlgItemText(g_hdlgProgress, IDC_UNCOMPRESS_FILECNT, szNum);

            break;
        }
    }

    CompressProgressYield();
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: UncompressProgDlg
//
// DESCRIPTION:
//
//   Display progress information.
//   Taken from WinFile.
//
//   NOTE:  This is a modeless dialog and must be terminated with DestroyWindow
//          and NOT EndDialog
//
// ARGUMENTS:
//
//    Standard dialog proc args.
//
// RETURNS:
//
//    TRUE  = Message handled.
//    FALSE = Message not handled.
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR APIENTRY UncompressProgDlg(
    HWND hDlg,
    UINT nMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    TCHAR szTemp[120];
    RECT  rect;

    switch (nMsg)
    {
        case ( WM_INITDIALOG ) :
        {
            CenterWindowInParent(hDlg);

            g_hdlgProgress = hDlg;

            //
            //  Clear Dialog items.
            //
            szTemp[0] = TEXT('\0');

            SetDlgItemText(hDlg, IDC_UNCOMPRESS_FILE,    szTemp);
            SetDlgItemText(hDlg, IDC_UNCOMPRESS_DIR,     szTemp);
            SetDlgItemText(hDlg, IDC_UNCOMPRESS_DIRCNT,  szTemp);
            SetDlgItemText(hDlg, IDC_UNCOMPRESS_FILECNT, szTemp);

            g_hdcDirectoryTextCtrl = GetDC(GetDlgItem(hDlg, IDC_UNCOMPRESS_DIR));
            GetClientRect(GetDlgItem(hDlg, IDC_UNCOMPRESS_DIR), &rect);
            g_cDirectoryTextCtrlWd = rect.right;

            EnableWindow(hDlg, TRUE);
            break;
        }
        case ( WM_COMMAND ) :
        {
            switch (LOWORD(wParam))
            {
                case ( IDOK ) :
                case ( IDCANCEL ) :
                {
                    if (LOWORD(wParam) == IDCANCEL)
                       g_pContext->uCompletionReason = SCCA_REASON_USERCANCEL;

                    if (g_hdcDirectoryTextCtrl)
                    {
                        ReleaseDC(GetDlgItem(hDlg, IDC_UNCOMPRESS_DIR), g_hdcDirectoryTextCtrl);
                        g_hdcDirectoryTextCtrl = NULL;
                    }
                    DestroyWindow(hDlg);
                    g_hdlgProgress = NULL;
                    break;
                }
                default :
                {
                    return (FALSE);
                }
            }
            break;
        }
        default :
        {
            return (FALSE);
        }
    }

    return (TRUE);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DisplayCompressProgress
//
// DESCRIPTION:
//
//  Update the progress of compressing files.
//
//   This routine uses the global variables to update the Dialog box items
//   which display the progress through the compression process.  The global
//   variables are updated by individual routines.  An ordinal value is sent
//   to this routine which determines which dialog box item to update.
//   Taken from WinFile.
//
//
// ARGUMENTS:
//
//    iType
//       Control value to determine what is to be updated.
//       Value names are self-descriptive.
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
void DisplayCompressProgress(int iType)
{
    TCHAR szTemp[120];
    TCHAR szNum[30];
    unsigned _int64 Percentage;

    if (!g_bShowProgress)
    {
        return;
    }

    switch (iType)
    {
        case ( PROGRESS_UPD_FILEANDDIR ) :
        case ( PROGRESS_UPD_FILENAME ) :
        {
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_FILE, g_szFile);
            if (iType != PROGRESS_UPD_FILEANDDIR)
            {
                break;
            }

            // else...fall thru
        }
        case ( PROGRESS_UPD_DIRECTORY ) :
        {
            RECT rect;
            //
            //  Preprocess the directory name to shorten it to fit
            //  into the alloted space.
            //
            GetWindowRect(GetDlgItem(g_hdlgProgress, IDC_COMPRESS_DIR), &rect);
            DrawTextEx(g_hdcDirectoryTextCtrl, g_szDirectory, lstrlen(g_szDirectory), &rect, DT_MODIFYSTRING | DT_PATH_ELLIPSIS, NULL);
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_DIR, g_szDirectory);

            break;
        }
        case ( PROGRESS_UPD_DIRCNT ) :
        {
            AddCommas((DWORD)g_cTotalDirectories, szNum);
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_DIRCNT, szNum);

            break;
        }
        case ( PROGRESS_UPD_FILENUMBERS ) :
        case ( PROGRESS_UPD_FILECNT ) :
        {
            AddCommas((DWORD)g_cTotalFiles, szNum);
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_FILECNT, szNum);
            if (iType != PROGRESS_UPD_FILENUMBERS)
            {
                break;
            }

            // else...fall thru
        }
        case ( PROGRESS_UPD_COMPRESSEDSIZE ) :
        {
            Int64ToString(g_iTotalCompressedSize, szTemp, ARRAYSIZE(szTemp), TRUE, &g_NumberFormat, NUMFMT_ALL);
            FormatStringWithArgs(g_szByteCntFmt, szNum, ARRAYSIZE(szNum), szTemp);
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_CSIZE, szNum);
            if (iType != PROGRESS_UPD_FILENUMBERS)
            {
                break;
            }

            // else...fall thru
        }
        case ( PROGRESS_UPD_FILESIZE ) :
        {
            Int64ToString(g_iTotalFileSize, szTemp, ARRAYSIZE(szTemp), TRUE, &g_NumberFormat, NUMFMT_ALL);
            FormatStringWithArgs(g_szByteCntFmt, szNum, ARRAYSIZE(szNum), szTemp);
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_USIZE, szNum);
            if (iType != PROGRESS_UPD_FILENUMBERS)
            {
                break;
            }

            // else...fall thru
        }

        case ( PROGRESS_UPD_PERCENTAGE ) :
        {
            if (g_iTotalFileSize != 0)
            {
                //
                //  Percentage = 100 - ((CompressSize * 100) / FileSize)
                //
                Percentage = (g_iTotalCompressedSize * 100) / g_iTotalFileSize;

                if (Percentage > 100)
                {
                    Percentage = 100;
                }
                else
                    Percentage = 100 - Percentage;
            }
            else
            {
                Percentage = 0;
            }
            //
            // Note that percentage string is not formatted.
            // i.e. no commas or decimal places.
            //
            Int64ToString(Percentage, szTemp, ARRAYSIZE(szTemp), FALSE, NULL, 0);
            wsprintf(szNum, TEXT("%s%%"), szTemp);
            SetDlgItemText(g_hdlgProgress, IDC_COMPRESS_RATIO, szNum);

            break;
        }
    }

    CompressProgressYield();
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressProgDlg
//
// DESCRIPTION:
//
//   Display progress information.
//   Taken from WinFile.
//
//   NOTE:  This is a modeless dialog and must be terminated with DestroyWindow
//          and NOT EndDialog
//
// ARGUMENTS:
//
//    Standard dialog proc args.
//
// RETURNS:
//
//    TRUE  = Message handled.
//    FALSE = Message not handled.
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR APIENTRY CompressProgDlg(
    HWND hDlg,
    UINT nMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    TCHAR szTemp[120];
    RECT  rect;

    switch (nMsg)
    {
        case ( WM_INITDIALOG ) :
        {
            CenterWindowInParent(hDlg);

            g_hdlgProgress = hDlg;

            //
            //  Clear Dialog items.
            //
            szTemp[0] = TEXT('\0');

            SetDlgItemText(hDlg, IDC_COMPRESS_FILE, szTemp);
            SetDlgItemText(hDlg, IDC_COMPRESS_DIR,  szTemp);
            SetDlgItemText(hDlg, IDC_COMPRESS_DIRCNT, szTemp);
            SetDlgItemText(hDlg, IDC_COMPRESS_FILECNT, szTemp);
            SetDlgItemText(hDlg, IDC_COMPRESS_CSIZE, szTemp);
            SetDlgItemText(hDlg, IDC_COMPRESS_USIZE, szTemp);
            SetDlgItemText(hDlg, IDC_COMPRESS_RATIO, szTemp);

            g_hdcDirectoryTextCtrl = GetDC(GetDlgItem(hDlg, IDC_COMPRESS_DIR));
            GetClientRect(GetDlgItem(hDlg, IDC_COMPRESS_DIR), &rect);
            g_cDirectoryTextCtrlWd = rect.right;

            //
            // Set Dialog message text.
            //
            LoadString(g_hmodThisDll, IDS_COMPRESS_DIR, szTemp, ARRAYSIZE(szTemp));
            EnableWindow(hDlg, TRUE);

            break;
        }
        case ( WM_COMMAND ) :
        {
            switch (LOWORD(wParam))
            {
                case ( IDOK ) :
                case ( IDCANCEL ) :
                {
                    if (LOWORD(wParam) == IDCANCEL)
                       g_pContext->uCompletionReason = SCCA_REASON_USERCANCEL;

                    if (g_hdcDirectoryTextCtrl)
                    {
                        ReleaseDC(GetDlgItem(hDlg, IDC_COMPRESS_DIR), g_hdcDirectoryTextCtrl);
                        g_hdcDirectoryTextCtrl = NULL;
                    }
                    DestroyWindow(hDlg);
                    g_hdlgProgress = NULL;

                    break;
                }
                default :
                {
                    return (FALSE);
                }
            }
            break;
        }
        default :
        {
            return (FALSE);
        }
    }

    return (TRUE);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: NotifyShellOfAttribChange
//
// DESCRIPTION:
//
//   If the item is visible or is a directory, tell the shell that it's
//   attributes have changed.  This will cause the shell to update any
//   compression-related display characteristics.
//
// ARGUMENTS:
//
//   pszPath
//      Fully-qualified path to file/directory that changed.
//
//   bIsDirectory
//      If TRUE, shell is notified.
//      If FALSE, shell is notified only if global recursion level counter
//          is 1.  This ensures that we don't send unnecessary notifications to
//          the shell for items that definitely are not visible in the shell view.
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
void NotifyShellOfAttribChange(LPTSTR pszPath, BOOL bIsDirectory)
{
    //
    // Only notify shell if item is visible.
    // All items handled at recursion level 1 are visible by default.
    // Subdirectories must always be updated because they may be visible
    // the Explorer tree view.
    //
    if (1 == g_iRecursionLevel || bIsDirectory)
    {
        if (PathIsRoot(pszPath))
        {
            //
            // Invalidate the drive type flags cache for this drive.
            // Cache will be updated with new information on next call to
            // RealDriveTypeFlags( ).
            //
            InvalidateDriveType(PathGetDriveNumber(pszPath));
        }
        else
        {
            PathRemoveTheBackslash(pszPath);
        }
        SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, pszPath, NULL);

#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: Shell notified. %s"), pszPath);
#endif

    }
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: ShellChangeCompressionAttribute
//
// DESCRIPTION:
//
//   Main entry point for file compression/uncompression.
//
// ARGUMENTS:
//
//    hwndParent
//       Handle to window for parenting dialogs.
//
//    szFileSpec
//       Fully-qualified path of file to be compressed/uncompressed.
//
//    pContext
//       Address of a context structure as defined in shcompui.h
//       This structure is created by the caller so that we can maintain
//       information about the compression process across a series of
//       calls to this function.  In particular, the user can press the
//       "Ignore All Errors" button and we must remember this during
//       subsequent calls.  The structure also maintains error count
//       information and completion status.  This may be used to supplement
//       the TRUE/FALSE return mechanism to further discriminate a FALSE
//       return value.
//
//    bCompressing
//       Control variable.  TRUE = compress file.  FALSE = uncompress file.
//
//    bShowUI
//       Control of message box and dialog displays.
//       TRUE  = Show all dialogs and messages.
//       FALSE = Hide all dialogs and messages.  Used when compression is desired
//               without any user interactiion.
//
//          ***********************************************************
//                                    NOTE
//
//            The bShowUI argument has been introduced to support
//            programmatic invocation of shell compression in cases
//            where a UI display is not wanted.  The functionality
//            of preventing UI display is not complete at this time.
//            To meet the SUR beta deadline, this parameter is ignored.
//
//          ***********************************************************
//
// RETURNS:
//
//    TRUE  = Operation successful.  Continue if iterating
//            through set of files/directories.
//    FALSE = User aborted compression/uncompression or error occurred.
//            Stop if iterating through set of files/directories.
//            Query the context structure for additional completion information.
//
///////////////////////////////////////////////////////////////////////////////
BOOL ShellChangeCompressionAttribute(
    HWND hwndParent,
    LPTSTR szNameSpec,
    LPSCCA_CONTEXT pContext,
    BOOL bCompressing,
    BOOL bShowUI)
{
    TCHAR   szTitle[MAX_DLGTITLE_LEN+1];
    TCHAR   szTemp[MAX_MESSAGE_LEN+1];
    TCHAR   szFilespec[_MAX_PATH+1];
    BOOL    bCompressionAttrChange;
    BOOL    bIsDir       = FALSE;
    BOOL    bRet         = TRUE;
    HCURSOR hCursor      = NULL;
    DWORD   dwAttribs    = 0;
    DWORD   dwNewAttribs = 0;
    DWORD   dwFlags      = 0;

    ASSERT(hwndParent != NULL);
    ASSERT(szNameSpec != NULL);

    //
    //  Make sure we're not in the middle of another compression operation.
    //  If so, put up an error box warning the user that they need to wait
    //  to do another compression operation.
    //
    if (WaitForSingleObject(g_hSemaphore, 0L) == WAIT_TIMEOUT)
    {
        //
        // REARCHITECT: Shouldn't assume the UI is being displayed on behalf of
        //         Explorer.  The code should be modified so that the app
        //         name is passed in through ShellChangeCompressionAtttribute.
        //
        LoadString(g_hmodThisDll, IDS_APP_NAME, szTitle, ARRAYSIZE(szTitle));
        LoadString(g_hmodThisDll, IDS_MULTI_COMPRESS_ERR, szMessage, ARRAYSIZE(szMessage));
        MessageBox(hwndParent, szMessage, szTitle, MB_OK | MB_ICONEXCLAMATION);

#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: Re-entrancy attempted and denied"));
#endif

        return (TRUE);
    }

    //
    // Give the context structure "file" scope.
    //
    g_pContext = pContext;

    //
    // Reset recursion level counter.
    //
    g_iRecursionLevel = 0;

    //
    //  Make sure the volume supports File Compression.
    //
    lstrcpy(szTemp, szNameSpec);
    PathStripToRoot(szTemp);
    // GetVolumeInformation requires a trailing backslash.  Append
    // one if this is a UNC path.
    if (PathIsUNC(szTemp))
    {
        lstrcat(szTemp, c_szBACKSLASH);
    }
    if (!GetVolumeInformation (szTemp, NULL, 0L, NULL, NULL, &dwFlags, NULL, 0L)
       || !(dwFlags & FS_FILE_COMPRESSION))
    {
        //
        //  The volume does not support file compression, so just
        //  quit out.  Do not return FALSE, since that will not
        //  allow any other attributes to be changed.
        //

#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: Volume %s doesn't support compression."), szTemp);
#endif
        ReleaseSemaphore(g_hSemaphore, 1, NULL);
        return (TRUE);
    }

    //
    //  Show the hour glass cursor.
    //
    if (hCursor = LoadCursor(NULL, IDC_WAIT))
    {
        hCursor = SetCursor(hCursor);
    }
    ShowCursor(TRUE);

    //
    //  Get the file attributes (current and new).
    //  On error, don't change the attribute.
    //
    if ((dwAttribs = GetFileAttributes(szNameSpec)) != (DWORD)-1)
    {
       if (bCompressing)
          dwNewAttribs = dwAttribs | FILE_ATTRIBUTE_COMPRESSED;
       else
          dwNewAttribs = dwAttribs & ~FILE_ATTRIBUTE_COMPRESSED;
   }

    //
    //  Determine if ATTR_COMPRESSED is changing state.
    //
    bCompressionAttrChange = ( (dwAttribs & FILE_ATTRIBUTE_COMPRESSED) !=
                               (dwNewAttribs & FILE_ATTRIBUTE_COMPRESSED) );

#ifdef TRACE_COMPRESSION
    {
       TCHAR szAttribStr[40];
       TCHAR szNewAttribStr[40];
       DbgOut(TEXT("SHCOMPUI: File = \"%-30s\"  Attr = [%s]  New = [%s]"), szNameSpec,
                               FileAttribString(dwAttribs, szAttribStr),
                               FileAttribString(dwNewAttribs, szNewAttribStr));
    }
#endif

    g_bShowProgress     = FALSE;
    g_bIgnoreAllErrors  = g_pContext->bIgnoreAllErrors;
    g_bDiskFull         = FALSE;
    g_pContext->cErrors = 0;          // Clear "current" error counter.


    //
    //  If the Compression attribute changed or if we're dealing with
    //  a directory, perform action.
    //
    bIsDir = PathIsDirectory(szNameSpec);
    if (bCompressionAttrChange || bIsDir)
    {
        INT cchLoaded = 0;

        //
        //  Reset globals before progress display.
        //
        g_cTotalDirectories       = 0;
        g_cTotalFiles             = 0;
        g_iTotalFileSize          = 0;
        g_iTotalCompressedSize    = 0;
        g_szFile[0]               = CH_NULL;
        g_szDirectory[0]          = CH_NULL;

        cchLoaded = LoadString(g_hmodThisDll, IDS_BYTECNT_FMT, g_szByteCntFmt, ARRAYSIZE(g_szByteCntFmt));
        ASSERT(cchLoaded > 0);

        if (bIsDir)
        {
            BOOL bIgnoreAll = FALSE;
            UINT_PTR uDlgResult = 0;
            CompressionDesc compdesc = { bCompressing, TEXT("") };

            lstrcpy(compdesc.szFileName, szNameSpec);
            uDlgResult = DialogBoxParam(g_hmodThisDll, MAKEINTRESOURCE(DLG_COMPRESS_CONFIRMATION),
                         hwndParent, CompressSubsConfirmDlgProc, (LPARAM)&compdesc);

            lstrcpy(szFilespec, c_szSTAR);
            g_bShowProgress = TRUE;
            switch(uDlgResult)
            {
               case COMPRESS_SUBSYES:
                  g_bDoSubdirectories = TRUE;
                  break;

               case COMPRESS_SUBSNO:
                  g_bDoSubdirectories = FALSE;
                  break;

               case COMPRESS_CANCELLED:
                  bRet = FALSE;
                  goto CancelCompress;
                  break;

               default:
                  ASSERT(0);
                  break;
            }

            if (g_bShowProgress)
            {
                g_hdlgProgress = CreateDialog(
                                   g_hmodThisDll,
                                   MAKEINTRESOURCE(bCompressing ? DLG_COMPRESS_PROGRESS : DLG_UNCOMPRESS_PROGRESS),
                                   hwndParent,
                                   (bCompressing ? CompressProgDlg :
                                                   UncompressProgDlg));


                ShowWindow(g_hdlgProgress, SW_SHOW);
            }
            PathAddBackslash(szNameSpec);
            lstrcpy(szTemp, szNameSpec);

            bRet = bCompressing ? DoCompress(hwndParent, szNameSpec, szFilespec) :
                                  DoUncompress(hwndParent, szNameSpec, szFilespec);

            //
            //  Set attribute on Directory if last call was successful.
            //
            if (bRet)
            {
                szFilespec[0] = TEXT('\0');
                g_bDoSubdirectories = FALSE;
                lstrcpy(szNameSpec, szTemp);
                bRet = bCompressing ? DoCompress(hwndParent, szNameSpec, szFilespec) :
                                      DoUncompress(hwndParent, szNameSpec, szFilespec);
            }

            //
            // If the progress dialog was being displayed, destroy it.
            //
            if (g_hdlgProgress)
            {
                if (g_hdcDirectoryTextCtrl)
                {
                    ReleaseDC( GetDlgItem(g_hdlgProgress,
                                          (bCompressing ? IDC_COMPRESS_DIR : IDC_UNCOMPRESS_DIR)),
                                          g_hdcDirectoryTextCtrl);
                    g_hdcDirectoryTextCtrl = NULL;
                }
                DestroyWindow(g_hdlgProgress);
                g_hdlgProgress = NULL;
            }
        }
        else
        {
            //
            //  Compress single file.
            //
            g_bDoSubdirectories = FALSE;
            lstrcpy(szFilespec, szNameSpec);

            PathStripPath(szFilespec);
            PathRemoveFileSpec(szNameSpec);

            PathAddBackslash(szNameSpec);

#ifdef TRACE_COMPRESSION
            DbgOut(TEXT("Compress/Uncompress single file %s%s"),szNameSpec,szFilespec);
#endif

            bRet = bCompressing ? DoCompress(hwndParent, szNameSpec, szFilespec) :
                                  DoUncompress(hwndParent, szNameSpec, szFilespec);
        }
    }

CancelCompress:

    //
    //  Reset the cursor.
    //
    if (hCursor)
    {
        SetCursor(hCursor);
    }
    ShowCursor(FALSE);

    //
    // Tell the shell that the free space on this volume has changed.
    //
    lstrcpy(szTemp, szNameSpec);
    PathStripToRoot(szTemp);
    if (PathIsUNC(szTemp))
    {
        lstrcat(szTemp, c_szBACKSLASH);
    }
    SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, szTemp, NULL);

    //
    //  Return the appropriate value.
    //
    g_pContext->bIgnoreAllErrors = g_bIgnoreAllErrors;
    ReleaseSemaphore(g_hSemaphore, 1, NULL);
    return (bRet);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressFile
//
// DESCRIPTION:
//
//   Compress a single file.
//   Originally take from WinFile.
//
// ARGUMENTS:
//
//    Handle
//       Handle to file to be compressed.
//
//    FileSpec
//       Full file specification.
//       Required to obtain compressed file size.
//       FindData->cFileName[] doesn't include the path.
//
//    FindData
//       Pointer to file search context data.  Contains file name.
//
// RETURNS:
//
//    TRUE  = Success.
//    FALSE = Device IO error during compression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL CompressFile(
    HANDLE Handle,
    LPTSTR FileSpec,
    PWIN32_FIND_DATA FindData)
{
    USHORT State;
    ULONG Length;
    LARGE_INTEGER TempLarge;


    //
    //  Print out the file name and then do the Ioctl to compress the
    //  file.  When we are done we'll print the okay message.
    //
    lstrcpy(g_szFile, FindData->cFileName);
    DisplayCompressProgress(PROGRESS_UPD_FILENAME);

    State = 1;

    if (!DeviceIoControl( Handle,
                          FSCTL_SET_COMPRESSION,
                          &State,
                          sizeof(USHORT),
                          NULL,
                          0,
                          &Length,
                          FALSE ))
    {
        g_pContext->cErrors++;
        g_pContext->cCummErrors++;

#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: CompressFile, DeviceIoControl failed with error 0x%08X"),
                       GetLastError());
#endif
        return (FALSE);
    }

    //
    //  Gather statistics (File size, compressed size and count).
    //
    TempLarge.LowPart  = FindData->nFileSizeLow;
    TempLarge.HighPart = FindData->nFileSizeHigh;
    g_iTotalFileSize += TempLarge.QuadPart;

    TempLarge.LowPart = GetCompressedFileSize(FileSpec, &(TempLarge.HighPart));
    g_iTotalCompressedSize += TempLarge.QuadPart;

    g_cTotalFiles++;

    DisplayCompressProgress(PROGRESS_UPD_FILENUMBERS);

    return (TRUE);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DoCompress
//
// DESCRIPTION:
//
//   Compress a directory and its subdirectories if necessary.
//   Originally take from WinFile.
//
// ARGUMENTS:
//
//   hwndParent
//      Window handle for parenting dialogs and message boxes.
//
//   DirectorySpec
//      Fully-qualified directory specification with backslash appended.
//
//   FileSpec
//      File name with directory path removed.
//
// RETURNS:
//
//    TRUE  = Success.
//    FALSE = Device IO error or user aborted compression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DoCompress(
    HWND hwndParent,
    LPTSTR DirectorySpec,
    LPTSTR FileSpec)
{
    LPTSTR DirectorySpecEnd;
    HANDLE FileHandle = INVALID_HANDLE_VALUE;
    USHORT State;
    ULONG  Length;
    HANDLE FindHandle;
    WIN32_FIND_DATA FindData;
    int MBRet;
    TCHAR szTitle[128];

    g_iRecursionLevel++;

#ifdef TRACE_COMPRESSION
    DbgOut(TEXT("SHCOMPUI: DoCompress with %s%s"), DirectorySpec, FileSpec);
    DbgOut(TEXT("          Recursion Level: %d -> %d"), g_iRecursionLevel-1, g_iRecursionLevel);
#endif

    //
    //  If the file spec is null, then set the compression bit for
    //  the directory spec and get out.
    //
    lstrcpy(g_szDirectory, DirectorySpec);
    g_szFile[0] = CH_NULL;
    DisplayCompressProgress(PROGRESS_UPD_FILEANDDIR);

    if (lstrlen(FileSpec) == 0)
    {
DoCompressRetryCreate:

        if (!OpenFileForCompress(&FileHandle, DirectorySpec))
        {
#ifdef TRACE_COMPRESSION
           DbgOut(TEXT("SHCOMPUI: DoCompress, OpenFileForCompress failed."));
#endif
            goto DoCompressError;
        }

DoCompressRetryDevIo:

        State = 1;
        if (!DeviceIoControl( FileHandle,
                              FSCTL_SET_COMPRESSION,
                              &State,
                              sizeof(USHORT),
                              NULL,
                              0,
                              &Length,
                              FALSE ))
        {
            g_pContext->cErrors++;
            g_pContext->cCummErrors++;

DoCompressError:

#ifdef TRACE_COMPRESSION
            DbgOut(TEXT("SHCOMPUI: DoCompress, DeviceIoControl failed with error 0x%08X"),
                       GetLastError());
#endif

            if (!g_bIgnoreAllErrors)
            {
                MBRet = CompressErrMessageBox( hwndParent,
                                               DirectorySpec,
                                               &FileHandle );
                if (MBRet == RETRY_CREATE)
                {
                    goto DoCompressRetryCreate;
                }
                else if (MBRet == RETRY_DEVIO)
                {
                    goto DoCompressRetryDevIo;
                }
                else if (MBRet == IDABORT)
                {
                    //
                    //  Return error.
                    //  File handle was closed by CompressErrMessageBox( ).
                    //
                    g_pContext->uCompletionReason = SCCA_REASON_USERABORT;
                    g_iRecursionLevel--;
                    return (FALSE);
                }
                //
                //  Else (MBRet == IDIGNORE)
                //  Continue on as if the error did not occur.
                //
            }
        }
        if (INVALID_HANDLE_VALUE != FileHandle)
        {
            CloseHandle(FileHandle);
            FileHandle = INVALID_HANDLE_VALUE;
        }

        g_cTotalDirectories++;
        g_cTotalFiles++;

        DisplayCompressProgress(PROGRESS_UPD_DIRCNT);
        DisplayCompressProgress(PROGRESS_UPD_FILECNT);

        NotifyShellOfAttribChange(DirectorySpec, TRUE);

        g_iRecursionLevel--;
        return (TRUE);
    }

    //
    //  Get a pointer to the end of the directory spec, so that we can
    //  keep appending names to the end of it.
    //
    DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);

    //
    //  List the directory that is being compressed and display
    //  its current compress attribute.
    //
    g_cTotalDirectories++;
    DisplayCompressProgress(PROGRESS_UPD_DIRCNT);

    //
    //  For every file in the directory that matches the file spec,
    //  open the file and compress it.
    //
    //  Setup the template for findfirst/findnext.
    //
    lstrcpy(DirectorySpecEnd, FileSpec);

    if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE)
    {
        do
        {
            DWORD dwAttrib = FindData.dwFileAttributes;

            //
            //  Make sure the user hasn't hit cancel.
            //
            if (g_bShowProgress && !g_hdlgProgress)
            {
               g_iRecursionLevel--;
               FindClose(FindHandle);
               return FALSE;
            }

            //
            //  Skip over the . and .. entries.
            //
            if ( !lstrcmp(FindData.cFileName, c_szDOT) ||
                 !lstrcmp(FindData.cFileName, c_szDOTDOT) )
            {
                continue;
            }
            else if ((DirectorySpecEnd == (DirectorySpec + 3)) &&
                     !lstrcmpi(FindData.cFileName, c_szNTLDR))
            {
               //
               //  Do not allow \NTLDR to be compressed.
               //  Put up OK message box and then continue.
               //
               lstrcpy(DirectorySpecEnd, FindData.cFileName);
               LoadString(g_hmodThisDll, IDS_NTLDR_COMPRESS_ERR, szTitle, ARRAYSIZE(szTitle));
               wsprintf(szMessage, szTitle, DirectorySpec);
               LoadString(g_hmodThisDll, IDS_APP_NAME, szTitle, ARRAYSIZE(szTitle));
               MessageBox(g_hdlgProgress ? g_hdlgProgress : hwndParent, szMessage,
                                          szTitle, MB_OK | MB_ICONEXCLAMATION);

               continue;
            }
            else
            {
                //
                //  Append the found file to the directory spec and
                //  open the file.
                //
                lstrcpy(DirectorySpecEnd, FindData.cFileName);

                if ((dwAttrib & FILE_ATTRIBUTE_COMPRESSED) ||
                    (!g_bDoSubdirectories && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)))
                {
                    //
                    //  File is a directory or is already compressed.
                    //  So just skip it.
                    //
                    continue;
                }

CompressFileRetryCreate:

                if (!OpenFileForCompress(&FileHandle, DirectorySpec))
                {
#ifdef TRACE_COMPRESSION
                   DbgOut(TEXT("SHCOMPUI: DoCompress, OpenFileForCompress failed."),
                       GetLastError());
#endif
                    goto CompressFileError;
                }

CompressFileRetryDevIo:

                //
                //  Compress the file.
                //
                if (!CompressFile(FileHandle, DirectorySpec, &FindData))
                {
CompressFileError:

                    if (!g_bIgnoreAllErrors)
                    {
                        MBRet = CompressErrMessageBox( hwndParent,
                                                       DirectorySpec,
                                                       &FileHandle );
                        if (MBRet == RETRY_CREATE)
                        {
                            goto CompressFileRetryCreate;
                        }
                        else if (MBRet == RETRY_DEVIO)
                        {
                            goto CompressFileRetryDevIo;
                        }
                        else if (MBRet == IDABORT)
                        {
                            //
                            //  Return error.
                            //  File handle was closed by CompressErrMessageBox( ).
                            //
                            g_pContext->uCompletionReason = SCCA_REASON_USERABORT;
                            g_iRecursionLevel--;
                            FindClose(FindHandle);
                            return (FALSE);
                        }
                        //
                        //  Else (MBRet == IDIGNORE)
                        //  Continue on as if the error did not occur.
                        //
                    }
                }
                if (INVALID_HANDLE_VALUE != FileHandle)
                {
                    CloseHandle(FileHandle);
                    FileHandle = INVALID_HANDLE_VALUE;
                }
            }

            NotifyShellOfAttribChange(DirectorySpec,
                                     (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);

            CompressProgressYield();

        } while (FindNextFile(FindHandle, &FindData));

        FindClose(FindHandle);
    }

    //
    //  If we are to do subdirectores, then look for every subdirectory
    //  and recursively call ourselves to list the subdirectory.
    //
    if (g_bDoSubdirectories)
    {
        //
        //  Setup findfirst/findnext to search the entire directory.
        //
        lstrcpy(DirectorySpecEnd, c_szSTAR);

        if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE)
        {
            do
            {
                //
                //  Skip over the . and .. entries, otherwise recurse.
                //
                if ( !lstrcmp(FindData.cFileName, c_szDOT) ||
                     !lstrcmp(FindData.cFileName, c_szDOTDOT) )
                {
                    continue;
                }
                else
                {
                    //
                    //  If the entry is for a directory, then tack
                    //  on the subdirectory name to the directory spec
                    //  and recurse.
                    //
                    if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    {
                        lstrcpy(DirectorySpecEnd, FindData.cFileName);
                        lstrcat(DirectorySpecEnd, c_szBACKSLASH);

                        if (!DoCompress(hwndParent, DirectorySpec, FileSpec))
                        {
                            g_iRecursionLevel--;
                            FindClose(FindHandle);
                            return (FALSE || g_bIgnoreAllErrors);
                        }
                    }
                }

            } while (FindNextFile(FindHandle, &FindData));

            FindClose(FindHandle);
        }
    }

    g_iRecursionLevel--;
    return (TRUE);
}



///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: UncompressFile
//
// DESCRIPTION:
//
//   Uncompress a single file.
//   Originally take from WinFile.
//
// ARGUMENTS:
//
//    hwndParent
//       Handle to window for parenting any dialogs.
//
//    hFile
//       Handle to file to be compressed.
//
//    FindData
//       Pointer to file search context data.  Contains file name.
//
// RETURNS:
//
//    TRUE  = Success.
//    FALSE = Device IO error during compression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL UncompressFile(HWND hwndParent, HANDLE hFile, PWIN32_FIND_DATA FindData)
{
    USHORT State;
    ULONG Length;

    //
    //  Print out the file name and then do the Ioctl to uncompress the
    //  file.  When we are done we'll print the okay message.
    //
    lstrcpy(g_szFile, FindData->cFileName);
    DisplayUncompressProgress(PROGRESS_UPD_FILENAME);

    State = 0;

    if (!DeviceIoControl( hFile,
                          FSCTL_SET_COMPRESSION,
                          &State,
                          sizeof(USHORT),
                          NULL,
                          0,
                          &Length,
                          FALSE )
#ifdef SIM_DISK_FULL
                          || TRUE
#endif
                          )
    {
        g_pContext->cErrors++;
        g_pContext->cCummErrors++;

#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: UncompressFile, DeviceIoControl failed with error 0x%08X"),
                       GetLastError());
#endif

#ifdef SIM_DISK_FULL
        SetLastError((DWORD)STATUS_DISK_FULL);
#endif

        if (GetLastError() == STATUS_DISK_FULL)
        {
           UncompressDiskFullError(hwndParent, hFile);
           g_bDiskFull = TRUE;
        }

        return (FALSE);
    }

    //
    //  Increment the running total.
    //
    g_cTotalFiles++;
    DisplayUncompressProgress(PROGRESS_UPD_FILENUMBERS);

    return (TRUE);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: DoUncompress
//
// DESCRIPTION:
//
//   Uncompress a directory and its subdirectories if necessary.
//   Originally take from WinFile.
//
// ARGUMENTS:
//
//   hwndParent
//      Window handle for parenting dialogs and message boxes.
//
//   DirectorySpec
//      Fully-qualified directory specification with backslash appended.
//
//   FileSpec
//      File name with directory path removed.
//
// RETURNS:
//
//    TRUE  = Success.
//    FALSE = Device IO error or user aborted uncompression.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DoUncompress(
    HWND hwndParent,
    LPTSTR DirectorySpec,
    LPTSTR FileSpec)
{
    LPTSTR DirectorySpecEnd;
    HANDLE FileHandle = INVALID_HANDLE_VALUE;
    USHORT State;
    ULONG  Length;
    HANDLE FindHandle;
    WIN32_FIND_DATA FindData;
    int MBRet;

    g_iRecursionLevel++;

#ifdef TRACE_COMPRESSION
    DbgOut(TEXT("SHCOMPUI: DoUncompress with %s%s"), DirectorySpec, FileSpec);
    DbgOut(TEXT("          Recursion Level: %d -> %d"), g_iRecursionLevel-1, g_iRecursionLevel);
#endif

    //
    //  If the file spec is null, then clear the compression bit for
    //  the directory spec and get out.
    //
    lstrcpy(g_szDirectory, DirectorySpec);
    g_szFile[0] = CH_NULL;
    DisplayUncompressProgress(PROGRESS_UPD_FILEANDDIR);

    if (lstrlen(FileSpec) == 0)
    {
DoUncompressRetryCreate:

        if (!OpenFileForCompress(&FileHandle, DirectorySpec))
        {
#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: UncompressDirecory, OpenFileForCompress failed."));
#endif
            goto DoUncompressError;
        }

DoUncompressRetryDevIo:

        State = 0;
        if (!DeviceIoControl( FileHandle,
                              FSCTL_SET_COMPRESSION,
                              &State,
                              sizeof(USHORT),
                              NULL,
                              0,
                              &Length,
                              FALSE )
#ifdef SIM_DISK_FULL
                              || TRUE
#endif
                              )
        {
            g_pContext->cErrors++;
            g_pContext->cCummErrors++;

DoUncompressError:

            //
            // Handle disk-full error.
            //
#ifdef TRACE_COMPRESSION
        DbgOut(TEXT("SHCOMPUI: DoUncompress, DeviceIoControl failed with error 0x%08X"),
                       GetLastError());
#endif
#ifdef SIM_DISK_FULL
            SetLastError((DWORD)STATUS_DISK_FULL);
#endif
            if (GetLastError() == STATUS_DISK_FULL)
            {
               UncompressDiskFullError(hwndParent, FileHandle);
               g_bDiskFull = TRUE;
               CloseHandle(FileHandle);
               g_iRecursionLevel--;
               return FALSE;
            }

            if (!g_bIgnoreAllErrors)
            {
                MBRet = CompressErrMessageBox( hwndParent,
                                               DirectorySpec,
                                               &FileHandle );
                if (MBRet == RETRY_CREATE)
                {
                    goto DoUncompressRetryCreate;
                }
                else if (MBRet == RETRY_DEVIO)
                {
                    goto DoUncompressRetryDevIo;
                }
                else if (MBRet == IDABORT)
                {
                    //
                    //  Return error.
                    //  File handle was closed by CompressErrMessageBox.
                    //
                    g_pContext->uCompletionReason = SCCA_REASON_USERABORT;
                    g_iRecursionLevel--;
                    return (FALSE);
                }
                //
                //  Else (MBRet == IDIGNORE)
                //  Continue on as if the error did not occur.
                //
            }
        }
        if (INVALID_HANDLE_VALUE != FileHandle)
        {
            CloseHandle(FileHandle);
            FileHandle = INVALID_HANDLE_VALUE;
        }

        g_cTotalDirectories++;
        g_cTotalFiles++;

        DisplayUncompressProgress(PROGRESS_UPD_DIRCNT);
        DisplayUncompressProgress(PROGRESS_UPD_FILECNT);

        NotifyShellOfAttribChange(DirectorySpec, TRUE);

        g_iRecursionLevel--;
        return (TRUE);
    }

    //
    //  Get a pointer to the end of the directory spec, so that we can
    //  keep appending names to the end of it.
    //
    DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);

    g_cTotalDirectories++;
    DisplayUncompressProgress(PROGRESS_UPD_DIRCNT);

    //
    //  For every file in the directory that matches the file spec,
    //  open the file and uncompress it.
    //
    //  Setup the template for findfirst/findnext.
    //
    lstrcpy(DirectorySpecEnd, FileSpec);

    if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE)
    {
        do
        {
            DWORD dwAttrib = FindData.dwFileAttributes;

            //
            //  Make sure the user hasn't hit cancel.
            //
            if (g_bShowProgress && !g_hdlgProgress)
            {
               g_iRecursionLevel--;
               FindClose(FindHandle);
               return FALSE;
            }

            //
            //  Skip over the . and .. entries.
            //
            if ( !lstrcmp(FindData.cFileName, c_szDOT) ||
                 !lstrcmp(FindData.cFileName, c_szDOTDOT) )
            {
                continue;
            }
            else
            {
                //
                //  Append the found file to the directory spec and
                //  open the file.
                //
                lstrcpy(DirectorySpecEnd, FindData.cFileName);

                if (!(dwAttrib & FILE_ATTRIBUTE_COMPRESSED) ||
                    (!g_bDoSubdirectories && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)))
                {
                    //
                    //  File is a directory or already uncompressed.
                    //  So skip it.
                    //
                    continue;
                }

UncompressFileRetryCreate:

                if (!OpenFileForCompress(&FileHandle, DirectorySpec))
                {
#ifdef TRACE_COMPRESSION
                    DbgOut(TEXT("SHCOMPUI: OpenFileForCompress failed."));
#endif
                    goto UncompressFileError;
                }

UncompressFileRetryDevIo:

                //
                //  Uncompress the file.
                //
                if (!UncompressFile(hwndParent, FileHandle, &FindData))
                {
UncompressFileError:

                    //
                    // If disk is full, UncompressFile( ) already handled the error.
                    // Don't handle it again.  Just return.
                    //
                    if (g_bDiskFull)
                    {
                       g_pContext->uCompletionReason = SCCA_REASON_DISKFULL;
                       CloseHandle(FileHandle);
                       FindClose(FindHandle);
                       g_iRecursionLevel--;
                       return FALSE;
                    }

                    if (!g_bIgnoreAllErrors)
                    {
                        MBRet = CompressErrMessageBox( hwndParent,
                                                       DirectorySpec,
                                                       &FileHandle );
                        if (MBRet == RETRY_CREATE)
                        {
                            goto UncompressFileRetryCreate;
                        }
                        else if (MBRet == RETRY_DEVIO)
                        {
                            goto UncompressFileRetryDevIo;
                        }
                        else if (MBRet == IDABORT)
                        {
                            //
                            //  Return error.
                            //  File handle was closed by CompressErrMessageBox.
                            //
                            g_pContext->uCompletionReason = SCCA_REASON_USERABORT;
                            g_iRecursionLevel--;
                            FindClose(FindHandle);
                            return (FALSE);
                        }
                        //
                        //  Else (MBRet == IDIGNORE)
                        //  Continue on as if the error did not occur.
                        //
                    }
                }
                if (INVALID_HANDLE_VALUE != FileHandle)
                {
                    CloseHandle(FileHandle);
                    FileHandle = INVALID_HANDLE_VALUE;
                }
            }

            NotifyShellOfAttribChange(DirectorySpec,
                                      (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);

            CompressProgressYield();

        } while (FindNextFile(FindHandle, &FindData));

        FindClose(FindHandle);
    }

    //
    //  If we are to do subdirectores, then look for every subdirectory
    //  and recursively call ourselves to list the subdirectory.
    //
    if (g_bDoSubdirectories)
    {
        //
        //  Setup findfirst/findnext to search the entire directory.
        //
        lstrcpy(DirectorySpecEnd, c_szSTAR);

        if ((FindHandle = FindFirstFile(DirectorySpec, &FindData)) != INVALID_HANDLE_VALUE)
        {
            do
            {
                //
                //  Skip over the . and .. entries, otherwise recurse.
                //
                if ( !lstrcmp(FindData.cFileName, c_szDOT) ||
                     !lstrcmp(FindData.cFileName, c_szDOTDOT) )
                {
                    continue;
                }
                else
                {
                    //
                    //  If the entry is for a directory, then tack
                    //  on the subdirectory name to the directory spec
                    //  and recurse.
                    //
                    if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    {
                        lstrcpy(DirectorySpecEnd, FindData.cFileName);
                        lstrcat(DirectorySpecEnd, c_szBACKSLASH);

                        if (!DoUncompress(hwndParent, DirectorySpec, FileSpec))
                        {
                            g_iRecursionLevel--;
                            FindClose(FindHandle);
                            return (FALSE || g_bIgnoreAllErrors);
                        }
                    }
                }

            } while (FindNextFile(FindHandle, &FindData));

            FindClose(FindHandle);
        }
    }

    g_iRecursionLevel--;
    return (TRUE);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressErrMessageBox
//
// DESCRIPTION:
//
//   Puts up the error message box when a file cannot be compressed or
//   uncompressed.  It also returns the user preference.
//
//   NOTE: The file handle is closed if the abort or ignore option is
//         chosen by the user.
//
// ARGUMENTS:
//
//
// RETURNS:
//
//    RETRY_CREATE            = User selected "Retry"
//    RETRY_DEVIO             = User selected "Retry"
//    IDABORT
//    IDIGNORE
//    IDC_COMPRESS_IGNOREALL
//
///////////////////////////////////////////////////////////////////////////////
int CompressErrMessageBox(
    HWND hwndActive,
    LPTSTR szFile,
    PHANDLE phFile)
{
    int rc;

    //
    //  Put up the error message box - ABORT, RETRY, IGNORE, IGNORE ALL.
    //
    rc = (int)DialogBoxParam( g_hmodThisDll,
                              (LPTSTR) MAKEINTRESOURCE(DLG_COMPRESS_ERROR),
                              g_hdlgProgress ? g_hdlgProgress : hwndActive,
                              CompressErrDialogProc,
                              (LPARAM)szFile );

    //
    //  Return the user preference.
    //
    if (rc == IDRETRY)
    {
        if (*phFile == INVALID_HANDLE_VALUE)
        {
            return (RETRY_CREATE);
        }
        else
        {
            return (RETRY_DEVIO);
        }
    }
    else
    {
        //
        //  IDABORT or IDIGNORE or IDC_COMPRESS_IGNOREALL
        //
        //  Close the file handle and return the message box result.
        //
        if (*phFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(*phFile);
            *phFile = INVALID_HANDLE_VALUE;
        }

        return (rc);
    }
}



///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: CompressErrDialogProc
//
// DESCRIPTION:
//
//   Puts up a dialog to allow the user to Abort, Retry, Ignore, or
//   Ignore All when an error occurs during compression.
//
//
//   Taken from WinFile source wffile.c.  Modified control resource IDs to
//   be consistent with other Explorer ID naming conventions.
//
// ARGUMENTS:
//
//   Standard Dialog Proc args.
//
// RETURNS:
//
//   Standard Dialog Proc return values..
//
//   Returns through EndDialog( ):
//
//      IDABORT
//      IDRETRY
//      IDIGNORE
//      IDC_COMPRESS_IGNOREALL
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR CALLBACK CompressErrDialogProc(
    HWND hDlg,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    WORD IdControl = TRUE;
    TCHAR szTitle[MAX_DLGTITLE_LEN + 1];

    switch (uMsg)
    {
        case ( WM_INITDIALOG ) :
        {
            //
            // Modify very long path names so that they fit into the message box.
            // They are formatted as "c:\dir1\dir2\dir3\...\dir8\filename.ext"
            // DrawTextEx isn't drawing on anything.  Only it's formatting capabilities are
            // being used. The DT_CALCRECT flag prevents drawing.
            //
            HDC  hDC        = GetDC(hDlg);
            LONG iBaseUnits = GetDialogBaseUnits();
            RECT rc;
            TCHAR szFilePath[MAX_PATH];         // Local copy of path name string.
            const int MAX_PATH_DISPLAY_WD = 50; // Max characters to display in path name.
            const int MAX_PATH_DISPLAY_HT =  1; // Path name is 1 character high.

            rc.left   = 0;
            rc.top    = 0;
            rc.right  = MAX_PATH_DISPLAY_WD * LOWORD(iBaseUnits);
            rc.bottom = MAX_PATH_DISPLAY_HT * HIWORD(iBaseUnits);

            lstrcpyn(szFilePath, (LPCTSTR)lParam, ARRAYSIZE(szFilePath));
            DrawTextEx(hDC, szFilePath, ARRAYSIZE(szFilePath), &rc,
                                        DT_CALCRECT | DT_PATH_ELLIPSIS | DT_MODIFYSTRING, NULL);
            ReleaseDC(hDlg, hDC);

            //
            //  Set the dialog message text.
            //
            LoadString( g_hmodThisDll,
                        IDS_COMPRESS_ATTRIB_ERR,
                        szTitle,
                        ARRAYSIZE(szTitle) );

            wsprintf(szMessage, szTitle, szFilePath);
            SetDlgItemText(hDlg, IDC_COMPRESS_ERRTEXT, szMessage);
            EnableWindow (hDlg, TRUE);

            break;
        }
        case ( WM_COMMAND ) :
        {
            IdControl = GET_WM_COMMAND_ID(wParam, lParam);
            switch (IdControl)
            {
                case ( IDC_COMPRESS_IGNOREALL ) :
                {
                    g_bIgnoreAllErrors = TRUE;

                    //  fall thru...
                }
                case ( IDABORT ) :
                case ( IDRETRY ) :
                case ( IDIGNORE ) :
                {
                    EndDialog(hDlg, IdControl);
                    break;
                }
                default :
                {
                    return (FALSE);
                }
            }
            break;
        }
        default :
        {
            return (FALSE);
        }
    }
    return (IdControl);
}

///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: OpenFileForCompress
//
// DESCRIPTION:
//
//   Opens the file for compression.  It handles the case where a READONLY
//   file is trying to be compressed or uncompressed.  Since read only files
//   cannot be opened for WRITE_DATA, it temporarily resets the file to NOT
//   be READONLY in order to open the file, and then sets it back once the
//   file has been compressed.
//
//   Taken from WinFile module wffile.c without change.  Originally from
//   G. Kimura's compact.c.
//
// ARGUMENTS:
//
//   phFile
//      Address of file handle variable for handle of open file if
//      successful.
//
//   szFile
//      Name string of file to be opened.
//
// RETURNS:
//
//    TRUE  = File successfully opened.  Handle in *phFile.
//    FALSE = File couldn't be opened. *phFile == INVALID_HANDLE_VALUE
//
///////////////////////////////////////////////////////////////////////////////
BOOL OpenFileForCompress(
    PHANDLE phFile,
    LPTSTR szFile)
{
    HANDLE hAttr;
    BY_HANDLE_FILE_INFORMATION fi;

    //
    //  Try to open the file - READ_DATA | WRITE_DATA.
    //
    if ((*phFile = CreateFile( szFile,
                               FILE_READ_DATA | FILE_WRITE_DATA,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL,
                               OPEN_EXISTING,
                               FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
                               NULL )) != INVALID_HANDLE_VALUE)
    {
        //
        //  Successfully opened the file.
        //
        return (TRUE);
    }

    if (GetLastError() != ERROR_ACCESS_DENIED)
    {
        return (FALSE);
    }

    //
    //  Try to open the file - READ_ATTRIBUTES | WRITE_ATTRIBUTES.
    //
    if ((hAttr = CreateFile( szFile,
                             FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             NULL,
                             OPEN_EXISTING,
                             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
                             NULL )) == INVALID_HANDLE_VALUE)
    {
        return (FALSE);
    }

    //
    //  See if the READONLY attribute is set.
    //
    if ( (!GetFileInformationByHandle(hAttr, &fi)) ||
         (!(fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) )
    {
        //
        //  If the file could not be open for some reason other than that
        //  the readonly attribute was set, then fail.
        //
        CloseHandle(hAttr);
        return (FALSE);
    }

    //
    //  Turn OFF the READONLY attribute.
    //
    fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
    if (!SetFileAttributes(szFile, fi.dwFileAttributes))
    {
        CloseHandle(hAttr);
        return (FALSE);
    }

    //
    //  Try again to open the file - READ_DATA | WRITE_DATA.
    //
    *phFile = CreateFile( szFile,
                          FILE_READ_DATA | FILE_WRITE_DATA,
                          FILE_SHARE_READ | FILE_SHARE_WRITE,
                          NULL,
                          OPEN_EXISTING,
                          FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
                          NULL );

    //
    //  Close the file handle opened for READ_ATTRIBUTE | WRITE_ATTRIBUTE.
    //
    CloseHandle(hAttr);

    //
    //  Make sure the open succeeded.  If it still couldn't be opened with
    //  the readonly attribute turned off, then fail.
    //
    if (*phFile == INVALID_HANDLE_VALUE)
    {
        return (FALSE);
    }

    //
    //  Turn the READONLY attribute back ON.
    //
    fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
    if (!SetFileAttributes(szFile, fi.dwFileAttributes))
    {
        CloseHandle(*phFile);
        *phFile = INVALID_HANDLE_VALUE;
        return (FALSE);
    }

    //
    //  Return success.  A valid file handle is in *phFile.
    //
    return (TRUE);
}


///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: UncompressDiskFullError
//
// DESCRIPTION:
//
//    To be called when disk space is exhausted during uncompression.
//    The function displays a message box with an OK button.
//    NTFS leaves a file partially compressed when DeviceIoControl( )
//    returns STATUS_DISK_FULL in GetLastError( ).  It also leaves the
//    compressed attribute as "compressed".
//    Once the user acknowledges the message, we attempt to re-compress the
//    file so that the entire file is compressed and matches its
//    attribute setting.
//
// ARGUMENTS:
//
//    hFile
//       Handle to file being uncompressed at the time of the error.
//
// RETURNS:
//
//    Nothing.
//
///////////////////////////////////////////////////////////////////////////////
static void UncompressDiskFullError(HWND hwndParent, HANDLE hFile)
{
   TCHAR szTitle[MAX_DLGTITLE_LEN + 1];
   USHORT State = 1; // 1 = Compress.
   ULONG Length = 0;

   LoadString(g_hmodThisDll, IDS_APP_NAME, szTitle, ARRAYSIZE(szTitle));
   LoadString(g_hmodThisDll, IDS_UNCOMPRESS_DISKFULL, szMessage, ARRAYSIZE(szMessage));

   MessageBox(hwndParent, szMessage, szTitle, MB_OK | MB_ICONSTOP);

   //
   // Try to compress the file.
   // Don't worry about errors on the compression attempt.
   // At worst case, the file is left partially compressed with the attribute
   // set as "compressed".  According to the NTFS people, this is not
   // harmful and the file is still usable.  Besides, there isn't much else
   // we could do at this point.
   //
   DeviceIoControl( hFile,
                    FSCTL_SET_COMPRESSION,
                    &State,
                    sizeof(USHORT),
                    NULL,
                    0,
                    &Length,
                    FALSE );

}



#endif        // ifdef WINNT
