summaryrefslogtreecommitdiff
path: root/zen/recycler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/recycler.cpp')
-rwxr-xr-x[-rw-r--r--]zen/recycler.cpp395
1 files changed, 72 insertions, 323 deletions
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index d8ee58c4..02ea026a 100644..100755
--- 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 <sys/stat.h>
- #include <gio/gio.h>
- #include "scope_guard.h"
-
-#elif defined ZEN_MAC
- #include <CoreServices/CoreServices.h>
-#endif
-
-using namespace zen;
-
-
-#ifdef ZEN_WIN
-void zen::recycleOrDeleteIfExists(const std::vector<Zstring>& itemPaths, const std::function<void (const std::wstring& displayPath)>& 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<ItemType> 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<std::wstring>(L"Glib Error Code %x:", L"%x", numberTo<std::wstring>(error->code)) + L" " + utfCvrtTo<std::wstring>(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<const UInt8*>(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<std::wstring>(oss);
-
- if (const char* description = ::GetMacOSStatusCommentString(oss)) //found no documentation for proper use of GetMacOSStatusCommentString
- errorDescr += L": " + utfCvrtTo<std::wstring>(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<ItemType> 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<void ()>& 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<FunType_checkRecycler> 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 <sys/stat.h>
+ #include <gio/gio.h>
+ #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<ItemType> 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<std::wstring>(L"Glib Error Code %x:", L"%x", numberTo<std::wstring>(error->code)) + L" " + utfCvrtTo<std::wstring>(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.
+*/
bgstack15