FileOp.cpp
上传用户:hy_wanghao
上传日期:2007-01-08
资源大小:279k
文件大小:16k
源码类别:

Shell编程

开发平台:

Visual C++

  1. // FileOp.cpp : Implementation of CFolder's file operation functions
  2. #include "stdafx.h"
  3. #include "Folder.h"
  4. #include "Misc.h"
  5. #include "DuplicateDlg.h"
  6. /////////////////////////////////////////////////////////////////////////////
  7. // CFolder
  8. // Macro checking if a FILEOP flags is present
  9. // Since the 'pdwFileOpFlags' argument is optional, we need to check
  10. // if the pointer is valid first.
  11. #define FILEOP_FLAG(x) ((pdwFileOpFlags!=NULL) && ((*pdwFileOpFlags & x)!=0))
  12. // Opens an Amiga disk
  13. HRESULT CFolder::_OpenAmigaDevice(LPCITEMIDLIST pidlPath, 
  14.                                   BOOL bReadOnly, 
  15.                                   LPCITEMIDLIST pidlStartFolder, 
  16.                                   CAdfDevice &dev, 
  17.                                   CAdfVolume &vol)
  18. {
  19.    ATLASSERT(pidlPath);
  20.    ATLASSERT(!dev.IsOpen());
  21.    ATLASSERT(!vol.IsOpen());
  22.    CShellPidlPath sDevPath(pidlPath);
  23.    return ::OpenAmigaDevice(sDevPath, bReadOnly, pidlStartFolder, dev, vol);
  24. }
  25. // Creates a new folder on the Amiga volume.
  26. // Note that the 'pstrDestName' argument may be modified.
  27. HRESULT CFolder::_CreateFolder(LPTSTR pstrDestName, LPDWORD pdwFileOpFlags)
  28. {
  29.    ATLASSERT(pstrDestName);
  30.    HRESULT Hr;
  31.    CAdfDevice dev;
  32.    CAdfVolume vol;
  33.    HR( _OpenAmigaDevice(m_pidl, FALSE, m_pidlPath, dev, vol) );
  34.    // Make sure it's a valid pathname
  35.    if( _ValidateFileName(pstrDestName, pdwFileOpFlags)==FALSE ) return E_FAIL;
  36.    // If we're asked to find a unique folder name, do so...
  37.    if( FILEOP_FLAG(FILEOP_UNIQUENAME) ) {
  38.       if( _FindUniqueFileName(vol, pstrDestName)==FALSE ) return E_FAIL;
  39.    }
  40.    // Check for duplicates
  41.    BOOL bIsDuplicate;
  42.    if( _CheckFileCollision(vol, pstrDestName, pdwFileOpFlags, &bIsDuplicate )!=IDOK ) return S_OK;  
  43.    if( bIsDuplicate ) return S_OK; // It's already there. Don't recreate it.
  44.    // Check that there is at least one disk block free for our new folder
  45.    // NOTE: The argument is 1 byte, but it gets translated into 512 bytes (1 block)
  46.    //       by the conversion function.
  47.    if( _CheckForDiskExchaustion(vol, 1, pdwFileOpFlags)==FALSE ) return E_UNEXPECTED;
  48.    // Go create the directory then...
  49.    if( vol.CreateDirectory(vol.GetDirectoryPtr(), pstrDestName)==FALSE ) return E_FAIL;
  50.    return S_OK;
  51. }
  52. // Deletes a list of files and folders from the current folder.
  53. HRESULT CFolder::_DeleteFiles(LPCITEMIDLIST *pidls, UINT nCount)
  54. {
  55.    ATLASSERT(pidls);
  56.    ATLASSERT(nCount>0);
  57.    if( pidls==NULL || nCount==0 ) return E_INVALIDARG;
  58.    HRESULT Hr;
  59.    CAdfDevice dev;
  60.    CAdfVolume vol;
  61.    HR( _OpenAmigaDevice(m_pidl, FALSE, m_pidlPath, dev, vol) );
  62.    // For all PIDL items, delete them...
  63.    for( UINT i=0; i<nCount; i++ ) {
  64.             
  65.       DWORD dwAttr = SFGAO_CANDELETE;
  66.       GetAttributesOf(1, &pidls[i], &dwAttr);
  67.       if( (dwAttr & SFGAO_CANDELETE)==0 ) continue;
  68.       TCHAR szFileName[MAXNAMELEN+1];
  69.       PidlGetName(szFileName, pidls[i]);
  70.       bool bIsFolder = ( PidlGetType(pidls[i])==PT_FOLDER );
  71.       bool bDeleted;
  72.       
  73.       if( bIsFolder ) {
  74.          bDeleted = ( _RemoveDirectory(vol, szFileName)==S_OK );
  75.       }
  76.       else {
  77.          bDeleted = ( vol.Delete(vol.GetDirectoryPtr(), szFileName)==TRUE );
  78.       }
  79.       if( bDeleted ) {
  80.          // Notify Shell so it can remove the folder in the Explorer tree
  81.          CPidl pidl;
  82.          pidl.Copy(m_pidl);
  83.          pidl.Concatenate(m_pidlPath);
  84.          pidl.Concatenate(pidls[i]);
  85.          ::SHChangeNotify(bIsFolder ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_IDLIST | SHCNF_FLUSH, pidl, NULL);
  86.       }
  87.    }
  88.    // Ask Shell to refresh current directory
  89.    CPidl pidl;
  90.    pidl.Copy(m_pidl);
  91.    pidl.Concatenate(m_pidlPath);
  92.    ::SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST | SHCNF_FLUSH, pidl, NULL);
  93.    return S_OK;
  94. }
  95. // Renames a file or a folder.
  96. HRESULT CFolder::_RenameFile(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidlNew)
  97. {
  98.    ATLASSERT(pidlOld);
  99.    ATLASSERT(pidlNew);
  100.    CAdfDevice dev;
  101.    CAdfVolume vol;
  102.    HRESULT Hr;
  103.    HR( _OpenAmigaDevice(m_pidl, FALSE, m_pidlPath, dev, vol) );
  104.    TCHAR szNewName[MAXNAMELEN+1];
  105.    PidlGetName(szNewName, pidlNew);
  106.    TCHAR szOldName[MAXNAMELEN+1];
  107.    PidlGetName(szOldName, pidlOld);
  108.    // Check for invalid chars
  109.    DWORD dwFlags = FILEOP_DONTTRUNCATE;
  110.    if( _ValidateFileName(szNewName, &dwFlags)==FALSE ) return E_FAIL;
  111.    // Check for duplicates
  112.    dwFlags = FILEOP_SILENT;
  113.    BOOL bIsDuplicate;
  114.    _CheckFileCollision(vol, szNewName, &dwFlags, &bIsDuplicate);
  115.    if( bIsDuplicate ) return E_FAIL;
  116.    // Ok, go ahead and rename
  117.    BOOL bRes = vol.Rename(vol.GetDirectoryPtr(), szOldName, vol.GetDirectoryPtr(), szNewName);
  118.    if( !bRes ) return E_FAIL;
  119.    // Notify Shell so it can rename the item in the Explorer tree
  120.    CPidl pidlFullOld;
  121.    pidlFullOld.Copy(m_pidl);
  122.    pidlFullOld.Concatenate(m_pidlPath);
  123.    pidlFullOld.Concatenate(pidlOld);
  124.    CPidl pidlFullNew;
  125.    pidlFullNew.Copy(m_pidl);
  126.    pidlFullNew.Concatenate(m_pidlPath);
  127.    pidlFullNew.Concatenate(pidlNew);
  128.    ::SHChangeNotify(
  129.       PidlGetType(pidlOld)==PT_FOLDER ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM, 
  130.       SHCNF_IDLIST, 
  131.       pidlFullOld, pidlFullNew);
  132.    CPidl pidlFullPath;
  133.    pidlFullPath.Copy(m_pidl);
  134.    pidlFullPath.Concatenate(m_pidlPath);
  135.    ::SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, pidlFullPath, NULL);
  136.    return S_OK;
  137. }
  138. // UnDeletes a list of files and folders from the current folder.
  139. HRESULT CFolder::_UnDeleteFiles(LPCITEMIDLIST *pidls, UINT nCount)
  140. {
  141.    ATLASSERT(pidls);
  142.    ATLASSERT(nCount>0);
  143.    HRESULT Hr;
  144.    CAdfDevice dev;
  145.    CAdfVolume vol;
  146.    HR( _OpenAmigaDevice(m_pidl, FALSE, m_pidlPath, dev, vol) );
  147.    CAdfDelList list;
  148.    if( vol.GetDeletedEntries(list)==FALSE ) return E_FAIL;
  149.    List *cell = list;
  150.    while( cell!=NULL ) {
  151.       GenBlock *entry = (GenBlock *)cell->content;
  152.       cell = cell->next;
  153.       if( entry->parent != vol.GetDirectoryPtr() ) continue;
  154.       for( UINT i=0; i<nCount; i++ ) {
  155.          if( _tcscmp(entry->name, (pidl_cast(pidls[0]))->szName)==0 ) {
  156.             if( vol.CheckDeletedFile(entry->sect) ) vol.UndeleteFile(entry->parent, entry->sect);
  157.          }
  158.       }
  159.    }
  160.    // Ask Shell to refresh current directory
  161.    CPidl pidl;
  162.    pidl.Copy(m_pidl);
  163.    pidl.Concatenate(m_pidlPath);
  164.    ::SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST | SHCNF_FLUSH, pidl, NULL);
  165.    return S_OK;
  166. }
  167. // Removes an Amiga folder.
  168. // The function removes sub-folders too. It is recursive.
  169. // The Volume must have the directory pointer placed at 
  170. // the parent directory structure of the folder to be deleted.
  171. HRESULT CFolder::_RemoveDirectory(CAdfVolume &vol, LPCTSTR pstrPath)
  172. {
  173.    ATLASSERT(pstrPath);
  174.    USES_CONVERSION;
  175.    // Change into the folder, and delete items recursively...
  176.    if( vol.ChangeDirectory(pstrPath)==FALSE ) return E_UNEXPECTED;
  177.    CAdfDirList dir;
  178.    if( vol.GetCurrentDirctory(dir)==FALSE ) return E_FAIL;
  179.    List *list = dir;
  180.    while (list) {
  181.       struct Entry *ent = (struct Entry *)list->content;
  182.       if( ent->type==ST_DIR ) {
  183.          // It's a dir - recurse into it
  184.          _RemoveDirectory(vol, A2CT(ent->name));
  185.       } 
  186.       else {
  187.          // It's a file or a link, just remove it
  188.          vol.Delete(vol.GetDirectoryPtr(), A2CT(ent->name));
  189.       }
  190.       list = list->next;
  191.    }
  192.    // Back to parent and delete the folder...
  193.    vol.ChangeDirectoryParent();
  194.    if( vol.Delete(vol.GetDirectoryPtr(), pstrPath)==FALSE ) return E_FAIL;
  195.    return S_OK;
  196. }
  197. // Moves a PC file to the Amiga device
  198. // Returns S_FALSE if the move operations was cancelled.
  199. HRESULT CFolder::_MoveFile(LPCTSTR pstrSource, LPCTSTR pstrDestPath, LPCTSTR pstrDestName, LPDWORD pdwFileOpFlags)
  200. {
  201.    ATLASSERT(pstrSource);
  202.    HRESULT Hr;
  203.    HR( _CopyFile(pstrSource, pstrDestPath, pstrDestName, pdwFileOpFlags) );
  204.    // S_OK means success and we should delete the source file
  205.    // S_FALSE is returned if the copy was cancelled
  206.    if( Hr==S_OK ) {
  207.       ::SetFileAttributes(pstrSource, FILE_ATTRIBUTE_NORMAL);
  208.       ::DeleteFile(pstrSource);
  209.    }
  210.    return Hr;
  211. }
  212. // Copies a PC file to the Amiga device
  213. // If a filename collision occurs, a dialog is shown.
  214. // Returns S_FALSE if the copy operations was cancelled.
  215. HRESULT CFolder::_CopyFile(LPCTSTR pstrSource, LPCTSTR pstrDestPath, LPCTSTR pstrDestName, LPDWORD pdwFileOpFlags)
  216. {
  217.    ATLASSERT(pstrSource);
  218.    ATLASSERT(pstrDestPath);
  219.    ATLASSERT(pstrDestName);
  220.    pstrDestPath;
  221.    if( *pstrSource==_T('') ) return E_FAIL;
  222.    if( ::PathFileExists(pstrSource)==FALSE ) return E_INVALIDARG;
  223.    HRESULT Hr;
  224.    CAdfDevice dev;
  225.    CAdfVolume vol;
  226.    HR( _OpenAmigaDevice(m_pidl, FALSE, m_pidlPath, dev, vol) );
  227.    TCHAR szDestName[MAX_PATH];
  228.    _tcscpy( szDestName, pstrDestName );
  229.    if( _ValidateFileName(szDestName, pdwFileOpFlags)==FALSE ) return E_FAIL;
  230.    BOOL bIsDuplicate;
  231.    if( _CheckFileCollision(vol, szDestName, pdwFileOpFlags, &bIsDuplicate )!=IDOK ) return S_FALSE;
  232.    if( bIsDuplicate ) {
  233.       // The file was already there. Need to delete it so we can replace it.
  234.       // BUG: If it was a folder, this will fail.
  235.       vol.Delete(vol.GetDirectoryPtr(), szDestName);      
  236.    }
  237.    // Get the file's attributes
  238.    DWORD dwFileAttribs = ::GetFileAttributes(pstrSource);
  239.    // Open the PC file and copy it to the Amiga device
  240.    CFile f;
  241.    if( f.Open(pstrSource)==FALSE ) return E_FAIL;
  242.    // Make sure there is disk space
  243.    if( _CheckForDiskExchaustion(vol, f.GetSize(), pdwFileOpFlags)==FALSE ) return E_UNEXPECTED;
  244.    // Create the new file
  245.    CAdfFile file;
  246.    if( vol.GetFile(szDestName, _T("w"), file)==FALSE ) return E_ACCESSDENIED;
  247.    // Read the buffer and write it to the Amiga file.
  248.    // Note the comma in the 'while' statement.
  249.    // We don't care about FALSE returned from Read()!
  250.    BYTE buf[512];
  251.    DWORD dwRead;
  252.    while( f.Read(buf, sizeof(buf), &dwRead), dwRead>0 ) {
  253.       if( file.Write(buf, dwRead)==FALSE ) return E_UNEXPECTED;
  254.    }
  255.    f.Close();
  256.    file.Close();
  257.    // Set the new file attributes
  258.    DWORD dwAccess = 0;
  259.    if( dwFileAttribs & FILE_ATTRIBUTE_READONLY ) dwAccess |= ACCMASK_W;
  260.    if( dwFileAttribs & FILE_ATTRIBUTE_HIDDEN ) dwAccess |= ACCMASK_H;
  261.    if( dwFileAttribs & FILE_ATTRIBUTE_ARCHIVE ) dwAccess |= ACCMASK_A;
  262.    vol.SetFileAccess(szDestName, vol.GetDirectoryPtr(), dwAccess);
  263.   
  264.    return S_OK;
  265. }
  266. // Check for free disk space.
  267. // The Volume must be opened with read/write access.
  268. // Will bring up an error dialog and update 'pdwFileOpFlags' argument with
  269. // FILEOP_CANCEL flag if there is insufficient disk space free.
  270. BOOL CFolder::_CheckForDiskExchaustion(CAdfVolume &vol, DWORD dwFileSize, LPDWORD pdwFileOpFlags)
  271. {
  272.    ATLASSERT(vol.IsOpen());
  273.    DWORD dwBlockNeeded = CAdfFile::_CalcBlocksNeeded(dwFileSize, vol.GetDataBlockSize());
  274.    if( vol.GetFreeBlockCount() < dwBlockNeeded ) {;
  275.       if( !FILEOP_FLAG(FILEOP_SILENT) ) {
  276.          // Display "Not enough free space" warning
  277.          CResString<200> sText(IDS_DISKFULL);
  278.          CResString<64> sCaption(IDS_WARNING);
  279.          ::MessageBox(NULL, sText, sCaption, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
  280.       }
  281.       // Make sure to abort operation completely after this
  282.       if( pdwFileOpFlags!=NULL ) *pdwFileOpFlags |= FILEOP_CANCEL;
  283.       return FALSE;
  284.    }
  285.    if( vol.IsReadOnly() ) {
  286.       if( !FILEOP_FLAG(FILEOP_SILENT) ) {
  287.          // Display "Disk is read-only" warning
  288.          CResString<200> sText(IDS_VOLUMEREADONLY);
  289.          CResString<64> sCaption(IDS_WARNING);
  290.          ::MessageBox(NULL, sText, sCaption, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
  291.       }
  292.       // Make sure to abort operation completely after this
  293.       if( pdwFileOpFlags!=NULL ) *pdwFileOpFlags |= FILEOP_CANCEL;
  294.       return FALSE;
  295.    }
  296.    return TRUE;
  297. }
  298. // Prompts user for confirmation of file delete
  299. // Returns:
  300. //  IDYES - if the user acceps
  301. //  IDNO  - if the user denies
  302. UINT CFolder::_ConfirmDelete(LPCITEMIDLIST *pidls, UINT nCount, LPDWORD pdwFileOpFlags)
  303. {
  304.    ATLASSERT(pidls);
  305.    ATLASSERT(nCount>0);
  306.    if( FILEOP_FLAG(FILEOP_SILENT) ) return IDYES;
  307.    if( FILEOP_FLAG(FILEOP_YESTOALL) ) return IDYES;
  308.    TCHAR szText[MAXNAMELEN+64];
  309.    if( nCount==1 ) {
  310.       CResString<64> sFormat(IDS_DELETEFILE);
  311.       TCHAR szName[MAXNAMELEN+1];
  312.       PidlGetName(szName, pidls[0]);
  313.       ::wsprintf(szText, sFormat, szName);
  314.    }
  315.    else {
  316.       CResString<64> sFormat(IDS_DELETEFILES);
  317.       ::wsprintf(szText, sFormat, nCount);
  318.    }
  319.    CResString<64> sCaption(IDS_CONFIRMATION);
  320.    return ::MessageBox(::GetActiveWindow(), szText, sCaption, MB_ICONQUESTION | MB_YESNO);
  321. }
  322. // Checks for file collision in the current directory.
  323. // Will bring up Duplicate dialog to allow user response.
  324. // The 'pdwFileOpFlags' and 'pbIsDuplicate' arguments are optional,
  325. // and will be updated accordingly if supplied.
  326. // Returns:
  327. //   IDOK             - if success
  328. //   IDNO or IDCANCEL - if cancelled or fails.
  329. UINT CFolder::_CheckFileCollision(CAdfVolume &vol, LPCTSTR pstrFileName, DWORD *pdwFileOpFlags, BOOL *pbIsDuplicate)
  330. {
  331.    ATLASSERT(pstrFileName);
  332.    ATLASSERT(vol.IsOpen());
  333.    // Do an initial check for flags and settings
  334.    if( pbIsDuplicate!=NULL ) *pbIsDuplicate = FALSE;
  335.    if( FILEOP_FLAG(FILEOP_CANCEL) ) return IDCANCEL;
  336.    USES_CONVERSION;
  337.    CResString<32> sType;   
  338.    // Scan the directory list for an existing entry...
  339.    CAdfDirList list;
  340.    if( vol.GetCurrentDirctory(list)==FALSE ) return IDCANCEL;
  341.    List *cell = list;
  342.    bool bFound = false;
  343.    while( cell!=NULL ) {
  344.       Entry *entry = (Entry *)cell->content;
  345.       if( _tcsicmp(A2CT(entry->name), pstrFileName)==0 ) {
  346.          sType.LoadString( CAdfFile::_IsDirectory(entry->type) ? IDS_FOLDER : IDS_FILE );
  347.          bFound = true;
  348.          break;
  349.       }
  350.       cell = cell->next;
  351.    }
  352.    if( !bFound ) return IDOK; // No collision detected, just continue...
  353.    // Update flags
  354.    if( pbIsDuplicate!=NULL ) *pbIsDuplicate = TRUE;
  355.    if( FILEOP_FLAG(FILEOP_YESTOALL) ) return IDOK;
  356.    if( FILEOP_FLAG(FILEOP_SILENT) ) return IDOK;
  357.    // Show Duplicate dialog
  358.    CDuplicateDlg dlg;
  359.    dlg.m_pstrFileName = pstrFileName;
  360.    dlg.m_pstrType = sType;
  361.    UINT res = dlg.DoModal();
  362.    switch( res ) {
  363.    case IDCANCEL:
  364.       if( pdwFileOpFlags!=NULL ) *pdwFileOpFlags |= FILEOP_CANCEL;
  365.       return IDCANCEL;
  366.    case IDNO:
  367.       return IDNO;
  368.    case IDOK:
  369.    case IDYES:
  370.       return IDOK;
  371.    case IDYESTOALL:
  372.       if( pdwFileOpFlags!=NULL ) *pdwFileOpFlags |= FILEOP_YESTOALL;
  373.       return IDOK;
  374.    }
  375.    return IDOK;
  376. }
  377. // Scan the directory list repeatingly until we
  378. // find a unique name entry.
  379. // If a name collision is detected, a number is appended and incremented
  380. // until a unique name is found.
  381. BOOL CFolder::_FindUniqueFileName(CAdfVolume &vol, LPTSTR pstrName)
  382. {
  383.    ATLASSERT(!::IsBadWritePtr(pstrName, MAXNAMELEN));
  384.    ATLASSERT(vol.IsOpen());
  385.    USES_CONVERSION;
  386.    const int MAX_TRIES = 200;
  387.    CAdfDirList list;
  388.    if( vol.GetCurrentDirctory(list)==FALSE ) return FALSE;
  389.    TCHAR szNewName[MAXNAMELEN+1];
  390.    bool bFoundUnique = false;
  391.    for( int i=1; !bFoundUnique && (i<MAX_TRIES); i++ ) {
  392.       if( i>1 ) ::wsprintf(szNewName, _T("%s (%d)"), pstrName, i); else _tcscpy( szNewName, pstrName );
  393.       List *cell = list;  
  394.       bool bFoundName = false;
  395.       while( cell!=NULL ) {
  396.          Entry *entry = (Entry *)cell->content;
  397.          if( _tcsicmp(A2CT(entry->name), szNewName)==0 ) {
  398.             bFoundName = true;
  399.             break;
  400.          }
  401.          cell = cell->next;
  402.       }
  403.       bFoundUnique = !bFoundName;
  404.    }
  405.    if( !bFoundUnique ) return FALSE;
  406.    _tcscpy( pstrName, szNewName );
  407.    return TRUE;
  408. }
  409. // Make sure the filename is valid on the Amiga.
  410. // The 'pstrName' string argument may be changed/truncated.
  411. // I don't remember all the limitations of the file system,
  412. // but at least we check length and sanity check for the most
  413. // obvious illegal characters.
  414. BOOL CFolder::_ValidateFileName(LPTSTR pstrName, LPDWORD pdwFileOpFlags)
  415. {
  416.    ATLASSERT(!::IsBadWritePtr(pstrName,MAXNAMELEN));
  417.    // If user don't wanna truncate, then return error if name is too long
  418.    if( FILEOP_FLAG(FILEOP_DONTTRUNCATE) ) {
  419.       if( _tcslen(pstrName)>MAXNAMELEN ) return FALSE;
  420.    }
  421.    // Check for known illegal characters
  422.    if( _tcspbrk(pstrName, _T("<>/#?:"))!=NULL ) return FALSE;
  423.    // Truncate string
  424.    pstrName[MAXNAMELEN] = _T('');
  425.    return TRUE;
  426. }