1
0
mirror of https://github.com/chylex/Nextcloud-Desktop.git synced 2025-05-28 08:34:02 +02:00

Read .sync_exclude.lst in each subdirectory

Signed-off-by: Samir Benmendil <me@rmz.io>
This commit is contained in:
Samir Benmendil 2019-08-08 23:30:49 +01:00
parent 7843660bbf
commit 14279104ae
4 changed files with 264 additions and 95 deletions

View File

@ -236,13 +236,29 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeC
return match; return match;
} }
static QByteArray leftIncludeLast(const QByteArray & arr, char c)
{
// left up to and including `c`
return arr.left(arr.lastIndexOf(c, arr.size() - 2) + 1);
}
using namespace OCC; using namespace OCC;
ExcludedFiles::ExcludedFiles() ExcludedFiles::ExcludedFiles(QString localPath)
: _localPath(std::move(localPath))
{ {
Q_ASSERT(_localPath.endsWith("/"));
// Windows used to use PathMatchSpec which allows *foo to match abc/deffoo. // Windows used to use PathMatchSpec which allows *foo to match abc/deffoo.
_wildcardsMatchSlash = Utility::isWindows(); _wildcardsMatchSlash = Utility::isWindows();
// We're in a detached exclude probably coming from a partial sync or test
if (_localPath.isEmpty())
return;
// Load exclude file from base dir
QFileInfo fi(_localPath + ".sync-exclude.lst");
if (fi.isReadable())
addInTreeExcludeFilePath(fi.absoluteFilePath());
} }
ExcludedFiles::~ExcludedFiles() ExcludedFiles::~ExcludedFiles()
@ -251,7 +267,13 @@ ExcludedFiles::~ExcludedFiles()
void ExcludedFiles::addExcludeFilePath(const QString &path) void ExcludedFiles::addExcludeFilePath(const QString &path)
{ {
_excludeFiles.insert(path); _excludeFiles[_localPath.toUtf8()].append(path);
}
void ExcludedFiles::addInTreeExcludeFilePath(const QString &path)
{
BasePathByteArray basePath = leftIncludeLast(path.toUtf8(), '/');
_excludeFiles[basePath].append(path);
} }
void ExcludedFiles::setExcludeConflictFiles(bool onoff) void ExcludedFiles::setExcludeConflictFiles(bool onoff)
@ -259,11 +281,15 @@ void ExcludedFiles::setExcludeConflictFiles(bool onoff)
_excludeConflictFiles = onoff; _excludeConflictFiles = onoff;
} }
void ExcludedFiles::addManualExclude(const QByteArray &expr) void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
{ {
_manualExcludes.append(expr); Q_ASSERT(basePath.startsWith('/'));
_allExcludes.append(expr); Q_ASSERT(basePath.endsWith('/'));
prepare();
auto key = basePath;
_manualExcludes[key].append(expr);
_allExcludes[key].append(expr);
prepare(key);
} }
void ExcludedFiles::clearManualExcludes() void ExcludedFiles::clearManualExcludes()
@ -282,21 +308,27 @@ bool ExcludedFiles::reloadExcludeFiles()
{ {
_allExcludes.clear(); _allExcludes.clear();
bool success = true; bool success = true;
foreach (const QString &file, _excludeFiles) { for (auto basePath : _excludeFiles.keys()) {
QFile f(file); for (auto file : _excludeFiles.value(basePath)) {
if (!f.open(QIODevice::ReadOnly)) { QFile f(file);
success = false; if (!f.open(QIODevice::ReadOnly)) {
continue; success = false;
}
while (!f.atEnd()) {
QByteArray line = f.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#'))
continue; continue;
csync_exclude_expand_escapes(line); }
_allExcludes.append(line); while (!f.atEnd()) {
QByteArray line = f.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#'))
continue;
csync_exclude_expand_escapes(line);
_allExcludes[basePath].append(line);
}
} }
} }
_allExcludes.append(_manualExcludes);
auto endManual = _manualExcludes.cend();
for (auto kv = _manualExcludes.cbegin(); kv != endManual; ++kv)
_allExcludes[kv.key()].append(kv.value());
prepare(); prepare();
return success; return success;
} }
@ -317,6 +349,8 @@ bool ExcludedFiles::isExcluded(
// We do want to be able to sync with a hidden folder as the target. // We do want to be able to sync with a hidden folder as the target.
while (path.size() > basePath.size()) { while (path.size() > basePath.size()) {
QFileInfo fi(path); QFileInfo fi(path);
//TODO probably not ignore `.sync-exclude.lst` files as it makes sense for them to be
//synced
if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) { if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) {
return true; return true;
} }
@ -340,7 +374,7 @@ bool ExcludedFiles::isExcluded(
return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED; return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED;
} }
CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype) const CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype)
{ {
auto match = _csync_excluded_common(path, _excludeConflictFiles); auto match = _csync_excluded_common(path, _excludeConflictFiles);
if (match != CSYNC_NOT_EXCLUDED) if (match != CSYNC_NOT_EXCLUDED)
@ -348,6 +382,16 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
if (_allExcludes.isEmpty()) if (_allExcludes.isEmpty())
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
// Directories are guaranteed to be visited before their files
if (filetype == ItemTypeDirectory) {
QFileInfo fi = QFileInfo(_localPath + path + "/.sync-exclude.lst");
if (fi.isReadable()) {
addInTreeExcludeFilePath(fi.absoluteFilePath());
//really we only need to load this file and prepare(this basePath)
reloadExcludeFiles();
}
}
// Check the bname part of the path to see whether the full // Check the bname part of the path to see whether the full
// regex should be run. // regex should be run.
@ -359,35 +403,53 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
} }
QString bnameStr = QString::fromUtf8(bname); QString bnameStr = QString::fromUtf8(bname);
QRegularExpressionMatch m; QByteArray basePath(_localPath.toUtf8() + path);
if (filetype == ItemTypeDirectory) { while (basePath.size() > _localPath.size()) {
m = _bnameTraversalRegexDir.match(bnameStr); basePath = leftIncludeLast(basePath, '/');
} else { QRegularExpressionMatch m;
m = _bnameTraversalRegexFile.match(bnameStr); if (filetype == ItemTypeDirectory
} && _bnameTraversalRegexDir.contains(basePath)) {
if (!m.hasMatch()) m = _bnameTraversalRegexDir[basePath].match(bnameStr);
return CSYNC_NOT_EXCLUDED; } else if (filetype == ItemTypeFile
if (m.capturedStart(QStringLiteral("exclude")) != -1) { && _bnameTraversalRegexFile.contains(basePath)) {
return CSYNC_FILE_EXCLUDE_LIST; m = _bnameTraversalRegexFile[basePath].match(bnameStr);
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { } else {
return CSYNC_FILE_EXCLUDE_AND_REMOVE; continue;
} }
// third capture: full path matching is triggered if (!m.hasMatch())
QString pathStr = QString::fromUtf8(path); return CSYNC_NOT_EXCLUDED;
if (filetype == ItemTypeDirectory) {
m = _fullTraversalRegexDir.match(pathStr);
} else {
m = _fullTraversalRegexFile.match(pathStr);
}
if (m.hasMatch()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) { if (m.capturedStart(QStringLiteral("exclude")) != -1) {
return CSYNC_FILE_EXCLUDE_LIST; return CSYNC_FILE_EXCLUDE_LIST;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE; return CSYNC_FILE_EXCLUDE_AND_REMOVE;
} }
} }
// third capture: full path matching is triggered
QString pathStr = QString::fromUtf8(path);
basePath = _localPath.toUtf8() + path;
while (basePath.size() > _localPath.size()) {
basePath = leftIncludeLast(basePath, '/');
QRegularExpressionMatch m;
if (filetype == ItemTypeDirectory
&& _fullTraversalRegexDir.contains(basePath)) {
m = _fullTraversalRegexDir[basePath].match(pathStr);
} else if (filetype == ItemTypeFile
&& _fullTraversalRegexFile.contains(basePath)) {
m = _fullTraversalRegexFile[basePath].match(pathStr);
} else {
continue;
}
if (m.hasMatch()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
return CSYNC_FILE_EXCLUDE_LIST;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
}
}
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
} }
@ -400,23 +462,38 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, ItemType fi
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
QString p = QString::fromUtf8(path); QString p = QString::fromUtf8(path);
QRegularExpressionMatch m; // `path` seems to always be relative to `_localPath`, the tests however have not been
if (filetype == ItemTypeDirectory) { // written that way... this makes the tests happy for now. TODO Fix the tests at some point
m = _fullRegexDir.match(p); if (path[0] == '/')
} else { ++path;
m = _fullRegexFile.match(p);
} QByteArray basePath(_localPath.toUtf8() + path);
if (m.hasMatch()) { while (basePath.size() > _localPath.size()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) { basePath = leftIncludeLast(basePath, '/');
return CSYNC_FILE_EXCLUDE_LIST; QRegularExpressionMatch m;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { if (filetype == ItemTypeDirectory
return CSYNC_FILE_EXCLUDE_AND_REMOVE; && _fullRegexDir.contains(basePath)) {
m = _fullRegexDir[basePath].match(p);
} else if (filetype == ItemTypeFile
&& _fullRegexFile.contains(basePath)) {
m = _fullRegexFile[basePath].match(p);
} else {
continue;
}
if (m.hasMatch()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
return CSYNC_FILE_EXCLUDE_LIST;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
} }
} }
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
} }
auto ExcludedFiles::csyncTraversalMatchFun() const auto ExcludedFiles::csyncTraversalMatchFun()
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)> -> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>
{ {
return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); }; return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); };
@ -555,6 +632,22 @@ static QString extractBnameTrigger(const QString &exclude, bool wildcardsMatchSl
void ExcludedFiles::prepare() void ExcludedFiles::prepare()
{ {
// clear all regex
_bnameTraversalRegexFile.clear();
_bnameTraversalRegexDir.clear();
_fullTraversalRegexFile.clear();
_fullTraversalRegexDir.clear();
_fullRegexFile.clear();
_fullRegexDir.clear();
for (auto const & basePath : _allExcludes.keys())
prepare(basePath);
}
void ExcludedFiles::prepare(const BasePathByteArray & basePath)
{
Q_ASSERT(_allExcludes.contains(basePath));
// Build regular expressions for the different cases. // Build regular expressions for the different cases.
// //
// To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex // To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex
@ -596,7 +689,7 @@ void ExcludedFiles::prepare()
pattern.append(appendMe); pattern.append(appendMe);
}; };
for (auto exclude : _allExcludes) { for (auto exclude : _allExcludes.value(basePath)) {
if (exclude[0] == '\n') if (exclude[0] == '\n')
continue; // empty line continue; // empty line
if (exclude[0] == '\r') if (exclude[0] == '\r')
@ -654,11 +747,11 @@ void ExcludedFiles::prepare()
// (exclude)|(excluderemove)|(bname triggers). // (exclude)|(excluderemove)|(bname triggers).
// If the third group matches, the fullActivatedRegex needs to be applied // If the third group matches, the fullActivatedRegex needs to be applied
// to the full path. // to the full path.
_bnameTraversalRegexFile.setPattern( _bnameTraversalRegexFile[basePath].setPattern(
"^(?P<exclude>" + bnameFileDirKeep + ")$|" "^(?P<exclude>" + bnameFileDirKeep + ")$|"
+ "^(?P<excluderemove>" + bnameFileDirRemove + ")$|" + "^(?P<excluderemove>" + bnameFileDirRemove + ")$|"
+ "^(?P<trigger>" + bnameTriggerFileDir + ")$"); + "^(?P<trigger>" + bnameTriggerFileDir + ")$");
_bnameTraversalRegexDir.setPattern( _bnameTraversalRegexDir[basePath].setPattern(
"^(?P<exclude>" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|" "^(?P<exclude>" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|"
+ "^(?P<excluderemove>" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|" + "^(?P<excluderemove>" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|"
+ "^(?P<trigger>" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$"); + "^(?P<trigger>" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$");
@ -667,13 +760,13 @@ void ExcludedFiles::prepare()
// the bname regex matches. Its basic form is (exclude)|(excluderemove)". // the bname regex matches. Its basic form is (exclude)|(excluderemove)".
// This pattern can be much simpler than fullRegex since we can assume a traversal // This pattern can be much simpler than fullRegex since we can assume a traversal
// situation and doesn't need to look for bname patterns in parent paths. // situation and doesn't need to look for bname patterns in parent paths.
_fullTraversalRegexFile.setPattern( _fullTraversalRegexFile[basePath].setPattern(
QLatin1String("") QLatin1String("")
// Full patterns are anchored to the beginning // Full patterns are anchored to the beginning
+ "^(?P<exclude>" + fullFileDirKeep + ")(?:$|/)" + "^(?P<exclude>" + fullFileDirKeep + ")(?:$|/)"
+ "|" + "|"
+ "^(?P<excluderemove>" + fullFileDirRemove + ")(?:$|/)"); + "^(?P<excluderemove>" + fullFileDirRemove + ")(?:$|/)");
_fullTraversalRegexDir.setPattern( _fullTraversalRegexDir[basePath].setPattern(
QLatin1String("") QLatin1String("")
+ "^(?P<exclude>" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "^(?P<exclude>" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)"
+ "|" + "|"
@ -681,7 +774,7 @@ void ExcludedFiles::prepare()
// The full regex is applied to the full path and incorporates both bname and // The full regex is applied to the full path and incorporates both bname and
// full-path patterns. It has the form "(exclude)|(excluderemove)". // full-path patterns. It has the form "(exclude)|(excluderemove)".
_fullRegexFile.setPattern( _fullRegexFile[basePath].setPattern(
QLatin1String("(?P<exclude>") QLatin1String("(?P<exclude>")
// Full patterns are anchored to the beginning // Full patterns are anchored to the beginning
+ "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|" + "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|"
@ -697,7 +790,7 @@ void ExcludedFiles::prepare()
+ "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|" + "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameDirRemove + ")/" + "(?:^|/)(?:" + bnameDirRemove + ")/"
+ ")"); + ")");
_fullRegexDir.setPattern( _fullRegexDir[basePath].setPattern(
QLatin1String("(?P<exclude>") QLatin1String("(?P<exclude>")
+ "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|" + "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)" + "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)"
@ -711,16 +804,16 @@ void ExcludedFiles::prepare()
QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption; QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
if (OCC::Utility::fsCasePreserving()) if (OCC::Utility::fsCasePreserving())
patternOptions |= QRegularExpression::CaseInsensitiveOption; patternOptions |= QRegularExpression::CaseInsensitiveOption;
_bnameTraversalRegexFile.setPatternOptions(patternOptions); _bnameTraversalRegexFile[basePath].setPatternOptions(patternOptions);
_bnameTraversalRegexFile.optimize(); _bnameTraversalRegexFile[basePath].optimize();
_bnameTraversalRegexDir.setPatternOptions(patternOptions); _bnameTraversalRegexDir[basePath].setPatternOptions(patternOptions);
_bnameTraversalRegexDir.optimize(); _bnameTraversalRegexDir[basePath].optimize();
_fullTraversalRegexFile.setPatternOptions(patternOptions); _fullTraversalRegexFile[basePath].setPatternOptions(patternOptions);
_fullTraversalRegexFile.optimize(); _fullTraversalRegexFile[basePath].optimize();
_fullTraversalRegexDir.setPatternOptions(patternOptions); _fullTraversalRegexDir[basePath].setPatternOptions(patternOptions);
_fullTraversalRegexDir.optimize(); _fullTraversalRegexDir[basePath].optimize();
_fullRegexFile.setPatternOptions(patternOptions); _fullRegexFile[basePath].setPatternOptions(patternOptions);
_fullRegexFile.optimize(); _fullRegexFile[basePath].optimize();
_fullRegexDir.setPatternOptions(patternOptions); _fullRegexDir[basePath].setPatternOptions(patternOptions);
_fullRegexDir.optimize(); _fullRegexDir[basePath].optimize();
} }

View File

@ -66,7 +66,7 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
ExcludedFiles(); ExcludedFiles(QString localPath = "/");
~ExcludedFiles(); ~ExcludedFiles();
/** /**
@ -75,6 +75,7 @@ public:
* Does not load the file. Use reloadExcludeFiles() afterwards. * Does not load the file. Use reloadExcludeFiles() afterwards.
*/ */
void addExcludeFilePath(const QString &path); void addExcludeFilePath(const QString &path);
void addInTreeExcludeFilePath(const QString &path);
/** /**
* Whether conflict files shall be excluded. * Whether conflict files shall be excluded.
@ -95,12 +96,12 @@ public:
bool excludeHidden) const; bool excludeHidden) const;
/** /**
* Adds an exclude pattern. * Adds an exclude pattern anchored to base path
* *
* Primarily used in tests. Patterns added this way are preserved when * Primarily used in tests. Patterns added this way are preserved when
* reloadExcludeFiles() is called. * reloadExcludeFiles() is called.
*/ */
void addManualExclude(const QByteArray &expr); void addManualExclude(const QByteArray &expr, const QByteArray &basePath = "/");
/** /**
* Removes all manually added exclude patterns. * Removes all manually added exclude patterns.
@ -121,7 +122,7 @@ public:
* Careful: The function will only be valid for as long as this * Careful: The function will only be valid for as long as this
* ExcludedFiles instance stays alive. * ExcludedFiles instance stays alive.
*/ */
auto csyncTraversalMatchFun() const auto csyncTraversalMatchFun()
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>; -> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>;
public slots: public slots:
@ -156,10 +157,32 @@ private:
* Note that this only matches patterns. It does not check whether the file * Note that this only matches patterns. It does not check whether the file
* or directory pointed to is hidden (or whether it even exists). * or directory pointed to is hidden (or whether it even exists).
*/ */
CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype) const; CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype);
// Our BasePath need to end with '/'
class BasePathByteArray : public QByteArray
{
public:
BasePathByteArray(QByteArray && other)
: QByteArray(std::move(other))
{
Q_ASSERT(this->endsWith('/'));
}
BasePathByteArray(const QByteArray & other)
: QByteArray(other)
{
Q_ASSERT(this->endsWith('/'));
}
BasePathByteArray(const char * data, int size = -1)
: BasePathByteArray(QByteArray(data, size))
{
}
};
/** /**
* Generate optimized regular expressions for the exclude patterns. * Generate optimized regular expressions for the exclude patterns anchored to basePath.
* *
* The optimization works in two steps: First, all supported patterns are put * The optimization works in two steps: First, all supported patterns are put
* into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full * into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full
@ -187,24 +210,28 @@ private:
* full matcher would exclude. Example: "b" is excluded. traversal("b/c") * full matcher would exclude. Example: "b" is excluded. traversal("b/c")
* returns not-excluded because "c" isn't a bname activation pattern. * returns not-excluded because "c" isn't a bname activation pattern.
*/ */
void prepare(const BasePathByteArray & basePath);
void prepare(); void prepare();
QString _localPath;
/// Files to load excludes from /// Files to load excludes from
QSet<QString> _excludeFiles; QMap<BasePathByteArray, QList<QString>> _excludeFiles;
/// Exclude patterns added with addManualExclude() /// Exclude patterns added with addManualExclude()
QList<QByteArray> _manualExcludes; QMap<BasePathByteArray, QList<QByteArray>> _manualExcludes;
/// List of all active exclude patterns /// List of all active exclude patterns
QList<QByteArray> _allExcludes; QMap<BasePathByteArray, QList<QByteArray>> _allExcludes;
/// see prepare() /// see prepare()
QRegularExpression _bnameTraversalRegexFile; QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexFile;
QRegularExpression _bnameTraversalRegexDir; QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexDir;
QRegularExpression _fullTraversalRegexFile; QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexFile;
QRegularExpression _fullTraversalRegexDir; QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexDir;
QRegularExpression _fullRegexFile; QMap<BasePathByteArray, QRegularExpression> _fullRegexFile;
QRegularExpression _fullRegexDir; QMap<BasePathByteArray, QRegularExpression> _fullRegexDir;
bool _excludeConflictFiles = true; bool _excludeConflictFiles = true;

View File

@ -91,7 +91,7 @@ SyncEngine::SyncEngine(AccountPtr account, const QString &localPath,
_csync_ctx.reset(new CSYNC(localPath.toUtf8().data(), journal)); _csync_ctx.reset(new CSYNC(localPath.toUtf8().data(), journal));
_excludedFiles.reset(new ExcludedFiles); _excludedFiles.reset(new ExcludedFiles(localPath));
_csync_ctx->exclude_traversal_fn = _excludedFiles->csyncTraversalMatchFun(); _csync_ctx->exclude_traversal_fn = _excludedFiles->csyncTraversalMatchFun();
_syncFileStatusTracker.reset(new SyncFileStatusTracker(this)); _syncFileStatusTracker.reset(new SyncFileStatusTracker(this));

View File

@ -21,6 +21,7 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <sys/time.h> #include <sys/time.h>
#include <stdio.h>
#define CSYNC_TEST 1 #define CSYNC_TEST 1
#include "csync_exclude.cpp" #include "csync_exclude.cpp"
@ -115,16 +116,27 @@ static void check_csync_exclude_add(void **)
excludedFiles->addManualExclude("/tmp/check_csync1/*"); excludedFiles->addManualExclude("/tmp/check_csync1/*");
assert_int_equal(check_file_full("/tmp/check_csync1/foo"), CSYNC_FILE_EXCLUDE_LIST); assert_int_equal(check_file_full("/tmp/check_csync1/foo"), CSYNC_FILE_EXCLUDE_LIST);
assert_int_equal(check_file_full("/tmp/check_csync2/foo"), CSYNC_NOT_EXCLUDED); assert_int_equal(check_file_full("/tmp/check_csync2/foo"), CSYNC_NOT_EXCLUDED);
assert_true(excludedFiles->_allExcludes.contains("/tmp/check_csync1/*")); assert_true(excludedFiles->_allExcludes["/"].contains("/tmp/check_csync1/*"));
assert_true(excludedFiles->_fullRegexFile.pattern().contains("csync1")); assert_true(excludedFiles->_fullRegexFile["/"].pattern().contains("csync1"));
assert_true(excludedFiles->_fullTraversalRegexFile.pattern().contains("csync1")); assert_true(excludedFiles->_fullTraversalRegexFile["/"].pattern().contains("csync1"));
assert_false(excludedFiles->_bnameTraversalRegexFile.pattern().contains("csync1")); assert_false(excludedFiles->_bnameTraversalRegexFile["/"].pattern().contains("csync1"));
excludedFiles->addManualExclude("foo"); excludedFiles->addManualExclude("foo");
assert_true(excludedFiles->_bnameTraversalRegexFile.pattern().contains("foo")); assert_true(excludedFiles->_bnameTraversalRegexFile["/"].pattern().contains("foo"));
assert_true(excludedFiles->_fullRegexFile.pattern().contains("foo")); assert_true(excludedFiles->_fullRegexFile["/"].pattern().contains("foo"));
assert_false(excludedFiles->_fullTraversalRegexFile.pattern().contains("foo")); assert_false(excludedFiles->_fullTraversalRegexFile["/"].pattern().contains("foo"));
}
static void check_csync_exclude_add_per_dir(void **)
{
excludedFiles->addManualExclude("*", "/tmp/check_csync1/");
assert_int_equal(check_file_full("/tmp/check_csync1/foo"), CSYNC_FILE_EXCLUDE_LIST);
assert_int_equal(check_file_full("/tmp/check_csync2/foo"), CSYNC_NOT_EXCLUDED);
assert_true(excludedFiles->_allExcludes["/tmp/check_csync1/"].contains("*"));
excludedFiles->addManualExclude("foo");
assert_true(excludedFiles->_fullRegexFile["/"].pattern().contains("foo"));
} }
static void check_csync_excluded(void **) static void check_csync_excluded(void **)
@ -232,6 +244,41 @@ static void check_csync_excluded(void **)
assert_int_equal(check_file_full("c [d]"), CSYNC_FILE_EXCLUDE_LIST); assert_int_equal(check_file_full("c [d]"), CSYNC_FILE_EXCLUDE_LIST);
} }
static void check_csync_excluded_per_dir(void **)
{
excludedFiles->addManualExclude("A");
excludedFiles->reloadExcludeFiles();
assert_int_equal(check_file_full("A"), CSYNC_FILE_EXCLUDE_LIST);
excludedFiles->clearManualExcludes();
excludedFiles->addManualExclude("A", "/B/");
excludedFiles->reloadExcludeFiles();
assert_int_equal(check_file_full("A"), CSYNC_NOT_EXCLUDED);
assert_int_equal(check_file_full("B/A"), CSYNC_FILE_EXCLUDE_LIST);
#define FOO_DIR "/tmp/check_csync1/foo"
#define FOO_EXCLUDE_LIST FOO_DIR "/.sync-exclude.lst"
int rc;
rc = system("mkdir -p " FOO_DIR);
assert_int_equal(rc, 0);
FILE *fh = fopen(FOO_EXCLUDE_LIST, "w");
assert_non_null(fh);
rc = fprintf(fh, "bar");
assert_int_not_equal(rc, 0);
rc = fclose(fh);
assert_int_equal(rc, 0);
excludedFiles->addInTreeExcludeFilePath(FOO_EXCLUDE_LIST);
excludedFiles->reloadExcludeFiles();
assert_int_equal(check_file_full(FOO_DIR), CSYNC_NOT_EXCLUDED);
assert_int_equal(check_file_full(FOO_DIR "/bar"), CSYNC_FILE_EXCLUDE_LIST);
assert_int_equal(check_file_full(FOO_DIR "/baz"), CSYNC_NOT_EXCLUDED);
#undef FOO_DIR
#undef FOO_EXCLUDE_LIST
}
static void check_csync_excluded_traversal(void **) static void check_csync_excluded_traversal(void **)
{ {
assert_int_equal(check_file_traversal(""), CSYNC_NOT_EXCLUDED); assert_int_equal(check_file_traversal(""), CSYNC_NOT_EXCLUDED);
@ -633,7 +680,9 @@ int torture_run_tests(void)
const struct CMUnitTest tests[] = { const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(T::check_csync_exclude_add, T::setup, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_exclude_add, T::setup, T::teardown),
cmocka_unit_test_setup_teardown(T::check_csync_exclude_add_per_dir, T::setup, T::teardown),
cmocka_unit_test_setup_teardown(T::check_csync_excluded, T::setup_init, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_excluded, T::setup_init, T::teardown),
cmocka_unit_test_setup_teardown(T::check_csync_excluded_per_dir, T::setup, T::teardown),
cmocka_unit_test_setup_teardown(T::check_csync_excluded_traversal, T::setup_init, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_excluded_traversal, T::setup_init, T::teardown),
cmocka_unit_test_setup_teardown(T::check_csync_dir_only, T::setup, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_dir_only, T::setup, T::teardown),
cmocka_unit_test_setup_teardown(T::check_csync_pathes, T::setup_init, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_pathes, T::setup_init, T::teardown),