diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:21:59 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:21:59 +0200 |
commit | d4af25c52a28b93484ffb55e0a8027bc4ce7856f (patch) | |
tree | 853d57468d6b370711e7a5dd2c3dc7d5bac81b10 /zen | |
parent | 5.8 (diff) | |
download | FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.tar.gz FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.tar.bz2 FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.zip |
5.9
Diffstat (limited to 'zen')
-rw-r--r-- | zen/IFileOperation/file_op.cpp | 241 | ||||
-rw-r--r-- | zen/IFileOperation/file_op.h | 13 | ||||
-rw-r--r-- | zen/com_error.h | 2 | ||||
-rw-r--r-- | zen/com_ptr.h | 24 | ||||
-rw-r--r-- | zen/debug_new.cpp | 63 | ||||
-rw-r--r-- | zen/debug_new.h | 74 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 78 | ||||
-rw-r--r-- | zen/dir_watcher.h | 24 | ||||
-rw-r--r-- | zen/dll.h | 6 | ||||
-rw-r--r-- | zen/error_log.h | 43 | ||||
-rw-r--r-- | zen/file_handling.cpp | 378 | ||||
-rw-r--r-- | zen/file_handling.h | 2 | ||||
-rw-r--r-- | zen/last_error.h | 10 | ||||
-rw-r--r-- | zen/recycler.cpp | 98 | ||||
-rw-r--r-- | zen/recycler.h | 13 | ||||
-rw-r--r-- | zen/scroll_window_under_cursor.cpp | 77 | ||||
-rw-r--r-- | zen/string_base.h | 24 | ||||
-rw-r--r-- | zen/thread.h | 4 | ||||
-rw-r--r-- | zen/tick_count.h | 2 |
19 files changed, 765 insertions, 411 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 ? diff --git a/zen/IFileOperation/file_op.h b/zen/IFileOperation/file_op.h index 86efc340..2f5d6f30 100644 --- a/zen/IFileOperation/file_op.h +++ b/zen/IFileOperation/file_op.h @@ -25,9 +25,15 @@ namespace fileop //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize //minimum OS: Windows Vista or later +//return false to abort operation +typedef bool (*RecyclerCallback)(const wchar_t* filename, //current item; may be empty string! + void* sink); //virtual function mechanism is not guaranteed to be compatible between different compilers, therefore we go the C-way + DLL_FUNCTION_DECLARATION bool moveToRecycleBin(const wchar_t* fileNames[], - size_t fileNo); //size of fileNames array + size_t fileCount, //size of fileNames array + RecyclerCallback callback, //optional + void* sink); // DLL_FUNCTION_DECLARATION bool copyFile(const wchar_t* sourceFile, @@ -49,7 +55,10 @@ const wchar_t* getLastError(); //no nullptr check required! /*---------- |typedefs| ----------*/ -typedef bool (*FunType_moveToRecycleBin)(const wchar_t* fileNames[], size_t fileNo); +typedef bool (*FunType_moveToRecycleBin)(const wchar_t* fileNames[], + size_t fileCount, + RecyclerCallback callback, + void* sink); typedef bool (*FunType_copyFile)(const wchar_t* sourceFile, const wchar_t* targetFile); typedef bool (*FunType_checkRecycler)(const wchar_t* dirname, bool& isRecycler); typedef bool (*FunType_getLockingProcesses)(const wchar_t* filename, const wchar_t*& procList); diff --git a/zen/com_error.h b/zen/com_error.h index eaa7744f..cd643b49 100644 --- a/zen/com_error.h +++ b/zen/com_error.h @@ -202,7 +202,7 @@ inline std::wstring numberToHexString(long number) { wchar_t result[100]; - ::swprintf(result, 100, L"0x%08x", number); + ::swprintf(result, 100, L"0x%08x", static_cast<int>(number)); return std::wstring(result); } diff --git a/zen/com_ptr.h b/zen/com_ptr.h index bb52b5cb..030a0801 100644 --- a/zen/com_ptr.h +++ b/zen/com_ptr.h @@ -34,16 +34,16 @@ template <class T> class ComPtr { public: - ComPtr() : ptr(nullptr) {} + ComPtr() : ptr(nullptr) {} // + ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); } //noexcept in C++11 + ComPtr( ComPtr&& other) : ptr(other.ptr) { other.ptr = nullptr; } // + ~ComPtr() { if (ptr) ptr->Release(); } //has exception spec of compiler-generated destructor by default - ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); } - ComPtr( ComPtr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + ComPtr& operator=(const ComPtr& other) { ComPtr(other).swap(*this); return *this; } //noexcept in C++11 + ComPtr& operator=(ComPtr&& tmp) { swap(tmp); return *this; } // + //don't use unifying assignment but save one move-construction in the r-value case instead! - ~ComPtr() { if (ptr) ptr->Release(); } - - ComPtr& operator=(ComPtr other) { swap(other); return *this; } //unifying assignment: no need for r-value reference assignment! - - void swap(ComPtr& rhs) { std::swap(ptr, rhs.ptr); } //throw() + void swap(ComPtr& rhs) { std::swap(ptr, rhs.ptr); } //noexcept in C++11 T** init() //get pointer for use with ::CoCreateInstance() { @@ -56,7 +56,7 @@ public: T* operator->() const { return ptr; } T& operator* () const { return *ptr; } - T* release() //throw() + T* release() //noexcept in C++11 { T* tmp = ptr; ptr = nullptr; @@ -74,7 +74,7 @@ public: template <class S, class T> -ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //throw() +ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //noexcept in C++11 @@ -99,7 +99,7 @@ ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //throw() //################# implementation ############################# -//we cannot specialize std::swap() for a class template and are not allowed to overload it => offer swap in own namespace +//we cannot partially specialize std::swap() for a class template and are not allowed to overload it => offer swap in own namespace template <class T> inline void swap(zen::ComPtr<T>& lhs, zen::ComPtr<T>& rhs) { @@ -108,7 +108,7 @@ void swap(zen::ComPtr<T>& lhs, zen::ComPtr<T>& rhs) template <class S, class T> inline -ComPtr<S> com_dynamic_cast(const ComPtr<T>& other) //throw() +ComPtr<S> com_dynamic_cast(const ComPtr<T>& other) //noexcept in C++11 { ComPtr<S> outPtr; if (other) diff --git a/zen/debug_new.cpp b/zen/debug_new.cpp index ea7771b4..40fb88c7 100644 --- a/zen/debug_new.cpp +++ b/zen/debug_new.cpp @@ -5,7 +5,9 @@ // ************************************************************************** #include "debug_new.h" - +#include <string> +#include <sstream> +#include <cstdlib> //malloc(), free() #include "win.h" //includes "windows.h" #include "DbgHelp.h" //available for MSC only #pragma comment(lib, "Dbghelp.lib") @@ -44,7 +46,7 @@ struct Dummy { Dummy() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} } -void mem_check::writeMinidump() +void debug_tools::writeMinidump() { //force exception to catch the state of this thread and hopefully get a valid call stack __try @@ -53,3 +55,60 @@ void mem_check::writeMinidump() } __except (writeDumpOnException(GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION) {} } + + +/* +No need to include the "operator new" declarations into every compilation unit: + +[basic.stc.dynamic] +"A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. +Any such function definition replaces the default version provided in the library (17.6.4.6). +The following allocation and deallocation functions (18.6) are implicitly declared in global scope in each translation unit of a program. +void* operator new(std::size_t); +void* operator new[](std::size_t); +void operator delete(void*); +void operator delete[](void*);" +*/ + +namespace +{ +class BadAllocDetailed : public std::bad_alloc +{ +public: + explicit BadAllocDetailed(size_t allocSize) + { + errorMsg = "Memory allocation failed: "; + errorMsg += numberToString(allocSize); + } + + virtual const char* what() const throw() + { + return errorMsg.c_str(); + } + +private: + template <class T> + static std::string numberToString(const T& number) //convert number to string the C++ way + { + std::ostringstream ss; + ss << number; + return ss.str(); + } + + std::string errorMsg; +}; +} + +void* operator new(size_t size) +{ + if (void* ptr = ::malloc(size)) + return ptr; + + debug_tools::writeMinidump(); + throw debug_tools::BadAllocDetailed(size); +} + +void operator delete(void* ptr) { ::free(ptr); } + +void* operator new[](size_t size) { return operator new(size); } +void operator delete[](void* ptr) { operator delete(ptr); } diff --git a/zen/debug_new.h b/zen/debug_new.h index 6007344d..4ef0106e 100644 --- a/zen/debug_new.h +++ b/zen/debug_new.h @@ -7,10 +7,6 @@ #ifndef DEBUGNEW_H_INCLUDED #define DEBUGNEW_H_INCLUDED -#include <string> -#include <sstream> -#include <cstdlib> //malloc(), free() - #ifndef _MSC_VER #error currently for use with MSC only #endif @@ -21,10 +17,9 @@ Better std::bad_alloc overwrite "operator new" to automatically write mini dump and get info about bytes requested 1. Compile "debug_new.cpp" -2. C/C++ -> Advanced: Forced Include File: zen/debug_new.h Minidumps http://msdn.microsoft.com/en-us/library/windows/desktop/ee416349(v=vs.85).aspx ---------- +---------------------------------------------------------------------------------------- 1. Compile "debug_new.cpp" 2. Compile "release" build with: - C/C++ -> General: Debug Information Format: "Program Database" (/Zi). @@ -36,74 +31,9 @@ Optional: - C/C++ -> Optimization: Disabled (/Od) */ -namespace mem_check -{ -class BadAllocDetailed : public std::bad_alloc +namespace debug_tools { -public: - explicit BadAllocDetailed(size_t allocSize) - { - errorMsg = "Memory allocation failed: "; - errorMsg += numberToString(allocSize); - } - - ~BadAllocDetailed() throw() {} - - virtual const char* what() const throw() - { - return errorMsg.c_str(); - } - -private: - template <class T> - static std::string numberToString(const T& number) //convert number to string the C++ way - { - std::ostringstream ss; - ss << number; - return ss.str(); - } - - std::string errorMsg; -}; - -#ifdef _MSC_VER void writeMinidump(); -#endif -} - -inline -void* operator new(size_t size) -{ - void* newMem = ::malloc(size); - if (!newMem) - { -#ifdef _MSC_VER - mem_check::writeMinidump(); -#endif - throw mem_check::BadAllocDetailed(size); - } - return newMem; -} - - -inline -void operator delete(void* ptr) -{ - ::free(ptr); -} - - -inline -void* operator new[](size_t size) -{ - return operator new(size); -} - - -inline -void operator delete[](void* ptr) -{ - operator delete(ptr); } #endif // DEBUGNEW_H_INCLUDED diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index c6f0b5e6..c02453c6 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -14,7 +14,6 @@ #include "notify_removal.h" #include "win.h" //includes "windows.h" #include "long_path_prefix.h" -//#include "privilege.h" #elif defined FFS_LINUX #include <sys/inotify.h> @@ -36,10 +35,8 @@ public: { boost::lock_guard<boost::mutex> dummy(lockAccess); - std::set<Zstring>& output = changedFiles; - if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change - output.insert(L"Overflow!"); + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, L"Overflow!")); else { const char* bufPos = &buffer[0]; @@ -50,10 +47,11 @@ public: const Zstring fullname = dirname + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR)); //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! + //and if this child element is a .ffs_lock file we'll want to ignore all associated events! [&] { - if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice - return; + //if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice; + // return; //note: this is NOT a cross-directory move, which will show up as create + delete if (notifyInfo.Action == FILE_ACTION_MODIFIED) { @@ -63,7 +61,20 @@ public: return; } - output.insert(fullname); + switch (notifyInfo.Action) + { + case FILE_ACTION_ADDED: + case FILE_ACTION_RENAMED_NEW_NAME: //harmonize with "move" which is notified as "create + delete" + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, fullname)); + break; + case FILE_ACTION_REMOVED: + case FILE_ACTION_RENAMED_OLD_NAME: + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_DELETE, fullname)); + break; + case FILE_ACTION_MODIFIED: + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_UPDATE, fullname)); + break; + } }(); if (notifyInfo.NextEntryOffset == 0) @@ -73,16 +84,16 @@ public: } } - //context of main thread - void addChange(const Zstring& dirname) //throw () - { - boost::lock_guard<boost::mutex> dummy(lockAccess); - changedFiles.insert(dirname); - } + ////context of main thread + //void addChange(const Zstring& dirname) //throw () + //{ + // boost::lock_guard<boost::mutex> dummy(lockAccess); + // changedFiles.insert(dirname); + //} //context of main thread - void getChanges(std::vector<Zstring>& output) //throw FileError, ErrorNotExisting + void getChanges(std::vector<DirWatcher::Entry>& output) //throw FileError, ErrorNotExisting { boost::lock_guard<boost::mutex> dummy(lockAccess); @@ -97,7 +108,7 @@ public: throw FileError(msg); } - output.assign(changedFiles.begin(), changedFiles.end()); + output.swap(changedFiles); changedFiles.clear(); } @@ -113,7 +124,7 @@ private: typedef Zbase<wchar_t> BasicWString; //thread safe string class for UI texts boost::mutex lockAccess; - std::set<Zstring> changedFiles; //get rid of duplicate entries (actually occur!) + std::vector<DirWatcher::Entry> changedFiles; std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occured in thread }; @@ -269,7 +280,7 @@ private: { //must release hDir immediately => stop monitoring! worker_.interrupt(); - worker_.join(); + worker_.join(); //we assume precondition "worker.joinable()"!!! //now hDir should have been released removalRequested = true; @@ -314,9 +325,9 @@ DirWatcher::~DirWatcher() } -std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError +std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError { - std::vector<Zstring> output; + std::vector<Entry> output; //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) @@ -330,7 +341,7 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>& process boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50)); } - output.push_back(pimpl_->dirname); //report removal as change to main directory + output.push_back(Entry(ACTION_DELETE, pimpl_->dirname)); //report removal as change to main directory } else //the normal case... pimpl_->shared->getChanges(output); //throw FileError @@ -412,12 +423,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(), IN_ONLYDIR | //watch directories only IN_DONT_FOLLOW | //don't follow symbolic links + IN_CREATE | IN_MODIFY | IN_CLOSE_WRITE | - IN_MOVE | - IN_CREATE | IN_DELETE | IN_DELETE_SELF | + IN_MOVED_FROM | + IN_MOVED_TO | IN_MOVE_SELF); if (wd == -1) { @@ -440,7 +452,7 @@ DirWatcher::~DirWatcher() } -std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //throw FileError +std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { //non-blocking call, see O_NONBLOCK std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); @@ -450,12 +462,12 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //thro { if (errno == EINTR || //Interrupted function call; When this happens, you should try the call again. errno == EAGAIN) //Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading - return std::vector<Zstring>(); + return std::vector<Entry>(); throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)) + L"\n\n" + getLastErrorFormatted()); } - std::set<Zstring> tmp; //get rid of duplicate entries (actually occur!) + std::vector<Entry> output; ssize_t bytePos = 0; while (bytePos < bytesRead) @@ -469,14 +481,26 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //thro { //Note: evt.len is NOT the size of the evt.name c-string, but the array size including all padding 0 characters! //It may be even 0 in which case evt.name must not be used! - tmp.insert(iter->second + evt.name); + const Zstring fullname = iter->second + evt.name; + + if ((evt.mask & IN_CREATE) || + (evt.mask & IN_MOVED_TO)) + output.push_back(Entry(ACTION_CREATE, fullname)); + else if ((evt.mask & IN_MODIFY) || + (evt.mask & IN_CLOSE_WRITE)) + output.push_back(Entry(ACTION_UPDATE, fullname)); + else if ((evt.mask & IN_DELETE ) || + (evt.mask & IN_DELETE_SELF) || + (evt.mask & IN_MOVE_SELF ) || + (evt.mask & IN_MOVED_FROM)) + output.push_back(Entry(ACTION_DELETE, fullname)); } } bytePos += sizeof(struct inotify_event) + evt.len; } - return std::vector<Zstring>(tmp.begin(), tmp.end()); + return output; } #endif diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index 56497040..b0df48bd 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -24,10 +24,10 @@ namespace zen removal of top watched directory is NOT notified! Windows: removal of top watched directory also NOT notified (e.g. brute force usb stick removal) however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped! - Renaming of top watched directory handled incorrectly: Not notified(!) + changes in subfolders - report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) + Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders + now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) - Overcome all issues portably: check existence of watched directory externally + reinstall watch after changes in directory structure (added directories) are possible + Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are possible */ class DirWatcher { @@ -35,8 +35,24 @@ public: DirWatcher(const Zstring& directory); //throw FileError, ErrorNotExisting ~DirWatcher(); + enum ActionType + { + ACTION_CREATE, + ACTION_UPDATE, + ACTION_DELETE, + }; + + struct Entry + { + Entry() : action_(ACTION_CREATE) {} + Entry(ActionType action, const Zstring& filename) : action_(action), filename_(filename) {} + + ActionType action_; + Zstring filename_; + }; + //extract accumulated changes since last call - std::vector<Zstring> getChanges(const std::function<void()>& processGuiMessages); //throw FileError + std::vector<Entry> getChanges(const std::function<void()>& processGuiMessages); //throw FileError private: DirWatcher(const DirWatcher&); @@ -20,12 +20,12 @@ Manage DLL function and library ownership - full value semantics Usage: - typedef BOOL (WINAPI* IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process); - const zen::SysDllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process"); + typedef BOOL (WINAPI* FunType_IsWow64Process)(HANDLE hProcess, PBOOL Wow64Process); + const zen::SysDllFun<FunType_IsWow64Process> isWow64Process(L"kernel32.dll", "IsWow64Process"); if (isWow64Process) ... use function ptr ... Usage 2: - #define DEF_DLL_FUN(name) DllFun<dll_ns::FunType_##name> name(getDllName(), dll_ns::funName_##name); + #define DEF_DLL_FUN(name) DllFun<dll_ns::FunType_##name> name(dll_ns::getDllName(), dll_ns::funName_##name); DEF_DLL_FUN(funname1); DEF_DLL_FUN(funname2); DEF_DLL_FUN(funname3); */ diff --git a/zen/error_log.h b/zen/error_log.h index e42ca89d..401581d7 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -7,11 +7,13 @@ #ifndef ERRORLOGGING_H_INCLUDED #define ERRORLOGGING_H_INCLUDED +#include <cassert> #include <algorithm> #include <vector> #include <string> -#include <zen/time.h> -#include <zen/i18n.h> +#include "time.h" +#include "i18n.h" +#include "string_base.h" namespace zen { @@ -23,20 +25,23 @@ enum MessageType TYPE_FATAL_ERROR = 0x8, }; +typedef Zbase<wchar_t> MsgString; //std::wstring may employ small string optimization: we cannot accept bloating the "logEntries" memory block below (think 1 million entries) + struct LogEntry { - time_t time; - MessageType type; - std::wstring message; + time_t time; + MessageType type; + MsgString message; }; -std::wstring formatMessage(const LogEntry& msg); +MsgString formatMessage(const LogEntry& msg); class ErrorLog { public: - void logMsg(const std::wstring& message, MessageType type); + template <class String> + void logMsg(const String& message, MessageType type); int getItemCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const; @@ -58,18 +63,11 @@ private: - - - - - - //######################## implementation ########################## - -inline -void ErrorLog::logMsg(const std::wstring& message, zen::MessageType type) +template <class String> inline +void ErrorLog::logMsg(const String& message, zen::MessageType type) { - const LogEntry newEntry = { std::time(nullptr), type, message }; + const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(message) }; logEntries.push_back(newEntry); } @@ -83,7 +81,7 @@ int ErrorLog::getItemCount(int typeFilter) const namespace { -std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage +MsgString formatMessageImpl(const LogEntry& entry) //internal linkage { auto getTypeName = [&]() -> std::wstring { @@ -98,10 +96,11 @@ std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage case TYPE_FATAL_ERROR: return _("Fatal Error"); } + assert(false); return std::wstring(); }; - std::wstring formattedText = L"[" + formatTime<std::wstring>(FORMAT_TIME, localTime(entry.time)) + L"] " + getTypeName() + L": "; + MsgString formattedText = L"[" + formatTime<MsgString>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<MsgString>(getTypeName()) + L": "; const size_t prefixLen = formattedText.size(); for (auto iter = entry.message.begin(); iter != entry.message.end(); ) @@ -109,7 +108,7 @@ std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage { formattedText += L'\n'; - std::wstring blanks; + MsgString blanks; blanks.resize(prefixLen, L' '); formattedText += blanks; @@ -126,8 +125,8 @@ std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage } } -inline std::wstring formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); } - +inline +MsgString formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); } } #endif //ERRORLOGGING_H_INCLUDED diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index a646a13f..589057ad 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -680,227 +680,247 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr } //####################################### DST hack ########################################### - //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links - //note: setting privileges requires admin rights! - - //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait! - //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20 - //-> enable as soon it turns out it is required! - - /*const int retryInterval = 50; - const int maxRetries = 2000 / retryInterval; - for (int i = 0; i < maxRetries; ++i) { - */ + //extra scope for debug check below - /* - if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) - ::Sleep(retryInterval); //wait then retry - else //success or unknown failure - break; - } - */ + //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links + //note: setting privileges requires admin rights! - //temporarily reset read-only flag if required - DWORD attribs = INVALID_FILE_ATTRIBUTES; - ZEN_ON_SCOPE_EXIT( - if (attribs != INVALID_FILE_ATTRIBUTES) - ::SetFileAttributes(applyLongPathPrefix(filename).c_str(), attribs); - ); + //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait! + //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20 + //-> enable as soon it turns out it is required! - auto removeReadonly = [&]() -> bool //may need to remove the readonly-attribute (e.g. on FAT usb drives) - { - if (attribs == INVALID_FILE_ATTRIBUTES) + /*const int retryInterval = 50; + const int maxRetries = 2000 / retryInterval; + for (int i = 0; i < maxRetries; ++i) { - const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); - if (tmpAttr == INVALID_FILE_ATTRIBUTES) - throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + */ - if (tmpAttr & FILE_ATTRIBUTE_READONLY) + /* + if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) + ::Sleep(retryInterval); //wait then retry + else //success or unknown failure + break; + } + */ + //temporarily reset read-only flag if required + DWORD attribs = INVALID_FILE_ATTRIBUTES; + ZEN_ON_SCOPE_EXIT( + if (attribs != INVALID_FILE_ATTRIBUTES) + ::SetFileAttributes(applyLongPathPrefix(filename).c_str(), attribs); + ); + + auto removeReadonly = [&]() -> bool //may need to remove the readonly-attribute (e.g. on FAT usb drives) + { + if (attribs == INVALID_FILE_ATTRIBUTES) { - if (!::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL)) - throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); + if (tmpAttr == INVALID_FILE_ATTRIBUTES) + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - attribs = tmpAttr; //reapplied on scope exit - return true; + if (tmpAttr & FILE_ATTRIBUTE_READONLY) + { + if (!::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL)) + throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + + attribs = tmpAttr; //reapplied on scope exit + return true; + } } - } - return false; - }; - - auto openFile = [&](bool conservativeApproach) - { - return ::CreateFile(applyLongPathPrefix(filename).c_str(), - (conservativeApproach ? - //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently! - //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 - //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first: - GENERIC_READ | GENERIC_WRITE : - //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call! - //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 - //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second. - FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory - (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks - nullptr); - }; - - HANDLE hFile = INVALID_HANDLE_VALUE; - for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :) - { - //1. be conservative - hFile = openFile(true); - if (hFile == INVALID_HANDLE_VALUE) - { - if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) - if (removeReadonly()) - continue; + return false; + }; - //2. be a *little* fancy - hFile = openFile(false); + auto openFile = [&](bool conservativeApproach) + { + return ::CreateFile(applyLongPathPrefix(filename).c_str(), + (conservativeApproach ? + //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently! + //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 + //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first: + GENERIC_READ | GENERIC_WRITE : + //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call! + //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 + //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second. + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory + (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks + nullptr); + }; + + HANDLE hFile = INVALID_HANDLE_VALUE; + for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :) + { + //1. be conservative + hFile = openFile(true); if (hFile == INVALID_HANDLE_VALUE) { - if (::GetLastError() == ERROR_ACCESS_DENIED) + if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) if (removeReadonly()) continue; - //3. after these herculean stunts we give up... - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + //2. be a *little* fancy + hFile = openFile(false); + if (hFile == INVALID_HANDLE_VALUE) + { + if (::GetLastError() == ERROR_ACCESS_DENIED) + if (removeReadonly()) + continue; + + //3. after these herculean stunts we give up... + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + } } + break; } - break; - } - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - auto isNullTime = [](const FILETIME& ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; }; - if (!::SetFileTime(hFile, //__in HANDLE hFile, - !isNullTime(creationTime) ? &creationTime : nullptr, //__in_opt const FILETIME *lpCreationTime, - nullptr, //__in_opt const FILETIME *lpLastAccessTime, - &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime - { - auto lastErr = ::GetLastError(); + auto isNullTime = [](const FILETIME& ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; }; - //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 - if (lastErr == ERROR_ACCESS_DENIED) + if (!::SetFileTime(hFile, //__in HANDLE hFile, + !isNullTime(creationTime) ? &creationTime : nullptr, //__in_opt const FILETIME *lpCreationTime, + nullptr, //__in_opt const FILETIME *lpLastAccessTime, + &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime { - //dynamically load windows API function: available with Windows Vista and later - typedef BOOL (WINAPI* SetFileInformationByHandleFunc)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); + auto lastErr = ::GetLastError(); - const SysDllFun<SetFileInformationByHandleFunc> setFileInformationByHandle(L"kernel32.dll", "SetFileInformationByHandle"); - if (setFileInformationByHandle) //if not: let the original error propagate! + //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 + if (lastErr == ERROR_ACCESS_DENIED) { - auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter! - { - if (!setFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo))) //__in DWORD dwBufferSize - throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - }; + //dynamically load windows API function: available with Windows Vista and later + typedef BOOL (WINAPI* SetFileInformationByHandleFunc)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); - auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER + const SysDllFun<SetFileInformationByHandleFunc> setFileInformationByHandle(L"kernel32.dll", "SetFileInformationByHandle"); + if (setFileInformationByHandle) //if not: let the original error propagate! { - LARGE_INTEGER tmp = {}; - tmp.LowPart = ft.dwLowDateTime; - tmp.HighPart = ft.dwHighDateTime; - return tmp; - }; - //--------------------------------------------------------------------------- - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (::GetFileInformationByHandle(hFile, &fileInfo)) - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter! { - FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change" - basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!! - basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); // - if (!isNullTime(creationTime)) - basicInfo.CreationTime = toLargeInteger(creationTime); - - //set file time + attributes - setFileInfo(basicInfo); //throw FileError - - try //... to restore original file attributes + if (!setFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo, //__in LPVOID lpFileInformation, + sizeof(basicInfo))) //__in DWORD dwBufferSize + throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + }; + + auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER + { + LARGE_INTEGER tmp = {}; + tmp.LowPart = ft.dwLowDateTime; + tmp.HighPart = ft.dwHighDateTime; + return tmp; + }; + //--------------------------------------------------------------------------- + + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (::GetFileInformationByHandle(hFile, &fileInfo)) + if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { - FILE_BASIC_INFO basicInfo2 = {}; - basicInfo2.FileAttributes = fileInfo.dwFileAttributes; - setFileInfo(basicInfo2); //throw FileError + FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change" + basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!! + basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); // + if (!isNullTime(creationTime)) + basicInfo.CreationTime = toLargeInteger(creationTime); + + //set file time + attributes + setFileInfo(basicInfo); //throw FileError + + try //... to restore original file attributes + { + FILE_BASIC_INFO basicInfo2 = {}; + basicInfo2.FileAttributes = fileInfo.dwFileAttributes; + setFileInfo(basicInfo2); //throw FileError + } + catch (FileError&) {} + + lastErr = ERROR_SUCCESS; } - catch (FileError&) {} - - lastErr = ERROR_SUCCESS; - } + } } - } - std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr); + std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr); - //add more meaningful message: FAT accepts only a subset of the NTFS date range - if (lastErr == ERROR_INVALID_PARAMETER && - dst::isFatDrive(filename)) - { - //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!! - auto fmtDate = [](const FILETIME& ft) -> Zstring + //add more meaningful message: FAT accepts only a subset of the NTFS date range + if (lastErr == ERROR_INVALID_PARAMETER && + dst::isFatDrive(filename)) { - SYSTEMTIME st = {}; - if (!::FileTimeToSystemTime(&ft, //__in const FILETIME *lpFileTime, - &st)) //__out LPSYSTEMTIME lpSystemTime - return Zstring(); - - Zstring dateTime; + //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!! + auto fmtDate = [](const FILETIME& ft) -> Zstring { - const int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - if (bufferSize > 0) + SYSTEMTIME st = {}; + if (!::FileTimeToSystemTime(&ft, //__in const FILETIME *lpFileTime, + &st)) //__out LPSYSTEMTIME lpSystemTime + return Zstring(); + + Zstring dateTime; { - std::vector<wchar_t> buffer(bufferSize); - if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale, - 0, //_In_ DWORD dwFlags, - &st, //_In_opt_ const SYSTEMTIME *lpDate, - nullptr, //_In_opt_ LPCTSTR lpFormat, - &buffer[0], //_Out_opt_ LPTSTR lpDateStr, - bufferSize) > 0) //_In_ int cchDate - dateTime = &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + const int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); + if (bufferSize > 0) + { + std::vector<wchar_t> buffer(bufferSize); + if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale, + 0, //_In_ DWORD dwFlags, + &st, //_In_opt_ const SYSTEMTIME *lpDate, + nullptr, //_In_opt_ LPCTSTR lpFormat, + &buffer[0], //_Out_opt_ LPTSTR lpDateStr, + bufferSize) > 0) //_In_ int cchDate + dateTime = &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + } } - } - const int bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - if (bufferSize > 0) - { - std::vector<wchar_t> buffer(bufferSize); - if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0) + const int bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); + if (bufferSize > 0) { - dateTime += L" "; - dateTime += &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + std::vector<wchar_t> buffer(bufferSize); + if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0) + { + dateTime += L" "; + dateTime += &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + } } - } - return dateTime; - }; + return dateTime; + }; - errorMsg += std::wstring(L"\nA FAT volume can only store dates between 1980 and 2107:\n") + - L"\twrite (UTC): \t" + fmtDate(lastWriteTime) + - (!isNullTime(creationTime) ? L"\n\tcreate (UTC): \t" + fmtDate(creationTime) : L""); - } + errorMsg += std::wstring(L"\nA FAT volume can only store dates between 1980 and 2107:\n") + + L"\twrite (UTC): \t" + fmtDate(lastWriteTime) + + (!isNullTime(creationTime) ? L"\n\tcreate (UTC): \t" + fmtDate(creationTime) : L""); + } - if (lastErr != ERROR_SUCCESS) - throw FileError(errorMsg); + if (lastErr != ERROR_SUCCESS) + throw FileError(errorMsg); + } } - #ifndef NDEBUG //dst hack: verify data written if (dst::isFatDrive(filename) && !dirExists(filename)) //throw() { - WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; - assert(::GetFileAttributesEx(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpFileName, - GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, - &debugeAttr)); //__out LPVOID lpFileInformation + FILETIME creationTimeDbg = {}; + FILETIME lastWriteTimeDbg = {}; + + HANDLE hFile = ::CreateFile(applyLongPathPrefix(filename).c_str(), + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + 0, + nullptr); + assert(hFile != INVALID_HANDLE_VALUE); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - assert(::CompareFileTime(&debugeAttr.ftCreationTime, &creationTime) == 0); - assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &lastWriteTime) == 0); + assert(::GetFileTime(hFile, //probably more up to date than GetFileAttributesEx()!? + &creationTimeDbg, + nullptr, + &lastWriteTimeDbg)); + + assert(::CompareFileTime(&creationTimeDbg, &creationTime) == 0); + assert(::CompareFileTime(&lastWriteTimeDbg, &lastWriteTime) == 0); } + //CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" seems to + //NOT PRESERVE the creation time of the .ffs_tmp file, but "reuses" whatever creation time the old "file.txt" had! + //this problem is therefore NOT detected by the check above! + //However during the next comparison the DST hack will be applied correctly. + #endif #elif defined FFS_LINUX @@ -1736,7 +1756,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, //stream-copy sourceFile to targetFile bool eof = false; - bool silentFailure = true; //try to detect failure reading encrypted files + bool someBytesWritten = false; //try to detect failure reading encrypted files do { DWORD bytesRead = 0; @@ -1775,14 +1795,14 @@ void copyFileWindowsSparse(const Zstring& sourceFile, callback->updateCopyStatus(Int64(bytesRead)); //throw X! if (bytesRead > 0) - silentFailure = false; + someBytesWritten = true; } while (!eof); //DST hack not required, since both source and target volumes cannot be FAT! //::BackupRead() silently fails reading encrypted files -> double check! - if (silentFailure && UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) + if (!someBytesWritten && UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)! throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + L"(unknown error)"); @@ -2044,7 +2064,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile, //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! - //trying to copy huge sparse files may fail with ERROR_DISK_FULL + //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function if (canCopyAsSparse(sourceFile, targetFile)) //throw () throw ErrorShouldCopyAsSparse(L"sparse dummy value2"); diff --git a/zen/file_handling.h b/zen/file_handling.h index 878c467c..e9e1685d 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -100,7 +100,7 @@ struct CallbackCopyFile //may throw: //Linux: unconditionally - //Windows: first exception is swallowed, updateCopyStatus() is then called again where it should throw again and exception will propagate as expected + //Windows: first exception is swallowed, updateCopyStatus() is then called again where it should throw again and the exception will propagate as expected virtual void updateCopyStatus(Int64 bytesDelta) = 0; //accummulated delta != file size! consider ADS, sparse, compressed files }; } diff --git a/zen/last_error.h b/zen/last_error.h index 356192ab..72d54d48 100644 --- a/zen/last_error.h +++ b/zen/last_error.h @@ -76,18 +76,18 @@ ErrorCode getLastError() #ifdef FFS_WIN return ::GetLastError(); #elif defined FFS_LINUX - return errno; + return errno; //don't use "::", errno is a macro! #endif } inline std::wstring getLastErrorFormatted(ErrorCode lastError) { -#ifdef FFS_WIN //determine error code if none was specified if (lastError == 0) - lastError = ::GetLastError(); + lastError = getLastError(); +#ifdef FFS_WIN std::wstring output = _("Windows Error Code %x:"); replace(output, L"%x", numberTo<std::wstring>(lastError)); @@ -108,10 +108,6 @@ std::wstring getLastErrorFormatted(ErrorCode lastError) return output; #elif defined FFS_LINUX - //determine error code if none was specified - if (lastError == 0) - lastError = errno; //don't use "::", errno is a macro! - std::wstring output = _("Linux Error Code %x:"); replace(output, L"%x", numberTo<std::wstring>(lastError)); diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 6593540a..c35dca56 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -29,9 +29,9 @@ using namespace zen; +#ifdef FFS_WIN namespace { -#ifdef FFS_WIN /* Performance test: delete 1000 files ------------------------------------ @@ -42,41 +42,42 @@ IFileOperation - multiple files 2,1s => SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! -Nevertheless, let's use IFileOperation for better error reporting! +Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! */ const bool useIFileOperation = vistaOrLater(); //caveat: function scope static initialization is not thread-safe in VS 2010! -//(try to) enhance error messages by showing which processed lock the file -Zstring getLockingProcessNames(const Zstring& filename) //throw(), empty string if none found or error occurred +struct CallbackData { - if (vistaOrLater()) - { - using namespace fileop; - const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); - const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + CallbackData(CallbackRecycling* cb) : + userCallback(cb), + exceptionInUserCallback(false) {} - if (getLockingProcesses && freeString) + CallbackRecycling* const userCallback; //optional! + bool exceptionInUserCallback; +}; + +bool recyclerCallback(const wchar_t* filename, void* sink) +{ + CallbackData& cbd = *static_cast<CallbackData*>(sink); //sink is NOT optional here + + if (cbd.userCallback) + try { - const wchar_t* procList = nullptr; - if (getLockingProcesses(filename.c_str(), procList)) - { - ZEN_ON_SCOPE_EXIT(freeString(procList)); - return procList; - } + cbd.userCallback->updateStatus(filename); //throw ? } - } - return Zstring(); + catch (...) + { + cbd.exceptionInUserCallback = true; //try again outside the C call stack! + return false; + } + return true; } -#endif } - -bool zen::recycleOrDelete(const Zstring& filename) //throw FileError +void zen::recycleOrDelete(const std::vector<Zstring>& filenames, CallbackRecycling* callback) { - if (!somethingExists(filename)) - return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! - -#ifdef FFS_WIN + if (filenames.empty()) + return; //::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL); //warning: moving long file paths to recycler does not work! //both ::SHFileOperation() and ::IFileOperation() cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success @@ -89,32 +90,40 @@ bool zen::recycleOrDelete(const Zstring& filename) //throw FileError const DllFun<FunType_getLastError> getLastError (getDllName(), funName_getLastError); if (!moveToRecycler || !getLastError) - throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)) + L"\n\n" + + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filenames[0])) + L"\n\n" + replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); - std::vector<const wchar_t*> filenames; - filenames.push_back(filename.c_str()); + std::vector<const wchar_t*> cNames; + for (auto iter = filenames.begin(); iter != filenames.end(); ++iter) //caution to not create temporary strings here!! + cNames.push_back(iter->c_str()); - if (!moveToRecycler(&filenames[0], filenames.size())) + CallbackData cbd(callback); + if (!moveToRecycler(&cNames[0], cNames.size(), recyclerCallback, &cbd)) { - const std::wstring shortMsg = replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)); + if (cbd.exceptionInUserCallback) //now we may throw... + callback->updateStatus(Zstring()); //should throw again! - //if something is locking our file -> emit better error message! - const Zstring procList = getLockingProcessNames(filename); //throw() - if (!procList.empty()) - throw FileError(shortMsg + L"\n\n" + _("The file is locked by another process:") + L"\n" + procList); + std::wstring filenameFmt = fmtFileName(filenames[0]); //probably not the correct file name for file lists larger than 1! + if (filenames.size() > 1) + filenameFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one - throw FileError(shortMsg + L"\n\n" + getLastError()); + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", filenameFmt) + + L"\n\n" + getLastError()); //already includes details about locking errors! } } else //regular recycle bin usage: available since XP { - const Zstring& filenameDoubleNull = filename + L'\0'; + Zstring filenamesDoubleNull; + for (auto iter = filenames.begin(); iter != filenames.end(); ++iter) + { + filenamesDoubleNull += *iter; + filenamesDoubleNull += L'\0'; + } SHFILEOPSTRUCT fileOp = {}; fileOp.hwnd = nullptr; fileOp.wFunc = FO_DELETE; - fileOp.pFrom = filenameDoubleNull.c_str(); + fileOp.pFrom = filenamesDoubleNull.c_str(); fileOp.pTo = nullptr; fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; @@ -124,9 +133,22 @@ bool zen::recycleOrDelete(const Zstring& filename) //throw FileError //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) { - throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename))); + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filenames[0]))); //probably not the correct file name for file list larger than 1! } } +} +#endif + + +bool zen::recycleOrDelete(const Zstring& filename) //throw FileError +{ + if (!somethingExists(filename)) + return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! + +#ifdef FFS_WIN + std::vector<Zstring> filenames; + filenames.push_back(filename); + recycleOrDelete(filenames, nullptr); //throw FileError #elif defined FFS_LINUX GFile* file = g_file_new_for_path(filename.c_str()); //never fails according to docu diff --git a/zen/recycler.h b/zen/recycler.h index cdadf371..4d33477d 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -7,6 +7,7 @@ #ifndef RECYCLER_H_INCLUDED #define RECYCLER_H_INCLUDED +#include <vector> #include <zen/file_error.h> #include <zen/zstring.h> @@ -40,8 +41,18 @@ enum StatusRecycler STATUS_REC_MISSING, STATUS_REC_UNKNOWN }; - StatusRecycler recycleBinStatus(const Zstring& pathName); //test existence of Recycle Bin API for certain path + +struct CallbackRecycling +{ + virtual ~CallbackRecycling() {} + + //may throw: first exception is swallowed, updateStatus() is then called again where it should throw again and the exception will propagate as expected + virtual void updateStatus(const Zstring& currentItem) = 0; +}; + +void recycleOrDelete(const std::vector<Zstring>& filenames, //throw FileError, return "true" if file/dir was actually deleted + CallbackRecycling* callback); //optional #endif } diff --git a/zen/scroll_window_under_cursor.cpp b/zen/scroll_window_under_cursor.cpp new file mode 100644 index 00000000..6031cd88 --- /dev/null +++ b/zen/scroll_window_under_cursor.cpp @@ -0,0 +1,77 @@ +// ************************************************************************** +// * 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 (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include <cassert> +#include "win.h" //includes "windows.h" +#include "Windowsx.h" //WM_MOUSEWHEEL + +//redirect mouse wheel events directly to window under cursor rather than window having input focus +//implementing new Windows Vista UI guidelines: http://msdn.microsoft.com/en-us/library/bb545459.aspx#wheel +//this is confirmed to be required for at least Windows 2000 to Windows 8 +//on Ubuntu Linux, this is already the default behavior + +//Usage: just include this file into a Windows project + +namespace +{ +#ifndef WM_MOUSEHWHEEL //MinGW is clueless... +#define WM_MOUSEHWHEEL 0x020E +#endif + +LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) +{ + //"if nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function + //without further processing and should return the value returned by CallNextHookEx" + if (nCode >= 0) + { + MSG& msgInfo = *reinterpret_cast<MSG*>(lParam); + + if (msgInfo.message == WM_MOUSEWHEEL || + msgInfo.message == WM_MOUSEHWHEEL) + { + POINT pt = {}; + pt.x = GET_X_LPARAM(msgInfo.lParam); //yes, there's also msgInfo.pt, but let's not take chances + pt.y = GET_Y_LPARAM(msgInfo.lParam); // + + //visible child window directly under cursor; attention: not necessarily from our process! + //http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx + if (HWND hWin = ::WindowFromPoint(pt)) + if (msgInfo.hwnd != hWin && ::GetCapture() == nullptr) + { + DWORD winProcessId = 0; + ::GetWindowThreadProcessId( //no-fail! + hWin, //_In_ HWND hWnd, + &winProcessId); //_Out_opt_ LPDWORD lpdwProcessId + if (winProcessId == ::GetCurrentProcessId()) //no-fail! + msgInfo.hwnd = hWin; //it would be a bug to set handle from another process here + } + } + } + + return ::CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +struct Dummy +{ + Dummy() + { + hHook = ::SetWindowsHookEx(WH_GETMESSAGE, //__in int idHook, + mouseInputHook, //__in HOOKPROC lpfn, + nullptr, //__in HINSTANCE hMod, + ::GetCurrentThreadId()); //__in DWORD dwThreadId + assert(hHook); + } + + ~Dummy() + { + if (hHook) + ::UnhookWindowsHookEx(hHook); + } + +private: + HHOOK hHook; +} dummy; +} diff --git a/zen/string_base.h b/zen/string_base.h index 19bf6267..c3ddde36 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -209,10 +209,12 @@ public: typedef Char& reference; typedef const Char& const_reference; typedef Char value_type; - const Char* begin() const; - const Char* end() const; Char* begin(); - Char* end(); + Char* end (); + const Char* begin() const; + const Char* end () const; + const Char* cbegin() const { return begin(); } + const Char* cend () const { return end(); } //std::string functions size_t length() const; @@ -235,7 +237,8 @@ public: void swap(Zbase& other); void push_back(Char val) { operator+=(val); } //STL access - Zbase& operator=(Zbase source); + Zbase& operator=(const Zbase& source); + Zbase& operator=(Zbase&& tmp); Zbase& operator=(const Char* source); Zbase& operator=(Char source); Zbase& operator+=(const Zbase& other); @@ -653,9 +656,18 @@ Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::append(const Char* source, size_t len) template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP> other) //unifying assignment: no need for r-value reference optimization! +Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(const Zbase<Char, SP, AP>& other) +{ + Zbase<Char, SP, AP>(other).swap(*this); + return *this; +} + + +template <class Char, template <class, class> class SP, class AP> inline +Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP>&& tmp) { - swap(other); + //don't use unifying assignment but save one move-construction in the r-value case instead! + swap(tmp); return *this; } diff --git a/zen/thread.h b/zen/thread.h index 440940ce..432a521e 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -20,6 +20,7 @@ #endif #ifdef _MSC_VER #pragma warning(disable : 4702) //unreachable code +#pragma warning(disable : 4913) //user defined binary operator ',' exists but no overload could convert all operands, default built-in binary operator ',' used #endif #include <boost/thread.hpp> @@ -28,7 +29,8 @@ #pragma GCC diagnostic pop #endif #ifdef _MSC_VER -#pragma warning(default : 4702) //unreachable code +#pragma warning(default : 4702) +#pragma warning(default : 4913) #endif namespace zen diff --git a/zen/tick_count.h b/zen/tick_count.h index 962ebcb0..4f1a047e 100644 --- a/zen/tick_count.h +++ b/zen/tick_count.h @@ -24,7 +24,7 @@ class TickVal; std::int64_t operator-(const TickVal& lhs, const TickVal& rhs); std::int64_t ticksPerSec(); //return 0 on error -TickVal getTicks(); //return invalid value on error +TickVal getTicks(); //return invalid value on error: !TickVal::isValid() |