//////////////////////////////////////////////////////////////////////
//
// CShellFileOp.cpp: implementation of the CShellFileOp class.
//
//////////////////////////////////////////////////////////////////////
//
// This utility written and copyright by Michael Dunn (mdunn at inreach
// dot com). You may freely use and redistribute this source code and
// binary as long as this notice is retained.
//
// Contact me if you have any questions, comments, or bug reports. Get
// the latest updates at http://home.inreach.com/mdunn/code/
//
//////////////////////////////////////////////////////////////////////
//
// Revision history:
// Feb 28, 2000: Version 1.0: First release.
//
// June 5, 2000: Version 1.1: Fixed (un)registration so the DLL works
// on NT/2000.
//
// Oct 28, 2001: Version 1.1.1: Added 4 default wildcards, *.ncb, *.aps,
// *.bsc, *.sbr.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "shlobj.h"
#include "objbase.h"
#include "CShellFileOp.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CShellFileOp::CShellFileOp()
{
ResetInternalData();
}
CShellFileOp::~CShellFileOp()
{
}
//////////////////////////////////////////////////////////////////////
// Public operations
//////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::AddSourceFile
//
// Description:
// Adds a file/dir to the list of source files for the next file operation.
//
// Input:
// szPath: [in] The path to the file/dir to be added.
//
// Returns:
// TRUE if the path was added successfully. A CMemoryException will be
// thrown in an out-of-memory condition.
//
/////////////////////////////////////////////////////////////////////////////
BOOL CShellFileOp::AddSourceFile ( LPCTSTR szPath )
{
ASSERT ( AfxIsValidString ( szPath ) );
try
{
m_lcstrSourceFiles.AddTail ( szPath );
}
catch ( CMemoryException )
{
TRACE0("Memory exception in CShellFileOp::AddSourceFile()!\n");
throw;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::AddDestFile
//
// Description:
// Adds a file/dir to the list of destination files for the next file
// operation.
//
// Input:
// szPath: [in] The path to the file/dir to be added.
//
// Returns:
// TRUE if the path was added successfully. A CMemoryException will be
// thrown in an out-of-memory condition.
//
/////////////////////////////////////////////////////////////////////////////
BOOL CShellFileOp::AddDestFile ( LPCTSTR szPath )
{
ASSERT ( AfxIsValidString ( szPath ) );
try
{
m_lcstrDestFiles.AddTail ( szPath );
}
catch ( CMemoryException )
{
TRACE0("Memory exception in CShellFileOp::AddDestFile()!\n");
throw;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::GetSourceFileList
//
// Description:
// Returns a reference to the internal list of source files for the next
// file operation.
//
// Input:
// Nothing.
//
// Returns:
// A CStringList reference. Mess with this at your own risk. :)
//
/////////////////////////////////////////////////////////////////////////////
const CStringList& CShellFileOp::GetSourceFileList()
{
return m_lcstrSourceFiles;
}
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::GetDestFileList
//
// Description:
// Returns a reference to the internal list of destination files for the next
// file operation.
//
// Input:
// Nothing.
//
// Returns:
// A CStringList reference. Mess with this at your own risk. :)
//
/////////////////////////////////////////////////////////////////////////////
const CStringList& CShellFileOp::GetDestFileList()
{
return m_lcstrDestFiles;
}
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::SetOperationFlags
//
// Description:
// Sets various parameters for the next file operation.
//
// Input:
// uOpType: [in] FO_COPY, FO_DELETE, FO_MOVE, or FO_RENAME.
// pWnd: [in] Pointer to the CWnd which will the parent window for any UI that
// the shell displays during the operation.
// Along with a bunch of other flags whose names I hope are self-explanatory.
// They're explained in full in the documentation.
//
// Returns:
// Nothing.
//
/////////////////////////////////////////////////////////////////////////////
void CShellFileOp::SetOperationFlags ( UINT uOpType,
CWnd* pWnd,
BOOL bSilent,
BOOL bAllowUndo,
BOOL bWildcardFilesOnly,
BOOL bNoConfirmation,
BOOL bNoConfirmationToMakeDir,
BOOL bRenameOnCollision,
BOOL bSimpleProgressDlg )
{
FILEOP_FLAGS fFlags = 0;
// Validate the operation type. If this assert fires, you sent in
// an invalid op type.
ASSERT ( uOpType == FO_COPY || uOpType == FO_DELETE ||
uOpType == FO_MOVE || uOpType == FO_RENAME );
ASSERT_VALID ( pWnd );
// store the op type
m_rFOS.wFunc = uOpType;
// store the parent window
m_rFOS.hwnd = pWnd->GetSafeHwnd();
// set the various flags...
if ( bSilent ) fFlags |= FOF_SILENT;
if ( bAllowUndo ) fFlags |= FOF_ALLOWUNDO;
if ( bWildcardFilesOnly ) fFlags |= FOF_FILESONLY;
if ( bNoConfirmation ) fFlags |= FOF_NOCONFIRMATION;
if ( bNoConfirmationToMakeDir ) fFlags |= FOF_NOCONFIRMMKDIR;
if ( bRenameOnCollision ) fFlags |= FOF_RENAMEONCOLLISION;
if ( bSimpleProgressDlg ) fFlags |= FOF_SIMPLEPROGRESS;
m_rFOS.fFlags = fFlags;
m_bFlagsSet = TRUE;
}
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::SetOperationFlags
//
// Description:
// Sets various parameters for the next file operation.
//
// Input:
// uOpType: [in] FO_COPY, FO_DELETE, FO_MOVE, or FO_RENAME.
// pWnd: [in] Pointer to the CWnd which will the parent window for any UI that
// the shell displays during the operation.
// fFlags: [in] Any legal combination of the FOF_* flags. See the docs for
// SHFileOperation() for info on the flags.
//
// Returns:
// Nothing.
//
/////////////////////////////////////////////////////////////////////////////
void CShellFileOp::SetOperationFlags ( UINT uOpType, CWnd* pWnd,
FILEOP_FLAGS fFlags )
{
// Validate the op type. If this assert fires, check the operation
// type param that you're passing in.
ASSERT ( uOpType == FO_COPY || uOpType == FO_DELETE ||
uOpType == FO_MOVE || uOpType == FO_RENAME );
ASSERT_VALID ( pWnd );
// store the op type
m_rFOS.wFunc = uOpType;
// store the parent window
m_rFOS.hwnd = pWnd->GetSafeHwnd();
// store the op flags
m_rFOS.fFlags = fFlags;
m_bFlagsSet = TRUE;
}
/////////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::SetProgressDlgTitle
//
// Description:
// Sets the string to be used if the simple progress dialog is used for
// the next file operation.
//
// Input:
// szTitle: [in] The string to use.
//
// Returns:
// Nothing.
//
// Note:
// The object maintains its own copy of the string in a CString, so the
// caller can destroy or reuse its string once this function returns.
//
/////////////////////////////////////////////////////////////////////////////
void CShellFileOp::SetProgressDlgTitle ( LPCTSTR szTitle )
{
ASSERT ( AfxIsValidString ( szTitle ) );
try
{
m_cstrProgressDlgTitle = szTitle;
}
catch ( CMemoryException )
{
TRACE0("Memory exception in CShellFileOp::SetProgressDlgTitle()!\n");
throw;
}
}
//////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::AnyOperationsAborted
//
// Description:
// Returns a flag indicating whether the user canceled the last file operation.
//
// Input:
// Nothing.
//
// Returns:
// TRUE if the user canceled the last file op, or FALSE if not.
//
//////////////////////////////////////////////////////////////////////////
BOOL CShellFileOp::AnyOperationsAborted()
{
// If this assert fires, it means you called this member function
// before calling Go(), or you did call Go() but Go() couldn't call
// the SHFileOperation() API due to incomplete or invalid infomation.
// You should have gotten an assert in Go() in the latter case - that's
// where you should be looking for the source of the trouble. :)
ASSERT ( m_bGoCalledAPI );
return m_rFOS.fAnyOperationsAborted;
}
//////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::Reset
//
// Description:
// Resets internal data used for file operations.
//
// Input:
// Nothing.
//
// Returns:
// Nothing.
//
// Note:
// If you are using a CShellFileOp object to do multiple operations, call
// this function after one operation to clear out all data from the
// operation, and "wipe the slate" for the next one.
//
//////////////////////////////////////////////////////////////////////////
void CShellFileOp::Reset()
{
ResetInternalData();
}
//////////////////////////////////////////////////////////////////////
// The Go() function!
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::Go
//
// Description:
// Validates data and starts a file operation.
//
// Input:
// lpbOperationStarted: [out] Pointer to a BOOL that receives TRUE if the
// SHFileOperation() API was called to start the
// operation, or FALSE if the API was not called.
// lpnAPIReturn: [out] Pointer to an int that receives the return value from
// SHFileOperation() if it was called. If the API is not called,
// the variable pointed to is not changed.
// lpbAnyOperationsAborted: [out] Pointer to a BOOL that receives TRUE if the
// user aborted the file operation, or FALSE if not.
//
// Returns:
// TRUE if and only if SHFileOperation() was called and it returned 0 (success).
//
//////////////////////////////////////////////////////////////////////////
BOOL CShellFileOp::Go ( BOOL* lpbOperationStarted,
int* lpnAPIReturn /*=NULL*/,
BOOL* lpbAnyOperationsAborted /*=NULL*/ )
{
TCHAR* szzSourceFiles = NULL;
TCHAR* szzDestFiles = NULL;
DWORD dwSourceBufferSize;
DWORD dwDestBufferSize;
int nAPIRet;
// Validate the pointers....
ASSERT ( AfxIsValidAddress ( lpbOperationStarted, sizeof(BOOL) ) );
ASSERT ( lpnAPIReturn == NULL ||
AfxIsValidAddress ( lpnAPIReturn, sizeof(int) ) );
ASSERT ( lpbAnyOperationsAborted == NULL ||
AfxIsValidAddress ( lpbAnyOperationsAborted, sizeof(BOOL) ) );
m_bGoCalledAPI = FALSE;
if ( NULL != lpbOperationStarted )
{
*lpbOperationStarted = FALSE;
}
// Do a bunch of validation before
// calling the API.
// 1. Did you call SetOperationFlags()?
if ( ! m_bFlagsSet )
{
TRACE0("Go() aborting because SetOperationFlags() was not called first.\n");
goto bailout;
}
// 2 Is the op type valid?
if ( ! ( m_rFOS.wFunc == FO_COPY || m_rFOS.wFunc == FO_DELETE ||
m_rFOS.wFunc == FO_MOVE || m_rFOS.wFunc == FO_RENAME ) )
{
TRACE0("Go() aborting because the operation type was invalid.\n");
goto bailout;
}
// 3 Is the source file list nonempty?
if ( m_lcstrSourceFiles.IsEmpty() )
{
TRACE0("Go() aborting because the source file list is empty.\n");
goto bailout;
}
// 4. Is the dest file list nonempty
// if the op needs dest files?
if ( m_rFOS.wFunc != FO_DELETE && m_lcstrDestFiles.IsEmpty() )
{
TRACE0("Go() aborting because the destination file list is empty.\n");
goto bailout;
}
// 5. Is the dest file list OK? There
// must either be one entry, or the same
// number of entries as in the source
// list.
if ( m_rFOS.wFunc != FO_DELETE )
{
if ( m_lcstrDestFiles.GetCount() != 1 &&
m_lcstrDestFiles.GetCount() != m_lcstrSourceFiles.GetCount() )
{
TRACE0("Go() aborting because the destination file list has the wrong number of strings.\n");
goto bailout;
}
}
// Everything checked out OK, so now
// build the big double-null-terminated
// buffers.
dwSourceBufferSize = GetRequiredBufferSize ( m_lcstrSourceFiles );
if ( m_rFOS.wFunc != FO_DELETE )
{
dwDestBufferSize = GetRequiredBufferSize ( m_lcstrDestFiles );
}
try
{
szzSourceFiles = (LPTSTR) new BYTE [ dwSourceBufferSize ];
if ( m_rFOS.wFunc != FO_DELETE )
{
szzDestFiles = (LPTSTR) new BYTE [ dwDestBufferSize ];
}
}
catch ( CMemoryException )
{
TRACE0("Memory exception in CShellFileOp::Go()!\n");
throw;
}
FillSzzBuffer ( szzSourceFiles, m_lcstrSourceFiles );
if ( m_rFOS.wFunc != FO_DELETE )
{
FillSzzBuffer ( szzDestFiles, m_lcstrDestFiles );
}
// and now, the moment you've all been
// waiting for
m_rFOS.pFrom = szzSourceFiles;
m_rFOS.pTo = szzDestFiles;
m_rFOS.lpszProgressTitle = (LPCTSTR) m_cstrProgressDlgTitle;
// If there are 2 or more strings in
// the destination list, set the
// MULTIDESTFILES flag.
if ( m_lcstrDestFiles.GetCount() > 1 )
{
m_rFOS.fFlags |= FOF_MULTIDESTFILES;
}
m_bGoCalledAPI = TRUE;
if ( NULL != lpbOperationStarted )
{
*lpbOperationStarted = TRUE;
}
// drum roll please....
nAPIRet = SHFileOperation ( &m_rFOS ); // tah-dah!
// Save the return value from the API.
if ( NULL != lpnAPIReturn )
{
*lpnAPIReturn = nAPIRet;
}
// Check if the user cancelled the
// operation.
if ( NULL != lpbAnyOperationsAborted )
{
*lpbAnyOperationsAborted = m_rFOS.fAnyOperationsAborted;
}
bailout:
// If we got here via one of the gotos, fire off an assert.
// If this assert fires, check the debug window for a TRACE output
// line that describes the problem.
ASSERT ( m_bGoCalledAPI );
// Free buffers.
if ( NULL != szzSourceFiles )
{
delete [] szzSourceFiles;
}
if ( NULL != szzDestFiles )
{
delete [] szzDestFiles;
}
return m_bGoCalledAPI && 0 == nAPIRet;
}
//////////////////////////////////////////////////////////////////////
// Private helper functions
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::ResetInternalData
//
// Description:
// Clears the CShellFileOp object's member variables in preparation for a
// new file operation.
//
// Input:
// Nothing.
//
// Returns:
// Nothing.
//
//////////////////////////////////////////////////////////////////////////
void CShellFileOp::ResetInternalData()
{
// Empty the string lists
m_lcstrSourceFiles.RemoveAll();
m_lcstrDestFiles.RemoveAll();
// Reset state variables
m_bFlagsSet = FALSE;
m_bGoCalledAPI = FALSE;
// And clear out other stuff...
m_cstrProgressDlgTitle.Empty();
ZeroMemory ( &m_rFOS, sizeof ( m_rFOS ) );
}
//////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::GetRequiredBufferSize
//
// Description:
// Calculates the number of bytes required to hold the passed-in string
// list in double-null-terminated character array form.
//
// Input:
// list: [in] The string list to look at.
//
// Returns:
// The number of bytes required.
//
//////////////////////////////////////////////////////////////////////////
DWORD CShellFileOp::GetRequiredBufferSize ( const CStringList& list )
{
DWORD dwRetVal = 0;
POSITION pos;
CString cstr;
// If this assert fires, the passed-in list was empty. This ought to
// never fire, actually, since Go() won't even call this function if
// either list is empty.
ASSERT ( !list.IsEmpty() );
pos = list.GetHeadPosition();
while ( NULL != pos )
{
cstr = list.GetNext ( pos );
// **NOTE** The MFC docs for CString::GetLength() say that it returns
// the number of bytes in the string, but that's wrong!! In Unicode,
// it returns the number of characters (which is half the number of
// bytes). Thus the multiplication by sizeof(TCHAR).
dwRetVal += sizeof(TCHAR) * ( cstr.GetLength() + 1 );
}
return dwRetVal + sizeof(TCHAR); // add one more for the final null
}
//////////////////////////////////////////////////////////////////////////
//
// Function: CShellFileOp::FillSzzBuffer
//
// Description:
// Copies a string list into a character array, making a double-null-terminated
// list of strings.
//
// Input:
// pBuffer: [out] The buffer that will hold the strings.
// list: [in] The string list to read.
//
// Returns:
// Nothing.
//
//////////////////////////////////////////////////////////////////////////
void CShellFileOp::FillSzzBuffer ( TCHAR* pBuffer, const CStringList& list )
{
TCHAR* pCurrPos;
POSITION pos;
CString cstr;
// If this assert fires, the passed-in list was empty. This ought to
// never fire, actually, since Go() won't even call this function if
// either list is empty when it shouldn't be.
ASSERT ( !list.IsEmpty() );
ASSERT ( pBuffer != NULL );
pCurrPos = pBuffer;
pos = list.GetHeadPosition();
while ( NULL != pos )
{
cstr = list.GetNext ( pos );
_tcscpy ( pCurrPos, (LPCTSTR) cstr );
pCurrPos = _tcsinc ( _tcschr ( pCurrPos, '\0' ) );
}
// Tack on the final null
*pCurrPos = '\0';
}