#include "csync_misc.h"
#include "common/utility.h"
+#include "../version.h"
#include <QString>
#include <QFileInfo>
+#include <QFile>
+#include <QDir>
/** Expands C-like escape sequences (in place)
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.
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);
QList<QByteArray> 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);
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,
{
Q_OBJECT
public:
+ typedef std::tuple<int, int, int> Version;
+
ExcludedFiles(QString localPath = "/");
~ExcludedFiles();
*/
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.
auto csyncTraversalMatchFun()
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>;
+ /**
+ * 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.
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 <op> <version>"
+ * where <op> can be <, <=, ==, >, >= and <version> 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.
*
*/
bool _wildcardsMatchSlash = false;
+ /**
+ * The client version. Used to evaluate version-dependent excludes,
+ * see versionDirectiveKeepNextLine().
+ */
+ Version _clientVersion;
+
friend class ExcludedFilesTest;
};
#include "owncloudsetupwizard.h"
#include "version.h"
+#include "csync_exclude.h"
#include "config.h"
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);
#include <sys/time.h>
#include <cstdio>
+#include <QTemporaryDir>
+
#define CSYNC_TEST 1
#include "csync_exclude.cpp"
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<std::pair<const char *, bool>> 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)
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);