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,"
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);
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
*
*/
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.
SqlQuery _getConflictRecordQuery;
SqlQuery _setConflictRecordQuery;
SqlQuery _deleteConflictRecordQuery;
+ SqlQuery _getPinStateQuery;
+ SqlQuery _setPinStateQuery;
/* Storing etags to these folders, or their parent folders, is filtered out.
*
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;
};