Просмотр исходного кода

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.
Christian Kamm 7 лет назад
Родитель
Сommit
68126ac208
3 измененных файлов с 140 добавлено и 0 удалено
  1. 51 0
      src/common/syncjournaldb.cpp
  2. 33 0
      src/common/syncjournaldb.h
  3. 56 0
      test/testsyncjournaldb.cpp

+ 51 - 0
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<PinState>(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<int>(state));
+    query.exec();
+}
+
 void SyncJournalDb::commit(const QString &context, bool startTrans)
 {
     QMutexLocker lock(&_mutex);

+ 33 - 0
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.
      *

+ 56 - 0
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;
 };