summaryrefslogtreecommitdiff
path: root/zen/IFileOperation/file_op.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/IFileOperation/file_op.cpp')
-rw-r--r--zen/IFileOperation/file_op.cpp349
1 files changed, 349 insertions, 0 deletions
diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp
new file mode 100644
index 00000000..7c75a8e8
--- /dev/null
+++ b/zen/IFileOperation/file_op.cpp
@@ -0,0 +1,349 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#include "file_op.h"
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#define WIN32_LEAN_AND_MEAN
+#include <zen/com_ptr.h>
+#include <zen/com_error.h>
+#include <zen/scope_guard.h>
+
+#include <boost/thread/tss.hpp>
+
+#include <RestartManager.h>
+#pragma comment(lib, "Rstrtmgr.lib")
+
+#define STRICT_TYPED_ITEMIDS //better type safety for IDLists
+#include <Shlobj.h>
+#include <shobjidl.h>
+#include <shellapi.h> //shell constants such as FO_* values
+
+using namespace zen;
+
+
+namespace
+{
+void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
+ size_t fileNo) //size of fileNames array
+{
+ ComPtr<IFileOperation> fileOp;
+ ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError
+ nullptr,
+ 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
+ FOF_NOCONFIRMATION |
+ FOF_SILENT |
+ FOF_NOERRORUI |
+ FOFX_EARLYFAILURE |
+ FOF_NO_CONNECTED_ELEMENTS));
+
+ int operationCount = 0;
+
+ for (size_t i = 0; i < fileNo; ++i)
+ {
+ //create file/folder item object
+ ComPtr<IShellItem> psiFile;
+ HRESULT hr = ::SHCreateItemFromParsingName(fileNames[i],
+ nullptr,
+ IID_PPV_ARGS(psiFile.init()));
+ if (FAILED(hr))
+ {
+ 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);
+ }
+
+ ZEN_CHECK_COM(fileOp->DeleteItem(psiFile.get(), nullptr));
+
+ ++operationCount;
+ }
+
+ if (operationCount == 0) //calling PerformOperations() without anything to do would result in E_UNEXPECTED
+ return;
+
+ //perform actual operations
+ ZEN_CHECK_COM(fileOp->PerformOperations());
+
+ //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors!
+ BOOL pfAnyOperationsAborted = FALSE;
+ ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted));
+
+ if (pfAnyOperationsAborted == TRUE)
+ throw ComError(L"Operation did not complete successfully.");
+}
+
+
+void copyFile(const wchar_t* sourceFile, //throw ComError
+ const wchar_t* targetFile)
+{
+ ComPtr<IFileOperation> fileOp;
+ ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError
+ nullptr,
+ 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_NOCONFIRMATION | //throw ComError
+ FOF_SILENT |
+ FOFX_EARLYFAILURE |
+ FOF_NOERRORUI));
+ //create source object
+ ComPtr<IShellItem> psiSourceFile;
+ {
+ HRESULT hr = ::SHCreateItemFromParsingName(sourceFile,
+ nullptr,
+ IID_PPV_ARGS(psiSourceFile.init()));
+ if (FAILED(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'\\');
+ if (pos == std::wstring::npos)
+ throw ComError(L"Target filename does not contain a path separator.");
+
+ const std::wstring targetFolder(targetFile, pos);
+ const std::wstring targetFileNameShort = targetFile + pos + 1;
+
+ //create target folder object
+ ComPtr<IShellItem> psiTargetFolder;
+ {
+ HRESULT hr = ::SHCreateItemFromParsingName(targetFolder.c_str(),
+ nullptr,
+ IID_PPV_ARGS(psiTargetFolder.init()));
+ if (FAILED(hr))
+ throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\"" + targetFolder + L"\".", hr);
+ }
+
+ //schedule file copy operation
+ ZEN_CHECK_COM(fileOp->CopyItem(psiSourceFile.get(), psiTargetFolder.get(), targetFileNameShort.c_str(), nullptr));
+
+ //perform actual operations
+ ZEN_CHECK_COM(fileOp->PerformOperations());
+
+ //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors!
+ BOOL pfAnyOperationsAborted = FALSE;
+ ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted));
+
+ if (pfAnyOperationsAborted == TRUE)
+ throw ComError(L"Operation did not complete successfully.");
+}
+
+
+void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw ComError
+{
+ ComPtr<IShellFolder> desktopFolder;
+ ZEN_CHECK_COM(::SHGetDesktopFolder(desktopFolder.init())); //throw ComError
+
+ PIDLIST_RELATIVE pidlFolder = nullptr;
+ ZEN_CHECK_COM(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
+ nullptr, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(dirname), // [in] LPWSTR pszDisplayName,
+ nullptr, // [out] ULONG *pchEaten,
+ &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl,
+ nullptr)); // [in, out] ULONG *pdwAttributes
+ ZEN_ON_SCOPE_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree
+
+ ComPtr<IPersist> persistFolder;
+ ZEN_CHECK_COM(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl,
+ nullptr, // [in] IBindCtx *pbc,
+ IID_PPV_ARGS(persistFolder.init()))); //throw ComError
+
+ ZEN_CHECK_COM(persistFolder->GetClassID(&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
+ DWORD sessionHandle = 0;
+ DWORD rv1 = ::RmStartSession(&sessionHandle, //__out DWORD *pSessionHandle,
+ 0, //__reserved DWORD dwSessionFlags,
+ sessionKey); //__out WCHAR strSessionKey[ ]
+ if (rv1 != ERROR_SUCCESS)
+ throw Win32Error(rv1);
+ ZEN_ON_SCOPE_EXIT(::RmEndSession(sessionHandle));
+
+ DWORD rv2 = ::RmRegisterResources(sessionHandle, //__in DWORD dwSessionHandle,
+ 1, //__in UINT nFiles,
+ &filename, //__in_opt LPCWSTR rgsFilenames[ ],
+ 0, //__in UINT nApplications,
+ nullptr, //__in_opt RM_UNIQUE_PROCESS rgApplications[ ],
+ 0, //__in UINT nServices,
+ nullptr); //__in_opt LPCWSTR rgsServiceNames[ ]
+ if (rv2 != ERROR_SUCCESS)
+ throw Win32Error(rv2);
+
+ UINT procInfoSize = 0;
+ UINT procInfoSizeNeeded = 0;
+ DWORD rebootReasons = 0;
+ ::RmGetList(sessionHandle, &procInfoSizeNeeded, &procInfoSize, nullptr, &rebootReasons); //get procInfoSizeNeeded
+ //fails with "access denied" for C:\pagefile.sys!
+
+ if (procInfoSizeNeeded == 0)
+ return std::vector<std::wstring>();
+
+ procInfoSize = procInfoSizeNeeded;
+ std::vector<RM_PROCESS_INFO> procInfo(procInfoSize);
+
+ DWORD rv3 = ::RmGetList(sessionHandle, //__in DWORD dwSessionHandle,
+ &procInfoSizeNeeded, //__out UINT *pnProcInfoNeeded,
+ &procInfoSize, //__inout UINT *pnProcInfo,
+ &procInfo[0], //__inout_opt RM_PROCESS_INFO rgAffectedApps[ ],
+ &rebootReasons); //__out LPDWORD lpdwRebootReasons
+ if (rv3 != ERROR_SUCCESS)
+ throw Win32Error(rv3);
+ procInfo.resize(procInfoSize);
+
+ std::vector<std::wstring> output;
+ for (auto iter = procInfo.begin(); iter != procInfo.end(); ++iter)
+ {
+ std::wstring processName = iter->strAppName;
+
+ //try to get process path
+ HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, //__in DWORD dwDesiredAccess,
+ false, //__in BOOL bInheritHandle,
+ iter->Process.dwProcessId); //__in DWORD dwProcessId
+ if (hProcess)
+ {
+ ZEN_ON_SCOPE_EXIT(::CloseHandle(hProcess));
+
+ FILETIME creationTime = {};
+ FILETIME exitTime = {};
+ FILETIME kernelTime = {};
+ FILETIME userTime = {};
+ if (::GetProcessTimes(hProcess, //__in HANDLE hProcess,
+ &creationTime, //__out LPFILETIME lpCreationTime,
+ &exitTime, //__out LPFILETIME lpExitTime,
+ &kernelTime, //__out LPFILETIME lpKernelTime,
+ &userTime)) //__out LPFILETIME lpUserTime
+ if (::CompareFileTime(&iter->Process.ProcessStartTime, &creationTime) == 0)
+ {
+ DWORD bufferSize = MAX_PATH;
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::QueryFullProcessImageName(hProcess, //__in HANDLE hProcess,
+ 0, //__in DWORD dwFlags,
+ &buffer[0], //__out LPTSTR lpExeName,
+ &bufferSize)) //__inout PDWORD lpdwSize
+ if (bufferSize < buffer.size())
+ processName += std::wstring(L" - ") + L"\"" + &buffer[0] + L"\"";
+ }
+ }
+ output.push_back(processName);
+ }
+ return output;
+}
+
+
+boost::thread_specific_ptr<std::wstring> lastErrorMessage; //use "thread_local" in C++11
+}
+
+
+bool fileop::moveToRecycleBin(const wchar_t* fileNames[], size_t fileNo) //size of fileNames array
+{
+ try
+ {
+ ::moveToRecycleBin(fileNames, fileNo); //throw ComError
+ return true;
+ }
+ catch (const ComError& e)
+ {
+ lastErrorMessage.reset(new std::wstring(e.toString()));
+ return false;
+ }
+}
+
+
+bool fileop::copyFile(const wchar_t* sourceFile,
+ const wchar_t* targetFile)
+{
+ try
+ {
+ ::copyFile(sourceFile, targetFile); //throw ComError
+ return true;
+ }
+ catch (const ComError& e)
+ {
+ lastErrorMessage.reset(new std::wstring(e.toString()));
+ return false;
+ }
+}
+
+
+bool fileop::checkRecycler(const wchar_t* dirname, bool& isRecycler)
+{
+ try
+ {
+ CLSID clsid = {};
+ getFolderClsid(dirname, clsid); //throw ComError
+ isRecycler = ::IsEqualCLSID(clsid, CLSID_RecycleBin) == TRUE; //silence perf warning
+ return true;
+ }
+ catch (const ComError& e)
+ {
+ lastErrorMessage.reset(new std::wstring(e.toString()));
+ return false;
+ }
+}
+
+
+const wchar_t* fileop::getLastError()
+{
+ return !lastErrorMessage.get() ? L"" : lastErrorMessage->c_str();
+}
+
+
+bool fileop::getLockingProcesses(const wchar_t* filename, const wchar_t*& procList)
+{
+ try
+ {
+ std::vector<std::wstring> result = ::getLockingProcesses(filename); //throw Win32Error
+
+ std::wstring buffer;
+ for (auto iter = result.begin(); iter != result.end(); ++iter)
+ {
+ buffer += *iter;
+ buffer += L'\n';
+ }
+ if (!buffer.empty())
+ buffer.resize(buffer.size() - 1); //remove last line break
+
+ auto tmp = new wchar_t [buffer.size() + 1]; //bad_alloc ?
+ ::wmemcpy(tmp, buffer.c_str(), buffer.size() + 1); //include 0-termination
+ procList = tmp; //ownership passed
+
+ return true;
+ }
+ catch (const Win32Error& e)
+ {
+ lastErrorMessage.reset(new std::wstring(formatWin32Msg(e.errorCode_)));
+ return false;
+ }
+}
+
+
+void fileop::freeString(const wchar_t* str)
+{
+ delete [] str;
+}
bgstack15