1
0
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:
Camila Ayres 2019-09-06 16:03:54 +02:00 committed by GitHub
commit 9ab5241459
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 749 additions and 360 deletions

View File

@ -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 )

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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

View File

@ -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;
};

View File

@ -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>

View 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

View 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

View 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>

View File

@ -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));

View File

@ -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),