mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2025-04-29 04:15:44 +02:00
Merge branch 'master' into w10-start-logo
This commit is contained in:
commit
9ab5241459
@ -3,10 +3,10 @@ set( APPLICATION_SHORTNAME "Nextcloud" )
|
||||
set( APPLICATION_EXECUTABLE "nextcloud" )
|
||||
set( APPLICATION_DOMAIN "nextcloud.com" )
|
||||
set( APPLICATION_VENDOR "Nextcloud GmbH" )
|
||||
set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE string "URL for updater" )
|
||||
set( APPLICATION_HELP_URL "" CACHE string "URL for the help menu" )
|
||||
set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING "URL for updater" )
|
||||
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
|
||||
set( APPLICATION_ICON_NAME "Nextcloud" )
|
||||
set( APPLICATION_SERVER_URL "" CACHE string "URL for the server to use. If entered the server can only connect to this instance" )
|
||||
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
|
||||
|
||||
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
|
||||
|
||||
@ -20,14 +20,14 @@ set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-back
|
||||
# set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt )
|
||||
|
||||
option( WITH_CRASHREPORTER "Build crashreporter" OFF )
|
||||
#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE string "URL for crash reporter" )
|
||||
#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE STRING "URL for crash reporter" )
|
||||
#set( CRASHREPORTER_ICON ":/owncloud-icon.png" )
|
||||
|
||||
option( WITH_PROVIDERS "Build with providers list" ON )
|
||||
|
||||
|
||||
## Theming options
|
||||
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "#0082c9" CACHE string "Hex color of the wizard header background")
|
||||
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE string "Hex color of the text in the wizard header")
|
||||
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "#0082c9" CACHE STRING "Hex color of the wizard header background")
|
||||
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header")
|
||||
option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.png' else the default application icon is used" ON )
|
||||
|
||||
|
@ -236,13 +236,29 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeC
|
||||
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;
|
||||
|
||||
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.
|
||||
_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()
|
||||
@ -251,7 +267,13 @@ ExcludedFiles::~ExcludedFiles()
|
||||
|
||||
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)
|
||||
@ -261,9 +283,18 @@ void ExcludedFiles::setExcludeConflictFiles(bool onoff)
|
||||
|
||||
void ExcludedFiles::addManualExclude(const QByteArray &expr)
|
||||
{
|
||||
_manualExcludes.append(expr);
|
||||
_allExcludes.append(expr);
|
||||
prepare();
|
||||
addManualExclude(expr, _localPath.toUtf8());
|
||||
}
|
||||
|
||||
void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
|
||||
{
|
||||
Q_ASSERT(basePath.startsWith('/'));
|
||||
Q_ASSERT(basePath.endsWith('/'));
|
||||
|
||||
auto key = basePath;
|
||||
_manualExcludes[key].append(expr);
|
||||
_allExcludes[key].append(expr);
|
||||
prepare(key);
|
||||
}
|
||||
|
||||
void ExcludedFiles::clearManualExcludes()
|
||||
@ -278,26 +309,47 @@ void ExcludedFiles::setWildcardsMatchSlash(bool onoff)
|
||||
prepare();
|
||||
}
|
||||
|
||||
bool ExcludedFiles::loadExcludeFile(const QByteArray & basePath, const QString & file)
|
||||
{
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
while (!f.atEnd()) {
|
||||
QByteArray line = f.readLine().trimmed();
|
||||
if (line.isEmpty() || line.startsWith('#'))
|
||||
continue;
|
||||
csync_exclude_expand_escapes(line);
|
||||
_allExcludes[basePath].append(line);
|
||||
}
|
||||
prepare(basePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExcludedFiles::reloadExcludeFiles()
|
||||
{
|
||||
_allExcludes.clear();
|
||||
// clear all regex
|
||||
_bnameTraversalRegexFile.clear();
|
||||
_bnameTraversalRegexDir.clear();
|
||||
_fullTraversalRegexFile.clear();
|
||||
_fullTraversalRegexDir.clear();
|
||||
_fullRegexFile.clear();
|
||||
_fullRegexDir.clear();
|
||||
|
||||
bool success = true;
|
||||
foreach (const QString &file, _excludeFiles) {
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
while (!f.atEnd()) {
|
||||
QByteArray line = f.readLine().trimmed();
|
||||
if (line.isEmpty() || line.startsWith('#'))
|
||||
continue;
|
||||
csync_exclude_expand_escapes(line);
|
||||
_allExcludes.append(line);
|
||||
for (auto basePath : _excludeFiles.keys()) {
|
||||
for (auto file : _excludeFiles.value(basePath)) {
|
||||
success = loadExcludeFile(basePath, file);
|
||||
}
|
||||
}
|
||||
_allExcludes.append(_manualExcludes);
|
||||
prepare();
|
||||
|
||||
auto endManual = _manualExcludes.cend();
|
||||
for (auto kv = _manualExcludes.cbegin(); kv != endManual; ++kv) {
|
||||
_allExcludes[kv.key()].append(kv.value());
|
||||
prepare(kv.key());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@ -311,13 +363,15 @@ bool ExcludedFiles::isExcluded(
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO this seems a waste, hidden files are ignored before hitting this function it seems
|
||||
if (excludeHidden) {
|
||||
QString path = filePath;
|
||||
// Check all path subcomponents, but to *not* check the base path:
|
||||
// We do want to be able to sync with a hidden folder as the target.
|
||||
while (path.size() > basePath.size()) {
|
||||
QFileInfo fi(path);
|
||||
if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) {
|
||||
if (fi.fileName() != ".sync-exclude.lst"
|
||||
&& (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -340,7 +394,7 @@ bool ExcludedFiles::isExcluded(
|
||||
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);
|
||||
if (match != CSYNC_NOT_EXCLUDED)
|
||||
@ -348,6 +402,15 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
|
||||
if (_allExcludes.isEmpty())
|
||||
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());
|
||||
loadExcludeFile(fi.absolutePath().toUtf8(), fi.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
// Check the bname part of the path to see whether the full
|
||||
// regex should be run.
|
||||
|
||||
@ -359,35 +422,53 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
|
||||
}
|
||||
QString bnameStr = QString::fromUtf8(bname);
|
||||
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
m = _bnameTraversalRegexDir.match(bnameStr);
|
||||
} else {
|
||||
m = _bnameTraversalRegexFile.match(bnameStr);
|
||||
}
|
||||
if (!m.hasMatch())
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
|
||||
}
|
||||
QByteArray basePath(_localPath.toUtf8() + path);
|
||||
while (basePath.size() > _localPath.size()) {
|
||||
basePath = leftIncludeLast(basePath, '/');
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory
|
||||
&& _bnameTraversalRegexDir.contains(basePath)) {
|
||||
m = _bnameTraversalRegexDir[basePath].match(bnameStr);
|
||||
} else if (filetype == ItemTypeFile
|
||||
&& _bnameTraversalRegexFile.contains(basePath)) {
|
||||
m = _bnameTraversalRegexFile[basePath].match(bnameStr);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// third capture: full path matching is triggered
|
||||
QString pathStr = QString::fromUtf8(path);
|
||||
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
m = _fullTraversalRegexDir.match(pathStr);
|
||||
} else {
|
||||
m = _fullTraversalRegexFile.match(pathStr);
|
||||
}
|
||||
if (m.hasMatch()) {
|
||||
if (!m.hasMatch())
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
|
||||
return CSYNC_FILE_EXCLUDE_LIST;
|
||||
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -400,23 +481,38 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, ItemType fi
|
||||
return CSYNC_NOT_EXCLUDED;
|
||||
|
||||
QString p = QString::fromUtf8(path);
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory) {
|
||||
m = _fullRegexDir.match(p);
|
||||
} else {
|
||||
m = _fullRegexFile.match(p);
|
||||
}
|
||||
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;
|
||||
// `path` seems to always be relative to `_localPath`, the tests however have not been
|
||||
// written that way... this makes the tests happy for now. TODO Fix the tests at some point
|
||||
if (path[0] == '/')
|
||||
++path;
|
||||
|
||||
QByteArray basePath(_localPath.toUtf8() + path);
|
||||
while (basePath.size() > _localPath.size()) {
|
||||
basePath = leftIncludeLast(basePath, '/');
|
||||
QRegularExpressionMatch m;
|
||||
if (filetype == ItemTypeDirectory
|
||||
&& _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;
|
||||
}
|
||||
|
||||
auto ExcludedFiles::csyncTraversalMatchFun() const
|
||||
auto ExcludedFiles::csyncTraversalMatchFun()
|
||||
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>
|
||||
{
|
||||
return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); };
|
||||
@ -555,6 +651,22 @@ static QString extractBnameTrigger(const QString &exclude, bool wildcardsMatchSl
|
||||
|
||||
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.
|
||||
//
|
||||
// To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex
|
||||
@ -596,7 +708,7 @@ void ExcludedFiles::prepare()
|
||||
pattern.append(appendMe);
|
||||
};
|
||||
|
||||
for (auto exclude : _allExcludes) {
|
||||
for (auto exclude : _allExcludes.value(basePath)) {
|
||||
if (exclude[0] == '\n')
|
||||
continue; // empty line
|
||||
if (exclude[0] == '\r')
|
||||
@ -618,6 +730,15 @@ void ExcludedFiles::prepare()
|
||||
auto &fullFileDir = removeExcluded ? fullFileDirRemove : fullFileDirKeep;
|
||||
auto &fullDir = removeExcluded ? fullDirRemove : fullDirKeep;
|
||||
|
||||
if (fullPath) {
|
||||
// The full pattern is matched against a path relative to _localPath, however exclude is
|
||||
// relative to basePath at this point.
|
||||
// We know for sure that both _localPath and basePath are absolute and that basePath is
|
||||
// contained in _localPath. So we can simply remove it from the begining.
|
||||
auto relPath = basePath.mid(_localPath.size());
|
||||
// Make exclude relative to _localPath
|
||||
exclude.prepend(relPath);
|
||||
}
|
||||
auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude), _wildcardsMatchSlash);
|
||||
if (!fullPath) {
|
||||
regexAppend(bnameFileDir, bnameDir, regexExclude, matchDirOnly);
|
||||
@ -654,11 +775,11 @@ void ExcludedFiles::prepare()
|
||||
// (exclude)|(excluderemove)|(bname triggers).
|
||||
// If the third group matches, the fullActivatedRegex needs to be applied
|
||||
// to the full path.
|
||||
_bnameTraversalRegexFile.setPattern(
|
||||
_bnameTraversalRegexFile[basePath].setPattern(
|
||||
"^(?P<exclude>" + bnameFileDirKeep + ")$|"
|
||||
+ "^(?P<excluderemove>" + bnameFileDirRemove + ")$|"
|
||||
+ "^(?P<trigger>" + bnameTriggerFileDir + ")$");
|
||||
_bnameTraversalRegexDir.setPattern(
|
||||
_bnameTraversalRegexDir[basePath].setPattern(
|
||||
"^(?P<exclude>" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|"
|
||||
+ "^(?P<excluderemove>" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|"
|
||||
+ "^(?P<trigger>" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$");
|
||||
@ -667,13 +788,13 @@ void ExcludedFiles::prepare()
|
||||
// the bname regex matches. Its basic form is (exclude)|(excluderemove)".
|
||||
// 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.
|
||||
_fullTraversalRegexFile.setPattern(
|
||||
_fullTraversalRegexFile[basePath].setPattern(
|
||||
QLatin1String("")
|
||||
// Full patterns are anchored to the beginning
|
||||
+ "^(?P<exclude>" + fullFileDirKeep + ")(?:$|/)"
|
||||
+ "|"
|
||||
+ "^(?P<excluderemove>" + fullFileDirRemove + ")(?:$|/)");
|
||||
_fullTraversalRegexDir.setPattern(
|
||||
_fullTraversalRegexDir[basePath].setPattern(
|
||||
QLatin1String("")
|
||||
+ "^(?P<exclude>" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)"
|
||||
+ "|"
|
||||
@ -681,7 +802,7 @@ void ExcludedFiles::prepare()
|
||||
|
||||
// The full regex is applied to the full path and incorporates both bname and
|
||||
// full-path patterns. It has the form "(exclude)|(excluderemove)".
|
||||
_fullRegexFile.setPattern(
|
||||
_fullRegexFile[basePath].setPattern(
|
||||
QLatin1String("(?P<exclude>")
|
||||
// Full patterns are anchored to the beginning
|
||||
+ "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|"
|
||||
@ -697,7 +818,7 @@ void ExcludedFiles::prepare()
|
||||
+ "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|"
|
||||
+ "(?:^|/)(?:" + bnameDirRemove + ")/"
|
||||
+ ")");
|
||||
_fullRegexDir.setPattern(
|
||||
_fullRegexDir[basePath].setPattern(
|
||||
QLatin1String("(?P<exclude>")
|
||||
+ "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|"
|
||||
+ "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)"
|
||||
@ -711,16 +832,16 @@ void ExcludedFiles::prepare()
|
||||
QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
|
||||
if (OCC::Utility::fsCasePreserving())
|
||||
patternOptions |= QRegularExpression::CaseInsensitiveOption;
|
||||
_bnameTraversalRegexFile.setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexFile.optimize();
|
||||
_bnameTraversalRegexDir.setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexDir.optimize();
|
||||
_fullTraversalRegexFile.setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexFile.optimize();
|
||||
_fullTraversalRegexDir.setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexDir.optimize();
|
||||
_fullRegexFile.setPatternOptions(patternOptions);
|
||||
_fullRegexFile.optimize();
|
||||
_fullRegexDir.setPatternOptions(patternOptions);
|
||||
_fullRegexDir.optimize();
|
||||
_bnameTraversalRegexFile[basePath].setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexFile[basePath].optimize();
|
||||
_bnameTraversalRegexDir[basePath].setPatternOptions(patternOptions);
|
||||
_bnameTraversalRegexDir[basePath].optimize();
|
||||
_fullTraversalRegexFile[basePath].setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexFile[basePath].optimize();
|
||||
_fullTraversalRegexDir[basePath].setPatternOptions(patternOptions);
|
||||
_fullTraversalRegexDir[basePath].optimize();
|
||||
_fullRegexFile[basePath].setPatternOptions(patternOptions);
|
||||
_fullRegexFile[basePath].optimize();
|
||||
_fullRegexDir[basePath].setPatternOptions(patternOptions);
|
||||
_fullRegexDir[basePath].optimize();
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExcludedFiles();
|
||||
ExcludedFiles(QString localPath = "/");
|
||||
~ExcludedFiles();
|
||||
|
||||
/**
|
||||
@ -75,6 +75,7 @@ public:
|
||||
* Does not load the file. Use reloadExcludeFiles() afterwards.
|
||||
*/
|
||||
void addExcludeFilePath(const QString &path);
|
||||
void addInTreeExcludeFilePath(const QString &path);
|
||||
|
||||
/**
|
||||
* Whether conflict files shall be excluded.
|
||||
@ -95,12 +96,13 @@ public:
|
||||
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
|
||||
* reloadExcludeFiles() is called.
|
||||
*/
|
||||
void addManualExclude(const QByteArray &expr);
|
||||
void addManualExclude(const QByteArray &expr, const QByteArray &basePath);
|
||||
|
||||
/**
|
||||
* Removes all manually added exclude patterns.
|
||||
@ -121,7 +123,7 @@ public:
|
||||
* Careful: The function will only be valid for as long as this
|
||||
* ExcludedFiles instance stays alive.
|
||||
*/
|
||||
auto csyncTraversalMatchFun() const
|
||||
auto csyncTraversalMatchFun()
|
||||
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>;
|
||||
|
||||
public slots:
|
||||
@ -129,6 +131,10 @@ public slots:
|
||||
* Reloads the exclude patterns from the registered paths.
|
||||
*/
|
||||
bool reloadExcludeFiles();
|
||||
/**
|
||||
* Loads the exclude patterns from file the registered base paths.
|
||||
*/
|
||||
bool loadExcludeFile(const QByteArray & basePath, const QString & file);
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -156,10 +162,32 @@ private:
|
||||
* Note that this only matches patterns. It does not check whether the file
|
||||
* 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
|
||||
* into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full
|
||||
@ -187,24 +215,28 @@ private:
|
||||
* full matcher would exclude. Example: "b" is excluded. traversal("b/c")
|
||||
* returns not-excluded because "c" isn't a bname activation pattern.
|
||||
*/
|
||||
void prepare(const BasePathByteArray & basePath);
|
||||
|
||||
void prepare();
|
||||
|
||||
|
||||
QString _localPath;
|
||||
/// Files to load excludes from
|
||||
QSet<QString> _excludeFiles;
|
||||
QMap<BasePathByteArray, QList<QString>> _excludeFiles;
|
||||
|
||||
/// Exclude patterns added with addManualExclude()
|
||||
QList<QByteArray> _manualExcludes;
|
||||
QMap<BasePathByteArray, QList<QByteArray>> _manualExcludes;
|
||||
|
||||
/// List of all active exclude patterns
|
||||
QList<QByteArray> _allExcludes;
|
||||
QMap<BasePathByteArray, QList<QByteArray>> _allExcludes;
|
||||
|
||||
/// see prepare()
|
||||
QRegularExpression _bnameTraversalRegexFile;
|
||||
QRegularExpression _bnameTraversalRegexDir;
|
||||
QRegularExpression _fullTraversalRegexFile;
|
||||
QRegularExpression _fullTraversalRegexDir;
|
||||
QRegularExpression _fullRegexFile;
|
||||
QRegularExpression _fullRegexDir;
|
||||
QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexFile;
|
||||
QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexDir;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexFile;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexDir;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullRegexFile;
|
||||
QMap<BasePathByteArray, QRegularExpression> _fullRegexDir;
|
||||
|
||||
bool _excludeConflictFiles = true;
|
||||
|
||||
|
@ -124,7 +124,9 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
* because it's a hidden file that should not be synced.
|
||||
* This code should probably be in csync_exclude, but it does not have the fs parameter.
|
||||
* Keep it here for now */
|
||||
if (ctx->ignore_hidden_files && (fs->is_hidden)) {
|
||||
if (ctx->ignore_hidden_files
|
||||
&& fs->is_hidden
|
||||
&& !fs->path.endsWith(".sync-exclude.lst")) {
|
||||
qCInfo(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
|
||||
excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ set(client_UI_SRCS
|
||||
generalsettings.ui
|
||||
legalnotice.ui
|
||||
ignorelisteditor.ui
|
||||
ignorelisttablewidget.ui
|
||||
networksettings.ui
|
||||
activitywidget.ui
|
||||
synclogdialog.ui
|
||||
@ -61,6 +62,7 @@ set(client_SRCS
|
||||
generalsettings.cpp
|
||||
legalnotice.cpp
|
||||
ignorelisteditor.cpp
|
||||
ignorelisttablewidget.cpp
|
||||
lockwatcher.cpp
|
||||
logbrowser.cpp
|
||||
navigationpanehelper.cpp
|
||||
|
@ -35,10 +35,12 @@
|
||||
#include "filesystem.h"
|
||||
#include "clientsideencryptionjobs.h"
|
||||
#include "syncresult.h"
|
||||
#include "ignorelisttablewidget.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMessageBox>
|
||||
@ -422,7 +424,7 @@ bool AccountSettings::canEncryptOrDecrypt (const FolderStatusModel::SubFolderInf
|
||||
return true;
|
||||
}
|
||||
|
||||
void AccountSettings::slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo)
|
||||
void AccountSettings::slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo)
|
||||
{
|
||||
if (!canEncryptOrDecrypt(folderInfo)) {
|
||||
return;
|
||||
@ -539,6 +541,51 @@ void AccountSettings::slotLockForDecryptionError(const QByteArray& fileId, int h
|
||||
qDebug() << "Error Locking for decryption";
|
||||
}
|
||||
|
||||
void AccountSettings::slotEditCurrentIgnoredFiles()
|
||||
{
|
||||
Folder *f = FolderMan::instance()->folder(selectedFolderAlias());
|
||||
if (f == nullptr)
|
||||
return;
|
||||
openIgnoredFilesDialog(f->path());
|
||||
}
|
||||
|
||||
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
|
||||
{
|
||||
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
|
||||
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
|
||||
return;
|
||||
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
|
||||
openIgnoredFilesDialog(fileName);
|
||||
}
|
||||
|
||||
void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath)
|
||||
{
|
||||
Q_ASSERT(absFolderPath.startsWith('/'));
|
||||
Q_ASSERT(absFolderPath.endsWith('/'));
|
||||
|
||||
const QString ignoreFile = absFolderPath + ".sync-exclude.lst";
|
||||
auto layout = new QVBoxLayout();
|
||||
auto ignoreListWidget = new IgnoreListTableWidget(this);
|
||||
ignoreListWidget->readIgnoreFile(ignoreFile);
|
||||
layout->addWidget(ignoreListWidget);
|
||||
|
||||
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
auto dialog = new QDialog();
|
||||
dialog->setLayout(layout);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton * button) {
|
||||
if (buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole)
|
||||
ignoreListWidget->slotWriteIgnoreFile(ignoreFile);
|
||||
dialog->close();
|
||||
});
|
||||
connect(buttonBox, &QDialogButtonBox::rejected,
|
||||
dialog, &QDialog::close);
|
||||
|
||||
dialog->open();
|
||||
}
|
||||
|
||||
void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index, const QPoint& pos)
|
||||
{
|
||||
Q_UNUSED(pos);
|
||||
@ -561,12 +608,16 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
|
||||
|
||||
if (!isEncrypted) {
|
||||
ac = menu.addAction(tr("Encrypt"));
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrpted(info); });
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrypted(info); });
|
||||
} else {
|
||||
// Ingore decrypting for now since it only works with an empty folder
|
||||
// connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info); });
|
||||
}
|
||||
}
|
||||
|
||||
ac = menu.addAction(tr("Edit Ignored Files"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentLocalIgnoredFiles);
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
@ -600,6 +651,9 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
QAction *ac = menu->addAction(tr("Open folder"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder);
|
||||
|
||||
ac = menu->addAction(tr("Edit Ignored Files"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
|
||||
|
||||
if (!ui->_folderList->isExpanded(index)) {
|
||||
ac = menu->addAction(tr("Choose what to sync"));
|
||||
ac->setEnabled(folderConnected);
|
||||
|
@ -80,6 +80,8 @@ protected slots:
|
||||
void slotRemoveCurrentFolder();
|
||||
void slotOpenCurrentFolder(); // sync folder
|
||||
void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder
|
||||
void slotEditCurrentIgnoredFiles();
|
||||
void slotEditCurrentLocalIgnoredFiles();
|
||||
void slotFolderWizardAccepted();
|
||||
void slotFolderWizardRejected();
|
||||
void slotDeleteAccount();
|
||||
@ -87,7 +89,7 @@ protected slots:
|
||||
void slotOpenAccountWizard();
|
||||
void slotAccountAdded(AccountState *);
|
||||
void refreshSelectiveSyncStatus();
|
||||
void slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotSubfolderContextMenuRequested(const QModelIndex& idx, const QPoint& point);
|
||||
void slotCustomContextMenuRequested(const QPoint &);
|
||||
@ -110,7 +112,7 @@ protected slots:
|
||||
void slotUploadMetadataSuccess(const QByteArray& folderId);
|
||||
void slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode);
|
||||
|
||||
// Remove Encryotion Bit.
|
||||
// Remove Encryption Bit.
|
||||
void slotLockForDecryptionSuccess(const QByteArray& folderId, const QByteArray& token);
|
||||
void slotLockForDecryptionError(const QByteArray& folderId, int httpReturnCode);
|
||||
void slotDeleteMetadataSuccess(const QByteArray& folderId);
|
||||
@ -125,6 +127,7 @@ private:
|
||||
QStringList errors = QStringList());
|
||||
bool event(QEvent *) override;
|
||||
void createAccountToolbox();
|
||||
void openIgnoredFilesDialog(const QString & absFolderPath);
|
||||
|
||||
/// Returns the alias of the selected folder, empty string if none
|
||||
QString selectedFolderAlias() const;
|
||||
|
@ -184,6 +184,7 @@ void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)
|
||||
void GeneralSettings::slotIgnoreFilesEditor()
|
||||
{
|
||||
if (_ignoreEditor.isNull()) {
|
||||
ConfigFile cfgFile;
|
||||
_ignoreEditor = new IgnoreListEditor(this);
|
||||
_ignoreEditor->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
_ignoreEditor->open();
|
||||
|
@ -14,8 +14,9 @@
|
||||
|
||||
#include "configfile.h"
|
||||
|
||||
#include "ignorelisteditor.h"
|
||||
#include "folderman.h"
|
||||
#include "generalsettings.h"
|
||||
#include "ignorelisteditor.h"
|
||||
#include "ui_ignorelisteditor.h"
|
||||
|
||||
#include <QFile>
|
||||
@ -27,10 +28,6 @@
|
||||
|
||||
namespace OCC {
|
||||
|
||||
static int patternCol = 0;
|
||||
static int deletableCol = 1;
|
||||
static int readOnlyRows = 3;
|
||||
|
||||
IgnoreListEditor::IgnoreListEditor(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::IgnoreListEditor)
|
||||
@ -39,28 +36,28 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent)
|
||||
ui->setupUi(this);
|
||||
|
||||
ConfigFile cfgFile;
|
||||
ui->descriptionLabel->setText(tr("Files or folders matching a pattern will not be synchronized.\n\n"
|
||||
"Items where deletion is allowed will be deleted if they prevent a "
|
||||
"directory from being removed. "
|
||||
"This is useful for meta data."));
|
||||
//FIXME This is not true. The entries are hardcoded below in setupTableReadOnlyItems
|
||||
readOnlyTooltip = tr("This entry is provided by the system at '%1' "
|
||||
"and cannot be modified in this view.")
|
||||
.arg(QDir::toNativeSeparators(cfgFile.excludeFile(ConfigFile::SystemScope)));
|
||||
|
||||
setupTableReadOnlyItems();
|
||||
readIgnoreFile(cfgFile.excludeFile(ConfigFile::UserScope), false);
|
||||
const auto userConfig = cfgFile.excludeFile(ConfigFile::Scope::UserScope);
|
||||
ui->ignoreTableWidget->readIgnoreFile(userConfig);
|
||||
|
||||
connect(this, &QDialog::accepted, this, &IgnoreListEditor::slotUpdateLocalIgnoreList);
|
||||
ui->removePushButton->setEnabled(false);
|
||||
connect(ui->tableWidget, &QTableWidget::itemSelectionChanged, this, &IgnoreListEditor::slotItemSelectionChanged);
|
||||
connect(ui->removePushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotRemoveCurrentItem);
|
||||
connect(ui->addPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotAddPattern);
|
||||
connect(ui->removeAllPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotRemoveAllItems);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &IgnoreListEditor::slotRestoreDefaults);
|
||||
connect(this, &QDialog::accepted, [=]() {
|
||||
ui->ignoreTableWidget->slotWriteIgnoreFile(userConfig);
|
||||
/* handle the hidden file checkbox */
|
||||
|
||||
ui->tableWidget->resizeColumnsToContents();
|
||||
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch);
|
||||
ui->tableWidget->verticalHeader()->setVisible(false);
|
||||
/* the ignoreHiddenFiles flag is a folder specific setting, but for now, it is
|
||||
* handled globally. Save it to every folder that is defined.
|
||||
* TODO this can now be fixed, simply attach this IgnoreListEditor to top-level account
|
||||
* settings
|
||||
*/
|
||||
FolderMan::instance()->setIgnoreHiddenFiles(ignoreHiddenFiles());
|
||||
});
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &IgnoreListEditor::slotRestoreDefaults);
|
||||
|
||||
ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles());
|
||||
}
|
||||
@ -70,12 +67,11 @@ IgnoreListEditor::~IgnoreListEditor()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void IgnoreListEditor::setupTableReadOnlyItems(){
|
||||
ui->tableWidget->setRowCount(0);
|
||||
addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
ui->removeAllPushButton->setEnabled(false);
|
||||
void IgnoreListEditor::setupTableReadOnlyItems()
|
||||
{
|
||||
ui->ignoreTableWidget->addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
ui->ignoreTableWidget->addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
ui->ignoreTableWidget->addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true);
|
||||
}
|
||||
|
||||
bool IgnoreListEditor::ignoreHiddenFiles()
|
||||
@ -83,140 +79,16 @@ bool IgnoreListEditor::ignoreHiddenFiles()
|
||||
return !ui->syncHiddenFilesCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotItemSelectionChanged()
|
||||
void IgnoreListEditor::slotRestoreDefaults(QAbstractButton *button)
|
||||
{
|
||||
QTableWidgetItem *item = ui->tableWidget->currentItem();
|
||||
if (!item) {
|
||||
ui->removePushButton->setEnabled(false);
|
||||
if(ui->buttonBox->buttonRole(button) != QDialogButtonBox::ResetRole)
|
||||
return;
|
||||
}
|
||||
|
||||
bool enable = item->flags() & Qt::ItemIsEnabled;
|
||||
ui->removePushButton->setEnabled(enable);
|
||||
}
|
||||
ui->ignoreTableWidget->slotRemoveAllItems();
|
||||
|
||||
void IgnoreListEditor::slotRemoveCurrentItem()
|
||||
{
|
||||
ui->tableWidget->removeRow(ui->tableWidget->currentRow());
|
||||
if(ui->tableWidget->rowCount() == readOnlyRows)
|
||||
ui->removeAllPushButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotRemoveAllItems()
|
||||
{
|
||||
ui->tableWidget->clearContents();
|
||||
setupTableReadOnlyItems();
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotUpdateLocalIgnoreList()
|
||||
{
|
||||
ConfigFile cfgFile;
|
||||
QString ignoreFile = cfgFile.excludeFile(ConfigFile::UserScope);
|
||||
QFile ignores(ignoreFile);
|
||||
if (ignores.open(QIODevice::WriteOnly)) {
|
||||
// rewrites the whole file since now the user can also remove system patterns
|
||||
QFile::resize(ignoreFile, 0);
|
||||
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
|
||||
QTableWidgetItem *patternItem = ui->tableWidget->item(row, patternCol);
|
||||
QTableWidgetItem *deletableItem = ui->tableWidget->item(row, deletableCol);
|
||||
if (patternItem->flags() & Qt::ItemIsEnabled) {
|
||||
QByteArray prepend;
|
||||
if (deletableItem->checkState() == Qt::Checked) {
|
||||
prepend = "]";
|
||||
} else if (patternItem->text().startsWith('#')) {
|
||||
prepend = "\\";
|
||||
}
|
||||
ignores.write(prepend + patternItem->text().toUtf8() + '\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Could not open file"),
|
||||
tr("Cannot write changes to '%1'.").arg(ignoreFile));
|
||||
}
|
||||
ignores.close(); //close the file before reloading stuff.
|
||||
|
||||
FolderMan *folderMan = FolderMan::instance();
|
||||
|
||||
/* handle the hidden file checkbox */
|
||||
|
||||
/* the ignoreHiddenFiles flag is a folder specific setting, but for now, it is
|
||||
* handled globally. Save it to every folder that is defined.
|
||||
*/
|
||||
folderMan->setIgnoreHiddenFiles(ignoreHiddenFiles());
|
||||
|
||||
// We need to force a remote discovery after a change of the ignore list.
|
||||
// Otherwise we would not download the files/directories that are no longer
|
||||
// ignored (because the remote etag did not change) (issue #3172)
|
||||
foreach (Folder *folder, folderMan->map()) {
|
||||
folder->journalDb()->forceRemoteDiscoveryNextSync();
|
||||
folderMan->scheduleFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotAddPattern()
|
||||
{
|
||||
bool okClicked;
|
||||
QString pattern = QInputDialog::getText(this, tr("Add Ignore Pattern"),
|
||||
tr("Add a new ignore pattern:"),
|
||||
QLineEdit::Normal, QString(), &okClicked);
|
||||
|
||||
if (!okClicked || pattern.isEmpty())
|
||||
return;
|
||||
|
||||
addPattern(pattern, false, false);
|
||||
ui->tableWidget->scrollToBottom();
|
||||
}
|
||||
|
||||
void IgnoreListEditor::slotRestoreDefaults(QAbstractButton *button){
|
||||
if(ui->buttonBox->buttonRole(button) == QDialogButtonBox::ResetRole){
|
||||
ConfigFile cfgFile;
|
||||
setupTableReadOnlyItems();
|
||||
readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), false);
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreListEditor::readIgnoreFile(const QString &file, bool readOnly)
|
||||
{
|
||||
QFile ignores(file);
|
||||
if (ignores.open(QIODevice::ReadOnly)) {
|
||||
while (!ignores.atEnd()) {
|
||||
QString line = QString::fromUtf8(ignores.readLine());
|
||||
line.chop(1);
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
bool deletable = false;
|
||||
if (line.startsWith(']')) {
|
||||
deletable = true;
|
||||
line = line.mid(1);
|
||||
}
|
||||
addPattern(line, deletable, readOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int IgnoreListEditor::addPattern(const QString &pattern, bool deletable, bool readOnly)
|
||||
{
|
||||
int newRow = ui->tableWidget->rowCount();
|
||||
ui->tableWidget->setRowCount(newRow + 1);
|
||||
|
||||
QTableWidgetItem *patternItem = new QTableWidgetItem;
|
||||
patternItem->setText(pattern);
|
||||
ui->tableWidget->setItem(newRow, patternCol, patternItem);
|
||||
|
||||
QTableWidgetItem *deletableItem = new QTableWidgetItem;
|
||||
deletableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
deletableItem->setCheckState(deletable ? Qt::Checked : Qt::Unchecked);
|
||||
ui->tableWidget->setItem(newRow, deletableCol, deletableItem);
|
||||
|
||||
if (readOnly) {
|
||||
patternItem->setFlags(patternItem->flags() ^ Qt::ItemIsEnabled);
|
||||
patternItem->setToolTip(readOnlyTooltip);
|
||||
deletableItem->setFlags(deletableItem->flags() ^ Qt::ItemIsEnabled);
|
||||
}
|
||||
|
||||
ui->removeAllPushButton->setEnabled(true);
|
||||
|
||||
return newRow;
|
||||
setupTableReadOnlyItems();
|
||||
ui->ignoreTableWidget->readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), false);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
@ -35,23 +35,16 @@ class IgnoreListEditor : public QDialog
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IgnoreListEditor(QWidget *parent = nullptr);
|
||||
IgnoreListEditor(QWidget *parent = nullptr);
|
||||
~IgnoreListEditor();
|
||||
|
||||
bool ignoreHiddenFiles();
|
||||
|
||||
private slots:
|
||||
void slotItemSelectionChanged();
|
||||
void slotRemoveCurrentItem();
|
||||
void slotUpdateLocalIgnoreList();
|
||||
void slotAddPattern();
|
||||
void slotRestoreDefaults(QAbstractButton *button);
|
||||
void slotRemoveAllItems();
|
||||
|
||||
private:
|
||||
void readIgnoreFile(const QString &file, bool readOnly);
|
||||
void setupTableReadOnlyItems();
|
||||
int addPattern(const QString &pattern, bool deletable, bool readOnly);
|
||||
QString readOnlyTooltip;
|
||||
Ui::IgnoreListEditor *ui;
|
||||
};
|
||||
|
@ -36,96 +36,8 @@
|
||||
<string>Files Ignored by Patterns</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>213</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="4">
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Pattern</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Allow Deletion</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="removePushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="addPushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="removeAllPushButton">
|
||||
<property name="text">
|
||||
<string>Remove all</string>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="0" column="0">
|
||||
<widget class="IgnoreListTableWidget" name="ignoreTableWidget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -139,6 +51,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>IgnoreListTableWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>ignorelisttablewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
167
src/gui/ignorelisttablewidget.cpp
Normal file
167
src/gui/ignorelisttablewidget.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include "ignorelisttablewidget.h"
|
||||
#include "ui_ignorelisttablewidget.h"
|
||||
|
||||
#include "folderman.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QInputDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
static constexpr int patternCol = 0;
|
||||
static constexpr int deletableCol = 1;
|
||||
static constexpr int readOnlyRows = 3;
|
||||
|
||||
IgnoreListTableWidget::IgnoreListTableWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, ui(new Ui::IgnoreListTableWidget)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->descriptionLabel->setText(tr("Files or folders matching a pattern will not be synchronized.\n\n"
|
||||
"Items where deletion is allowed will be deleted if they prevent a "
|
||||
"directory from being removed. "
|
||||
"This is useful for meta data."));
|
||||
|
||||
ui->removePushButton->setEnabled(false);
|
||||
connect(ui->tableWidget, &QTableWidget::itemSelectionChanged,
|
||||
this, &IgnoreListTableWidget::slotItemSelectionChanged);
|
||||
connect(ui->removePushButton, &QAbstractButton::clicked,
|
||||
this, &IgnoreListTableWidget::slotRemoveCurrentItem);
|
||||
connect(ui->addPushButton, &QAbstractButton::clicked,
|
||||
this, &IgnoreListTableWidget::slotAddPattern);
|
||||
connect(ui->removeAllPushButton, &QAbstractButton::clicked,
|
||||
this, &IgnoreListTableWidget::slotRemoveAllItems);
|
||||
|
||||
ui->tableWidget->resizeColumnsToContents();
|
||||
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch);
|
||||
ui->tableWidget->verticalHeader()->setVisible(false);
|
||||
}
|
||||
|
||||
IgnoreListTableWidget::~IgnoreListTableWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotItemSelectionChanged()
|
||||
{
|
||||
QTableWidgetItem *item = ui->tableWidget->currentItem();
|
||||
if (!item) {
|
||||
ui->removePushButton->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool enable = item->flags() & Qt::ItemIsEnabled;
|
||||
ui->removePushButton->setEnabled(enable);
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotRemoveCurrentItem()
|
||||
{
|
||||
ui->tableWidget->removeRow(ui->tableWidget->currentRow());
|
||||
if(ui->tableWidget->rowCount() == readOnlyRows)
|
||||
ui->removeAllPushButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotRemoveAllItems()
|
||||
{
|
||||
ui->tableWidget->setRowCount(0);
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotWriteIgnoreFile(const QString & file)
|
||||
{
|
||||
QFile ignores(file);
|
||||
if (ignores.open(QIODevice::WriteOnly)) {
|
||||
// rewrites the whole file since now the user can also remove system patterns
|
||||
QFile::resize(file, 0);
|
||||
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
|
||||
QTableWidgetItem *patternItem = ui->tableWidget->item(row, patternCol);
|
||||
QTableWidgetItem *deletableItem = ui->tableWidget->item(row, deletableCol);
|
||||
if (patternItem->flags() & Qt::ItemIsEnabled) {
|
||||
QByteArray prepend;
|
||||
if (deletableItem->checkState() == Qt::Checked) {
|
||||
prepend = "]";
|
||||
} else if (patternItem->text().startsWith('#')) {
|
||||
prepend = "\\";
|
||||
}
|
||||
ignores.write(prepend + patternItem->text().toUtf8() + '\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Could not open file"),
|
||||
tr("Cannot write changes to '%1'.").arg(file));
|
||||
}
|
||||
ignores.close(); //close the file before reloading stuff.
|
||||
|
||||
FolderMan *folderMan = FolderMan::instance();
|
||||
|
||||
// We need to force a remote discovery after a change of the ignore list.
|
||||
// Otherwise we would not download the files/directories that are no longer
|
||||
// ignored (because the remote etag did not change) (issue #3172)
|
||||
foreach (Folder *folder, folderMan->map()) {
|
||||
folder->journalDb()->forceRemoteDiscoveryNextSync();
|
||||
folderMan->scheduleFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::slotAddPattern()
|
||||
{
|
||||
bool okClicked;
|
||||
QString pattern = QInputDialog::getText(this, tr("Add Ignore Pattern"),
|
||||
tr("Add a new ignore pattern:"),
|
||||
QLineEdit::Normal, QString(), &okClicked);
|
||||
|
||||
if (!okClicked || pattern.isEmpty())
|
||||
return;
|
||||
|
||||
addPattern(pattern, false, false);
|
||||
ui->tableWidget->scrollToBottom();
|
||||
}
|
||||
|
||||
void IgnoreListTableWidget::readIgnoreFile(const QString &file, bool readOnly)
|
||||
{
|
||||
QFile ignores(file);
|
||||
if (ignores.open(QIODevice::ReadOnly)) {
|
||||
while (!ignores.atEnd()) {
|
||||
QString line = QString::fromUtf8(ignores.readLine());
|
||||
line.chop(1);
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
bool deletable = false;
|
||||
if (line.startsWith(']')) {
|
||||
deletable = true;
|
||||
line = line.mid(1);
|
||||
}
|
||||
addPattern(line, deletable, readOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int IgnoreListTableWidget::addPattern(const QString &pattern, bool deletable, bool readOnly)
|
||||
{
|
||||
int newRow = ui->tableWidget->rowCount();
|
||||
ui->tableWidget->setRowCount(newRow + 1);
|
||||
|
||||
QTableWidgetItem *patternItem = new QTableWidgetItem;
|
||||
patternItem->setText(pattern);
|
||||
ui->tableWidget->setItem(newRow, patternCol, patternItem);
|
||||
|
||||
QTableWidgetItem *deletableItem = new QTableWidgetItem;
|
||||
deletableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
deletableItem->setCheckState(deletable ? Qt::Checked : Qt::Unchecked);
|
||||
ui->tableWidget->setItem(newRow, deletableCol, deletableItem);
|
||||
|
||||
if (readOnly) {
|
||||
patternItem->setFlags(patternItem->flags() ^ Qt::ItemIsEnabled);
|
||||
patternItem->setToolTip(readOnlyTooltip);
|
||||
deletableItem->setFlags(deletableItem->flags() ^ Qt::ItemIsEnabled);
|
||||
}
|
||||
|
||||
ui->removeAllPushButton->setEnabled(true);
|
||||
|
||||
return newRow;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
38
src/gui/ignorelisttablewidget.h
Normal file
38
src/gui/ignorelisttablewidget.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QAbstractButton;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
namespace Ui {
|
||||
class IgnoreListTableWidget;
|
||||
}
|
||||
|
||||
class IgnoreListTableWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
IgnoreListTableWidget(QWidget *parent = nullptr);
|
||||
~IgnoreListTableWidget();
|
||||
|
||||
void readIgnoreFile(const QString &file, bool readOnly = false);
|
||||
int addPattern(const QString &pattern, bool deletable, bool readOnly);
|
||||
|
||||
public slots:
|
||||
void slotRemoveAllItems();
|
||||
void slotWriteIgnoreFile(const QString & file);
|
||||
|
||||
private slots:
|
||||
void slotItemSelectionChanged();
|
||||
void slotRemoveCurrentItem();
|
||||
void slotAddPattern();
|
||||
|
||||
private:
|
||||
void setupTableReadOnlyItems();
|
||||
QString readOnlyTooltip;
|
||||
Ui::IgnoreListTableWidget *ui;
|
||||
};
|
||||
} // namespace OCC
|
112
src/gui/ignorelisttablewidget.ui
Normal file
112
src/gui/ignorelisttablewidget.ui
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OCC::IgnoreListTableWidget</class>
|
||||
<widget class="QWidget" name="OCC::IgnoreListTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>342</width>
|
||||
<height>378</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>IgnoreListTableWidget</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" rowspan="4">
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Pattern</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Allow Deletion</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="addPushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="removePushButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="removeAllPushButton">
|
||||
<property name="text">
|
||||
<string>Remove all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>322</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -91,7 +91,7 @@ SyncEngine::SyncEngine(AccountPtr account, const QString &localPath,
|
||||
|
||||
_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();
|
||||
|
||||
_syncFileStatusTracker.reset(new SyncFileStatusTracker(this));
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CSYNC_TEST 1
|
||||
#include "csync_exclude.cpp"
|
||||
@ -115,16 +116,32 @@ static void check_csync_exclude_add(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.contains("/tmp/check_csync1/*"));
|
||||
assert_true(excludedFiles->_allExcludes["/"].contains("/tmp/check_csync1/*"));
|
||||
|
||||
assert_true(excludedFiles->_fullRegexFile.pattern().contains("csync1"));
|
||||
assert_true(excludedFiles->_fullTraversalRegexFile.pattern().contains("csync1"));
|
||||
assert_false(excludedFiles->_bnameTraversalRegexFile.pattern().contains("csync1"));
|
||||
assert_true(excludedFiles->_fullRegexFile["/"].pattern().contains("csync1"));
|
||||
assert_true(excludedFiles->_fullTraversalRegexFile["/"].pattern().contains("csync1"));
|
||||
assert_false(excludedFiles->_bnameTraversalRegexFile["/"].pattern().contains("csync1"));
|
||||
|
||||
excludedFiles->addManualExclude("foo");
|
||||
assert_true(excludedFiles->_bnameTraversalRegexFile.pattern().contains("foo"));
|
||||
assert_true(excludedFiles->_fullRegexFile.pattern().contains("foo"));
|
||||
assert_false(excludedFiles->_fullTraversalRegexFile.pattern().contains("foo"));
|
||||
assert_true(excludedFiles->_bnameTraversalRegexFile["/"].pattern().contains("foo"));
|
||||
assert_true(excludedFiles->_fullRegexFile["/"].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"));
|
||||
|
||||
excludedFiles->addManualExclude("foo/bar", "/tmp/check_csync1/");
|
||||
assert_true(excludedFiles->_fullRegexFile["/tmp/check_csync1/"].pattern().contains("bar"));
|
||||
assert_true(excludedFiles->_fullTraversalRegexFile["/tmp/check_csync1/"].pattern().contains("bar"));
|
||||
assert_false(excludedFiles->_bnameTraversalRegexFile["/tmp/check_csync1/"].pattern().contains("foo"));
|
||||
}
|
||||
|
||||
static void check_csync_excluded(void **)
|
||||
@ -232,6 +249,58 @@ static void check_csync_excluded(void **)
|
||||
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);
|
||||
|
||||
excludedFiles->clearManualExcludes();
|
||||
excludedFiles->addManualExclude("A/a1", "/B/");
|
||||
excludedFiles->reloadExcludeFiles();
|
||||
|
||||
assert_int_equal(check_file_full("A"), CSYNC_NOT_EXCLUDED);
|
||||
assert_int_equal(check_file_full("B/A/a1"), 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_per_dir(void **)
|
||||
{
|
||||
assert_int_equal(check_file_traversal("/"), CSYNC_NOT_EXCLUDED);
|
||||
|
||||
/* path wildcards */
|
||||
excludedFiles->addManualExclude("*/*.tex.tmp", "/latex/");
|
||||
assert_int_equal(check_file_traversal("latex/my_manuscript.tex.tmp"), CSYNC_NOT_EXCLUDED);
|
||||
assert_int_equal(check_file_traversal("latex/songbook/my_manuscript.tex.tmp"), CSYNC_FILE_EXCLUDE_LIST);
|
||||
}
|
||||
|
||||
static void check_csync_excluded_traversal(void **)
|
||||
{
|
||||
assert_int_equal(check_file_traversal(""), CSYNC_NOT_EXCLUDED);
|
||||
@ -633,8 +702,11 @@ int torture_run_tests(void)
|
||||
|
||||
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_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_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_per_dir, 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_wildcards, T::setup, T::teardown),
|
||||
|
Loading…
Reference in New Issue
Block a user