summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
Diffstat (limited to 'zen')
-rw-r--r--zen/IFileOperation/file_op.cpp241
-rw-r--r--zen/IFileOperation/file_op.h13
-rw-r--r--zen/com_error.h2
-rw-r--r--zen/com_ptr.h24
-rw-r--r--zen/debug_new.cpp63
-rw-r--r--zen/debug_new.h74
-rw-r--r--zen/dir_watcher.cpp78
-rw-r--r--zen/dir_watcher.h24
-rw-r--r--zen/dll.h6
-rw-r--r--zen/error_log.h43
-rw-r--r--zen/file_handling.cpp378
-rw-r--r--zen/file_handling.h2
-rw-r--r--zen/last_error.h10
-rw-r--r--zen/recycler.cpp98
-rw-r--r--zen/recycler.h13
-rw-r--r--zen/scroll_window_under_cursor.cpp77
-rw-r--r--zen/string_base.h24
-rw-r--r--zen/thread.h4
-rw-r--r--zen/tick_count.h2
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&);
diff --git a/zen/dll.h b/zen/dll.h
index 6f139ac3..3e7a0655 100644
--- a/zen/dll.h
+++ b/zen/dll.h
@@ -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()
bgstack15