581 lines
32 KiB
C++
581 lines
32 KiB
C++
// *****************************************************************************
|
|
// * 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 *
|
|
// *****************************************************************************
|
|
|
|
#ifndef ABSTRACT_H_873450978453042524534234
|
|
#define ABSTRACT_H_873450978453042524534234
|
|
|
|
#include <functional>
|
|
#include <chrono>
|
|
#include <zen/file_error.h>
|
|
#include <zen/file_path.h>
|
|
#include <zen/serialize.h> //InputStream/OutputStream support buffered stream concept
|
|
#include <wx+/image_holder.h> //NOT a wxWidgets dependency!
|
|
|
|
|
|
namespace fff
|
|
{
|
|
struct AfsPath;
|
|
AfsPath sanitizeDeviceRelativePath(Zstring relPath);
|
|
|
|
struct AbstractFileSystem;
|
|
|
|
//==============================================================================================================
|
|
using AfsDevice = zen::SharedRef<const AbstractFileSystem>;
|
|
|
|
struct AfsPath //= path relative to the file system root folder (no leading/traling separator)
|
|
{
|
|
AfsPath() {}
|
|
explicit AfsPath(const Zstring& p) : value(p) { assert(zen::isValidRelPath(value)); }
|
|
Zstring value;
|
|
|
|
std::strong_ordering operator<=>(const AfsPath&) const = default;
|
|
};
|
|
|
|
struct AbstractPath //THREAD-SAFETY: like an int!
|
|
{
|
|
AbstractPath(const AfsDevice& deviceIn, const AfsPath& pathIn) : afsDevice(deviceIn), afsPath(pathIn) {}
|
|
|
|
//template <class T1, class T2> -> don't use forwarding constructor: it circumvents AfsPath's explicit constructor!
|
|
//AbstractPath(T1&& deviceIn, T2&& pathIn) : afsDevice(std::forward<T1>(deviceIn)), afsPath(std::forward<T2>(pathIn)) {}
|
|
|
|
AfsDevice afsDevice; //"const AbstractFileSystem" => all accesses expected to be thread-safe!!!
|
|
AfsPath afsPath; //relative to device root
|
|
};
|
|
//==============================================================================================================
|
|
|
|
struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model thread-safe access!
|
|
{
|
|
//=============== convenience =================
|
|
static Zstring getItemName(const AbstractPath& itemPath) { assert(getParentPath(itemPath)); return getItemName(itemPath.afsPath); }
|
|
static Zstring getItemName(const AfsPath& itemPath) { using namespace zen; return afterLast(itemPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); }
|
|
|
|
static bool isNullPath(const AbstractPath& itemPath) { return isNullDevice(itemPath.afsDevice) /*&& itemPath.afsPath.value.empty()*/; }
|
|
|
|
static AbstractPath appendRelPath(const AbstractPath& itemPath, const Zstring& relPath);
|
|
|
|
static std::optional<AbstractPath> getParentPath(const AbstractPath& itemPath);
|
|
static std::optional<AfsPath> getParentPath(const AfsPath& itemPath);
|
|
//=============================================
|
|
|
|
static std::weak_ordering compareDevice(const AbstractFileSystem& lhs, const AbstractFileSystem& rhs);
|
|
|
|
static bool isNullDevice(const AfsDevice& afsDevice) { return afsDevice.ref().isNullFileSystem(); }
|
|
|
|
static std::wstring getDisplayPath(const AbstractPath& itemPath) { return itemPath.afsDevice.ref().getDisplayPath(itemPath.afsPath); }
|
|
|
|
static Zstring getInitPathPhrase(const AbstractPath& itemPath) { return itemPath.afsDevice.ref().getInitPathPhrase(itemPath.afsPath); }
|
|
|
|
static std::vector<Zstring> getPathPhraseAliases(const AbstractPath& itemPath) { return itemPath.afsDevice.ref().getPathPhraseAliases(itemPath.afsPath); }
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
using RequestPasswordFun = std::function<Zstring(const std::wstring& msg, const std::wstring& lastErrorMsg)>; //throw X
|
|
static void authenticateAccess(const AfsDevice& afsDevice, const RequestPasswordFun& requestPassword /*throw X*/) //throw FileError, X
|
|
{ return afsDevice.ref().authenticateAccess(requestPassword); }
|
|
|
|
static bool supportPermissionCopy(const AbstractPath& sourcePath, const AbstractPath& targetPath); //throw FileError
|
|
|
|
static bool hasNativeTransactionalCopy(const AbstractPath& itemPath) { return itemPath.afsDevice.ref().hasNativeTransactionalCopy(); }
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
using FingerPrint = uint64_t; //AfsDevice-dependent persistent unique ID
|
|
|
|
enum class ItemType : unsigned char
|
|
{
|
|
file,
|
|
folder,
|
|
symlink,
|
|
};
|
|
//(hopefully) fast: does not distinguish between error/not existing
|
|
//root path? => do access test
|
|
static ItemType getItemType(const AbstractPath& itemPath) { return itemPath.afsDevice.ref().getItemType(itemPath.afsPath); } //throw FileError
|
|
|
|
//assumes: - folder traversal access right (=> yes, because we can assume base path exist at this point; e.g. avoids problem when SFTP parent paths might deny access)
|
|
// - all child item path parts must correspond to folder traversal
|
|
// => conclude whether an item is *not* existing anymore by doing a *case-sensitive* name search => potentially SLOW!
|
|
// - root path? => do access test
|
|
static std::optional<ItemType> getItemTypeIfExists(const AbstractPath& itemPath)
|
|
{ return itemPath.afsDevice.ref().getItemTypeIfExists(itemPath.afsPath); } //throw FileError
|
|
|
|
static bool itemExists(const AbstractPath& itemPath) { return static_cast<bool>(getItemTypeIfExists(itemPath)); } //throw FileError
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
//already existing: fail
|
|
//does NOT create parent directories recursively if not existing
|
|
static void createFolderPlain(const AbstractPath& folderPath) { folderPath.afsDevice.ref().createFolderPlain(folderPath.afsPath); } //throw FileError
|
|
|
|
//creates directories recursively if not existing
|
|
//returns false if folder already exists
|
|
static void createFolderIfMissingRecursion(const AbstractPath& folderPath); //throw FileError
|
|
|
|
static void removeFolderIfExistsRecursion(const AbstractPath& folderPath, //throw FileError
|
|
const std::function<void(const std::wstring& displayPath)>& onBeforeFileDeletion /*throw X*/, //
|
|
const std::function<void(const std::wstring& displayPath)>& onBeforeSymlinkDeletion /*throw X*/, //optional; one call for each object!
|
|
const std::function<void(const std::wstring& displayPath)>& onBeforeFolderDeletion /*throw X*/) //
|
|
{ return folderPath.afsDevice.ref().removeFolderIfExistsRecursion(folderPath.afsPath, onBeforeFileDeletion, onBeforeSymlinkDeletion, onBeforeFolderDeletion); }
|
|
|
|
static void removeFileIfExists (const AbstractPath& filePath); //
|
|
static void removeSymlinkIfExists (const AbstractPath& linkPath); //throw FileError
|
|
static void removeEmptyFolderIfExists(const AbstractPath& folderPath); //
|
|
|
|
static void removeFilePlain (const AbstractPath& filePath ) { filePath .afsDevice.ref().removeFilePlain (filePath .afsPath); } //
|
|
static void removeSymlinkPlain(const AbstractPath& linkPath ) { linkPath .afsDevice.ref().removeSymlinkPlain(linkPath .afsPath); } //throw FileError
|
|
static void removeFolderPlain (const AbstractPath& folderPath) { folderPath.afsDevice.ref().removeFolderPlain (folderPath.afsPath); } //
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
//static void setModTime(const AbstractPath& itemPath, time_t modTime) { itemPath.afsDevice.ref().setModTime(itemPath.afsPath, modTime); } //throw FileError, follows symlinks
|
|
|
|
static AbstractPath getSymlinkResolvedPath(const AbstractPath& linkPath) { return linkPath.afsDevice.ref().getSymlinkResolvedPath(linkPath.afsPath); } //throw FileError
|
|
static bool equalSymlinkContent(const AbstractPath& linkPathL, const AbstractPath& linkPathR); //throw FileError
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
static zen::FileIconHolder getFileIcon (const AbstractPath& filePath, int pixelSize) { return filePath.afsDevice.ref().getFileIcon (filePath.afsPath, pixelSize); } //throw FileError; optional return value
|
|
static zen::ImageHolder getThumbnailImage(const AbstractPath& filePath, int pixelSize) { return filePath.afsDevice.ref().getThumbnailImage(filePath.afsPath, pixelSize); } //throw FileError; optional return value
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
struct StreamAttributes
|
|
{
|
|
time_t modTime; //number of seconds since Jan. 1st 1970 GMT
|
|
uint64_t fileSize;
|
|
FingerPrint filePrint; //optional
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
struct InputStream
|
|
{
|
|
virtual ~InputStream() {}
|
|
virtual size_t getBlockSize() = 0; //throw FileError; non-zero block size is AFS contract!
|
|
virtual size_t tryRead(void* buffer, size_t bytesToRead, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) = 0; //throw FileError, ErrorFileLocked, X
|
|
//may return short; only 0 means EOF! CONTRACT: bytesToRead > 0!
|
|
|
|
//only returns attributes if they are already buffered within stream handle and determination would be otherwise expensive (e.g. FTP/SFTP):
|
|
virtual std::optional<StreamAttributes> tryGetAttributesFast() = 0; //throw FileError
|
|
};
|
|
//return value always bound:
|
|
static std::unique_ptr<InputStream> getInputStream(const AbstractPath& filePath) { return filePath.afsDevice.ref().getInputStream(filePath.afsPath); } //throw FileError, ErrorFileLocked
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
struct FinalizeResult
|
|
{
|
|
FingerPrint filePrint = 0; //optional
|
|
std::optional<zen::FileError> errorModTime;
|
|
};
|
|
|
|
struct OutputStreamImpl
|
|
{
|
|
virtual ~OutputStreamImpl() {}
|
|
virtual size_t getBlockSize() = 0; //throw FileError; non-zero block size is AFS contract
|
|
virtual size_t tryWrite(const void* buffer, size_t bytesToWrite, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) = 0; //throw FileError, X; may return short! CONTRACT: bytesToWrite > 0
|
|
virtual FinalizeResult finalize(const zen::IoCallback& notifyUnbufferedIO /*throw X*/) = 0; //throw FileError, X
|
|
};
|
|
|
|
struct OutputStream
|
|
{
|
|
OutputStream(std::unique_ptr<OutputStreamImpl>&& outStream, const AbstractPath& filePath, std::optional<uint64_t> streamSize);
|
|
~OutputStream();
|
|
size_t getBlockSize() { return outStream_->getBlockSize(); } //throw FileError
|
|
size_t tryWrite(const void* buffer, size_t bytesToWrite, const zen::IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X may return short!
|
|
FinalizeResult finalize(const zen::IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X
|
|
//call finalize when done!() when done, or (incomplete) file will be automatically deleted
|
|
|
|
private:
|
|
std::unique_ptr<OutputStreamImpl> outStream_; //bound!
|
|
const AbstractPath filePath_;
|
|
const std::optional<uint64_t> bytesExpected_;
|
|
uint64_t bytesWrittenTotal_ = 0;
|
|
};
|
|
//already existing: undefined behavior! (e.g. fail/overwrite/auto-rename)
|
|
static std::unique_ptr<OutputStream> getOutputStream(const AbstractPath& filePath, //throw FileError
|
|
std::optional<uint64_t> streamSize,
|
|
std::optional<time_t> modTime)
|
|
{ return std::make_unique<OutputStream>(filePath.afsDevice.ref().getOutputStream(filePath.afsPath, streamSize, modTime), filePath, streamSize); }
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
struct SymlinkInfo
|
|
{
|
|
Zstring itemName;
|
|
time_t modTime;
|
|
};
|
|
|
|
struct FileInfo
|
|
{
|
|
Zstring itemName;
|
|
uint64_t fileSize; //unit: bytes!
|
|
time_t modTime; //number of seconds since Jan. 1st 1970 GMT
|
|
FingerPrint filePrint; //optional; persistent + unique (relative to device) or 0!
|
|
bool isFollowedSymlink;
|
|
};
|
|
|
|
struct FolderInfo
|
|
{
|
|
Zstring itemName;
|
|
bool isFollowedSymlink;
|
|
};
|
|
|
|
struct TraverserCallback
|
|
{
|
|
virtual ~TraverserCallback() {}
|
|
|
|
enum class HandleLink
|
|
{
|
|
follow, //follows link, then calls "onFolder()" or "onFile()"
|
|
skip
|
|
};
|
|
|
|
enum class HandleError
|
|
{
|
|
retry,
|
|
ignore
|
|
};
|
|
|
|
virtual void onFile (const FileInfo& fi) = 0; //
|
|
virtual HandleLink onSymlink(const SymlinkInfo& si) = 0; //throw X
|
|
virtual std::shared_ptr<TraverserCallback> onFolder (const FolderInfo& fi) = 0; //
|
|
//nullptr: ignore directory, non-nullptr: traverse into, using the (new) callback
|
|
|
|
struct ErrorInfo
|
|
{
|
|
std::wstring msg;
|
|
std::chrono::steady_clock::time_point failTime;
|
|
size_t retryNumber = 0;
|
|
};
|
|
|
|
virtual HandleError reportDirError (const ErrorInfo& errorInfo) = 0; //failed directory traversal -> consider directory data at current level as incomplete!
|
|
virtual HandleError reportItemError(const ErrorInfo& errorInfo, const Zstring& itemName) = 0; //failed to get data for single file/dir/symlink only!
|
|
};
|
|
|
|
using TraverserWorkload = std::vector<std::pair<AfsPath, std::shared_ptr<TraverserCallback> /*throw X*/>>;
|
|
|
|
//- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...)
|
|
static void traverseFolderRecursive(const AfsDevice& afsDevice, const TraverserWorkload& workload /*throw X*/, size_t parallelOps) { afsDevice.ref().traverseFolderRecursive(workload, parallelOps); }
|
|
|
|
static void traverseFolder(const AbstractPath& folderPath, //throw FileError
|
|
const std::function<void(const FileInfo& fi)>& onFile, //
|
|
const std::function<void(const FolderInfo& fi)>& onFolder, //optional
|
|
const std::function<void(const SymlinkInfo& si)>& onSymlink) //
|
|
{ folderPath.afsDevice.ref().traverseFolder(folderPath.afsPath, onFile, onFolder, onSymlink); }
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
//already existing: undefined behavior! (e.g. fail/overwrite)
|
|
static void moveAndRenameItem(const AbstractPath& pathFrom, const AbstractPath& pathTo); //throw FileError, ErrorMoveUnsupported
|
|
|
|
static std::wstring generateMoveErrorMsg(const AbstractPath& pathFrom, const AbstractPath& pathTo) { return pathFrom.afsDevice.ref().generateMoveErrorMsg(pathFrom.afsPath, pathTo); }
|
|
|
|
|
|
//Note: it MAY happen that copyFileTransactional() leaves temp files behind, e.g. temporary network drop.
|
|
// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending:
|
|
static inline constexpr ZstringView TEMP_FILE_ENDING = Zstr(".ffs_tmp"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
|
|
// caveat: ending is hard-coded by RealTimeSync
|
|
|
|
struct FileCopyResult
|
|
{
|
|
uint64_t fileSize = 0;
|
|
time_t modTime = 0; //number of seconds since Jan. 1st 1970 GMT
|
|
FingerPrint sourceFilePrint = 0; //optional
|
|
FingerPrint targetFilePrint = 0; //
|
|
std::optional<zen::FileError> errorModTime; //failure to set modification time
|
|
};
|
|
|
|
//symlink handling: follow
|
|
//already existing + no onDeleteTargetFile: undefined behavior! (e.g. fail/overwrite/auto-rename)
|
|
//returns current attributes at the time of copy
|
|
static FileCopyResult copyFileTransactional(const AbstractPath& sourcePath, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X
|
|
const AbstractPath& targetPath,
|
|
bool copyFilePermissions,
|
|
bool transactionalCopy,
|
|
//if target is existing user *must* implement deletion to avoid undefined behavior
|
|
//if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it.
|
|
const std::function<void()>& onDeleteTargetFile /*throw X*/,
|
|
//accummulated delta != file size! consider ADS, sparse, compressed files
|
|
const zen::IoCallback& notifyUnbufferedIO /*throw X*/);
|
|
//already existing: fail
|
|
//symlink handling: follow
|
|
static void copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions); //throw FileError
|
|
|
|
//already existing: fail
|
|
static void copySymlink(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions); //throw FileError
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
//- returns < 0 if not available
|
|
//- folderPath does not need to exist (yet)
|
|
static int64_t getFreeDiskSpace(const AbstractPath& folderPath) { return folderPath.afsDevice.ref().getFreeDiskSpace(folderPath.afsPath); } //throw FileError
|
|
|
|
struct RecycleSession
|
|
{
|
|
virtual ~RecycleSession() {}
|
|
|
|
//- multi-threaded access: internally synchronized!
|
|
void moveToRecycleBinIfExists(const AbstractPath& itemPath, const Zstring& logicalRelPath); //throw FileError, RecycleBinUnavailable
|
|
|
|
//- fails if item is not existing: don't leave user wonder why it isn't in the recycle bin!
|
|
//- multi-threaded access: internally synchronized!
|
|
virtual void moveToRecycleBin(const AbstractPath& itemPath, const Zstring& logicalRelPath) = 0; //throw FileError, RecycleBinUnavailable
|
|
|
|
virtual void tryCleanup(const std::function<void(const std::wstring& displayPath)>& notifyDeletionStatus /*throw X*; displayPath may be empty*/) = 0; //throw FileError, X
|
|
};
|
|
|
|
//- return value always bound!
|
|
//- constructor will be running on main thread => *no* file I/O!
|
|
static std::unique_ptr<RecycleSession> createRecyclerSession(const AbstractPath& folderPath) { return folderPath.afsDevice.ref().createRecyclerSession(folderPath.afsPath); } //throw FileError, RecycleBinUnavailable
|
|
|
|
//- returns empty on success, item type if recycle bin is not available
|
|
static void moveToRecycleBinIfExists(const AbstractPath& itemPath); //throw FileError, RecycleBinUnavailable
|
|
|
|
//fails if item is not existing
|
|
static void moveToRecycleBin(const AbstractPath& itemPath) { itemPath.afsDevice.ref().moveToRecycleBin(itemPath.afsPath); }; //throw FileError, RecycleBinUnavailable
|
|
|
|
//================================================================================================================
|
|
|
|
//no need to protect access:
|
|
virtual ~AbstractFileSystem() {}
|
|
|
|
|
|
protected:
|
|
//default implementation: folder traversal
|
|
virtual void removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileError
|
|
const std::function<void(const std::wstring& displayPath)>& onBeforeFileDeletion,
|
|
const std::function<void(const std::wstring& displayPath)>& onBeforeSymlinkDeletion,
|
|
const std::function<void(const std::wstring& displayPath)>& onBeforeFolderDeletion) const = 0;
|
|
|
|
void traverseFolder(const AfsPath& folderPath, //throw FileError
|
|
const std::function<void(const FileInfo& fi)>& onFile, //
|
|
const std::function<void(const FolderInfo& fi)>& onFolder, //optional
|
|
const std::function<void(const SymlinkInfo& si)>& onSymlink) const; //
|
|
|
|
//already existing: undefined behavior! (e.g. fail/overwrite/auto-rename)
|
|
FileCopyResult copyFileAsStream(const AfsPath& sourcePath, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X
|
|
const AbstractPath& targetPath, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const;
|
|
|
|
|
|
std::wstring generateMoveErrorMsg(const AfsPath& pathFrom, const AbstractPath& pathTo) const
|
|
{
|
|
using namespace zen;
|
|
|
|
if (getParentPath(pathFrom) == getParentPath(pathTo.afsPath)) //pure "rename"
|
|
return replaceCpy(replaceCpy(_("Cannot rename %x to %y."),
|
|
L"%x", fmtPath(getDisplayPath(pathFrom))),
|
|
L"%y", fmtPath(getItemName(pathTo)));
|
|
else //"move" or "move + rename"
|
|
return trimCpy(replaceCpy(replaceCpy(_("Cannot move %x to %y."),
|
|
L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
|
|
L"%y", L'\n' + fmtPath(getDisplayPath(pathTo))));
|
|
}
|
|
|
|
private:
|
|
virtual std::optional<Zstring> getNativeItemPath(const AfsPath& itemPath) const { return {}; };
|
|
|
|
virtual Zstring getInitPathPhrase(const AfsPath& itemPath) const = 0;
|
|
|
|
virtual std::vector<Zstring> getPathPhraseAliases(const AfsPath& itemPath) const = 0;
|
|
|
|
virtual std::wstring getDisplayPath(const AfsPath& itemPath) const = 0;
|
|
|
|
virtual bool isNullFileSystem() const = 0;
|
|
|
|
virtual std::weak_ordering compareDeviceSameAfsType(const AbstractFileSystem& afsRhs) const = 0;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
virtual ItemType getItemType(const AfsPath& itemPath) const = 0; //throw FileError
|
|
|
|
virtual std::optional<ItemType> getItemTypeIfExists(const AfsPath& itemPath) const = 0; //throw FileError
|
|
|
|
//already existing: fail
|
|
virtual void createFolderPlain(const AfsPath& folderPath) const = 0; //throw FileError
|
|
|
|
//non-recursive folder deletion:
|
|
virtual void removeFilePlain (const AfsPath& filePath ) const = 0; //throw FileError
|
|
virtual void removeSymlinkPlain(const AfsPath& linkPath ) const = 0; //throw FileError
|
|
virtual void removeFolderPlain (const AfsPath& folderPath) const = 0; //throw FileError
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
//virtual void setModTime(const AfsPath& itemPath, time_t modTime) const = 0; //throw FileError, follows symlinks
|
|
|
|
virtual AbstractPath getSymlinkResolvedPath(const AfsPath& linkPath) const = 0; //throw FileError
|
|
virtual bool equalSymlinkContentForSameAfsType(const AfsPath& linkPathL, const AbstractPath& linkPathR) const = 0; //throw FileError
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
virtual std::unique_ptr<InputStream> getInputStream(const AfsPath& filePath) const = 0; //throw FileError, ErrorFileLocked
|
|
|
|
//already existing: undefined behavior! (e.g. fail/overwrite/auto-rename)
|
|
virtual std::unique_ptr<OutputStreamImpl> getOutputStream(const AfsPath& filePath, //throw FileError
|
|
std::optional<uint64_t> streamSize,
|
|
std::optional<time_t> modTime) const = 0;
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
virtual void traverseFolderRecursive(const TraverserWorkload& workload /*throw X*/, size_t parallelOps) const = 0;
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
virtual bool supportsPermissions(const AfsPath& folderPath) const = 0; //throw FileError
|
|
|
|
//already existing: undefined behavior! (e.g. fail/overwrite)
|
|
virtual void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const = 0; //throw FileError, ErrorMoveUnsupported
|
|
|
|
//symlink handling: follow
|
|
//already existing: undefined behavior! (e.g. fail/overwrite/auto-rename)
|
|
virtual FileCopyResult copyFileForSameAfsType(const AfsPath& sourcePath, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X
|
|
const AbstractPath& targetPath, bool copyFilePermissions,
|
|
//accummulated delta != file size! consider ADS, sparse, compressed files
|
|
const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const = 0;
|
|
|
|
|
|
//symlink handling: follow
|
|
//already existing: fail
|
|
virtual void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const = 0; //throw FileError
|
|
|
|
//already existing: fail
|
|
virtual void copySymlinkForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const = 0; //throw FileError
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
virtual zen::FileIconHolder getFileIcon (const AfsPath& filePath, int pixelSize) const = 0; //throw FileError; optional return value
|
|
virtual zen::ImageHolder getThumbnailImage(const AfsPath& filePath, int pixelSize) const = 0; //throw FileError; optional return value
|
|
|
|
virtual void authenticateAccess(const RequestPasswordFun& requestPassword /*throw X*/) const = 0; //throw FileError, X
|
|
|
|
virtual bool hasNativeTransactionalCopy() const = 0;
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
|
|
virtual int64_t getFreeDiskSpace(const AfsPath& folderPath) const = 0; //throw FileError, returns < 0 if not available
|
|
virtual std::unique_ptr<RecycleSession> createRecyclerSession(const AfsPath& folderPath) const = 0; //throw FileError, RecycleBinUnavailable
|
|
virtual void moveToRecycleBin(const AfsPath& itemPath) const = 0; //throw FileError, RecycleBinUnavailable
|
|
};
|
|
|
|
|
|
inline std::weak_ordering operator<=>(const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()); }
|
|
inline bool operator== (const AfsDevice& lhs, const AfsDevice& rhs) { return (lhs <=> rhs) == std::weak_ordering::equivalent; }
|
|
|
|
inline
|
|
std::weak_ordering operator<=>(const AbstractPath& lhs, const AbstractPath& rhs)
|
|
{
|
|
return std::tie(lhs.afsDevice, lhs.afsPath) <=>
|
|
std::tie(rhs.afsDevice, rhs.afsPath);
|
|
}
|
|
|
|
inline
|
|
bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { return lhs.afsPath == rhs.afsPath && lhs.afsDevice == rhs.afsDevice; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//------------------------------------ implementation -----------------------------------------
|
|
inline
|
|
AbstractPath AbstractFileSystem::appendRelPath(const AbstractPath& itemPath, const Zstring& relPath)
|
|
{
|
|
return AbstractPath(itemPath.afsDevice, AfsPath(appendPath(itemPath.afsPath.value, relPath)));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
|
|
inline
|
|
AbstractFileSystem::OutputStream::OutputStream(std::unique_ptr<OutputStreamImpl>&& outStream, const AbstractPath& filePath, std::optional<uint64_t> streamSize) :
|
|
outStream_(std::move(outStream)),
|
|
filePath_(filePath),
|
|
bytesExpected_(streamSize) {}
|
|
|
|
|
|
inline
|
|
AbstractFileSystem::OutputStream::~OutputStream()
|
|
{
|
|
}
|
|
|
|
|
|
inline
|
|
size_t AbstractFileSystem::OutputStream::tryWrite(const void* buffer, size_t bytesToWrite, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
|
|
{
|
|
const size_t bytesWritten = outStream_->tryWrite(buffer, bytesToWrite, notifyUnbufferedIO /*throw X*/); //throw FileError, X may return short!
|
|
bytesWrittenTotal_ += bytesWritten;
|
|
return bytesWritten;
|
|
}
|
|
|
|
|
|
inline
|
|
AbstractFileSystem::FinalizeResult AbstractFileSystem::OutputStream::finalize(const zen::IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
|
|
{
|
|
using namespace zen;
|
|
|
|
//important check: catches corrupt SFTP download with libssh2!
|
|
if (bytesExpected_ && *bytesExpected_ != bytesWrittenTotal_)
|
|
throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getDisplayPath(filePath_))), //instead we should report the source file, but don't have it here...
|
|
_("Unexpected size of data stream:") + L' ' + formatNumber(bytesWrittenTotal_) + L'\n' +
|
|
_("Expected:") + L' ' + formatNumber(*bytesExpected_));
|
|
|
|
const FinalizeResult result = outStream_->finalize(notifyUnbufferedIO); //throw FileError, X
|
|
return result;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
inline
|
|
bool AbstractFileSystem::supportPermissionCopy(const AbstractPath& sourcePath, const AbstractPath& targetPath) //throw FileError
|
|
{
|
|
if (typeid(sourcePath.afsDevice.ref()) != typeid(targetPath.afsDevice.ref()))
|
|
return false;
|
|
|
|
return sourcePath.afsDevice.ref().supportsPermissions(sourcePath.afsPath) && //throw FileError
|
|
targetPath.afsDevice.ref().supportsPermissions(targetPath.afsPath);
|
|
}
|
|
|
|
|
|
inline
|
|
bool AbstractFileSystem::equalSymlinkContent(const AbstractPath& linkPathL, const AbstractPath& linkPathR) //throw FileError
|
|
{
|
|
if (typeid(linkPathL.afsDevice.ref()) != typeid(linkPathR.afsDevice.ref()))
|
|
return false;
|
|
|
|
return linkPathL.afsDevice.ref().equalSymlinkContentForSameAfsType(linkPathL.afsPath, linkPathR); //throw FileError
|
|
}
|
|
|
|
|
|
inline
|
|
void AbstractFileSystem::moveAndRenameItem(const AbstractPath& pathFrom, const AbstractPath& pathTo) //throw FileError, ErrorMoveUnsupported
|
|
{
|
|
using namespace zen;
|
|
|
|
if (typeid(pathFrom.afsDevice.ref()) != typeid(pathTo.afsDevice.ref()))
|
|
throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices."));
|
|
|
|
//already existing: undefined behavior! (e.g. fail/overwrite)
|
|
pathFrom.afsDevice.ref().moveAndRenameItemForSameAfsType(pathFrom.afsPath, pathTo); //throw FileError, ErrorMoveUnsupported
|
|
}
|
|
|
|
|
|
inline
|
|
void AbstractFileSystem::copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) //throw FileError
|
|
{
|
|
using namespace zen;
|
|
|
|
if (typeid(sourcePath.afsDevice.ref()) != typeid(targetPath.afsDevice.ref())) //fall back:
|
|
{
|
|
//already existing: fail
|
|
createFolderPlain(targetPath); //throw FileError
|
|
|
|
if (copyFilePermissions)
|
|
throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(getDisplayPath(targetPath))),
|
|
_("Operation not supported between different devices."));
|
|
}
|
|
else
|
|
sourcePath.afsDevice.ref().copyNewFolderForSameAfsType(sourcePath.afsPath, targetPath, copyFilePermissions); //throw FileError
|
|
}
|
|
|
|
|
|
//already existing: fail
|
|
inline
|
|
void AbstractFileSystem::copySymlink(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) //throw FileError
|
|
{
|
|
using namespace zen;
|
|
|
|
if (typeid(sourcePath.afsDevice.ref()) != typeid(targetPath.afsDevice.ref()))
|
|
throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."),
|
|
L"%x", L'\n' + fmtPath(getDisplayPath(sourcePath))),
|
|
L"%y", L'\n' + fmtPath(getDisplayPath(targetPath))), _("Operation not supported between different devices."));
|
|
|
|
//already existing: fail
|
|
sourcePath.afsDevice.ref().copySymlinkForSameAfsType(sourcePath.afsPath, targetPath, copyFilePermissions); //throw FileError
|
|
}
|
|
}
|
|
|
|
#endif //ABSTRACT_H_873450978453042524534234
|