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:
parent
7843660bbf
commit
14279104ae
src
test/csync/csync_tests
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user