From: Christian Kamm Date: Mon, 26 Nov 2018 12:40:51 +0000 (+0100) Subject: vfs: Introduce PinState db storage #6815 X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~21^2~468^2~359 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=68126ac208cf3c07057396fff0a027c15fe49ec8;p=nextcloud-desktop.git vfs: Introduce PinState db storage #6815 The idea is to allow folders (and later maybe files?) to be - pinned to be available locally - pinned to be online only - inherit their pin from the parent Where this pinning only controls the default for new files. Subfolders may have a different pin state, and contained files may be hydrated or dehydrated based on user actions. This value is stored in a new 'flags' table. The idea is to store data there that doesn't necessarily exist for each metadata entry. The selective sync state could be migrated to this table. --- diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 7fff8467f..7ccb38c79 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -465,6 +465,15 @@ bool SyncJournalDb::checkConnect() return sqlFail("Create table datafingerprint", createQuery); } + // create the flags table. + createQuery.prepare("CREATE TABLE IF NOT EXISTS flags (" + "path TEXT PRIMARY KEY," + "pinState INTEGER" + ");"); + if (!createQuery.exec()) { + return sqlFail("Create table flags", createQuery); + } + // create the conflicts table. createQuery.prepare("CREATE TABLE IF NOT EXISTS conflicts(" "path TEXT PRIMARY KEY," @@ -2062,6 +2071,48 @@ void SyncJournalDb::markVirtualFileForDownloadRecursively(const QByteArray &path query.exec(); } +PinState SyncJournalDb::pinStateForPath(const QByteArray &path) +{ + QMutexLocker lock(&_mutex); + if (!checkConnect()) + return PinState::Unspecified; + + auto &query = _getPinStateQuery; + ASSERT(query.initOrReset(QByteArrayLiteral( + "SELECT pinState FROM flags WHERE" + " " IS_PREFIX_PATH_OR_EQUAL("path", "?1") + " AND pinState is not null AND pinState != 0" + " ORDER BY length(path) DESC;"), + _db)); + query.bindValue(1, path); + query.exec(); + + if (!query.next()) + return PinState::Unspecified; + + return static_cast(query.intValue(0)); +} + +void SyncJournalDb::setPinStateForPath(const QByteArray &path, PinState state) +{ + QMutexLocker lock(&_mutex); + if (!checkConnect()) + return; + + auto &query = _setPinStateQuery; + ASSERT(query.initOrReset(QByteArrayLiteral( + // If we had sqlite >=3.24.0 everywhere this could be an upsert, + // making further flags columns easy + //"INSERT INTO flags(path, pinState) VALUES(?1, ?2)" + //" ON CONFLICT(path) DO UPDATE SET pinState=?2;"), + // Simple version that doesn't work nicely with multiple columns: + "INSERT OR REPLACE INTO flags(path, pinState) VALUES(?1, ?2);"), + _db)); + query.bindValue(1, path); + query.bindValue(2, static_cast(state)); + query.exec(); +} + void SyncJournalDb::commit(const QString &context, bool startTrans) { QMutexLocker lock(&_mutex); diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index 01e66734b..ddf5a1989 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -32,6 +32,24 @@ namespace OCC { class SyncJournalFileRecord; +/** Determines whether files should be available locally or not + * + * For new remote files the file's PinState is calculated by looking for + * the closest parent folder that isn't Unspecified. + * + * TODO: It seems to make sense to also store per-file PinStates. + * Maybe these could communicate intent, similar to ItemTypeVirtualFileDownload + * and ...FileDehydrate? + */ +enum class PinState { + /// Inherit the PinState of the parent directory (default) + Unspecified = 0, + /// Download file and keep it updated. + AlwaysLocal = 1, + /// File shall be virtual locally. + OnlineOnly = 2, +}; + /** * @brief Class that handles the sync database * @@ -242,6 +260,19 @@ public: */ void markVirtualFileForDownloadRecursively(const QByteArray &path); + /** + * Gets the PinState for the path. + * + * If the exact path has no entry or has an unspecified state, + * the state is inherited through the parent. + */ + PinState pinStateForPath(const QByteArray &path); + + /** + * Sets a path's pin state. + */ + void setPinStateForPath(const QByteArray &path, PinState state); + /** * Only used for auto-test: * when positive, will decrease the counter for every database operation. @@ -306,6 +337,8 @@ private: SqlQuery _getConflictRecordQuery; SqlQuery _setConflictRecordQuery; SqlQuery _deleteConflictRecordQuery; + SqlQuery _getPinStateQuery; + SqlQuery _setPinStateQuery; /* Storing etags to these folders, or their parent folders, is filtered out. * diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index 8d4cb0890..cd42f0f4b 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -320,6 +320,62 @@ private slots: QVERIFY(checkElements()); } + void testPinState() + { + auto make = [&](const QByteArray &path, PinState state) { + _db.setPinStateForPath(path, state); + }; + auto get = [&](const QByteArray &path) { + return _db.pinStateForPath(path); + }; + + // Make a thrice-nested setup + make("local", PinState::AlwaysLocal); + make("online", PinState::OnlineOnly); + make("unspec", PinState::Unspecified); + for (auto base : {"local/", "online/", "unspec/"}) { + make(QByteArray(base) + "unspec", PinState::Unspecified); + make(QByteArray(base) + "local", PinState::AlwaysLocal); + make(QByteArray(base) + "online", PinState::OnlineOnly); + + for (auto base2 : {"local/", "online/", "unspec/"}) { + make(QByteArray(base) + base2 + "/unspec", PinState::Unspecified); + make(QByteArray(base) + base2 + "/local", PinState::AlwaysLocal); + make(QByteArray(base) + base2 + "/online", PinState::OnlineOnly); + } + } + + // Baseline direct checks + QCOMPARE(get("local"), PinState::AlwaysLocal); + QCOMPARE(get("online"), PinState::OnlineOnly); + QCOMPARE(get("unspec"), PinState::Unspecified); + QCOMPARE(get("nonexistant"), PinState::Unspecified); + QCOMPARE(get("online/local"), PinState::AlwaysLocal); + QCOMPARE(get("local/online"), PinState::OnlineOnly); + QCOMPARE(get("unspec/local"), PinState::AlwaysLocal); + QCOMPARE(get("unspec/online"), PinState::OnlineOnly); + QCOMPARE(get("unspec/unspec"), PinState::Unspecified); + QCOMPARE(get("unspec/nonexistant"), PinState::Unspecified); + + // Inheriting checks, level 1 + QCOMPARE(get("local/unspec"), PinState::AlwaysLocal); + QCOMPARE(get("local/nonexistant"), PinState::AlwaysLocal); + QCOMPARE(get("online/unspec"), PinState::OnlineOnly); + QCOMPARE(get("online/nonexistant"), PinState::OnlineOnly); + + // Inheriting checks, level 2 + QCOMPARE(get("local/unspec/unspec"), PinState::AlwaysLocal); + QCOMPARE(get("local/local/unspec"), PinState::AlwaysLocal); + QCOMPARE(get("local/local/nonexistant"), PinState::AlwaysLocal); + QCOMPARE(get("local/online/unspec"), PinState::OnlineOnly); + QCOMPARE(get("local/online/nonexistant"), PinState::OnlineOnly); + QCOMPARE(get("online/unspec/unspec"), PinState::OnlineOnly); + QCOMPARE(get("online/local/unspec"), PinState::AlwaysLocal); + QCOMPARE(get("online/local/nonexistant"), PinState::AlwaysLocal); + QCOMPARE(get("online/online/unspec"), PinState::OnlineOnly); + QCOMPARE(get("online/online/nonexistant"), PinState::OnlineOnly); + } + private: SyncJournalDb _db; };