// ***************************************************************************** // * 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 #include #include #include #include #include #include #include #include #include "abstract_impl.h" #include "../base/icon_loader.h" #include //statfs #include #include #include //fallocate, fcntl using namespace zen; using namespace fff; using AFS = AbstractFileSystem; namespace { void initComForThread() //throw FileError { } //==================================================================================================== //==================================================================================================== //persistent + unique (relative to volume) or 0! inline AFS::FingerPrint getFileFingerprint(FileIndex fileIndex) { static_assert(sizeof(fileIndex) == sizeof(AFS::FingerPrint)); return fileIndex; //== 0 if not supported /* File details ------------ st_mtim (Linux) st_mtimespec (macOS): nanosecond-precision for improved uniqueness? => essentially unknown after file copy (e.g. to FAT) without extra directory traversal :( macOS st_birthtimespec: "if not supported by file system, holds the ctime instead" ctime: inode modification time => changed on* rename*! => FU... Volume details -------------- st_dev: "st_dev value is not necessarily consistent across reboots or system crashes" https://freefilesync.org/forum/viewtopic.php?t=8054 only locally unique and depends on device mount point! => FU... f_fsid: "Some operating systems use the device number..." => fuck! "Several OSes restrict giving out the f_fsid field to the superuser only" f_bsize macOS: "fundamental file system block size" Linux: "optimal transfer block size" -> no! for all intents and purposes this *is* the "fundamental file system block size": https://stackoverflow.com/a/54835515 f_blocks => meh... f_type Linux: documented values, nice! https://linux.die.net/man/2/statfs macOS: - not stable between macOS releases: https://developer.apple.com/forums/thread/87745 - Apple docs say: "generally not a useful value" - f_fstypename can be used as alternative DADiskGetBSDName(): macOS only */ } struct NativeFileInfo { FileTimeNative modTime; uint64_t fileSize; AFS::FingerPrint filePrint; }; NativeFileInfo getNativeFileInfo(FileBase& file) //throw FileError { const struct stat& fileInfo = file.getStatBuffered(); //throw FileError return { fileInfo.st_mtim, makeUnsigned(fileInfo.st_size), getFileFingerprint(fileInfo.st_ino) }; } struct FsItem { Zstring itemName; }; std::vector 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)), "opendir"); ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash std::vector output; for (;;) { /* Linux: https://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 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)), "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; //skip "." and ".." if (itemNameRaw[0] == '.' && (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) continue; if (itemNameRaw[0] == 0) //show error instead of endless recursion!!! throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError("readdir", L"", L"Folder contains an item without name.")); output.push_back({itemNameRaw}); /* Unicode normalization is file-system-dependent: OS Accepts Gives back ---------- ------- ---------- macOS (HFS+) all NFD Linux all Windows (NTFS, FAT) all some file systems return precomposed others decomposed UTF8: https://developer.apple.com/library/archive/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! - 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 - list file name hex chars in terminal: ls | od -c -t x1 - 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 => Update 2020-04-24: converting to NFC doesn't help: both NFD/NFC forms fail(ENOENT) lstat in FFS, AS WELL AS IN FINDER (silently skipped!) => macOS bug! */ } } struct FsItemDetails { ItemType type; time_t modTime; //number of seconds since Jan. 1st 1970 GMT uint64_t fileSize; //unit: bytes! AFS::FingerPrint filePrint; }; FsItemDetails getItemDetails(const Zstring& itemPath) //throw FileError { struct stat itemInfo = {}; if (::lstat(itemPath.c_str(), &itemInfo) != 0) //lstat() does not resolve symlinks THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat"); return {S_ISLNK(itemInfo.st_mode) ? ItemType::symlink : //on Linux there is no distinction between file and directory symlinks! /**/ (S_ISDIR(itemInfo.st_mode) ? ItemType::folder : ItemType::file), //a file or named pipe, etc. S_ISREG, S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK //=> dont't check using S_ISREG(): see comment in file_traverser.cpp itemInfo.st_mtime, makeUnsigned(itemInfo.st_size), getFileFingerprint(itemInfo.st_ino)}; } FsItemDetails getSymlinkTargetDetails(const Zstring& linkPath) //throw FileError { try { struct stat itemInfo = {}; if (::stat(linkPath.c_str(), &itemInfo) != 0) THROW_LAST_SYS_ERROR("stat"); const ItemType targetType = S_ISDIR(itemInfo.st_mode) ? ItemType::folder : ItemType::file; const AFS::FingerPrint filePrint = targetType == ItemType::folder ? 0 : getFileFingerprint(itemInfo.st_ino); return {targetType, itemInfo.st_mtime, makeUnsigned(itemInfo.st_size), filePrint}; } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), e.toString()); } } class SingleFolderTraverser { public: SingleFolderTraverser(const std::vector>>& 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] : getDirContentFlat(dirPath)) //throw FileError { const Zstring itemPath = appendPath(dirPath, itemName); FsItemDetails itemDetails = {}; if (!tryReportingItemError([&] //throw X { itemDetails = getItemDetails(itemPath); //throw FileError }, cb, itemName)) continue; //ignore error: skip file switch (itemDetails.type) { case ItemType::file: cb.onFile({itemName, itemDetails.fileSize, itemDetails.modTime, itemDetails.filePrint, false /*isFollowedSymlink*/}); //throw X break; case ItemType::folder: if (std::shared_ptr cbSub = cb.onFolder({itemName, false /*isFollowedSymlink*/})) //throw X workload_.push_back({itemPath, std::move(cbSub)}); break; case ItemType::symlink: switch (cb.onSymlink({itemName, itemDetails.modTime})) //throw X { case AFS::TraverserCallback::HandleLink::follow: { FsItemDetails targetDetails = {}; if (!tryReportingItemError([&] //throw X { targetDetails = getSymlinkTargetDetails(itemPath); //throw FileError }, cb, itemName)) continue; if (targetDetails.type == ItemType::folder) { if (std::shared_ptr cbSub = cb.onFolder({itemName, true /*isFollowedSymlink*/})) //throw X workload_.push_back({itemPath, std::move(cbSub)}); //symlink may link to different volume! } else //a file or named pipe, etc. cb.onFile({itemName, targetDetails.fileSize, targetDetails.modTime, targetDetails.filePrint, true /*isFollowedSymlink*/}); //throw X } break; case AFS::TraverserCallback::HandleLink::skip: break; } break; } } } struct WorkItem { Zstring dirPath; std::shared_ptr cb; }; std::vector workload_; }; void traverseFolderRecursiveNative(const std::vector>>& workload /*throw X*/, size_t) //throw X { SingleFolderTraverser dummy(workload); //throw X } //==================================================================================================== //==================================================================================================== class RecycleSessionNative : public AFS::RecycleSession { public: explicit RecycleSessionNative(const Zstring& baseFolderPath) : baseFolderPath_(baseFolderPath) {} //constructor will be running on main thread => keep trivial and defer work to getRecyclerTempPath()! void moveToRecycleBin(const AbstractPath& itemPath, const Zstring& logicalRelPath) override; //throw FileError, RecycleBinUnavailable void tryCleanup(const std::function& notifyDeletionStatus /*throw X*/) override; //throw FileError, X private: const Zstring baseFolderPath_; }; //=========================================================================================================================== struct InputStreamNative : public AFS::InputStream { explicit InputStreamNative(const Zstring& filePath) : fileIn_(filePath) {} //throw FileError, ErrorFileLocked size_t getBlockSize() override { return fileIn_.getBlockSize(); } //throw FileError; non-zero block size is AFS contract! //may return short; only 0 means EOF! CONTRACT: bytesToRead > 0! size_t tryRead(void* buffer, size_t bytesToRead, const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, ErrorFileLocked, X { const size_t bytesRead = fileIn_.tryRead(buffer, bytesToRead); //throw FileError, ErrorFileLocked if (notifyUnbufferedIO) notifyUnbufferedIO(bytesRead); //throw X return bytesRead; } std::optional tryGetAttributesFast() override //throw FileError { const NativeFileInfo& fileInfo = getNativeFileInfo(fileIn_); //throw FileError return AFS::StreamAttributes({nativeFileTimeToTimeT(fileInfo.modTime), fileInfo.fileSize, fileInfo.filePrint}); } private: FileInputPlain fileIn_; }; //=========================================================================================================================== struct OutputStreamNative : public AFS::OutputStreamImpl { OutputStreamNative(const Zstring& filePath, std::optional streamSize, std::optional modTime) : fileOut_(filePath), //throw FileError, ErrorTargetExisting modTime_(modTime) { if (streamSize) //preallocate disk space + reduce fragmentation fileOut_.reserveSpace(*streamSize); //throw FileError } size_t getBlockSize() override { return fileOut_.getBlockSize(); } //throw FileError size_t tryWrite(const void* buffer, size_t bytesToWrite, const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X; may return short! CONTRACT: bytesToWrite > 0 { const size_t bytesWritten = fileOut_.tryWrite(buffer, bytesToWrite); //throw FileError if (notifyUnbufferedIO) notifyUnbufferedIO(bytesWritten); //throw X return bytesWritten; } AFS::FinalizeResult finalize(const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X { AFS::FinalizeResult result; result.filePrint = getNativeFileInfo(fileOut_).filePrint; //throw FileError fileOut_.close(); //throw FileError //output finalized => no more exceptions from here on! //-------------------------------------------------------------------- /* is setting modtime after closing the file handle a pessimization? no, needed for functional correctness, see file_access.cpp::copyNewFile() for macOS/Linux even required on Windows: https://freefilesync.org/forum/viewtopic.php?t=10781 */ try { if (modTime_) setFileTime(fileOut_.getFilePath(), *modTime_, ProcSymlink::follow); //throw FileError } catch (const FileError& e) { result.errorModTime = e; /*might slice derived class?*/ } return result; } private: FileOutputPlain fileOut_; const std::optional modTime_; }; //=========================================================================================================================== class NativeFileSystem : public AbstractFileSystem { public: explicit NativeFileSystem(const Zstring& rootPath) : rootPath_(rootPath) {} Zstring getNativePath(const AfsPath& itemPath) const { return isNullFileSystem() ? Zstring{} : appendPath(rootPath_, itemPath.value); } private: Zstring getInitPathPhrase(const AfsPath& itemPath) const override { return makePathPhrase(getNativePath(itemPath)); } std::vector getPathPhraseAliases(const AfsPath& itemPath) const override { if (isNullFileSystem()) return {}; return ::getPathPhraseAliases(getNativePath(itemPath)); } std::wstring getDisplayPath(const AfsPath& itemPath) const override { return utfTo(getNativePath(itemPath)); } bool isNullFileSystem() const override { return rootPath_.empty(); } std::weak_ordering compareDeviceSameAfsType(const AbstractFileSystem& afsRhs) const override { return compareNativePath(rootPath_, static_cast(afsRhs).rootPath_); } //---------------------------------------------------------------------------------------------------------------- static ItemType zenToAfsItemType(zen::ItemType type) { switch (type) { 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 static_cast(type); } ItemType getItemType(const AfsPath& itemPath) const override //throw FileError { initComForThread(); //throw FileError return zenToAfsItemType(zen::getItemType(getNativePath(itemPath))); //throw FileError } std::optional getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError { initComForThread(); //throw FileError if (const std::optional type = zen::getItemTypeIfExists(getNativePath(itemPath))) //throw FileError return zenToAfsItemType(*type); return std::nullopt; } //---------------------------------------------------------------------------------------------------------------- //already existing: fail void createFolderPlain(const AfsPath& folderPath) const override //throw FileError { initComForThread(); //throw FileError createDirectory(getNativePath(folderPath)); //throw FileError, ErrorTargetExisting } void removeFilePlain(const AfsPath& filePath) const override //throw FileError { initComForThread(); //throw FileError zen::removeFilePlain(getNativePath(filePath)); //throw FileError } void removeSymlinkPlain(const AfsPath& linkPath) const override //throw FileError { initComForThread(); //throw FileError zen::removeSymlinkPlain(getNativePath(linkPath)); //throw FileError } void removeFolderPlain(const AfsPath& folderPath) const override //throw FileError { initComForThread(); //throw FileError zen::removeDirectoryPlain(getNativePath(folderPath)); //throw FileError } void removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileError const std::function& onBeforeFileDeletion /*throw X*/, const std::function& onBeforeSymlinkDeletion/*throw X*/, const std::function& onBeforeFolderDeletion /*throw X*/) const override { //default implementation: folder traversal AFS::removeFolderIfExistsRecursion(folderPath, onBeforeFileDeletion, onBeforeSymlinkDeletion, onBeforeFolderDeletion); //throw FileError, X } //---------------------------------------------------------------------------------------------------------------- AbstractPath getSymlinkResolvedPath(const AfsPath& linkPath) const override //throw FileError { initComForThread(); //throw FileError const Zstring nativePath = getNativePath(linkPath); const Zstring resolvedPath = zen::getSymlinkResolvedPath(nativePath); //throw FileError const std::optional comp = parsePathComponents(resolvedPath); if (!comp) throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(nativePath)), replaceCpy(L"Invalid path %x.", L"%x", fmtPath(resolvedPath))); return AbstractPath(makeSharedRef(comp->rootPath), AfsPath(comp->relPath)); } bool equalSymlinkContentForSameAfsType(const AfsPath& linkPathL, const AbstractPath& linkPathR) const override //throw FileError { initComForThread(); //throw FileError const NativeFileSystem& nativeFsR = static_cast(linkPathR.afsDevice.ref()); const SymlinkRawContent linkContentL = getSymlinkRawContent(getNativePath(linkPathL)); //throw FileError const SymlinkRawContent linkContentR = getSymlinkRawContent(nativeFsR.getNativePath(linkPathR.afsPath)); //throw FileError if (linkContentL.targetPath != linkContentR.targetPath) return false; return true; } //---------------------------------------------------------------------------------------------------------------- //return value always bound: std::unique_ptr getInputStream(const AfsPath& filePath) const override //throw FileError, ErrorFileLocked { initComForThread(); //throw FileError return std::make_unique(getNativePath(filePath)); //throw FileError, ErrorFileLocked } //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) //=> actual behavior: fail with clear error message std::unique_ptr getOutputStream(const AfsPath& filePath, //throw FileError std::optional streamSize, std::optional modTime) const override { initComForThread(); //throw FileError return std::make_unique(getNativePath(filePath), streamSize, modTime); //throw FileError, ErrorTargetExisting } //---------------------------------------------------------------------------------------------------------------- void traverseFolderRecursive(const TraverserWorkload& workload /*throw X*/, size_t parallelOps) const override { //initComForThread() -> done on traverser worker threads std::vector>> initialWorkItems; for (const auto& [folderPath, cb] : workload) initialWorkItems.emplace_back(getNativePath(folderPath), cb); traverseFolderRecursiveNative(initialWorkItems, parallelOps); //throw X } //---------------------------------------------------------------------------------------------------------------- //symlink handling: follow //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) //=> actual behavior: fail with clear error message FileCopyResult copyFileForSameAfsType(const AfsPath& sourcePath, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X const AbstractPath& targetPath, bool copyFilePermissions, const IoCallback& notifyUnbufferedIO /*throw X*/) const override { const Zstring nativePathTarget = static_cast(targetPath.afsDevice.ref()).getNativePath(targetPath.afsPath); initComForThread(); //throw FileError const zen::FileCopyResult nativeResult = copyNewFile(getNativePath(sourcePath), nativePathTarget, notifyUnbufferedIO); //throw FileError, ErrorTargetExisting, ErrorFileLocked, X //at this point we know we created a new file, so it's fine to delete it for cleanup! ZEN_ON_SCOPE_FAIL(try { zen::removeFilePlain(nativePathTarget); } catch (const FileError& e) { logExtraError(e.toString()); }); if (copyFilePermissions) copyItemPermissions(getNativePath(sourcePath), nativePathTarget, ProcSymlink::follow); //throw FileError FileCopyResult result; result.fileSize = nativeResult.fileSize; //caveat: modTime will be incorrect for file systems with imprecise file times, e.g. see FAT_FILE_TIME_PRECISION_SEC result.modTime = nativeFileTimeToTimeT(nativeResult.sourceModTime); result.sourceFilePrint = getFileFingerprint(nativeResult.sourceFileIdx); result.targetFilePrint = getFileFingerprint(nativeResult.targetFileIdx); result.errorModTime = nativeResult.errorModTime; return result; } //symlink handling: follow //already existing: fail void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError { initComForThread(); //throw FileError const Zstring& sourcePathNative = getNativePath(sourcePath); const Zstring& targetPathNative = static_cast(targetPath.afsDevice.ref()).getNativePath(targetPath.afsPath); zen::createDirectory(targetPathNative); //throw FileError, ErrorTargetExisting try { copyDirectoryAttributes(sourcePathNative, targetPathNative); //throw FileError } catch (FileError&) {} //[!] too unimportant + too frequent for external devices, e.g. "ERROR_INVALID_PARAMETER [SetFileInformationByHandle(FileBasicInfo)]" on Samba share if (copyFilePermissions) copyItemPermissions(sourcePathNative, targetPathNative, ProcSymlink::follow); //throw FileError } //already existing: fail void copySymlinkForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError { const Zstring targetPathNative = static_cast(targetPath.afsDevice.ref()).getNativePath(targetPath.afsPath); initComForThread(); //throw FileError zen::copySymlink(getNativePath(sourcePath), targetPathNative); //throw FileError ZEN_ON_SCOPE_FAIL(try { zen::removeSymlinkPlain(targetPathNative); } catch (const FileError& e) { logExtraError(e.toString()); }); if (copyFilePermissions) copyItemPermissions(getNativePath(sourcePath), targetPathNative, ProcSymlink::asLink); //throw FileError } //already existing: undefined behavior! (e.g. fail/overwrite) //=> actual behavior: fail with 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()) != std::weak_ordering::equivalent) throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices.")); initComForThread(); //throw FileError const Zstring nativePathTarget = static_cast(pathTo.afsDevice.ref()).getNativePath(pathTo.afsPath); zen::moveAndRenameItem(getNativePath(pathFrom), nativePathTarget, false /*replaceExisting*/); //throw FileError, ErrorTargetExisting, ErrorMoveUnsupported //may fail with ERROR_ALREADY_EXISTS despite previously existing file already deleted //=> reason: corrupted disk, fixable via Windows error checking! https://freefilesync.org/forum/viewtopic.php?t=9776 } bool supportsPermissions(const AfsPath& folderPath) const override //throw FileError { initComForThread(); //throw FileError return zen::supportsPermissions(getNativePath(folderPath)); } //---------------------------------------------------------------------------------------------------------------- FileIconHolder getFileIcon(const AfsPath& filePath, int pixelSize) const override //throw FileError; (optional return value) { initComForThread(); //throw FileError try { return fff::getFileIcon(getNativePath(filePath), pixelSize); //throw SysError } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getDisplayPath(filePath))), e.toString()); } } ImageHolder getThumbnailImage(const AfsPath& filePath, int pixelSize) const override //throw FileError; (optional return value) { initComForThread(); //throw FileError try { return fff::getThumbnailImage(getNativePath(filePath), pixelSize); //throw SysError } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getDisplayPath(filePath))), e.toString()); } } void authenticateAccess(const RequestPasswordFun& requestPassword /*throw X*/) const override //throw FileError, (X) { } bool hasNativeTransactionalCopy() const override { return false; } //---------------------------------------------------------------------------------------------------------------- int64_t getFreeDiskSpace(const AfsPath& folderPath) const override //throw FileError, returns < 0 if not available { initComForThread(); //throw FileError return zen::getFreeDiskSpace(getNativePath(folderPath)); //throw FileError } std::unique_ptr createRecyclerSession(const AfsPath& folderPath) const override //throw FileError, (RecycleBinUnavailable) { initComForThread(); //throw FileError return std::make_unique(getNativePath(folderPath)); } //fails if item is not existing void moveToRecycleBin(const AfsPath& itemPath) const override //throw FileError, RecycleBinUnavailable { initComForThread(); //throw FileError zen::moveToRecycleBin(getNativePath(itemPath)); //throw FileError, RecycleBinUnavailable } const Zstring rootPath_; }; //=========================================================================================================================== //- fails if item is not existing //- multi-threaded access: internally synchronized! void RecycleSessionNative::moveToRecycleBin(const AbstractPath& itemPath, const Zstring& logicalRelPath) //throw FileError, RecycleBinUnavailable { const Zstring& itemPathNative = getNativeItemPath(itemPath); if (itemPathNative.empty()) throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); zen::moveToRecycleBin(itemPathNative); //throw FileError, RecycleBinUnavailable } void RecycleSessionNative::tryCleanup(const std::function& 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 (path.empty()) //eat up empty paths before other AFS implementations get a chance! return true; 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! return static_cast(parsePathComponents(path)); } AbstractPath fff::createItemPathNative(const Zstring& itemPathPhrase) //noexcept { const Zstring& itemPath = getResolvedFilePath(itemPathPhrase); return createItemPathNativeNoFormatting(itemPath); } AbstractPath fff::createItemPathNativeNoFormatting(const Zstring& nativePath) //noexcept { if (const std::optional pc = parsePathComponents(nativePath)) return AbstractPath(makeSharedRef(pc->rootPath), AfsPath(pc->relPath)); assert(nativePath.empty()); //broken path syntax return AbstractPath(makeSharedRef(nativePath), AfsPath()); } Zstring fff::getNativeItemPath(const AbstractPath& itemPath) { if (const auto nativeDevice = dynamic_cast(&itemPath.afsDevice.ref())) return nativeDevice->getNativePath(itemPath.afsPath); return {}; }