From: Christian Kamm Date: Fri, 26 Jan 2018 12:14:54 +0000 (+0100) Subject: Placeholders: Ignore placeholder files in older clients X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~21^2~468^2~611 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=12d6f680f276db0647af5d727f01d5d3da5cf388;p=nextcloud-desktop.git Placeholders: Ignore placeholder files in older clients To do this, we add the placeholder extension to the user exclude file automatically. However, newer clients shouldn't use that exclude pattern: so we also add version directives that allow making exclude patterns dependent on the client version. --- diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index 61bfdd443..1bc5a6c5d 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -33,9 +33,12 @@ #include "csync_misc.h" #include "common/utility.h" +#include "../version.h" #include #include +#include +#include /** Expands C-like escape sequences (in place) @@ -246,6 +249,7 @@ using namespace OCC; ExcludedFiles::ExcludedFiles(QString localPath) : _localPath(std::move(localPath)) + , _clientVersion(MIRALL_VERSION_MAJOR, MIRALL_VERSION_MINOR, MIRALL_VERSION_PATCH) { Q_ASSERT(_localPath.endsWith("/")); // Windows used to use PathMatchSpec which allows *foo to match abc/deffoo. @@ -311,6 +315,34 @@ void ExcludedFiles::setWildcardsMatchSlash(bool onoff) prepare(); } +void ExcludedFiles::setClientVersion(ExcludedFiles::Version version) +{ + _clientVersion = version; +} + +void ExcludedFiles::setupPlaceholderExclude( + const QString &excludeFile, const QByteArray &placeholderExtension) +{ + if (!QFile::exists(excludeFile)) { + // Ensure the parent paths exist + QDir().mkpath(QFileInfo(excludeFile).dir().absolutePath()); + } else { + // Does the exclude file contain the exclude already? + QFile file(excludeFile); + file.open(QIODevice::ReadOnly | QIODevice::Text); + auto data = file.readAll(); + file.close(); + if (data.contains("\n*" + placeholderExtension + "\n")) + return; + } + + // Add it to the file + QFile file(excludeFile); + file.open(QIODevice::ReadWrite | QIODevice::Append); + file.write("\n#!version < 2.5.0\n*" + placeholderExtension + "\n"); + file.close(); +} + bool ExcludedFiles::loadExcludeFile(const QByteArray & basePath, const QString & file) { QFile f(file); @@ -320,6 +352,10 @@ bool ExcludedFiles::loadExcludeFile(const QByteArray & basePath, const QString & QList patterns; while (!f.atEnd()) { QByteArray line = f.readLine().trimmed(); + if (line.startsWith("#!version")) { + if (!versionDirectiveKeepNextLine(line)) + f.readLine(); + } if (line.isEmpty() || line.startsWith('#')) continue; csync_exclude_expand_escapes(line); @@ -363,6 +399,32 @@ bool ExcludedFiles::reloadExcludeFiles() return success; } +bool ExcludedFiles::versionDirectiveKeepNextLine(const QByteArray &directive) const +{ + if (!directive.startsWith("#!version")) + return true; + QByteArrayList args = directive.split(' '); + if (args.size() != 3) + return true; + QByteArray op = args[1]; + QByteArrayList argVersions = args[2].split('.'); + if (argVersions.size() != 3) + return true; + + auto argVersion = std::make_tuple(argVersions[0].toInt(), argVersions[1].toInt(), argVersions[2].toInt()); + if (op == "<=") + return _clientVersion <= argVersion; + if (op == "<") + return _clientVersion < argVersion; + if (op == ">") + return _clientVersion > argVersion; + if (op == ">=") + return _clientVersion >= argVersion; + if (op == "==") + return _clientVersion == argVersion; + return true; +} + bool ExcludedFiles::isExcluded( const QString &filePath, const QString &basePath, diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index 8134d2384..4267aa14f 100644 --- a/src/csync/csync_exclude.h +++ b/src/csync/csync_exclude.h @@ -65,6 +65,8 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject { Q_OBJECT public: + typedef std::tuple Version; + ExcludedFiles(QString localPath = "/"); ~ExcludedFiles(); @@ -115,6 +117,11 @@ public: */ void setWildcardsMatchSlash(bool onoff); + /** + * Sets the client version, only used for testing. + */ + void setClientVersion(Version version); + /** * Generate a hook for traversal exclude pattern matching * that csync can use. @@ -125,6 +132,12 @@ public: auto csyncTraversalMatchFun() -> std::function; + /** + * Adds the exclude that skips placeholder files in older versions + * to the user exclude file. + */ + static void setupPlaceholderExclude(const QString &excludeFile, const QByteArray &placeholderExtension); + public slots: /** * Reloads the exclude patterns from the registered paths. @@ -136,6 +149,23 @@ public slots: bool loadExcludeFile(const QByteArray & basePath, const QString & file); private: + /** + * Returns true if the version directive indicates the next line + * should be skipped. + * + * A version directive has the form "#!version " + * where can be <, <=, ==, >, >= and can be any version + * like 2.5.0. + * + * Example: + * + * #!version < 2.5.0 + * myexclude + * + * Would enable the "myexclude" pattern only for versions before 2.5.0. + */ + bool versionDirectiveKeepNextLine(const QByteArray &directive) const; + /** * @brief Match the exclude pattern against the full path. * @@ -247,6 +277,12 @@ private: */ bool _wildcardsMatchSlash = false; + /** + * The client version. Used to evaluate version-dependent excludes, + * see versionDirectiveKeepNextLine(). + */ + Version _clientVersion; + friend class ExcludedFilesTest; }; diff --git a/src/gui/application.cpp b/src/gui/application.cpp index c4fb2009a..d7be07740 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -41,6 +41,7 @@ #include "owncloudsetupwizard.h" #include "version.h" +#include "csync_exclude.h" #include "config.h" @@ -191,6 +192,9 @@ Application::Application(int &argc, char **argv) if (!AbstractNetworkJob::httpTimeout) AbstractNetworkJob::httpTimeout = cfg.timeout(); + ExcludedFiles::setupPlaceholderExclude( + cfg.excludeFile(ConfigFile::UserScope), OWNCLOUD_PLACEHOLDER_SUFFIX); + _folderManager.reset(new FolderMan); connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); diff --git a/test/csync/csync_tests/check_csync_exclude.cpp b/test/csync/csync_tests/check_csync_exclude.cpp index 1a0dca0f1..fa62df538 100644 --- a/test/csync/csync_tests/check_csync_exclude.cpp +++ b/test/csync/csync_tests/check_csync_exclude.cpp @@ -23,6 +23,8 @@ #include #include +#include + #define CSYNC_TEST 1 #include "csync_exclude.cpp" @@ -694,6 +696,81 @@ static void check_csync_exclude_expand_escapes(void **state) assert_true(0 == strcmp(line.constData(), "\\")); } +static void check_placeholder_exclude(void **state) +{ + (void)state; + + auto readFile = [](const QString &file) { + QFile f(file); + f.open(QIODevice::ReadOnly | QIODevice::Text); + return f.readAll(); + }; + + QTemporaryDir tempDir; + QString path; + QByteArray expected = "\n#!version < 2.5.0\n*.owncloud\n"; + + // Case 1: No file exists yet, parent dirs are missing too + path = tempDir.filePath("foo/bar/exclude.lst"); + ExcludedFiles::setupPlaceholderExclude(path, ".owncloud"); + + assert_true(QFile::exists(path)); + assert_true(readFile(path) == expected); + + // Case 2: Running it again + ExcludedFiles::setupPlaceholderExclude(path, ".owncloud"); + assert_true(readFile(path) == expected); + + // Case 3: File exists, has some data + { + QFile f(path); + f.open(QIODevice::WriteOnly | QIODevice::Truncate); + f.write("# bla\nmyexclude\n\nanotherexclude"); + f.close(); + } + ExcludedFiles::setupPlaceholderExclude(path, ".owncloud"); + assert_true(readFile(path) == "# bla\nmyexclude\n\nanotherexclude" + expected); + + // Case 4: Running it again still does nothing + ExcludedFiles::setupPlaceholderExclude(path, ".owncloud"); + assert_true(readFile(path) == "# bla\nmyexclude\n\nanotherexclude" + expected); + + // Case 5: Verify that reading this file doesn't actually include the exclude + ExcludedFiles excludes; + excludes.addExcludeFilePath(path); + excludes.reloadExcludeFiles(); + assert_false(excludes._allExcludes.value("/").contains("*.owncloud")); + assert_true(excludes._allExcludes.value("/").contains("myexclude")); +} + +static void check_version_directive(void **state) +{ + (void)state; + + ExcludedFiles excludes; + excludes.setClientVersion(ExcludedFiles::Version(2, 5, 0)); + + std::vector> tests = { + { "#!version == 2.5.0", true }, + { "#!version == 2.6.0", false }, + { "#!version < 2.6.0", true }, + { "#!version <= 2.6.0", true }, + { "#!version > 2.6.0", false }, + { "#!version >= 2.6.0", false }, + { "#!version < 2.4.0", false }, + { "#!version <= 2.4.0", false }, + { "#!version > 2.4.0", true }, + { "#!version >= 2.4.0", true }, + { "#!version < 2.5.0", false }, + { "#!version <= 2.5.0", true }, + { "#!version > 2.5.0", false }, + { "#!version >= 2.5.0", true }, + }; + for (auto test : tests) { + assert_true(excludes.versionDirectiveKeepNextLine(test.first) == test.second); + } +} + }; // class ExcludedFilesTest int torture_run_tests(void) @@ -715,6 +792,8 @@ int torture_run_tests(void) cmocka_unit_test_setup_teardown(T::check_csync_is_windows_reserved_word, T::setup_init, T::teardown), cmocka_unit_test_setup_teardown(T::check_csync_excluded_performance, T::setup_init, T::teardown), cmocka_unit_test(T::check_csync_exclude_expand_escapes), + cmocka_unit_test(T::check_placeholder_exclude), + cmocka_unit_test(T::check_version_directive), }; return cmocka_run_group_tests(tests, nullptr, nullptr);