From 9d071d2a2cec9a7662a02669488569a017f0ea35 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Mon, 13 Feb 2017 21:25:04 -0700 Subject: 8.9 --- zen/recycler.cpp | 395 ++++++++++--------------------------------------------- 1 file changed, 72 insertions(+), 323 deletions(-) mode change 100644 => 100755 zen/recycler.cpp (limited to 'zen/recycler.cpp') diff --git a/zen/recycler.cpp b/zen/recycler.cpp old mode 100644 new mode 100755 index d8ee58c4..02ea026a --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -1,323 +1,72 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#include "recycler.h" -#include "file_access.h" - -#ifdef ZEN_WIN - #include "thread.h" - - #ifdef ZEN_WIN_VISTA_AND_LATER - #include "vista_file_op.h" - #else - #include "com_tools.h" - #endif - -#elif defined ZEN_LINUX - #include - #include - #include "scope_guard.h" - -#elif defined ZEN_MAC - #include -#endif - -using namespace zen; - - -#ifdef ZEN_WIN -void zen::recycleOrDeleteIfExists(const std::vector& itemPaths, const std::function& onRecycleItem) -{ - //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 - //both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix! - - /* - Performance test: delete 1000 files - ------------------------------------ - SHFileOperation - single file 33s - SHFileOperation - multiple files 2,1s - IFileOperation - single file 33s - IFileOperation - multiple files 2,1s - - => SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! - - Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! - */ - -#ifdef ZEN_WIN_VISTA_AND_LATER //Win Vista recycle bin usage: 1. good error reporting 2. late failure - vista::moveToRecycleBinIfExists(itemPaths, onRecycleItem); //throw FileError - -#else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure - //TODO: this XP version fails if any item is not existing violating this function's API - if (itemPaths.empty()) return; - - Zstring itemPathsDoubleNull; - for (const Zstring& itemPath : itemPaths) - { - itemPathsDoubleNull += itemPath; - itemPathsDoubleNull += L'\0'; - } - - SHFILEOPSTRUCT fileOp = {}; - fileOp.hwnd = nullptr; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = itemPathsDoubleNull.c_str(); - fileOp.pTo = nullptr; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; - fileOp.fAnyOperationsAborted = false; - fileOp.hNameMappings = nullptr; - fileOp.lpszProgressTitle = nullptr; - - //"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) //fails if items are not existing! - { - std::wstring itempathFmt = fmtPath(itemPaths[0]); //probably not the correct file name for file lists larger than 1! - if (itemPaths.size() > 1) - itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt)); - } -#endif -} -#endif - - -bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError -{ -#ifdef ZEN_WIN -#ifdef ZEN_WIN_VISTA_AND_LATER - return vista::moveToRecycleBinIfExists({ itemPath }, nullptr) != 0; //throw FileError - -#else - Zstring itemPathDoubleNull = itemPath; - itemPathDoubleNull += L'\0'; - - SHFILEOPSTRUCT fileOp = {}; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = itemPathDoubleNull.c_str(); - fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; - - if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if item is not existing! - { - try - { - if (!getItemTypeIfExists(itemPath)) //throw FileError - return false; - } - catch (FileError&) {} //previous exception is more relevant - - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath))); - } - return true; -#endif - -#elif defined ZEN_LINUX - GFile* file = ::g_file_new_for_path(itemPath.c_str()); //never fails according to docu - ZEN_ON_SCOPE_EXIT(g_object_unref(file);) - - GError* error = nullptr; - ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); - - if (!::g_file_trash(file, nullptr, &error)) - { - const Opt type = getItemTypeIfExists(itemPath); //throw FileError - if (!type) - return false; - - const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)); - if (!error) - throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this - - //implement same behavior as in Windows: if recycler is not existing, delete permanently - if (error->code == G_IO_ERROR_NOT_SUPPORTED) - { - if (*type == ItemType::FOLDER) - removeDirectoryPlainRecursion(itemPath); //throw FileError - else - removeFilePlain(itemPath); //throw FileError - return true; - } - - throw FileError(errorMsg, replaceCpy(L"Glib Error Code %x:", L"%x", numberTo(error->code)) + L" " + utfCvrtTo(error->message)); - //g_quark_to_string(error->domain) - } - return true; - -#elif defined ZEN_MAC - //we cannot use FSPathMoveObjectToTrashSync directly since it follows symlinks! - - static_assert(sizeof(Zchar) == sizeof(char), ""); - const UInt8* itemPathUtf8 = reinterpret_cast(itemPath.c_str()); - - auto throwFileError = [&](OSStatus oss) - { - const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)); - std::wstring errorDescr = L"OSStatus Code " + numberTo(oss); - - if (const char* description = ::GetMacOSStatusCommentString(oss)) //found no documentation for proper use of GetMacOSStatusCommentString - errorDescr += L": " + utfCvrtTo(description); - throw FileError(errorMsg, errorDescr); - }; - - //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin unsupported" - //both "not supported" and "item missing" let FSMoveObjectToTrashSync fail with -120 - const Opt type = getItemTypeIfExists(itemPath); //throw FileError - if (!type) - return false; - - FSRef objectRef = {}; //= POD structure not a pointer type! - OSStatus rv = ::FSPathMakeRefWithOptions(itemPathUtf8, //const UInt8 *path, - kFSPathMakeRefDoNotFollowLeafSymlink, //OptionBits options, - &objectRef, //FSRef *ref, - nullptr); //Boolean *isDirectory - if (rv != noErr) - throwFileError(rv); - - //deprecated since OS X 10.8!!! "trashItemAtURL" should be used instead - OSStatus rv2 = ::FSMoveObjectToTrashSync(&objectRef, //const FSRef *source, - nullptr, //FSRef *target, - kFSFileOperationDefaultOptions); //OptionBits options - if (rv2 != noErr) - { - //implement same behavior as in Windows: if recycler is not existing, delete permanently - if (rv2 == -120) //=="Directory not found or incomplete pathname." but should really be "recycle bin directory not found"! - { - if (*type == ItemType::FOLDER) - removeDirectoryPlainRecursion(itemPath); //throw FileError - else - removeFilePlain(itemPath); //throw FileError - return true; - } - - throwFileError(rv2); - } - return true; -#endif -} - - -#ifdef ZEN_WIN -bool zen::recycleBinExists(const Zstring& dirPath, const std::function& onUpdateGui) //throw FileError -{ -#ifdef ZEN_WIN_VISTA_AND_LATER - return vista::supportsRecycleBin(dirPath); //throw FileError - -#else - //excessive runtime if recycle bin exists, is full and drive is slow: - auto ft = runAsync([dirPath]() -> HRESULT - { - try - { - ComInitializer ci; //throw SysError - - SHQUERYRBINFO recInfo = {}; - recInfo.cbSize = sizeof(recInfo); - return ::SHQueryRecycleBin(dirPath.c_str(), //__in_opt LPCTSTR pszRootPath, - &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo - } - catch (SysError&) { assert(false); return ERROR_GEN_FAILURE; } - }); - - while (ft.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready) - if (onUpdateGui) - onUpdateGui(); //may throw! - - return ft.get() == S_OK; -#endif - - //1. ::SHQueryRecycleBin() is excessive: traverses whole $Recycle.Bin directory tree each time!!!! But it's safe and correct. - - //2. we can't simply buffer the ::SHQueryRecycleBin() based on volume serial number: - // "subst S:\ C:\" => GetVolumeInformation() returns same serial for C:\ and S:\, but S:\ does not support recycle bin! - - //3. we would prefer to use CLSID_RecycleBinManager beginning with Vista... if only this interface were documented!!! - - //4. check directory existence of "C:\$Recycle.Bin, C:\RECYCLER, C:\RECYCLED" - // -> not upward-compatible, wrong result for subst-alias: recycler assumed existing, although it is not! - - //5. alternative approach a'la Raymond Chen: https://blogs.msdn.microsoft.com/oldnewthing/20080918-00/?p=20843/ - //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although recycler is not available! - - /* - Zstring rootPathPf = appendSeparator(&buffer[0]); - - const bool canUseFastCheckForRecycler = winXpOrLater(); - if (!canUseFastCheckForRecycler) //== "checkForRecycleBin" - return STATUS_REC_UNKNOWN; - - using namespace fileop; - const DllFun checkRecycler(getDllName(), funName_checkRecycler); - - if (!checkRecycler) - return STATUS_REC_UNKNOWN; //actually an error since we're >= XP - - //search root directories for recycle bin folder... - - WIN32_FIND_DATA dataRoot = {}; - HANDLE hFindRoot = ::FindFirstFile(applyLongPathPrefix(rootPathPf + L'*').c_str(), &dataRoot); - if (hFindRoot == INVALID_HANDLE_VALUE) - return STATUS_REC_UNKNOWN; - ZEN_ON_SCOPE_EXIT(FindClose(hFindRoot)); - - auto shouldSkip = [](const Zstring& shortname) { return shortname == L"." || shortname == L".."; }; - - do - { - if (!shouldSkip(dataRoot.cFileName) && - (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && - (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM ) != 0 && //risky to rely on these attributes!!! - (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) != 0) // - { - WIN32_FIND_DATA dataChild = {}; - const Zstring childDirPf = rootPathPf + dataRoot.cFileName + L"\\"; - - HANDLE hFindChild = ::FindFirstFile(applyLongPathPrefix(childDirPf + L'*').c_str(), &dataChild); - if (hFindChild != INVALID_HANDLE_VALUE) //if we can't access a subdir, it's probably not the recycler - { - ZEN_ON_SCOPE_EXIT(FindClose(hFindChild)); - do - { - if (!shouldSkip(dataChild.cFileName) && - (dataChild.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) - { - bool isRecycler = false; - if (checkRecycler((childDirPf + dataChild.cFileName).c_str(), isRecycler)) - { - if (isRecycler) - return STATUS_REC_EXISTS; - } - else assert(false); - } - } - while (::FindNextFile(hFindChild, &dataChild)); //ignore errors other than ERROR_NO_MORE_FILES - } - } - } - while (::FindNextFile(hFindRoot, &dataRoot)); // - - return STATUS_REC_MISSING; - */ -} - -#elif defined ZEN_LINUX || defined ZEN_MAC -/* -We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not! - -The following function looks perfect, alas it is restricted to local files and to the implementation of GIO only: - - gboolean _g_local_file_has_trash_dir(const char* dirpath, dev_t dir_dev); - See: http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.h - - Just checking for "G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH" is not correct, since we find in - http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.c - - g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, - writable && parent_info->has_trash_dir); - - => We're NOT interested in whether the specified folder can be trashed, but whether it supports thrashing its child elements! (Only support, not actual write access!) - This renders G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH useless for this purpose. -*/ -#endif +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "recycler.h" +#include "file_access.h" + + #include + #include + #include "scope_guard.h" + + +using namespace zen; + + + + +bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError +{ + GFile* file = ::g_file_new_for_path(itemPath.c_str()); //never fails according to docu + ZEN_ON_SCOPE_EXIT(g_object_unref(file);) + + GError* error = nullptr; + ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); + + if (!::g_file_trash(file, nullptr, &error)) + { + const Opt type = getItemTypeIfExists(itemPath); //throw FileError + if (!type) + return false; + + const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)); + if (!error) + throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this + + //implement same behavior as in Windows: if recycler is not existing, delete permanently + if (error->code == G_IO_ERROR_NOT_SUPPORTED) + { + if (*type == ItemType::FOLDER) + removeDirectoryPlainRecursion(itemPath); //throw FileError + else + removeFilePlain(itemPath); //throw FileError + return true; + } + + throw FileError(errorMsg, replaceCpy(L"Glib Error Code %x:", L"%x", numberTo(error->code)) + L" " + utfCvrtTo(error->message)); + //g_quark_to_string(error->domain) + } + return true; + +} + + +/* +We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not! + +The following function looks perfect, alas it is restricted to local files and to the implementation of GIO only: + + gboolean _g_local_file_has_trash_dir(const char* dirpath, dev_t dir_dev); + See: http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.h + + Just checking for "G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH" is not correct, since we find in + http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.c + + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + writable && parent_info->has_trash_dir); + + => We're NOT interested in whether the specified folder can be trashed, but whether it supports thrashing its child elements! (Only support, not actual write access!) + This renders G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH useless for this purpose. +*/ -- cgit