summaryrefslogtreecommitdiff
path: root/zen/IFileOperation/file_op.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:21:59 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:21:59 +0200
commitd4af25c52a28b93484ffb55e0a8027bc4ce7856f (patch)
tree853d57468d6b370711e7a5dd2c3dc7d5bac81b10 /zen/IFileOperation/file_op.cpp
parent5.8 (diff)
downloadFreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.tar.gz
FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.tar.bz2
FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.zip
5.9
Diffstat (limited to 'zen/IFileOperation/file_op.cpp')
-rw-r--r--zen/IFileOperation/file_op.cpp241
1 files changed, 209 insertions, 32 deletions
diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp
index 8acc3d17..3591de12 100644
--- a/zen/IFileOperation/file_op.cpp
+++ b/zen/IFileOperation/file_op.cpp
@@ -13,6 +13,7 @@
#include <zen/com_ptr.h>
#include <zen/com_error.h>
#include <zen/scope_guard.h>
+#include <zen/stl_tools.h>
#include <boost/thread/tss.hpp>
@@ -29,8 +30,140 @@ using namespace zen;
namespace
{
-void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
- size_t fileNo) //size of fileNames array
+struct Win32Error
+{
+ Win32Error(DWORD errorCode) : errorCode_(errorCode) {}
+ DWORD errorCode_;
+};
+
+std::vector<std::wstring> getLockingProcesses(const wchar_t* filename); //throw Win32Error
+
+
+class RecyclerProgressCallback : public IFileOperationProgressSink
+{
+ //Sample implementation: %ProgramFiles%\Microsoft SDKs\Windows\v7.1\Samples\winui\shell\appplatform\FileOperationProgressSink
+
+ ~RecyclerProgressCallback() {} //private: do not allow stack usage "thanks" to IUnknown lifetime management!
+
+public:
+ RecyclerProgressCallback(fileop::RecyclerCallback callback, void* sink) :
+ cancellationRequested(false),
+ callback_(callback),
+ sink_(sink),
+ refCount(1) {}
+
+ //IUnknown: reference implementation according to: http://msdn.microsoft.com/en-us/library/office/cc839627.aspx
+ virtual ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return ::InterlockedIncrement(&refCount);
+ }
+
+ virtual ULONG STDMETHODCALLTYPE Release()
+ {
+ ULONG newRefCount = ::InterlockedDecrement(&refCount);
+ if (newRefCount == 0) //race condition caveat: do NOT check refCount, which might have changed already!
+ delete this;
+ return newRefCount;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR* __RPC_FAR* ppvObject)
+ {
+ if (!ppvObject)
+ return E_INVALIDARG;
+
+ if (riid == IID_IUnknown || riid == IID_IFileOperationProgressSink)
+ {
+ *ppvObject = this;
+ AddRef();
+ return S_OK;
+ }
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ //IFileOperationProgressSink
+ virtual HRESULT STDMETHODCALLTYPE StartOperations() { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT hrResult) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PreRenameItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PostRenameItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_string LPCWSTR pszNewName, HRESULT hrRename, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PreMoveItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PostMoveItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, HRESULT hrMove, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PreCopyItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PostCopyItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, HRESULT hrCopy, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PreNewItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PostNewItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, __RPC__in_opt_string LPCWSTR pszTemplateName, DWORD dwFileAttributes, HRESULT hrNew, __RPC__in_opt IShellItem* psiNewItem) { return S_OK; }
+
+ virtual HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem)
+ {
+ if (psiItem)
+ {
+ LPWSTR itemPath = nullptr;
+ HRESULT hr = psiItem->GetDisplayName(SIGDN_FILESYSPATH, &itemPath);
+ if (FAILED(hr))
+ return hr;
+ ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(itemPath));
+
+ currentItem = itemPath;
+ }
+ //"Returns S_OK if successful, or an error value otherwise. In the case of an error value, the delete operation
+ //and all subsequent operations pending from the call to IFileOperation are canceled."
+ return cancellationRequested ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD dwFlags,
+ __RPC__in_opt IShellItem* psiItem,
+ HRESULT hrDelete,
+ __RPC__in_opt IShellItem* psiNewlyCreated)
+ {
+ if (FAILED(hrDelete))
+ lastError = make_unique<std::pair<std::wstring, HRESULT>>(currentItem, hrDelete);
+
+ currentItem.clear();
+ //"Returns S_OK if successful, or an error value otherwise. In the case of an error value,
+ //all subsequent operations pending from the call to IFileOperation are canceled."
+ return cancellationRequested ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE UpdateProgress(UINT iWorkTotal, UINT iWorkSoFar)
+ {
+ if (callback_)
+ try
+ {
+ if (!callback_(currentItem.c_str(), sink_)) //should not throw!
+ cancellationRequested = true;
+ }
+ catch (...) { return E_UNEXPECTED; }
+ //"If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code."
+ //-> this probably means, we cannot rely on returning a custom error code here and have IFileOperation::PerformOperations() fail with same
+ //=> defer cancellation to PreDeleteItem()/PostDeleteItem()
+ return S_OK;
+ }
+ virtual HRESULT STDMETHODCALLTYPE ResetTimer() { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE PauseTimer() { return S_OK; }
+ virtual HRESULT STDMETHODCALLTYPE ResumeTimer() { return S_OK; }
+
+ //call after IFileOperation::PerformOperations()
+ const std::pair<std::wstring, HRESULT>* getLastError() const { return lastError.get(); } //(file path, error code)
+
+private:
+ std::wstring currentItem;
+ bool cancellationRequested;
+
+ std::unique_ptr<std::pair<std::wstring, HRESULT>> lastError;
+
+ //file_op user callback
+ fileop::RecyclerCallback callback_;
+ void* sink_;
+
+ //support IUnknown
+ LONG refCount;
+};
+
+
+void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
+ size_t fileCount,
+ fileop::RecyclerCallback callback,
+ void* sink)
{
ComPtr<IFileOperation> fileOp;
ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError
@@ -38,21 +171,48 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
CLSCTX_ALL,
IID_PPV_ARGS(fileOp.init())));
- // Set the operation flags. Turn off all UI
- // from being shown to the user during the
- // operation. This includes error, confirmation
- // and progress dialogs.
- ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | //throw ComError
+ // Set the operation flags. Turn off all UI from being shown to the user during the
+ // operation. This includes error, confirmation and progress dialogs.
+ ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO |
FOF_NOCONFIRMATION |
- FOF_SILENT |
+ FOF_SILENT | //no progress dialog box
FOF_NOERRORUI |
- FOFX_EARLYFAILURE |
+ FOFX_EARLYFAILURE |
+ //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!?
+ //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :(((
+ //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible"
+ //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be
+ //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail
FOF_NO_CONNECTED_ELEMENTS));
+ //use FOFX_RECYCLEONDELETE when Windows 8 is available!?
+
+ ComPtr<RecyclerProgressCallback> opProgress;
+ *opProgress.init() = new (std::nothrow) RecyclerProgressCallback(callback, sink);
+ if (!opProgress)
+ throw ComError(L"Error creating RecyclerProgressCallback.", E_OUTOFMEMORY);
+
+ DWORD callbackID = 0;
+ ZEN_CHECK_COM(fileOp->Advise(opProgress.get(), &callbackID));
+ ZEN_ON_SCOPE_EXIT(fileOp->Unadvise(callbackID)); //RecyclerProgressCallback might outlive current scope, so cut access to "callback, sink"
int operationCount = 0;
- for (size_t i = 0; i < fileNo; ++i)
+ for (size_t i = 0; i < fileCount; ++i)
{
+ //SHCreateItemFromParsingName() physically checks file existence => callback
+ if (callback)
+ {
+ bool continueExecution = false;
+ try
+ {
+ continueExecution = callback(fileNames[i], sink); //should not throw!
+ }
+ catch (...) { throw ComError(L"Unexpected exception in callback.", E_UNEXPECTED); }
+
+ if (!continueExecution)
+ throw ComError(L"Operation cancelled.", HRESULT_FROM_WIN32(ERROR_CANCELLED));
+ }
+
//create file/folder item object
ComPtr<IShellItem> psiFile;
HRESULT hr = ::SHCreateItemFromParsingName(fileNames[i],
@@ -63,7 +223,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || //file not existing anymore
hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
continue;
- throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + fileNames[i] + L"\".", hr);
+ throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + fileNames[i] + L"\'.", hr);
}
ZEN_CHECK_COM(fileOp->DeleteItem(psiFile.get(), nullptr));
@@ -71,13 +231,37 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
++operationCount;
}
- if (operationCount == 0) //calling PerformOperations() without anything to do would result in E_UNEXPECTED
+ if (operationCount == 0) //calling PerformOperations() without anything to do would yielt E_UNEXPECTED
return;
- //perform actual operations
- ZEN_CHECK_COM(fileOp->PerformOperations());
+ //perform planned operations
+ try
+ {
+ ZEN_CHECK_COM(fileOp->PerformOperations());
+ }
+ catch (const ComError&)
+ {
+ //first let's check if we have more detailed error information available
+ if (const std::pair<std::wstring, HRESULT>* lastError = opProgress->getLastError())
+ {
+ try //create an even better error message if we detect a locking issue:
+ {
+ std::vector<std::wstring> processes = getLockingProcesses(lastError->first.c_str()); //throw Win32Error
+ if (!processes.empty())
+ {
+ std::wstring msg = L"The file \'" + lastError->first + L"\' is locked by another process:";
+ std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { msg += L'\n'; msg += proc; });
+ throw ComError(msg); //message is already descriptive enough, no need to add the HRESULT code
+ }
+ }
+ catch (const Win32Error&) {}
- //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors!
+ throw ComError(std::wstring(L"Error during \"PerformOperations\" for file:\n") + L"\'" + lastError->first + L"\'.", lastError->second);
+ }
+ throw;
+ }
+
+ //if FOF_NOERRORUI without FOFX_EARLYFAILURE is set, PerformOperations() can return with success despite errors, but sets the following "aborted" flag instead
BOOL pfAnyOperationsAborted = FALSE;
ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted));
@@ -110,7 +294,7 @@ void copyFile(const wchar_t* sourceFile, //throw ComError
nullptr,
IID_PPV_ARGS(psiSourceFile.init()));
if (FAILED(hr))
- throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + sourceFile + L"\".", hr);
+ throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + sourceFile + L"\'.", hr);
}
const size_t pos = std::wstring(targetFile).find_last_of(L'\\');
@@ -127,7 +311,7 @@ void copyFile(const wchar_t* sourceFile, //throw ComError
nullptr,
IID_PPV_ARGS(psiTargetFolder.init()));
if (FAILED(hr))
- throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\"" + targetFolder + L"\".", hr);
+ throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\'" + targetFolder + L"\'.", hr);
}
//schedule file copy operation
@@ -168,12 +352,6 @@ void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw ComError
}
-struct Win32Error
-{
- Win32Error(DWORD errorCode) : errorCode_(errorCode) {}
- DWORD errorCode_;
-};
-
std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw Win32Error
{
wchar_t sessionKey[CCH_RM_SESSION_KEY + 1] = {}; //fixes two bugs: http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx
@@ -260,11 +438,14 @@ boost::thread_specific_ptr<std::wstring> lastErrorMessage; //use "thread_local"
}
-bool fileop::moveToRecycleBin(const wchar_t* fileNames[], size_t fileNo) //size of fileNames array
+bool fileop::moveToRecycleBin(const wchar_t* fileNames[],
+ size_t fileCount,
+ RecyclerCallback callback,
+ void* sink)
{
try
{
- ::moveToRecycleBin(fileNames, fileNo); //throw ComError
+ ::moveToRecycleBin(fileNames, fileCount, callback, sink); //throw ComError
return true;
}
catch (const ComError& e)
@@ -318,15 +499,11 @@ bool fileop::getLockingProcesses(const wchar_t* filename, const wchar_t*& procLi
{
try
{
- std::vector<std::wstring> result = ::getLockingProcesses(filename); //throw Win32Error
+ std::vector<std::wstring> processes = ::getLockingProcesses(filename); //throw Win32Error
std::wstring buffer;
- for (auto iter = result.begin(); iter != result.end(); ++iter)
- {
- buffer += *iter;
- buffer += L'\n';
- }
- if (!buffer.empty())
+ std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { buffer += proc; buffer += L'\n'; });
+ if (!processes.empty())
buffer.resize(buffer.size() - 1); //remove last line break
auto tmp = new wchar_t [buffer.size() + 1]; //bad_alloc ?
bgstack15