OSDN Git Service

Fix GitHub issue #339: Long filename issue
authorTakashi Sawanaka <sdottaka@users.sourceforge.net>
Sun, 10 May 2020 09:51:52 +0000 (18:51 +0900)
committerTakashi Sawanaka <sdottaka@users.sourceforge.net>
Sun, 10 May 2020 09:51:52 +0000 (18:51 +0900)
Src/Common/ShellFileOperations.cpp
Src/DirActions.cpp
Src/DirActions.h
Src/DirView.cpp
Src/FileActionScript.cpp
Src/FileActionScript.h

index 7446f64..f0f2281 100644 (file)
 #include <tchar.h>
 #include <vector>
 #include <shellAPI.h>
+#pragma warning (push)                 // prevent "warning C4091: 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared"
+#pragma warning (disable:4091) // VC bug when using XP enabled toolsets.
+#include <shlobj.h>
+#pragma warning (pop)
+#include <shlobj.h>
+#include <comip.h>
 #include "UnicodeString.h"
+#include "paths.h"
+#include "TFile.h"
 
 using std::vector;
+typedef _com_ptr_t<_com_IIID<IFileOperation, &__uuidof(IFileOperation)>> IFileOperationPtr;
+typedef _com_ptr_t<_com_IIID<IShellItem, &__uuidof(IShellItem)>> IShellItemPtr;
 
 /**
  * @brief Constructor.
@@ -178,7 +188,7 @@ bool ShellFileOperations::Run()
                m_function != FO_DELETE ? &destStr[0] : nullptr, m_flags, FALSE, 0, 0};
        int ret = SHFileOperation(&fileop);
 
-       if (ret == 0x75) // DE_OPCANCELLED
+       if (ret == 0x75 || fileop.fAnyOperationsAborted) // DE_OPCANCELLED
                m_isCanceled = true;
 
        bool anyAborted = !!fileop.fAnyOperationsAborted;
@@ -186,7 +196,94 @@ bool ShellFileOperations::Run()
        // SHFileOperation returns 0 when succeeds
        if (ret == 0 && !anyAborted)
                return true;
-       return false;
+       if (anyAborted)
+               return false;
+
+       // If SHFileOperation() function fails, file operations are performed using the IFileOperation interface.
+       // Later, make this the default if Vista or higher
+       HRESULT hr;
+       IFileOperationPtr pFileOperation;
+       if (FAILED(hr = pFileOperation.CreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL)))
+               return false;
+       
+       auto CreateShellItemParseDisplayName = [](const String& path, IShellItem **psi)
+       {
+               HRESULT hr;
+               PIDLIST_ABSOLUTE pidl;
+               if (FAILED(hr = SHParseDisplayName(path.c_str(), nullptr, &pidl, 0, nullptr)))
+               {
+                       TCHAR szShortPath[32768] = {};
+                       if (GetShortPathName(TFile(path).wpath().c_str(), szShortPath, sizeof(szShortPath) / sizeof(szShortPath[0])) == 0)
+                       {
+                               hr = E_FAIL;
+                       }
+                       else
+                       {
+                               String shortPath = szShortPath;
+                               strutils::replace(shortPath, _T("\\\\?\\UNC\\"), _T("\\\\"));
+                               strutils::replace(shortPath, _T("\\\\?\\"), _T(""));
+                               hr = SHParseDisplayName(shortPath.c_str(), nullptr, &pidl, 0, nullptr);
+                       }
+               }
+               if (SUCCEEDED(hr))
+               {
+                       hr = SHCreateShellItem(nullptr, nullptr, pidl, psi);
+                       ILFree(pidl);
+               }
+               return hr;
+       };
+
+       pFileOperation->SetOperationFlags(m_flags);
+
+       auto itsrc = m_sources.begin();
+       auto itdst = m_destinations.begin();
+
+       while (itsrc != m_sources.end() || itdst != m_destinations.end())
+       {
+               IShellItemPtr pShellItemSrc;
+               IShellItemPtr pShellItemDst;
+               String dstFileName;
+               if (itsrc != m_sources.end())
+               {
+                       if (FAILED(CreateShellItemParseDisplayName(*itsrc, &pShellItemSrc)))
+                               return false;
+                       ++itsrc;
+               }
+               if (itdst != m_destinations.end())
+               {
+                       String parent = paths::GetParentPath(*itdst);
+                       if (FAILED(CreateShellItemParseDisplayName(parent, &pShellItemDst)))
+                       {
+                               TFile(parent).createDirectories();
+                               if (FAILED(CreateShellItemParseDisplayName(parent, &pShellItemDst)))
+                                       return false;
+                       }
+                       dstFileName = paths::FindFileName(*itdst);
+                       ++itdst;
+               }
+               switch (m_function)
+               {
+               case FO_COPY:
+                       hr = pFileOperation->CopyItem(pShellItemSrc, pShellItemDst, dstFileName.c_str(), nullptr);
+                       break;
+               case FO_MOVE:
+                       hr = pFileOperation->MoveItem(pShellItemSrc, pShellItemDst, dstFileName.c_str(), nullptr);
+                       break;
+               case FO_DELETE:
+                       hr = pFileOperation->DeleteItem(pShellItemSrc, nullptr);
+                       break;
+               case FO_RENAME:
+                       hr = pFileOperation->RenameItem(pShellItemSrc, dstFileName.c_str(), nullptr);
+                       break;
+               }
+               if (FAILED(hr))
+                       return false;
+       }
+       hr = pFileOperation->PerformOperations();
+       BOOL fAnyOperationsAborted = FALSE;
+       pFileOperation->GetAnyOperationsAborted(&fAnyOperationsAborted);
+       m_isCanceled = fAnyOperationsAborted;
+       return SUCCEEDED(hr) && !fAnyOperationsAborted;
 }
 
 /**
index 4f6208a..6136de7 100644 (file)
@@ -40,6 +40,11 @@ ContentsChangedException::ContentsChangedException(const String& failpath)
 {
 }
 
+FileOperationException::FileOperationException(const String& msg)
+       : m_msg(msg)
+{
+}
+
 /**
  * @brief Ask user a confirmation for copying item(s).
  * Shows a confirmation dialog for copy operation. Depending ont item count
index d68de2d..4bf5bb9 100644 (file)
@@ -208,6 +208,12 @@ struct ContentsChangedException
        String m_msg;
 };
 
+struct FileOperationException
+{
+       explicit FileOperationException(const String& msg);
+       String m_msg;
+};
+
 struct DirActions
 {
        typedef bool (DirActions::*method_type2)(const DIFFITEM& di) const;
index 8b5de32..4853faf 100644 (file)
@@ -921,6 +921,8 @@ void CDirView::DoDirAction(DirActions::method_type func, const String& status_me
                ConfirmAndPerformActions(actionScript);
        } catch (ContentsChangedException& e) {
                AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
+       } catch (FileOperationException& e) {
+               AfxMessageBox(e.m_msg.c_str(), MB_ICONWARNING);
        }
 }
 
@@ -1003,9 +1005,12 @@ void CDirView::PerformActionList(FileActionScript & actionScript)
        actionScript.SetParentWindow(GetMainFrame()->GetSafeHwnd());
 
        theApp.AddOperation();
-       if (actionScript.Run())
+       bool succeeded = actionScript.Run();
+       if (succeeded)
                UpdateAfterFileScript(actionScript);
        theApp.RemoveOperation();
+       if (!succeeded && !actionScript.IsCanceled())
+               throw FileOperationException(_T("File operation failed"));
 }
 
 /**
index e3eb86a..0e509c8 100644 (file)
@@ -46,6 +46,7 @@ FileActionScript::FileActionScript()
 , m_pMoveOperations(new ShellFileOperations())
 , m_pRenameOperations(new ShellFileOperations())
 , m_pDelOperations(new ShellFileOperations())
+, m_bCanceled(false)
 {
 }
 
@@ -263,5 +264,7 @@ bool FileActionScript::Run()
        if (!bFileOpSucceed || bUserCancelled)
                bRetVal = false;
 
+       m_bCanceled = bUserCancelled;
+
        return bRetVal;
 }
index 14871f2..7104893 100644 (file)
@@ -130,6 +130,8 @@ public:
         */
        FileActionItem GetHeadActionItem() const { return m_actions[0]; }
 
+       bool IsCanceled() const { return m_bCanceled; }
+
        String m_destBase; /**< Base destination path for some operations */
 
 protected:
@@ -148,6 +150,7 @@ private:
        bool m_bHasDelOperations; /**< flag if we've put anything into m_pDelOperations */
        bool m_bUseRecycleBin; /**< Use recycle bin for script actions? */
        HWND m_hParentWindow; /**< Parent window for showing messages */
+       bool m_bCanceled;
 };
 
 /**