Files
2025-12-10 14:38:26 -08:00

1494 lines
52 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 FILE_HIERARCHY_H_257235289645296
#define FILE_HIERARCHY_H_257235289645296
#include <string>
#include <unordered_map>
#include "structures.h"
#include "path_filter.h"
#include "../afs/abstract.h"
namespace fff
{
struct FileAttributes
{
time_t modTime = 0; //number of seconds since Jan. 1st 1970 GMT
uint64_t fileSize = 0;
AFS::FingerPrint filePrint = 0; //optional
bool isFollowedSymlink = false;
static_assert(std::is_signed_v<time_t>, "we need time_t to be signed");
std::strong_ordering operator<=>(const FileAttributes&) const = default;
};
struct LinkAttributes
{
time_t modTime = 0; //number of seconds since Jan. 1st 1970 GMT
};
struct FolderAttributes
{
bool isFollowedSymlink = false;
};
struct FolderContainer
{
//------------------------------------------------------------------
//key: raw file name, without any (Unicode) normalization, preserving original upper-/lower-case
//"Changing data [...] to NFC would cause interoperability problems. Always leave data as it is."
using FolderList = std::unordered_map<Zstring, std::pair<FolderAttributes, FolderContainer>>;
using FileList = std::unordered_map<Zstring, FileAttributes>;
using SymlinkList = std::unordered_map<Zstring, LinkAttributes>;
//------------------------------------------------------------------
FolderContainer() = default;
FolderContainer (const FolderContainer&) = delete; //catch accidental (and unnecessary) copying
FolderContainer& operator=(const FolderContainer&) = delete; //
FileList files;
SymlinkList symlinks; //non-followed symlinks
FolderList folders;
void addFile(const Zstring& itemName, const FileAttributes& attr)
{
files.insert_or_assign(itemName, attr); //update entry if already existing (e.g. during folder traverser "retry")
}
void addSymlink(const Zstring& itemName, const LinkAttributes& attr)
{
symlinks.insert_or_assign(itemName, attr);
}
FolderContainer& addFolder(const Zstring& itemName, const FolderAttributes& attr)
{
auto& p = folders[itemName]; //value default-construction is okay here
p.first = attr;
return p.second;
}
};
//------------------------------------------------------------------
enum class SelectSide
{
left,
right
};
template <SelectSide side>
constexpr SelectSide getOtherSide = side == SelectSide::left ? SelectSide::right : SelectSide::left;
template <SelectSide side, class T> inline
T& selectParam(T& left, T& right)
{
if constexpr (side == SelectSide::left)
return left;
else
return right;
}
enum class FileContentCategory : unsigned char
{
unknown,
equal,
leftNewer,
rightNewer,
invalidTime,
different,
conflict,
};
inline
SyncDirection getEffectiveSyncDir(SyncOperation syncOp)
{
switch (syncOp)
{
case SO_CREATE_LEFT:
case SO_DELETE_LEFT:
case SO_OVERWRITE_LEFT:
case SO_RENAME_LEFT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
return SyncDirection::left;
case SO_CREATE_RIGHT:
case SO_DELETE_RIGHT:
case SO_OVERWRITE_RIGHT:
case SO_RENAME_RIGHT:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_RIGHT_TO:
return SyncDirection::right;
case SO_DO_NOTHING:
case SO_EQUAL:
case SO_UNRESOLVED_CONFLICT:
break; //nothing to do
}
return SyncDirection::none;
}
std::wstring getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, const AbstractPath& itemPathR);
class FileSystemObject;
class SymlinkPair;
class FilePair;
class FolderPair;
class BaseFolderPair;
/*------------------------------------------------------------------
inheritance diagram:
std::enable_shared_from_this PathInformation
/|\ /|\
|____________ _________|_________
| | |
FileSystemObject ContainerObject
/|\ /|\
___________|___________ ______|______
| | | | |
SymlinkPair FilePair FolderPair BaseFolderPair
------------------------------------------------------------------*/
struct PathInformation //diamond-shaped inheritance!
{
virtual ~PathInformation() {}
template <SelectSide side> AbstractPath getAbstractPath() const;
template <SelectSide side> Zstring getRelativePath() const; //get path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR)
private:
virtual AbstractPath getAbstractPathL() const = 0; //implemented by FileSystemObject + BaseFolderPair
virtual AbstractPath getAbstractPathR() const = 0; //
virtual Zstring getRelativePathL() const = 0; //implemented by SymlinkPair/FilePair + ContainerObject
virtual Zstring getRelativePathR() const = 0; //
};
template <> inline AbstractPath PathInformation::getAbstractPath<SelectSide::left >() const { return getAbstractPathL(); }
template <> inline AbstractPath PathInformation::getAbstractPath<SelectSide::right>() const { return getAbstractPathR(); }
template <> inline Zstring PathInformation::getRelativePath<SelectSide::left >() const { return getRelativePathL(); }
template <> inline Zstring PathInformation::getRelativePath<SelectSide::right>() const { return getRelativePathR(); }
//------------------------------------------------------------------
class ContainerObject : public virtual PathInformation
{
friend class FileSystemObject; //access to updateRelPathsRecursion()
public:
using FileList = std::vector<zen::SharedRef<FilePair>>; //MergeSides::execute() requires a structure that doesn't invalidate pointers after push_back()
using SymlinkList = std::vector<zen::SharedRef<SymlinkPair>>; //
using FolderList = std::vector<zen::SharedRef<FolderPair>>;
FolderPair& addFolder(const Zstring& itemNameL, const FolderAttributes& attribL,
const Zstring& itemNameR, const FolderAttributes& attribR); //exists on both sides
template <SelectSide side>
FolderPair& addFolder(const Zstring& itemName, const FolderAttributes& attr); //exists on one side only
FilePair& addFile(const Zstring& itemNameL, const FileAttributes& attribL,
const Zstring& itemNameR, const FileAttributes& attribR); //exists on both sides
template <SelectSide side>
FilePair& addFile(const Zstring& itemName, const FileAttributes& attr); //exists on one side only
SymlinkPair& addSymlink(const Zstring& itemNameL, const LinkAttributes& attribL,
const Zstring& itemNameR, const LinkAttributes& attribR); //exists on both sides
template <SelectSide side>
SymlinkPair& addSymlink(const Zstring& itemName, const LinkAttributes& attr); //exists on one side only
zen::Range<zen::DerefIter<FileList::const_iterator, const FilePair>> files() const { return {files_.begin(), files_.end()}; }
zen::Range<zen::DerefIter<FileList:: iterator, FilePair>> files() { return {files_.begin(), files_.end()}; }
zen::Range<zen::DerefIter<SymlinkList::const_iterator, const SymlinkPair>> symlinks() const { return {symlinks_.begin(), symlinks_.end()}; }
zen::Range<zen::DerefIter<SymlinkList:: iterator, SymlinkPair>> symlinks() { return {symlinks_.begin(), symlinks_.end()}; }
zen::Range<zen::DerefIter<FolderList::const_iterator, const FolderPair>> subfolders() const { return {subfolders_.begin(), subfolders_.end()}; }
zen::Range<zen::DerefIter<FolderList:: iterator, FolderPair>> subfolders() { return {subfolders_.begin(), subfolders_.end()}; }
void clearFiles () { files_ .clear(); }
void clearSymlinks () { symlinks_ .clear(); }
void clearSubfolders() { subfolders_.clear(); }
template <class Function>
void foldersRemoveIf(Function fun) { zen::eraseIf(subfolders_, [fun](auto& fsObj) { return fun(fsObj.ref()); }); }
const BaseFolderPair& getBase() const { return base_; }
/**/ BaseFolderPair& getBase() { return base_; }
void removeDoubleEmpty(); //remove all invalid entries (where both sides are empty) recursively
virtual void flip();
protected:
explicit ContainerObject(BaseFolderPair& baseFolder) : //used during BaseFolderPair constructor
base_(baseFolder) //take reference only: baseFolder *not yet* fully constructed at this point!
{ assert(relPathL_.c_str() == relPathR_.c_str()); } //expected by the following contructor!
explicit ContainerObject(const FileSystemObject& fsAlias); //used during FolderPair constructor
virtual ~ContainerObject() //don't need polymorphic deletion, but we have a vtable anyway
{ assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_); }
template <SelectSide side>
void updateRelPathsRecursion(const FileSystemObject& fsAlias);
private:
ContainerObject (const ContainerObject&) = delete; //this class is referenced by its child elements => make it non-copyable/movable!
ContainerObject& operator=(const ContainerObject&) = delete;
Zstring getRelativePathL() const override { return relPathL_; }
Zstring getRelativePathR() const override { return relPathR_; }
FileList files_;
SymlinkList symlinks_;
FolderList subfolders_;
Zstring relPathL_; //path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR)
Zstring relPathR_; //class invariant: shared Zstring iff equal!
BaseFolderPair& base_;
};
//------------------------------------------------------------------
enum class BaseFolderStatus
{
existing,
notExisting,
failure,
};
class BaseFolderPair : public ContainerObject
{
public:
BaseFolderPair(const AbstractPath& folderPathLeft,
BaseFolderStatus folderStatusLeft,
const AbstractPath& folderPathRight,
BaseFolderStatus folderStatusRight,
const FilterRef& filter,
CompareVariant cmpVar,
unsigned int fileTimeTolerance,
const std::vector<unsigned int>& ignoreTimeShiftMinutes) :
ContainerObject(*this), //trust that ContainerObject knows that *this is not yet fully constructed!
filter_(filter), cmpVar_(cmpVar), fileTimeTolerance_(fileTimeTolerance), ignoreTimeShiftMinutes_(ignoreTimeShiftMinutes),
folderStatusLeft_ (folderStatusLeft),
folderStatusRight_(folderStatusRight),
folderPathLeft_(folderPathLeft),
folderPathRight_(folderPathRight) {}
template <SelectSide side> BaseFolderStatus getFolderStatus() const; //base folder status at the time of comparison!
template <SelectSide side> void setFolderStatus(BaseFolderStatus value); //update after creating the directory in FFS
//get settings which were used while creating BaseFolderPair:
const PathFilter& getFilter() const { return filter_.ref(); }
CompareVariant getCompVariant() const { return cmpVar_; }
unsigned int getFileTimeTolerance() const { return fileTimeTolerance_; }
const std::vector<unsigned int>& getIgnoredTimeShift() const { return ignoreTimeShiftMinutes_; }
void flip() override;
private:
AbstractPath getAbstractPathL() const override { return folderPathLeft_; }
AbstractPath getAbstractPathR() const override { return folderPathRight_; }
const FilterRef filter_; //filter used while scanning directory: represents sub-view of actual files!
const CompareVariant cmpVar_;
const unsigned int fileTimeTolerance_;
const std::vector<unsigned int> ignoreTimeShiftMinutes_;
BaseFolderStatus folderStatusLeft_;
BaseFolderStatus folderStatusRight_;
AbstractPath folderPathLeft_;
AbstractPath folderPathRight_;
};
using FolderComparison = std::vector<zen::SharedRef<BaseFolderPair>>; //make sure pointers to sub-elements remain valid
//don't change this back to std::vector<BaseFolderPair> inconsiderately: comparison uses push_back to add entries which may result in a full copy!
zen::Range<zen::DerefIter<FolderComparison::iterator, BaseFolderPair>> inline asRange( FolderComparison& vect) { return {vect.begin(), vect.end()}; }
zen::Range<zen::DerefIter<FolderComparison::const_iterator, const BaseFolderPair>> inline asRange(const FolderComparison& vect) { return {vect.begin(), vect.end()}; }
//------------------------------------------------------------------
struct FSObjectVisitor
{
virtual ~FSObjectVisitor() {}
virtual void visit(const FilePair& file ) = 0;
virtual void visit(const SymlinkPair& symlink) = 0;
virtual void visit(const FolderPair& folder ) = 0;
};
//------------------------------------------------------------------
class FileSystemObject : public std::enable_shared_from_this<FileSystemObject>, public virtual PathInformation
{
public:
virtual void accept(FSObjectVisitor& visitor) const = 0;
bool isPairEmpty() const; //true, if both sides are empty
template <SelectSide side> bool isEmpty() const;
//path getters always return valid values, even if isEmpty<side>()!
template <SelectSide side> Zstring getItemName() const; //case sensitive!
bool hasEquivalentItemNames() const; //*quick* check if left/right names are equivalent when ignoring Unicode normalization forms
//for use during compare() only:
virtual void setCategoryConflict(const Zstringc& description) = 0;
//comparison result
virtual CompareFileResult getCategory() const = 0;
virtual Zstringc getCategoryCustomDescription() const = 0; //optional
//sync settings
void setSyncDir(SyncDirection newDir);
void setSyncDirConflict(const Zstringc& description); //set syncDir = SyncDirection::none + fill conflict description
bool isActive() const { return selectedForSync_; }
void setActive(bool active);
//sync operation
virtual SyncOperation testSyncOperation(SyncDirection testSyncDir) const; //"what if" semantics! assumes "active, no conflict, no recursion (directory)!
virtual SyncOperation getSyncOperation() const;
std::wstring getSyncOpConflict() const; //return conflict when determining sync direction or (still unresolved) conflict during categorization
const ContainerObject& parent() const { return parent_; }
/**/ ContainerObject& parent() { return parent_; }
const BaseFolderPair& base() const { return parent_.getBase(); }
/**/ BaseFolderPair& base() { return parent_.getBase(); }
bool passFileFilter(const PathFilter& filter) const; //optimized for perf!
virtual void flip();
template <SelectSide side>
void setItemName(const Zstring& itemName);
protected:
FileSystemObject(const Zstring& itemNameL,
const Zstring& itemNameR,
ContainerObject& parentObj) :
itemNameL_(itemNameL),
itemNameR_(itemNameL == itemNameR ? itemNameL : itemNameR), //perf: no measurable speed drawback; -3% peak memory => further needed by ContainerObject construction!
parent_(parentObj)
{
assert(itemNameL_.c_str() == itemNameR_.c_str() || itemNameL_ != itemNameR_); //also checks ref-counted string precondition
FileSystemObject::notifySyncCfgChanged(); //non-virtual call! (=> anyway in a constructor!)
}
virtual ~FileSystemObject() //don't need polymorphic deletion, but we have a vtable anyway
{ assert(itemNameL_.c_str() == itemNameR_.c_str() || itemNameL_ != itemNameR_); }
virtual void notifySyncCfgChanged()
{
if (auto fsParent = dynamic_cast<FileSystemObject*>(&parent_))
fsParent->notifySyncCfgChanged(); //propagate!
}
template <SelectSide side> void removeFsObject();
private:
FileSystemObject (const FileSystemObject&) = delete;
FileSystemObject& operator=(const FileSystemObject&) = delete;
AbstractPath getAbstractPathL() const override { return AFS::appendRelPath(base().getAbstractPath<SelectSide::left >(), getRelativePath<SelectSide::left >()); }
AbstractPath getAbstractPathR() const override { return AFS::appendRelPath(base().getAbstractPath<SelectSide::right>(), getRelativePath<SelectSide::right>()); }
template <SelectSide side>
void propagateChangedItemName(); //required after any itemName changes
bool selectedForSync_ = true;
SyncDirection syncDir_ = SyncDirection::none;
Zstringc syncDirectionConflict_; //non-empty if we have a conflict setting sync-direction
//conserve memory (avoid std::string SSO overhead + allow ref-counting!)
Zstring itemNameL_; //use as indicator: empty means "not existing on this side"
Zstring itemNameR_; //class invariant: same Zstring.c_str() pointer iff equal!
ContainerObject& parent_;
};
//------------------------------------------------------------------
class FolderPair : public FileSystemObject, public ContainerObject
{
public:
void accept(FSObjectVisitor& visitor) const override;
CompareFileResult getCategory() const override;
CompareDirResult getDirCategory() const { return static_cast<CompareDirResult>(getCategory()); }
FolderPair(const Zstring& itemNameL, const FolderAttributes& attrL, //use empty itemName if "not existing"
const Zstring& itemNameR, const FolderAttributes& attrR,
ContainerObject& parentObj) :
FileSystemObject(itemNameL, itemNameR, parentObj),
ContainerObject(static_cast<FileSystemObject&>(*this)), //FileSystemObject fully constructed at this point!
attrL_(attrL),
attrR_(attrR) {}
template <SelectSide side> bool isFollowedSymlink() const;
SyncOperation getSyncOperation() const override;
template <SelectSide sideTrg>
void setSyncedTo(bool isSymlinkTrg, bool isSymlinkSrc); //call after successful sync
bool passDirFilter(const PathFilter& filter, bool* childItemMightMatch) const; //optimized for perf!
void flip() override;
void setCategoryConflict(const Zstringc& description) override;
Zstringc getCategoryCustomDescription() const override; //optional
template <SelectSide side> void removeItem();
private:
void notifySyncCfgChanged() override { syncOpBuffered_ = {}; FileSystemObject::notifySyncCfgChanged(); }
mutable std::optional<SyncOperation> syncOpBuffered_; //determining sync-op for directory may be expensive as it depends on child-objects => buffer
FolderAttributes attrL_;
FolderAttributes attrR_;
Zstringc categoryConflict_; //conserve memory (avoid std::string SSO overhead + allow ref-counting!
};
//------------------------------------------------------------------
class FilePair : public FileSystemObject
{
public:
void accept(FSObjectVisitor& visitor) const override;
FilePair(const Zstring& itemNameL, //use empty string if "not existing"
const FileAttributes& attrL,
const Zstring& itemNameR, //
const FileAttributes& attrR,
ContainerObject& parentObj) :
FileSystemObject(itemNameL, itemNameR, parentObj),
attrL_(attrL),
attrR_(attrR) {}
CompareFileResult getCategory() const override;
template <SelectSide side> time_t getLastWriteTime() const;
template <SelectSide side> uint64_t getFileSize() const;
template <SelectSide side> bool isFollowedSymlink() const;
template <SelectSide side> FileAttributes getAttributes() const;
template <SelectSide side> AFS::FingerPrint getFilePrint() const;
template <SelectSide side> void clearFilePrint();
void setMovePair(FilePair* ref); //reference to corresponding moved/renamed file
FilePair* getMovePair() const; //may be nullptr
SyncOperation testSyncOperation(SyncDirection testSyncDir) const override; //semantics: "what if"! assumes "active, no conflict, no recursion (directory)!
SyncOperation getSyncOperation() const override;
template <SelectSide sideTrg>
void setSyncedTo(uint64_t fileSize,
time_t lastWriteTimeTrg,
time_t lastWriteTimeSrc,
AFS::FingerPrint filePrintTrg,
AFS::FingerPrint filePrintSrc,
bool isSymlinkTrg,
bool isSymlinkSrc); //call after successful sync
void flip() override;
void setCategoryConflict(const Zstringc& description) override;
void setCategoryInvalidTime(const Zstringc& description);
Zstringc getCategoryCustomDescription() const override; //optional
void setContentCategory(FileContentCategory category);
FileContentCategory getContentCategory() const;
template <SelectSide side> void removeItem();
private:
Zstring getRelativePathL() const override { return appendPath(parent().getRelativePath<SelectSide::left >(), getItemName<SelectSide::left >()); }
Zstring getRelativePathR() const override { return appendPath(parent().getRelativePath<SelectSide::right>(), getItemName<SelectSide::right>()); }
SyncOperation applyMoveOptimization(SyncOperation op) const;
FileAttributes attrL_;
FileAttributes attrR_;
std::weak_ptr<FilePair> moveFileRef_; //optional, filled by DetectMovedFiles::findAndSetMovePair()
FileContentCategory contentCategory_ = FileContentCategory::unknown;
Zstringc categoryDescr_; //optional: custom category description (e.g. FileContentCategory::conflict or invalidTime)
};
//------------------------------------------------------------------
class SymlinkPair : public FileSystemObject //models an unresolved symbolic link: followed-links should go in FilePair/FolderPair
{
public:
void accept(FSObjectVisitor& visitor) const override;
SymlinkPair(const Zstring& itemNameL, //use empty string if "not existing"
const LinkAttributes& attrL,
const Zstring& itemNameR, //use empty string if "not existing"
const LinkAttributes& attrR,
ContainerObject& parentObj) :
FileSystemObject(itemNameL, itemNameR, parentObj),
attrL_(attrL),
attrR_(attrR) {}
CompareFileResult getCategory() const override;
CompareSymlinkResult getLinkCategory() const { return static_cast<CompareSymlinkResult>(getCategory()); }
template <SelectSide side> time_t getLastWriteTime() const; //write time of the link, NOT target!
template <SelectSide sideTrg>
void setSyncedTo(time_t lastWriteTimeTrg,
time_t lastWriteTimeSrc); //call after successful sync
void flip() override;
void setCategoryConflict(const Zstringc& description) override;
void setCategoryInvalidTime(const Zstringc& description);
Zstringc getCategoryCustomDescription() const override; //optional
void setContentCategory(FileContentCategory category);
FileContentCategory getContentCategory() const;
template <SelectSide side> void removeItem();
private:
Zstring getRelativePathL() const override { return appendPath(parent().getRelativePath<SelectSide::left >(), getItemName<SelectSide::left >()); }
Zstring getRelativePathR() const override { return appendPath(parent().getRelativePath<SelectSide::right>(), getItemName<SelectSide::right>()); }
LinkAttributes attrL_;
LinkAttributes attrR_;
FileContentCategory contentCategory_ = FileContentCategory::unknown;
Zstringc categoryDescr_; //optional: custom category description (e.g. FileContentCategory::conflict or invalidTime)
};
//------------------------------------------------------------------
//generic descriptions (usecase CSV legend, sync config)
std::wstring getCategoryDescription(CompareFileResult cmpRes);
std::wstring getSyncOpDescription(SyncOperation op);
//item-specific descriptions
std::wstring getCategoryDescription(const FileSystemObject& fsObj);
std::wstring getSyncOpDescription(const FileSystemObject& fsObj);
//------------------------------------------------------------------
namespace impl
{
template <class Function1, class Function2, class Function3>
struct FSObjectLambdaVisitor : public FSObjectVisitor
{
FSObjectLambdaVisitor(Function1 onFolder,
Function2 onFile,
Function3 onSymlink) : //unifying assignment
onFolder_(std::move(onFolder)), onFile_(std::move(onFile)), onSymlink_(std::move(onSymlink)) {}
private:
void visit(const FolderPair& folder ) override { onFolder_ (folder); }
void visit(const FilePair& file ) override { onFile_ (file); }
void visit(const SymlinkPair& symlink) override { onSymlink_(symlink); }
const Function1 onFolder_;
const Function2 onFile_;
const Function3 onSymlink_;
};
}
template <class Function1, class Function2, class Function3> inline
void visitFSObject(const FileSystemObject& fsObj, Function1 onFolder, Function2 onFile, Function3 onSymlink)
{
impl::FSObjectLambdaVisitor<Function1, Function2, Function3> visitor(onFolder, onFile, onSymlink);
fsObj.accept(visitor);
}
template <class Function1, class Function2, class Function3> inline
void visitFSObject(FileSystemObject& fsObj, Function1 onFolder, Function2 onFile, Function3 onSymlink)
{
visitFSObject(static_cast<const FileSystemObject&>(fsObj),
[onFolder ](const FolderPair& folder) { onFolder (const_cast<FolderPair& >(folder)); }, //
[onFile ](const FilePair& file) { onFile (const_cast<FilePair& >(file )); }, //physical object is not const anyway
[onSymlink](const SymlinkPair& symlink) { onSymlink(const_cast<SymlinkPair&>(symlink)); }); //
}
//------------------------------------------------------------------
namespace impl
{
template <class Function1, class Function2, class Function3>
class RecursiveObjectVisitor
{
public:
RecursiveObjectVisitor(Function1 onFolder,
Function2 onFile,
Function3 onSymlink) : //unifying assignment
onFolder_ (std::move(onFolder)),
onFile_ (std::move(onFile)),
onSymlink_(std::move(onSymlink)) {}
void execute(ContainerObject& conObj)
{
for (FilePair& file : conObj.files())
onFile_(file);
for (SymlinkPair& symlink : conObj.symlinks())
onSymlink_(symlink);
for (FolderPair& subFolder : conObj.subfolders())
{
onFolder_(subFolder);
execute(subFolder);
}
}
private:
RecursiveObjectVisitor (const RecursiveObjectVisitor&) = delete;
RecursiveObjectVisitor& operator=(const RecursiveObjectVisitor&) = delete;
const Function1 onFolder_;
const Function2 onFile_;
const Function3 onSymlink_;
};
}
template <class Function1, class Function2, class Function3> inline
void visitFSObjectRecursively(ContainerObject& conObj, //consider contained items only
Function1 onFolder,
Function2 onFile,
Function3 onSymlink)
{
impl::RecursiveObjectVisitor(onFolder, onFile, onSymlink).execute(conObj);
}
template <class Function1, class Function2, class Function3> inline
void visitFSObjectRecursively(FileSystemObject& fsObj, //consider item and contained items (if folder)
Function1 onFolder,
Function2 onFile,
Function3 onSymlink)
{
visitFSObject(fsObj, [onFolder, onFile, onSymlink](FolderPair& folder)
{
onFolder(folder);
impl::RecursiveObjectVisitor(onFolder, onFile, onSymlink).execute(const_cast<FolderPair&>(folder));
}, onFile, onSymlink);
}
//--------------------- implementation ------------------------------------------
//inline virtual... admittedly its use may be limited
inline void FolderPair ::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
inline void FilePair ::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
inline void SymlinkPair::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
inline
void FileSystemObject::setSyncDir(SyncDirection newDir)
{
syncDir_ = newDir;
syncDirectionConflict_.clear();
notifySyncCfgChanged();
}
inline
void FileSystemObject::setSyncDirConflict(const Zstringc& description)
{
assert(!description.empty());
syncDir_ = SyncDirection::none;
syncDirectionConflict_ = description;
notifySyncCfgChanged();
}
inline
std::wstring FileSystemObject::getSyncOpConflict() const
{
assert(getSyncOperation() == SO_UNRESOLVED_CONFLICT);
return zen::utfTo<std::wstring>(syncDirectionConflict_);
}
inline
void FileSystemObject::setActive(bool active)
{
selectedForSync_ = active;
notifySyncCfgChanged();
}
template <SelectSide side> inline
bool FileSystemObject::isEmpty() const
{
return selectParam<side>(itemNameL_, itemNameR_).empty();
}
inline
bool FileSystemObject::isPairEmpty() const
{
return isEmpty<SelectSide::left>() && isEmpty<SelectSide::right>();
}
template <SelectSide side> inline
Zstring FileSystemObject::getItemName() const
{
//assert(!itemNameL_.empty() || !itemNameR_.empty()); //-> file pair might be temporarily empty (until removed after sync)
const Zstring& itemName = selectParam<side>(itemNameL_, itemNameR_); //empty if not existing
if (!itemName.empty()) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!)
return itemName;
return selectParam<getOtherSide<side>>(itemNameL_, itemNameR_);
}
inline
bool FileSystemObject::hasEquivalentItemNames() const
{
if (itemNameL_.c_str() == itemNameR_.c_str() || //most likely case
itemNameL_.empty() || itemNameR_.empty()) //
return true;
assert(itemNameL_ != itemNameR_); //class invariant
return getUnicodeNormalForm(itemNameL_) == getUnicodeNormalForm(itemNameR_);
}
template <SelectSide side> inline
void FileSystemObject::removeFsObject()
{
if (isEmpty<getOtherSide<side>>())
{
selectParam<side>(itemNameL_, itemNameR_) = selectParam<getOtherSide<side>>(itemNameL_, itemNameR_); //ensure (c_str) class invariant!
setSyncDir(SyncDirection::none); //calls notifySyncCfgChanged()
}
else
{
selectParam<side>(itemNameL_, itemNameR_).clear();
//keep current syncDir_
notifySyncCfgChanged(); //needed!?
}
propagateChangedItemName<side>();
}
template <SelectSide side> inline
void FilePair::removeItem()
{
if (isEmpty<getOtherSide<side>>())
setMovePair(nullptr); //cut ties between "move" pairs
selectParam<side>(attrL_, attrR_) = FileAttributes();
contentCategory_ = FileContentCategory::unknown;
removeFsObject<side>();
}
template <SelectSide side> inline
void SymlinkPair::removeItem()
{
selectParam<side>(attrL_, attrR_) = LinkAttributes();
contentCategory_ = FileContentCategory::unknown;
removeFsObject<side>();
}
template <SelectSide side> inline
void FolderPair::removeItem()
{
for (FilePair& file : files())
file.removeItem<side>();
for (SymlinkPair& symlink : symlinks())
symlink.removeItem<side>();
for (FolderPair& folder : subfolders())
folder.removeItem<side>();
selectParam<side>(attrL_, attrR_) = FolderAttributes();
removeFsObject<side>();
}
template <SelectSide side> inline
void FileSystemObject::setItemName(const Zstring& itemName)
{
assert(!itemName.empty());
assert(!isPairEmpty());
selectParam<side>(itemNameL_, itemNameR_) = itemName;
if (itemNameL_.c_str() != itemNameR_.c_str() &&
itemNameL_ == itemNameR_)
itemNameL_ = itemNameR_; //preserve class invariant
assert(itemNameL_.c_str() == itemNameR_.c_str() || itemNameL_ != itemNameR_);
propagateChangedItemName<side>();
}
template <SelectSide side> inline
void FileSystemObject::propagateChangedItemName()
{
if (itemNameL_.empty() && itemNameR_.empty()) return; //both sides might just have been deleted by removeItem<>
if (auto conObj = dynamic_cast<ContainerObject*>(this))
{
const Zstring& itemNameOld = zen::getItemName(conObj->getRelativePath<side>());
if (itemNameOld != getItemName<side>()) //perf: premature optimization?
conObj->updateRelPathsRecursion<side>(*this);
}
}
template <SelectSide side> inline
void ContainerObject::updateRelPathsRecursion(const FileSystemObject& fsAlias)
{
//perf: only call if actual item name changed:
assert(selectParam<side>(relPathL_, relPathR_) != appendPath(fsAlias.parent().getRelativePath<side>(), fsAlias.getItemName<side>()));
constexpr SelectSide otherSide = getOtherSide<side>;
if (fsAlias.isEmpty<otherSide>()) //=> 1. other side's relPath also needs updating! 2. both sides have same name
selectParam<otherSide>(relPathL_, relPathR_) = appendPath(selectParam<otherSide>(fsAlias.parent().relPathL_,
fsAlias.parent().relPathR_), fsAlias.getItemName<otherSide>());
else //assume relPath on other side is up to date!
assert(selectParam<otherSide>(relPathL_, relPathR_) == appendPath(fsAlias.parent().getRelativePath<otherSide>(), fsAlias.getItemName<otherSide>()));
if (fsAlias.parent().relPathL_.c_str() == //
fsAlias.parent().relPathR_.c_str() && //see ContainerObject constructor and setItemName()
fsAlias.getItemName<SelectSide::left >().c_str() == //
fsAlias.getItemName<SelectSide::right>().c_str()) //
selectParam<side>(relPathL_, relPathR_) = selectParam<otherSide>(relPathL_, relPathR_);
else
selectParam<side>(relPathL_, relPathR_) = appendPath(selectParam<side>(fsAlias.parent().relPathL_,
fsAlias.parent().relPathR_), fsAlias.getItemName<side>());
assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_);
for (FolderPair& folder : subfolders())
folder.updateRelPathsRecursion<side>(folder);
}
inline
ContainerObject::ContainerObject(const FileSystemObject& fsAlias) :
relPathL_(appendPath(fsAlias.parent().relPathL_, fsAlias.getItemName<SelectSide::left>())),
relPathR_(fsAlias.parent().relPathL_.c_str() == //
fsAlias.parent().relPathR_.c_str() && //take advantage of FileSystemObject's Zstring reuse:
fsAlias.getItemName<SelectSide::left >().c_str() == //=> perf: 12% faster merge phase; -4% peak memory
fsAlias.getItemName<SelectSide::right>().c_str() ? //
relPathL_ : //ternary-WTF! (implicit copy-constructor call!!) => no big deal for a Zstring
appendPath(fsAlias.parent().relPathR_, fsAlias.getItemName<SelectSide::right>())),
base_(fsAlias.parent().base_)
{
assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_);
}
inline
FolderPair& ContainerObject::addFolder(const Zstring& itemNameL, const FolderAttributes& attribL,
const Zstring& itemNameR, const FolderAttributes& attribR)
{
subfolders_.push_back(makeSharedRef<FolderPair>(itemNameL, attribL, itemNameR, attribR, *this));
return subfolders_.back().ref();
}
template <> inline
FolderPair& ContainerObject::addFolder<SelectSide::left>(const Zstring& itemName, const FolderAttributes& attr)
{
return addFolder(itemName, attr, Zstring(), FolderAttributes());
}
template <> inline
FolderPair& ContainerObject::addFolder<SelectSide::right>(const Zstring& itemName, const FolderAttributes& attr)
{
return addFolder(Zstring(), FolderAttributes(), itemName, attr);
}
inline
FilePair& ContainerObject::addFile(const Zstring& itemNameL, const FileAttributes& attribL,
const Zstring& itemNameR, const FileAttributes& attribR)
{
files_.push_back(makeSharedRef<FilePair>(itemNameL, attribL, itemNameR, attribR, *this));
return files_.back().ref();
}
template <> inline
FilePair& ContainerObject::addFile<SelectSide::left>(const Zstring& itemName, const FileAttributes& attr)
{
return addFile(itemName, attr, Zstring(), FileAttributes());
}
template <> inline
FilePair& ContainerObject::addFile<SelectSide::right>(const Zstring& itemName, const FileAttributes& attr)
{
return addFile(Zstring(), FileAttributes(), itemName, attr);
}
inline
SymlinkPair& ContainerObject::addSymlink(const Zstring& itemNameL, const LinkAttributes& attribL,
const Zstring& itemNameR, const LinkAttributes& attribR)
{
symlinks_.push_back(makeSharedRef<SymlinkPair>(itemNameL, attribL, itemNameR, attribR, *this));
return symlinks_.back().ref();
}
template <> inline
SymlinkPair& ContainerObject::addSymlink<SelectSide::left>(const Zstring& itemName, const LinkAttributes& attr)
{
return addSymlink(itemName, attr, Zstring(), LinkAttributes());
}
template <> inline
SymlinkPair& ContainerObject::addSymlink<SelectSide::right>(const Zstring& itemName, const LinkAttributes& attr)
{
return addSymlink(Zstring(), LinkAttributes(), itemName, attr);
}
inline
void FileSystemObject::flip()
{
std::swap(itemNameL_, itemNameR_);
notifySyncCfgChanged();
}
inline
void ContainerObject::flip()
{
for (FilePair& file : files())
file.flip();
for (SymlinkPair& symlink : symlinks())
symlink.flip();
for (FolderPair& folder : subfolders())
folder.flip();
std::swap(relPathL_, relPathR_);
}
inline
void BaseFolderPair::flip()
{
ContainerObject::flip();
std::swap(folderStatusLeft_, folderStatusRight_);
std::swap(folderPathLeft_, folderPathRight_);
}
inline
void FolderPair::flip() //this overrides both ContainerObject/FileSystemObject::flip!
{
ContainerObject ::flip(); //call base class versions
FileSystemObject::flip(); //
std::swap(attrL_, attrR_);
}
inline
void FilePair::flip()
{
FileSystemObject::flip(); //call base class version
std::swap(attrL_, attrR_);
switch (contentCategory_)
{
case FileContentCategory::unknown:
case FileContentCategory::equal:
case FileContentCategory::invalidTime:
case FileContentCategory::different:
case FileContentCategory::conflict: break;
case FileContentCategory::leftNewer: contentCategory_ = FileContentCategory::rightNewer; break;
case FileContentCategory::rightNewer: contentCategory_ = FileContentCategory::leftNewer; break;
}
}
inline
void SymlinkPair::flip()
{
FileSystemObject::flip(); //call base class versions
std::swap(attrL_, attrR_);
switch (contentCategory_)
{
case FileContentCategory::unknown:
case FileContentCategory::equal:
case FileContentCategory::invalidTime:
case FileContentCategory::different:
case FileContentCategory::conflict: break;
case FileContentCategory::leftNewer: contentCategory_ = FileContentCategory::rightNewer; break;
case FileContentCategory::rightNewer: contentCategory_ = FileContentCategory::leftNewer; break;
}
}
template <SelectSide side> inline
BaseFolderStatus BaseFolderPair::getFolderStatus() const
{
return selectParam<side>(folderStatusLeft_, folderStatusRight_);
}
template <SelectSide side> inline
void BaseFolderPair::setFolderStatus(BaseFolderStatus value)
{
selectParam<side>(folderStatusLeft_, folderStatusRight_) = value;
}
inline
void FolderPair::setCategoryConflict(const Zstringc& description)
{
assert(!description.empty());
categoryConflict_ = description;
}
inline
void FilePair::setCategoryConflict(const Zstringc& description)
{
assert(!description.empty());
categoryDescr_ = description;
contentCategory_ = FileContentCategory::conflict;
}
inline
void SymlinkPair::setCategoryConflict(const Zstringc& description)
{
assert(!description.empty());
categoryDescr_ = description;
contentCategory_ = FileContentCategory::conflict;
}
inline
void FilePair::setCategoryInvalidTime(const Zstringc& description)
{
assert(!description.empty());
categoryDescr_ = description;
contentCategory_ = FileContentCategory::invalidTime;
}
inline
void SymlinkPair::setCategoryInvalidTime(const Zstringc& description)
{
assert(!description.empty());
categoryDescr_ = description;
contentCategory_ = FileContentCategory::invalidTime;
}
inline Zstringc FolderPair ::getCategoryCustomDescription() const { return categoryConflict_; }
inline Zstringc FilePair ::getCategoryCustomDescription() const { return categoryDescr_; }
inline Zstringc SymlinkPair::getCategoryCustomDescription() const { return categoryDescr_; }
inline
void FilePair::setContentCategory(FileContentCategory category)
{
assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
assert(category != FileContentCategory::unknown);
contentCategory_ = category;
}
inline
void SymlinkPair::setContentCategory(FileContentCategory category)
{
assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
assert(category != FileContentCategory::unknown);
contentCategory_ = category;
}
inline
FileContentCategory FilePair::getContentCategory() const
{
assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
return contentCategory_;
}
inline
FileContentCategory SymlinkPair::getContentCategory() const
{
assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
return contentCategory_;
}
inline
CompareFileResult FolderPair::getCategory() const
{
if (!categoryConflict_.empty())
return FILE_CONFLICT;
if (isEmpty<SelectSide::left>())
{
if (isEmpty<SelectSide::right>())
return FILE_EQUAL;
else
return FILE_RIGHT_ONLY;
}
else
{
if (isEmpty<SelectSide::right>())
return FILE_LEFT_ONLY;
else
return hasEquivalentItemNames() ? FILE_EQUAL : FILE_RENAMED;
}
}
inline
CompareFileResult FilePair::getCategory() const
{
assert(contentCategory_ == FileContentCategory::conflict ||
(isEmpty<SelectSide::left>() || isEmpty<SelectSide::right>()) == (contentCategory_ == FileContentCategory::unknown));
assert(contentCategory_ != FileContentCategory::conflict || !categoryDescr_.empty());
if (contentCategory_ == FileContentCategory::conflict)
{
assert(!categoryDescr_.empty());
return FILE_CONFLICT;
}
if (isEmpty<SelectSide::left>())
{
if (isEmpty<SelectSide::right>())
return FILE_EQUAL;
else
return FILE_RIGHT_ONLY;
}
else
{
if (isEmpty<SelectSide::right>())
return FILE_LEFT_ONLY;
else
//Caveat:
//1. FILE_EQUAL may only be set if names match in case: InSyncFolder's mapping tables use file name as a key! see db_file.cpp
//2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
//3. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
switch (contentCategory_)
{
case FileContentCategory::unknown:
case FileContentCategory::conflict: assert(false); return FILE_CONFLICT;
case FileContentCategory::equal: return hasEquivalentItemNames() ? FILE_EQUAL : FILE_RENAMED;
case FileContentCategory::leftNewer: return FILE_LEFT_NEWER;
case FileContentCategory::rightNewer: return FILE_RIGHT_NEWER;
case FileContentCategory::invalidTime: return FILE_TIME_INVALID;
case FileContentCategory::different: return FILE_DIFFERENT_CONTENT;
}
}
throw std::logic_error(std::string(__FILE__) + '[' + zen::numberTo<std::string>(__LINE__) + "] Contract violation!");
}
inline
CompareFileResult SymlinkPair::getCategory() const
{
assert(contentCategory_ == FileContentCategory::conflict ||
(isEmpty<SelectSide::left>() || isEmpty<SelectSide::right>()) == (contentCategory_ == FileContentCategory::unknown));
assert(contentCategory_ != FileContentCategory::conflict || !categoryDescr_.empty());
if (contentCategory_ == FileContentCategory::conflict)
{
assert(!categoryDescr_.empty());
return FILE_CONFLICT;
}
if (isEmpty<SelectSide::left>())
{
if (isEmpty<SelectSide::right>())
return FILE_EQUAL;
else
return FILE_RIGHT_ONLY;
}
else
{
if (isEmpty<SelectSide::right>())
return FILE_LEFT_ONLY;
else
//Caveat:
//1. SYMLINK_EQUAL may only be set if names match in case: InSyncFolder's mapping tables use link name as a key! see db_file.cpp
//2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
switch (contentCategory_)
{
case FileContentCategory::unknown:
case FileContentCategory::conflict: assert(false); return FILE_CONFLICT;
case FileContentCategory::equal: return hasEquivalentItemNames() ? FILE_EQUAL : FILE_RENAMED;
case FileContentCategory::leftNewer: return FILE_LEFT_NEWER;
case FileContentCategory::rightNewer: return FILE_RIGHT_NEWER;
case FileContentCategory::invalidTime: return FILE_TIME_INVALID;
case FileContentCategory::different: return FILE_DIFFERENT_CONTENT;
}
}
throw std::logic_error(std::string(__FILE__) + '[' + zen::numberTo<std::string>(__LINE__) + "] Contract violation!");
}
template <SelectSide side> inline
FileAttributes FilePair::getAttributes() const
{
assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_);
}
template <SelectSide side> inline
time_t FilePair::getLastWriteTime() const
{
assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).modTime;
}
template <SelectSide side> inline
uint64_t FilePair::getFileSize() const
{
assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).fileSize;
}
template <SelectSide side> inline
bool FolderPair::isFollowedSymlink() const
{
assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).isFollowedSymlink;
}
template <SelectSide side> inline
bool FilePair::isFollowedSymlink() const
{
assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).isFollowedSymlink;
}
template <SelectSide side> inline
AFS::FingerPrint FilePair::getFilePrint() const
{
assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).filePrint;
}
template <SelectSide side> inline
void FilePair::clearFilePrint()
{
selectParam<side>(attrL_, attrR_).filePrint = 0;
}
inline
void FilePair::setMovePair(FilePair* ref)
{
FilePair* refOld = getMovePair();
if (ref != refOld)
{
if (refOld)
refOld->moveFileRef_.reset();
if (ref)
{
FilePair* refOld2 = ref->getMovePair();
assert(!refOld2); //destroying already exising pair!? why?
if (refOld2)
refOld2 ->moveFileRef_.reset();
/**/ moveFileRef_ = std::static_pointer_cast<FilePair>(ref->shared_from_this());
ref->moveFileRef_ = std::static_pointer_cast<FilePair>( shared_from_this());
}
else
moveFileRef_.reset();
}
else
assert(!ref); //are we called needlessly!?
}
inline
FilePair* FilePair::getMovePair() const
{
if (moveFileRef_.expired()) //skip std::shared_ptr construction => premature optimization?
return nullptr;
FilePair* ref = moveFileRef_.lock().get();
assert(!ref || (isEmpty<SelectSide::left>() != isEmpty<SelectSide::right>()));
assert(!ref || ref->moveFileRef_.lock().get() == this); //both ends should agree
return ref;
}
template <SelectSide sideTrg> inline
void FolderPair::setSyncedTo(bool isSymlinkTrg,
bool isSymlinkSrc)
{
selectParam< sideTrg >(attrL_, attrR_) = {.isFollowedSymlink = isSymlinkTrg};
selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = {.isFollowedSymlink = isSymlinkSrc};
setItemName<sideTrg>(getItemName<getOtherSide<sideTrg>>());
categoryConflict_.clear();
setSyncDir(SyncDirection::none);
}
template <SelectSide sideTrg> inline
void FilePair::setSyncedTo(uint64_t fileSize,
time_t lastWriteTimeTrg,
time_t lastWriteTimeSrc,
AFS::FingerPrint filePrintTrg,
AFS::FingerPrint filePrintSrc,
bool isSymlinkTrg,
bool isSymlinkSrc)
{
setMovePair(nullptr); //cut ties between "move" pairs
selectParam< sideTrg >(attrL_, attrR_) = {lastWriteTimeTrg, fileSize, filePrintTrg, isSymlinkTrg};
selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = {lastWriteTimeSrc, fileSize, filePrintSrc, isSymlinkSrc};
setItemName<sideTrg>(getItemName<getOtherSide<sideTrg>>());
contentCategory_ = FileContentCategory::equal;
categoryDescr_.clear();
setSyncDir(SyncDirection::none);
}
template <SelectSide sideTrg> inline
void SymlinkPair::setSyncedTo(time_t lastWriteTimeTrg,
time_t lastWriteTimeSrc)
{
selectParam< sideTrg >(attrL_, attrR_) = {.modTime = lastWriteTimeTrg};
selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = {.modTime = lastWriteTimeSrc};
setItemName<sideTrg>(getItemName<getOtherSide<sideTrg>>());
contentCategory_ = FileContentCategory::equal;
categoryDescr_.clear();
setSyncDir(SyncDirection::none);
}
inline
bool FolderPair::passDirFilter(const PathFilter& filter, bool* childItemMightMatch) const
{
const Zstring& relPathL = getRelativePath<SelectSide::left >();
const Zstring& relPathR = getRelativePath<SelectSide::right>();
assert(relPathL.c_str() == relPathR.c_str() || relPathL!= relPathR);
if (filter.passDirFilter(relPathL, childItemMightMatch))
return relPathL.c_str() == relPathR.c_str() /*perf!*/ || equalNoCase(relPathL, relPathR) ||
filter.passDirFilter(relPathR, childItemMightMatch);
else
{
if (childItemMightMatch && *childItemMightMatch &&
relPathL.c_str() != relPathR.c_str() /*perf!*/ && !equalNoCase(relPathL, relPathR))
filter.passDirFilter(relPathR, childItemMightMatch);
return false;
}
}
inline
bool FileSystemObject::passFileFilter(const PathFilter& filter) const
{
assert(!dynamic_cast<const FolderPair*>(this));
assert(parent().getRelativePath<SelectSide::left >().c_str() ==
parent().getRelativePath<SelectSide::right>().c_str() ||
parent().getRelativePath<SelectSide::left >()!=
parent().getRelativePath<SelectSide::right>());
assert(getItemName<SelectSide::left >().c_str() ==
getItemName<SelectSide::right>().c_str() ||
getItemName<SelectSide::left >() !=
getItemName<SelectSide::right>());
const Zstring relPathL = getRelativePath<SelectSide::left>();
if (!filter.passFileFilter(relPathL))
return false;
if (parent().getRelativePath<SelectSide::left >().c_str() == //
parent().getRelativePath<SelectSide::right>().c_str() && //perf! see ContainerObject constructor
getItemName<SelectSide::left >().c_str() == //
getItemName<SelectSide::right>().c_str()) //
return true;
const Zstring relPathR = getRelativePath<SelectSide::right>();
if (equalNoCase(relPathL, relPathR))
return true;
return filter.passFileFilter(relPathR);
}
template <SelectSide side> inline
time_t SymlinkPair::getLastWriteTime() const
{
return selectParam<side>(attrL_, attrR_).modTime;
}
}
#endif //FILE_HIERARCHY_H_257235289645296