diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2019-05-12 22:44:22 +0000 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2019-05-12 22:44:22 +0000 |
commit | 2cb4599782d970f386a67dfd4f4dab0d531d4348 (patch) | |
tree | b873b15f50a981aacf8bb49fd646bfded2e7a73b /FreeFileSync/Source/afs/native.cpp | |
parent | Merge branch '10.11' into 'master' (diff) | |
parent | 10.12 (diff) | |
download | FreeFileSync-2cb4599782d970f386a67dfd4f4dab0d531d4348.tar.gz FreeFileSync-2cb4599782d970f386a67dfd4f4dab0d531d4348.tar.bz2 FreeFileSync-2cb4599782d970f386a67dfd4f4dab0d531d4348.zip |
Merge branch '10.12' into 'master'10.12
10.12
See merge request opensource-tracking/FreeFileSync!9
Diffstat (limited to 'FreeFileSync/Source/afs/native.cpp')
-rw-r--r-- | FreeFileSync/Source/afs/native.cpp | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp new file mode 100644 index 00000000..4e883279 --- /dev/null +++ b/FreeFileSync/Source/afs/native.cpp @@ -0,0 +1,685 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "native.h" +#include <zen/file_access.h> +#include <zen/symlink_target.h> +#include <zen/file_io.h> +#include <zen/file_id_def.h> +#include <zen/stl_tools.h> +#include <zen/recycler.h> +#include <zen/thread.h> +#include <zen/guid.h> +#include <zen/crc.h> +#include "abstract_impl.h" +#include "../base/resolve_path.h" +#include "../base/icon_loader.h" + + + #include <cstddef> //offsetof + #include <sys/stat.h> + #include <dirent.h> + #include <fcntl.h> //fallocate, fcntl + +using namespace zen; +using namespace fff; +using AFS = AbstractFileSystem; + + +namespace +{ +void initComForThread() //throw FileError +{ +} + +//==================================================================================================== +//==================================================================================================== + +inline +AFS::FileId convertToAbstractFileId(const zen::FileId& fid) +{ + if (fid == zen::FileId()) + return AFS::FileId(); + + AFS::FileId out(reinterpret_cast<const char*>(&fid.volumeId), sizeof(fid.volumeId)); + out. append(reinterpret_cast<const char*>(&fid.fileIndex), sizeof(fid.fileIndex)); + return out; +} + + +struct NativeFileInfo +{ + time_t modTime; + uint64_t fileSize; + FileId fileId; //optional +}; +NativeFileInfo getFileAttributes(FileBase::FileHandle fh) //throw SysError +{ + struct ::stat fileAttr = {}; + if (::fstat(fh, &fileAttr) != 0) + THROW_LAST_SYS_ERROR(L"fstat"); + + return + { + fileAttr.st_mtime, + makeUnsigned(fileAttr.st_size), + generateFileId(fileAttr) + }; +} + + +struct FsItemRaw +{ + Zstring itemName; + Zstring itemPath; +}; +std::vector<FsItemRaw> getDirContentFlat(const Zstring& dirPath) //throw FileError +{ + //no need to check for endless recursion: + //1. Linux has a fixed limit on the number of symbolic links in a path + //2. fails with "too many open files" or "path too long" before reaching stack overflow + + DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" + if (!folder) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir"); + ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash + + std::vector<FsItemRaw> output; + for (;;) + { + /* + Linux: + http://man7.org/linux/man-pages/man3/readdir_r.3.html + "It is recommended that applications use readdir(3) instead of readdir_r" + "... in modern implementations (including the glibc implementation), concurrent calls to readdir(3) that specify different directory streams are thread-safe" + + macOS: + - libc: readdir thread-safe already in code from 2000: https://opensource.apple.com/source/Libc/Libc-166/gen.subproj/readdir.c.auto.html + - and in the latest version from 2017: https://opensource.apple.com/source/Libc/Libc-1244.30.3/gen/FreeBSD/readdir.c.auto.html + */ + errno = 0; + const struct ::dirent* dirEntry = ::readdir(folder); + if (!dirEntry) + { + if (errno == 0) //errno left unchanged => no more items + return output; + + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir"); + //don't retry but restart dir traversal on error! https://devblogs.microsoft.com/oldnewthing/20140612-00/?p=753 + } + + const char* itemNameRaw = dirEntry->d_name; //evaluate dirEntry *before* going into recursion + + //skip "." and ".." + if (itemNameRaw[0] == '.' && + (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) + continue; + + /* + Unicode normalization is file-system-dependent: + + OS Accepts Gives back + ---------- ------- ---------- + macOS (HFS+) all NFD + Linux all <input> + Windows (NTFS, FAT) all <input> + + some file systems return precomposed others decomposed UTF8: http://developer.apple.com/library/mac/#qa/qa1173/_index.html + - OS X edit controls and text fields may return precomposed UTF as directly received by keyboard or decomposed UTF that was copy & pasted in! + - Posix APIs require decomposed form: https://freefilesync.org/forum/viewtopic.php?t=2480 + + => General recommendation: always preserve input UNCHANGED (both unicode normalization and case sensitivity) + => normalize only when needed during string comparison + + Create sample files on Linux: touch decomposed-$'\x6f\xcc\x81'.txt + touch precomposed-$'\xc3\xb3'.txt + + - SMB sharing case-sensitive or NFD file names is fundamentally broken on macOS: + => the macOS SMB manager internally buffers file names as case-insensitive and NFC (= just like NTFS on Windows) + => test: create SMB share from Linux => *boom* on macOS: "Error Code 2: No such file or directory [lstat]" + or WORSE: folders "test" and "Test" *both* incorrectly return the content of one of the two + */ + const Zstring& itemName = itemNameRaw; + if (itemName.empty()) + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name."); + + const Zstring& itemPath = appendSeparator(dirPath) + itemName; + + output.push_back({ itemName, itemPath}); + } +} + + +struct ItemDetailsRaw +{ + ItemType type; + time_t modTime; //number of seconds since Jan. 1st 1970 UTC + uint64_t fileSize; //unit: bytes! + FileId fileId; +}; +ItemDetailsRaw getItemDetails(const Zstring& itemPath) //throw FileError +{ + struct ::stat statData = {}; + if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat"); + + return { S_ISLNK(statData.st_mode) ? ItemType::SYMLINK : //on Linux there is no distinction between file and directory symlinks! + (S_ISDIR(statData.st_mode) ? ItemType::FOLDER : + ItemType::FILE), //a file or named pipe, etc. => dont't check using S_ISREG(): see comment in file_traverser.cpp + statData.st_mtime, makeUnsigned(statData.st_size), generateFileId(statData) }; +} + +ItemDetailsRaw getSymlinkTargetDetails(const Zstring& linkPath) //throw FileError +{ + struct ::stat statData = {}; + if (::stat(linkPath.c_str(), &statData) != 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"stat"); + + return { S_ISDIR(statData.st_mode) ? ItemType::FOLDER : ItemType::FILE, statData.st_mtime, makeUnsigned(statData.st_size), generateFileId(statData) }; +} + + +class SingleFolderTraverser +{ +public: + SingleFolderTraverser(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& workload /*throw X*/) + { + for (const auto& [folderPath, cb] : workload) + workload_.push_back({ folderPath, cb }); + + while (!workload_.empty()) + { + WorkItem wi = std::move(workload_. back()); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workload_.pop_back(); // + + tryReportingDirError([&] //throw X + { + traverseWithException(wi.dirPath, *wi.cb); //throw FileError, X + }, *wi.cb); + } + } + +private: + SingleFolderTraverser (const SingleFolderTraverser&) = delete; + SingleFolderTraverser& operator=(const SingleFolderTraverser&) = delete; + + void traverseWithException(const Zstring& dirPath, AFS::TraverserCallback& cb) //throw FileError, X + { + for (const auto& [itemName, itemPath] : getDirContentFlat(dirPath)) //throw FileError + { + ItemDetailsRaw detailsRaw = {}; + if (!tryReportingItemError([&] //throw X + { + detailsRaw = getItemDetails(itemPath); //throw FileError + }, cb, itemName)) + continue; //ignore error: skip file + + switch (detailsRaw.type) + { + case ItemType::FILE: + cb.onFile({ itemName, detailsRaw.fileSize, detailsRaw.modTime, convertToAbstractFileId(detailsRaw.fileId), nullptr /*symlinkInfo*/ }); //throw X + break; + + case ItemType::FOLDER: + if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb.onFolder({ itemName, nullptr /*symlinkInfo*/ })) //throw X + workload_.push_back({ itemPath, std::move(cbSub) }); + break; + + case ItemType::SYMLINK: + switch (cb.onSymlink({ itemName, detailsRaw.modTime })) //throw X + { + case AFS::TraverserCallback::LINK_FOLLOW: + { + ItemDetailsRaw linkDetails = {}; + if (!tryReportingItemError([&] //throw X + { + linkDetails = getSymlinkTargetDetails(itemPath); //throw FileError + }, cb, itemName)) + continue; + + const AFS::SymlinkInfo linkInfo = { itemName, linkDetails.modTime }; + + if (linkDetails.type == ItemType::FOLDER) + { + if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb.onFolder({ itemName, &linkInfo })) //throw X + workload_.push_back({ itemPath, std::move(cbSub) }); + } + else //a file or named pipe, etc. + cb.onFile({ itemName, linkDetails.fileSize, linkDetails.modTime, convertToAbstractFileId(linkDetails.fileId), &linkInfo }); //throw X + } + break; + + case AFS::TraverserCallback::LINK_SKIP: + break; + } + break; + } + } + } + + struct WorkItem + { + Zstring dirPath; + std::shared_ptr<AFS::TraverserCallback> cb; + }; + std::vector<WorkItem> workload_; +}; + + +void traverseFolderRecursiveNative(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& workload /*throw X*/, size_t) //throw X +{ + SingleFolderTraverser dummy(workload); //throw X +} +//==================================================================================================== +//==================================================================================================== + +class RecycleSessionNative : public AbstractFileSystem::RecycleSession +{ +public: + RecycleSessionNative(const Zstring& baseFolderPath) : baseFolderPath_(baseFolderPath) {} + + void recycleItemIfExists(const AbstractPath& itemPath, const Zstring& logicalRelPath) override; //throw FileError + void tryCleanup(const std::function<void (const std::wstring& displayPath)>& notifyDeletionStatus /*throw X*/) override; //throw FileError, X + +private: + const Zstring baseFolderPath_; //ends with path separator +}; + +//=========================================================================================================================== + +struct InputStreamNative : public AbstractFileSystem::InputStream +{ + InputStreamNative(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : fi_(filePath, notifyUnbufferedIO) {} //throw FileError, ErrorFileLocked + + size_t read(void* buffer, size_t bytesToRead) override { return fi_.read(buffer, bytesToRead); } //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! + size_t getBlockSize() const override { return fi_.getBlockSize(); } //non-zero block size is AFS contract! + std::optional<AFS::StreamAttributes> getAttributesBuffered() override //throw FileError + { + try + { + const NativeFileInfo fileInfo = getFileAttributes(fi_.getHandle()); //throw SysError + return + AFS::StreamAttributes( + { + fileInfo.modTime, + fileInfo.fileSize, + convertToAbstractFileId(fileInfo.fileId) + }); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(fi_.getFilePath())), e.toString()); } + } + +private: + FileInput fi_; +}; + +//=========================================================================================================================== + +struct OutputStreamNative : public AbstractFileSystem::OutputStreamImpl +{ + OutputStreamNative(const Zstring& filePath, + std::optional<uint64_t> streamSize, + std::optional<time_t> modTime, + const IOCallback& notifyUnbufferedIO /*throw X*/) : + fo_(FileOutput::ACC_CREATE_NEW, filePath, notifyUnbufferedIO), //throw FileError, ErrorTargetExisting + modTime_(modTime) + { + if (streamSize) //pre-allocate file space, because we can + fo_.preAllocateSpaceBestEffort(*streamSize); //throw FileError + } + + void write(const void* buffer, size_t bytesToWrite) override { fo_.write(buffer, bytesToWrite); } //throw FileError, X + + AFS::FinalizeResult finalize() override //throw FileError, X + { + AFS::FinalizeResult result; + try + { + result.fileId = convertToAbstractFileId(getFileAttributes(fo_.getHandle()).fileId); //throw SysError + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(fo_.getFilePath())), e.toString()); } + + fo_.finalize(); //throw FileError, X + + try + { + if (modTime_) + zen::setFileTime(fo_.getFilePath(), *modTime_, ProcSymlink::FOLLOW); //throw FileError + /* is setting modtime after closing the file handle a pessimization? + Native: no, needed for functional correctness, see file_access.cpp */ + } + catch (const FileError& e) { result.errorModTime = FileError(e.toString()); /*avoid slicing*/ } + + return result; + } + +private: + FileOutput fo_; + const std::optional<time_t> modTime_; +}; + +//=========================================================================================================================== + +class NativeFileSystem : public AbstractFileSystem +{ +public: + NativeFileSystem(const Zstring& rootPath) : rootPath_(rootPath) {} + +private: + Zstring getNativePath(const AfsPath& afsPath) const { return nativeAppendPaths(rootPath_, afsPath.value); } + + std::optional<Zstring> getNativeItemPath(const AfsPath& afsPath) const override { return getNativePath(afsPath); } + + Zstring getInitPathPhrase(const AfsPath& afsPath) const override { return getNativePath(afsPath); } + + std::wstring getDisplayPath(const AfsPath& afsPath) const override { return utfTo<std::wstring>(getNativePath(afsPath)); } + + bool isNullFileSystem() const override { return rootPath_.empty(); } + + int compareDeviceSameAfsType(const AbstractFileSystem& afsRhs) const override + { + const Zstring& rootPathRhs = static_cast<const NativeFileSystem&>(afsRhs).rootPath_; + + return compareNativePath(rootPath_, rootPathRhs); + } + + //---------------------------------------------------------------------------------------------------------------- + ItemType getItemType(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + switch (zen::getItemType(getNativePath(afsPath))) //throw FileError + { + case zen::ItemType::FILE: + return AFS::ItemType::FILE; + case zen::ItemType::FOLDER: + return AFS::ItemType::FOLDER; + case zen::ItemType::SYMLINK: + return AFS::ItemType::SYMLINK; + } + assert(false); + return AFS::ItemType::FILE; + } + + std::optional<ItemType> itemStillExists(const AfsPath& afsPath) const override //throw FileError + { + //default implementation: folder traversal + return AbstractFileSystem::itemStillExists(afsPath); //throw FileError + } + //---------------------------------------------------------------------------------------------------------------- + + //already existing: fail/ignore + //=> Native will fail and give a clear error message + void createFolderPlain(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + createDirectory(getNativePath(afsPath)); //throw FileError, ErrorTargetExisting + } + + void removeFilePlain(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + zen::removeFilePlain(getNativePath(afsPath)); //throw FileError + } + + void removeSymlinkPlain(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + zen::removeSymlinkPlain(getNativePath(afsPath)); //throw FileError + } + + void removeFolderPlain(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + zen::removeDirectoryPlain(getNativePath(afsPath)); //throw FileError + } + + void removeFolderIfExistsRecursion(const AfsPath& afsPath, //throw FileError + const std::function<void (const std::wstring& displayPath)>& onBeforeFileDeletion /*throw X*/, //optional + const std::function<void (const std::wstring& displayPath)>& onBeforeFolderDeletion) const override //one call for each object! + { + //default implementation: folder traversal + AbstractFileSystem::removeFolderIfExistsRecursion(afsPath, onBeforeFileDeletion, onBeforeFolderDeletion); //throw FileError, X + } + + //---------------------------------------------------------------------------------------------------------------- + AbstractPath getSymlinkResolvedPath(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + const Zstring nativePath = getNativePath(afsPath); + + const Zstring resolvedPath = zen::getSymlinkResolvedPath(nativePath); //throw FileError + const std::optional<zen::PathComponents> comp = parsePathComponents(resolvedPath); + if (!comp) + throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(nativePath)), + replaceCpy<std::wstring>(L"Invalid path %x.", L"%x", fmtPath(resolvedPath))); + + return AbstractPath(makeSharedRef<NativeFileSystem>(comp->rootPath), AfsPath(comp->relPath)); + } + + std::string getSymlinkBinaryContent(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + const Zstring nativePath = getNativePath(afsPath); + + std::string content = utfTo<std::string>(getSymlinkTargetRaw(nativePath)); //throw FileError + return content; + } + //---------------------------------------------------------------------------------------------------------------- + + //return value always bound: + std::unique_ptr<InputStream> getInputStream(const AfsPath& afsPath, const IOCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, ErrorFileLocked + { + initComForThread(); //throw FileError + return std::make_unique<InputStreamNative>(getNativePath(afsPath), notifyUnbufferedIO); //throw FileError, ErrorFileLocked + } + + //target existing: undefined behavior! (fail/overwrite/auto-rename) => Native will fail and give a clear error message + std::unique_ptr<OutputStreamImpl> getOutputStream(const AfsPath& afsPath, //throw FileError + std::optional<uint64_t> streamSize, + std::optional<time_t> modTime, + const IOCallback& notifyUnbufferedIO /*throw X*/) const override + { + initComForThread(); //throw FileError + return std::make_unique<OutputStreamNative>(getNativePath(afsPath), streamSize, modTime, notifyUnbufferedIO); //throw FileError + } + + //---------------------------------------------------------------------------------------------------------------- + void traverseFolderRecursive(const TraverserWorkload& workload /*throw X*/, size_t parallelOps) const override + { + //initComForThread() -> done on traverser worker threads + + std::vector<std::pair<Zstring, std::shared_ptr<TraverserCallback>>> initialWorkItems; + for (const auto& [folderPath, cb] : workload) + initialWorkItems.emplace_back(getNativePath(folderPath), cb); + + traverseFolderRecursiveNative(initialWorkItems, parallelOps); //throw X + } + //---------------------------------------------------------------------------------------------------------------- + + //symlink handling: follow link! + //target existing: undefined behavior! (fail/overwrite/auto-rename) => Native will fail and give a clear error message + FileCopyResult copyFileForSameAfsType(const AfsPath& afsPathSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X + const AbstractPath& apTarget, bool copyFilePermissions, const IOCallback& notifyUnbufferedIO /*throw X*/) const override + { + const Zstring nativePathTarget = static_cast<const NativeFileSystem&>(apTarget.afsDevice.ref()).getNativePath(apTarget.afsPath); + + initComForThread(); //throw FileError + + const zen::FileCopyResult nativeResult = copyNewFile(getNativePath(afsPathSource), nativePathTarget, //throw FileError, ErrorTargetExisting, ErrorFileLocked, X + copyFilePermissions, notifyUnbufferedIO); + FileCopyResult result; + result.fileSize = nativeResult.fileSize; + result.modTime = nativeResult.modTime; + result.sourceFileId = convertToAbstractFileId(nativeResult.sourceFileId); + result.targetFileId = convertToAbstractFileId(nativeResult.targetFileId); + result.errorModTime = nativeResult.errorModTime; + return result; + } + + //target existing: fail/ignore => Native will fail and give a clear error message + //symlink handling: follow link! + void copyNewFolderForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const override //throw FileError + { + initComForThread(); //throw FileError + + const Zstring& sourcePath = getNativePath(afsPathSource); + const Zstring& targetPath = static_cast<const NativeFileSystem&>(apTarget.afsDevice.ref()).getNativePath(apTarget.afsPath); + + zen::createDirectory(targetPath); //throw FileError, ErrorTargetExisting + + ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); } + catch (FileError&) {}); + + //do NOT copy attributes for volume root paths which return as: FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY + //https://freefilesync.org/forum/viewtopic.php?t=5550 + if (getParentPath(afsPathSource)) //=> not a root path + tryCopyDirectoryAttributes(sourcePath, targetPath); //throw FileError + + if (copyFilePermissions) + copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError + } + + void copySymlinkForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const override //throw FileError + { + const Zstring nativePathTarget = static_cast<const NativeFileSystem&>(apTarget.afsDevice.ref()).getNativePath(apTarget.afsPath); + + initComForThread(); //throw FileError + zen::copySymlink(getNativePath(afsPathSource), nativePathTarget, copyFilePermissions); //throw FileError + } + + //target existing: undefined behavior! (fail/overwrite/auto-rename) => Native will fail and give a clear error message + void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported + { + //perf test: detecting different volumes by path is ~30 times faster than having ::MoveFileEx() fail with ERROR_NOT_SAME_DEVICE (6µs vs 190µs) + //=> maybe we can even save some actual I/O in some cases? + if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != 0) + throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."), + L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), + L"%y", L"\n" + fmtPath(AFS::getDisplayPath(pathTo))), + _("Operation not supported between different devices.")); + initComForThread(); //throw FileError + const Zstring nativePathTarget = static_cast<const NativeFileSystem&>(pathTo.afsDevice.ref()).getNativePath(pathTo.afsPath); + zen::moveAndRenameItem(getNativePath(pathFrom), nativePathTarget, false /*replaceExisting*/); //throw FileError, ErrorTargetExisting, ErrorMoveUnsupported + } + + bool supportsPermissions(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + return zen::supportsPermissions(getNativePath(afsPath)); + } + + //---------------------------------------------------------------------------------------------------------------- + ImageHolder getFileIcon(const AfsPath& afsPath, int pixelSize) const override //noexcept; optional return value + { + try + { + initComForThread(); //throw FileError + return fff::getFileIcon(getNativePath(afsPath), pixelSize); + } + catch (FileError&) { assert(false); return ImageHolder(); } + } + + ImageHolder getThumbnailImage(const AfsPath& afsPath, int pixelSize) const override //noexcept; optional return value + { + try + { + initComForThread(); //throw FileError + return fff::getThumbnailImage(getNativePath(afsPath), pixelSize); + } + catch (FileError&) { assert(false); return ImageHolder(); } + } + + void authenticateAccess(bool allowUserInteraction) const override //throw FileError + { + } + + int getAccessTimeout() const override { return 0; } //returns "0" if no timeout in force + + bool hasNativeTransactionalCopy() const override { return false; } + //---------------------------------------------------------------------------------------------------------------- + + uint64_t getFreeDiskSpace(const AfsPath& afsPath) const override //throw FileError, returns 0 if not available + { + initComForThread(); //throw FileError + return zen::getFreeDiskSpace(getNativePath(afsPath)); //throw FileError + } + + bool supportsRecycleBin(const AfsPath& afsPath) const override //throw FileError + { + return true; //truth be told: no idea!!! + } + + std::unique_ptr<RecycleSession> createRecyclerSession(const AfsPath& afsPath) const override //throw FileError, return value must be bound! + { + initComForThread(); //throw FileError + assert(supportsRecycleBin(afsPath)); + return std::make_unique<RecycleSessionNative>(getNativePath(afsPath)); + } + + void recycleItemIfExists(const AfsPath& afsPath) const override //throw FileError + { + initComForThread(); //throw FileError + zen::recycleOrDeleteIfExists(getNativePath(afsPath)); //throw FileError + } + + const Zstring rootPath_; +}; + +//=========================================================================================================================== + + + +//- return true if item existed +//- multi-threaded access: internally synchronized! +void RecycleSessionNative::recycleItemIfExists(const AbstractPath& itemPath, const Zstring& logicalRelPath) //throw FileError +{ + assert(!startsWith(logicalRelPath, FILE_NAME_SEPARATOR)); + + std::optional<Zstring> itemPathNative = AFS::getNativeItemPath(itemPath); + if (!itemPathNative) + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + recycleOrDeleteIfExists(*itemPathNative); //throw FileError +} + + +void RecycleSessionNative::tryCleanup(const std::function<void (const std::wstring& displayPath)>& notifyDeletionStatus /*throw X*/) //throw FileError, X +{ +} +} + + +//coordinate changes with getResolvedFilePath()! +bool fff::acceptsItemPathPhraseNative(const Zstring& itemPathPhrase) //noexcept +{ + Zstring path = expandMacros(itemPathPhrase); //expand before trimming! + trim(path); + + + if (startsWith(path, Zstr("["))) //drive letter by volume name syntax + return true; + + //don't accept relative paths!!! indistinguishable from MTP paths as shown in Explorer's address bar! + //don't accept empty paths (see drag & drop validation!) + return static_cast<bool>(parsePathComponents(path)); +} + + +AbstractPath fff::createItemPathNative(const Zstring& itemPathPhrase) //noexcept +{ + //TODO: get volume by name hangs for idle HDD! => run createItemPathNative during getFolderStatusNonBlocking() but getResolvedFilePath currently not thread-safe! + const Zstring itemPath = getResolvedFilePath(itemPathPhrase); + return createItemPathNativeNoFormatting(itemPath); +} + + +AbstractPath fff::createItemPathNativeNoFormatting(const Zstring& nativePath) //noexcept +{ + if (const std::optional<PathComponents> comp = parsePathComponents(nativePath)) + return AbstractPath(makeSharedRef<NativeFileSystem>(comp->rootPath), AfsPath(comp->relPath)); + else //broken path syntax + return AbstractPath(makeSharedRef<NativeFileSystem>(nativePath), AfsPath()); +} |