From ed9d885bedede0d357335c0b6008b99a19298fb5 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 26 Jun 2024 00:22:07 +0300 Subject: [PATCH] Add option to not create new clients --- src/api/add_snapshot.rs | 24 +++++--- src/api/add_version.rs | 24 +++++--- src/bin/taskchampion-sync-server.rs | 95 +++++++++++++++++++++-------- src/server.rs | 14 ++++- src/storage/inmemory.rs | 4 ++ src/storage/mod.rs | 3 + src/storage/sqlite.rs | 16 +++++ 7 files changed, 137 insertions(+), 43 deletions(-) diff --git a/src/api/add_snapshot.rs b/src/api/add_snapshot.rs index d92174c..c6ff011 100644 --- a/src/api/add_snapshot.rs +++ b/src/api/add_snapshot.rs @@ -52,14 +52,22 @@ pub(crate) async fn service( let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; // get, or create, the client - let client = match txn.get_client(client_id).map_err(failure_to_ise)? { - Some(client) => client, - None => { - txn.new_client(client_id, NIL_VERSION_ID) - .map_err(failure_to_ise)?; - txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() - } - }; + let client; + if server_state.config.create_client_on_request { + client = match txn.get_client(client_id).map_err(failure_to_ise)? { + Some(client) => client, + None => { + txn.new_client(client_id, NIL_VERSION_ID) + .map_err(failure_to_ise)?; + txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() + } + }; + } else { + client = txn + .get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + } add_snapshot( txn, diff --git a/src/api/add_version.rs b/src/api/add_version.rs index 7428d94..82359a4 100644 --- a/src/api/add_version.rs +++ b/src/api/add_version.rs @@ -60,14 +60,22 @@ pub(crate) async fn service( let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; // get, or create, the client - let client = match txn.get_client(client_id).map_err(failure_to_ise)? { - Some(client) => client, - None => { - txn.new_client(client_id, NIL_VERSION_ID) - .map_err(failure_to_ise)?; - txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() - } - }; + let client; + if server_state.config.create_client_on_request { + client = match txn.get_client(client_id).map_err(failure_to_ise)? { + Some(client) => client, + None => { + txn.new_client(client_id, NIL_VERSION_ID) + .map_err(failure_to_ise)?; + txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() + } + }; + } else { + client = txn + .get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + } let (result, snap_urgency) = add_version( txn, diff --git a/src/bin/taskchampion-sync-server.rs b/src/bin/taskchampion-sync-server.rs index 64210e4..f99304c 100644 --- a/src/bin/taskchampion-sync-server.rs +++ b/src/bin/taskchampion-sync-server.rs @@ -2,59 +2,102 @@ use actix_web::{middleware::Logger, App, HttpServer}; use clap::{arg, builder::ValueParser, value_parser, Command}; +use uuid::Uuid; use std::ffi::OsString; -use taskchampion_sync_server::storage::SqliteStorage; +use std::path::{Path, PathBuf}; +use taskchampion_sync_server::storage::{SqliteStorage, Storage}; use taskchampion_sync_server::{Server, ServerConfig}; #[actix_web::main] async fn main() -> anyhow::Result<()> { env_logger::init(); let defaults = ServerConfig::default(); - let default_snapshot_versions = defaults.snapshot_versions.to_string(); - let default_snapshot_days = defaults.snapshot_days.to_string(); + + let config_arg = arg!(-c --config "Config file") + .default_value(PathBuf::from(&defaults.data_dir).join("config").to_string_lossy().to_string()); + let matches = Command::new("taskchampion-sync-server") .version(env!("CARGO_PKG_VERSION")) .about("Server for TaskChampion") - .arg( - arg!(-p --port "Port on which to serve") - .help("Port on which to serve") - .value_parser(value_parser!(usize)) - .default_value("8080"), - ) + .arg(&config_arg) .arg( arg!(-d --"data-dir" "Directory in which to store data") .value_parser(ValueParser::os_string()) - .default_value("/var/lib/taskchampion-sync-server"), + .default_value(&defaults.data_dir), + ) + .subcommand( + Command::new("clients") + .subcommand(Command::new("list")) + .subcommand(Command::new("add").arg(arg!())) + .subcommand(Command::new("remove").arg(arg!())) + ) + .arg( + arg!(-p --port "Port on which to serve") + .value_parser(value_parser!(u16)) + .default_value(defaults.port.to_string()), ) .arg( arg!(--"snapshot-versions" "Target number of versions between snapshots") .value_parser(value_parser!(u32)) - .default_value(default_snapshot_versions), + .default_value(defaults.snapshot_versions.to_string()), ) .arg( arg!(--"snapshot-days" "Target number of days between snapshots") .value_parser(value_parser!(i64)) - .default_value(default_snapshot_days), + .default_value(defaults.snapshot_days.to_string()), ) .get_matches(); let data_dir: &OsString = matches.get_one("data-dir").unwrap(); - let port: usize = *matches.get_one("port").unwrap(); - let snapshot_versions: u32 = *matches.get_one("snapshot-versions").unwrap(); - let snapshot_days: i64 = *matches.get_one("snapshot-days").unwrap(); + // TODO: let config_path: &String = matches.get_one("config").unwrap(); - let config = ServerConfig::from_args(snapshot_days, snapshot_versions)?; - let server = Server::new(config, Box::new(SqliteStorage::new(data_dir)?)); + let storage = SqliteStorage::new(data_dir)?; + + match matches.subcommand() { + Some(("clients", sub_matches)) => { + + match sub_matches.subcommand() { + Some(("add", sub_matches)) => { + let client_id = sub_matches.get_one::("CLIENT_ID").unwrap(); + let client_id = Uuid::parse_str(client_id)?; + let mut t = storage.txn()?; + t.new_client(client_id, Uuid::nil())? + }, + Some(("remove", _sub_matches)) => { + todo!() + }, + _ => { + let mut t = storage.txn()?; + for client in t.list_clients()? { + print!("{client}\n"); + } + } + } + }, + None => { + let port: u16 = *matches.get_one("port").unwrap(); + let snapshot_versions: u32 = *matches.get_one("snapshot-versions").unwrap(); + let snapshot_days: i64 = *matches.get_one("snapshot-days").unwrap(); + + let data_dir_owned = data_dir.to_string_lossy().into(); + let mut config = ServerConfig::from_args(snapshot_days, snapshot_versions, port, data_dir_owned)?; + config.create_client_on_request = false; + + let server = Server::new(config, Box::new(storage)); + + log::warn!("Serving on port {}", port); + HttpServer::new(move || { + App::new() + .wrap(Logger::default()) + .configure(|cfg| server.config(cfg)) + }) + .bind(format!("0.0.0.0:{}", port))? + .run() + .await?; + }, + _ => unreachable!() + }; - log::warn!("Serving on port {}", port); - HttpServer::new(move || { - App::new() - .wrap(Logger::default()) - .configure(|cfg| server.config(cfg)) - }) - .bind(format!("0.0.0.0:{}", port))? - .run() - .await?; Ok(()) } diff --git a/src/server.rs b/src/server.rs index 61e349e..01dcc73 100644 --- a/src/server.rs +++ b/src/server.rs @@ -24,6 +24,12 @@ pub struct ServerConfig { /// Target number of versions between snapshots. pub snapshot_versions: u32, + + pub port: u16, + + pub data_dir: String, + + pub create_client_on_request: bool, } impl Default for ServerConfig { @@ -31,15 +37,21 @@ impl Default for ServerConfig { ServerConfig { snapshot_days: 14, snapshot_versions: 100, + port: 8080, + data_dir: String::from("/var/lib/taskchampion-sync-server"), + create_client_on_request: true } } } impl ServerConfig { - pub fn from_args(snapshot_days: i64, snapshot_versions: u32) -> anyhow::Result { + pub fn from_args(snapshot_days: i64, snapshot_versions: u32, port: u16, data_dir: String) -> anyhow::Result { Ok(ServerConfig { snapshot_days, snapshot_versions, + port, + data_dir, + create_client_on_request: false }) } } diff --git a/src/storage/inmemory.rs b/src/storage/inmemory.rs index 3b17720..1fe8a56 100644 --- a/src/storage/inmemory.rs +++ b/src/storage/inmemory.rs @@ -46,6 +46,10 @@ impl<'a> StorageTxn for InnerTxn<'a> { Ok(self.0.clients.get(&client_id).cloned()) } + fn list_clients(&mut self) -> anyhow::Result> { + Ok(self.0.clients.keys().map(|key| key.clone()).collect()) + } + fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { if self.0.clients.get(&client_id).is_some() { return Err(anyhow::anyhow!("Client {} already exists", client_id)); diff --git a/src/storage/mod.rs b/src/storage/mod.rs index a2e468f..276cfce 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -41,6 +41,9 @@ pub trait StorageTxn { /// Get information about the given client fn get_client(&mut self, client_id: Uuid) -> anyhow::Result>; + /// Get information about the given client + fn list_clients(&mut self) -> anyhow::Result>; + /// Create a new client with the given latest_version_id fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>; diff --git a/src/storage/sqlite.rs b/src/storage/sqlite.rs index a8571bb..63d4439 100644 --- a/src/storage/sqlite.rs +++ b/src/storage/sqlite.rs @@ -166,6 +166,22 @@ impl StorageTxn for Txn { Ok(result) } + fn list_clients(&mut self) -> anyhow::Result> { + let t = self.get_txn()?; + + let mut stmt = t.prepare("SELECT client_id FROM clients")?; + let rows = stmt + .query_map([], |r| r.get::(0)) + .context("Error getting clients")?; + + let mut result = Vec::new(); + for client_id_result in rows { + result.push(client_id_result?.0); + } + + Ok(result) + } + fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { let t = self.get_txn()?;