mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
WIP
This commit is contained in:
parent
027225d2a3
commit
991b29da6c
2 changed files with 57 additions and 62 deletions
|
@ -3,7 +3,6 @@ use anyhow::Context;
|
||||||
use rusqlite::types::{FromSql, ToSql};
|
use rusqlite::types::{FromSql, ToSql};
|
||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum SqliteError {
|
enum SqliteError {
|
||||||
|
|
|
@ -1,12 +1,34 @@
|
||||||
|
use rusqlite::params;
|
||||||
|
use anyhow::Context;
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID,
|
AddVersionResult, GetVersionResult, HistorySegment, Server, VersionId, NO_VERSION_ID,
|
||||||
};
|
};
|
||||||
use crate::utils::Key;
|
|
||||||
use kv::msgpack::Msgpack;
|
|
||||||
use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use rusqlite::types::{FromSql, ToSql};
|
||||||
|
use rusqlite::OptionalExtension;
|
||||||
|
|
||||||
|
// FIXME: Duplicated
|
||||||
|
/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid`
|
||||||
|
pub struct StoredUuid(Uuid);
|
||||||
|
|
||||||
|
/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob)
|
||||||
|
impl FromSql for StoredUuid {
|
||||||
|
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
|
||||||
|
let u = Uuid::parse_str(value.as_str()?)
|
||||||
|
.map_err(|_| rusqlite::types::FromSqlError::InvalidType)?;
|
||||||
|
Ok(StoredUuid(u))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store Uuid as string in database
|
||||||
|
impl ToSql for StoredUuid {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||||
|
let s = self.0.to_string();
|
||||||
|
Ok(s.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
struct Version {
|
struct Version {
|
||||||
|
@ -15,58 +37,48 @@ struct Version {
|
||||||
history_segment: HistorySegment,
|
history_segment: HistorySegment,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocalServer<'t> {
|
pub struct LocalServer {
|
||||||
store: Store,
|
con: rusqlite::Connection,
|
||||||
// NOTE: indexed by parent_version_id!
|
|
||||||
versions_bucket: Bucket<'t, Key, ValueBuf<Msgpack<Version>>>,
|
|
||||||
latest_version_bucket: Bucket<'t, Integer, ValueBuf<Msgpack<Uuid>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> LocalServer<'t> {
|
impl LocalServer {
|
||||||
/// A test server has no notion of clients, signatures, encryption, etc.
|
fn txn<'a>(&'a mut self) -> anyhow::Result<rusqlite::Transaction> {
|
||||||
pub fn new<P: AsRef<Path>>(directory: P) -> anyhow::Result<LocalServer<'t>> {
|
let txn = self.con.transaction()?;
|
||||||
let mut config = Config::default(directory);
|
Ok(txn)
|
||||||
config.bucket("versions", None);
|
}
|
||||||
config.bucket("numbers", None);
|
|
||||||
config.bucket("latest_version", None);
|
|
||||||
config.bucket("operations", None);
|
|
||||||
config.bucket("working_set", None);
|
|
||||||
let store = Store::new(config)?;
|
|
||||||
|
|
||||||
// versions are stored indexed by VersionId (uuid)
|
/// A server which has no notion of clients, signatures, encryption, etc.
|
||||||
let versions_bucket = store.bucket::<Key, ValueBuf<Msgpack<Version>>>(Some("versions"))?;
|
pub fn new<P: AsRef<Path>>(directory: P) -> anyhow::Result<LocalServer> {
|
||||||
|
let db_file = directory.as_ref().join("taskchampion-local-sync-server.sqlite3");
|
||||||
|
let con = rusqlite::Connection::open(&db_file)?;
|
||||||
|
|
||||||
// this bucket contains the latest version at key 0
|
let queries = vec![
|
||||||
let latest_version_bucket =
|
"CREATE TABLE IF NOT EXISTS data (key STRING PRIMARY KEY, value STRING);",
|
||||||
store.int_bucket::<ValueBuf<Msgpack<Uuid>>>(Some("latest_version"))?;
|
];
|
||||||
|
for q in queries {
|
||||||
|
con.execute(q, []).context("Creating table")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(LocalServer {
|
Ok(LocalServer {
|
||||||
store,
|
con,
|
||||||
versions_bucket,
|
|
||||||
latest_version_bucket,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_latest_version_id(&mut self) -> anyhow::Result<VersionId> {
|
fn get_latest_version_id(&mut self) -> anyhow::Result<VersionId> {
|
||||||
let txn = self.store.read_txn()?;
|
let t = self.txn()?;
|
||||||
let base_version = match txn.get(&self.latest_version_bucket, 0.into()) {
|
let result: Option<StoredUuid> = t.query_row("SELECT value FROM data WHERE key = latest_version_id LIMIT 1", rusqlite::params![], |r| r.get(0)).optional()?;
|
||||||
Ok(buf) => buf,
|
Ok(result.map(|x| x.0).unwrap_or(NO_VERSION_ID))
|
||||||
Err(Error::NotFound) => return Ok(NO_VERSION_ID),
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
}
|
|
||||||
.inner()?
|
|
||||||
.to_serde();
|
|
||||||
Ok(base_version as VersionId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> {
|
fn set_latest_version_id(&mut self, version_id: VersionId) -> anyhow::Result<()> {
|
||||||
let mut txn = self.store.write_txn()?;
|
|
||||||
txn.set(
|
let t = self.txn()?;
|
||||||
&self.latest_version_bucket,
|
t.execute(
|
||||||
0.into(),
|
"INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)",
|
||||||
Msgpack::to_value_buf(version_id as Uuid)?,
|
params![&StoredUuid(version_id)],
|
||||||
)?;
|
)
|
||||||
txn.commit()?;
|
.context("Update task query")?;
|
||||||
|
t.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,31 +86,15 @@ impl<'t> LocalServer<'t> {
|
||||||
&mut self,
|
&mut self,
|
||||||
parent_version_id: VersionId,
|
parent_version_id: VersionId,
|
||||||
) -> anyhow::Result<Option<Version>> {
|
) -> anyhow::Result<Option<Version>> {
|
||||||
let txn = self.store.read_txn()?;
|
todo!()
|
||||||
|
|
||||||
let version = match txn.get(&self.versions_bucket, parent_version_id.into()) {
|
|
||||||
Ok(buf) => buf,
|
|
||||||
Err(Error::NotFound) => return Ok(None),
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
}
|
|
||||||
.inner()?
|
|
||||||
.to_serde();
|
|
||||||
Ok(Some(version))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_version_by_parent_version_id(&mut self, version: Version) -> anyhow::Result<()> {
|
fn add_version_by_parent_version_id(&mut self, version: Version) -> anyhow::Result<()> {
|
||||||
let mut txn = self.store.write_txn()?;
|
todo!()
|
||||||
txn.set(
|
|
||||||
&self.versions_bucket,
|
|
||||||
version.parent_version_id.into(),
|
|
||||||
Msgpack::to_value_buf(version)?,
|
|
||||||
)?;
|
|
||||||
txn.commit()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Server for LocalServer<'t> {
|
impl Server for LocalServer {
|
||||||
// TODO: better transaction isolation for add_version (gets and sets should be in the same
|
// TODO: better transaction isolation for add_version (gets and sets should be in the same
|
||||||
// transaction)
|
// transaction)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue