Menu
Public repository / master
AkurAI-Drive / client/src/state.rs
Latest change feb7ac86b5e60d55b429eacb7146cf0dc32c6628 - Add native Linux sync client (akurai-drive-sync) by olibuijr
//! Local SQLite sync-state: the record of what each key looked like at the last
//! successful sync, used to detect changes on both sides.
use anyhow::{Context, Result};
use rusqlite::Connection;
use std::collections::BTreeMap;
use std::path::Path;
/// One persisted state row (plus mtime, used only for the local hashing
/// fast-path — not part of the change-decision identity).
#[derive(Debug, Clone)]
pub struct Record {
pub sha256: String,
pub etag: String,
pub size: u64,
pub mtime: i64,
}
pub struct State {
conn: Connection,
}
impl State {
pub fn open(path: &Path) -> Result<Self> {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir).with_context(|| format!("creating {}", dir.display()))?;
}
let conn = Connection::open(path)
.with_context(|| format!("opening state db {}", path.display()))?;
conn.execute_batch(
"PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS files (
key TEXT PRIMARY KEY,
sha256 TEXT NOT NULL,
etag TEXT NOT NULL,
size INTEGER NOT NULL,
mtime INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS meta (
k TEXT PRIMARY KEY,
v TEXT NOT NULL
);",
)
.context("initializing state schema")?;
Ok(Self { conn })
}
/// Load every tracked key into memory.
pub fn load(&self) -> Result<BTreeMap<String, Record>> {
let mut stmt = self
.conn
.prepare("SELECT key, sha256, etag, size, mtime FROM files")?;
let rows = stmt.query_map([], |row| {
Ok((
row.get::<_, String>(0)?,
Record {
sha256: row.get(1)?,
etag: row.get(2)?,
size: row.get::<_, i64>(3)? as u64,
mtime: row.get(4)?,
},
))
})?;
let mut map = BTreeMap::new();
for r in rows {
let (k, v) = r?;
map.insert(k, v);
}
Ok(map)
}
pub fn upsert(&self, key: &str, rec: &Record) -> Result<()> {
self.conn.execute(
"INSERT INTO files (key, sha256, etag, size, mtime)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(key) DO UPDATE SET
sha256 = excluded.sha256,
etag = excluded.etag,
size = excluded.size,
mtime = excluded.mtime",
rusqlite::params![key, rec.sha256, rec.etag, rec.size as i64, rec.mtime],
)?;
Ok(())
}
pub fn remove(&self, key: &str) -> Result<()> {
self.conn
.execute("DELETE FROM files WHERE key = ?1", [key])?;
Ok(())
}
pub fn meta_set(&self, k: &str, v: &str) -> Result<()> {
self.conn.execute(
"INSERT INTO meta (k, v) VALUES (?1, ?2)
ON CONFLICT(k) DO UPDATE SET v = excluded.v",
rusqlite::params![k, v],
)?;
Ok(())
}
pub fn meta_get(&self, k: &str) -> Result<Option<String>> {
let v = self
.conn
.query_row("SELECT v FROM meta WHERE k = ?1", [k], |row| row.get(0))
.ok();
Ok(v)
}
}