1
0

Add option to not create new clients

This commit is contained in:
Rokas Puzonas 2024-06-26 00:22:07 +03:00
parent 1ed97d6b83
commit ed9d885bed
7 changed files with 137 additions and 43 deletions

View File

@ -52,7 +52,9 @@ pub(crate) async fn service(
let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
// get, or create, the client // get, or create, the client
let client = match txn.get_client(client_id).map_err(failure_to_ise)? { 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, Some(client) => client,
None => { None => {
txn.new_client(client_id, NIL_VERSION_ID) txn.new_client(client_id, NIL_VERSION_ID)
@ -60,6 +62,12 @@ pub(crate) async fn service(
txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() 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( add_snapshot(
txn, txn,

View File

@ -60,7 +60,9 @@ pub(crate) async fn service(
let mut txn = server_state.storage.txn().map_err(failure_to_ise)?; let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
// get, or create, the client // get, or create, the client
let client = match txn.get_client(client_id).map_err(failure_to_ise)? { 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, Some(client) => client,
None => { None => {
txn.new_client(client_id, NIL_VERSION_ID) txn.new_client(client_id, NIL_VERSION_ID)
@ -68,6 +70,12 @@ pub(crate) async fn service(
txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() 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( let (result, snap_urgency) = add_version(
txn, txn,

View File

@ -2,49 +2,88 @@
use actix_web::{middleware::Logger, App, HttpServer}; use actix_web::{middleware::Logger, App, HttpServer};
use clap::{arg, builder::ValueParser, value_parser, Command}; use clap::{arg, builder::ValueParser, value_parser, Command};
use uuid::Uuid;
use std::ffi::OsString; 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}; use taskchampion_sync_server::{Server, ServerConfig};
#[actix_web::main] #[actix_web::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
env_logger::init(); env_logger::init();
let defaults = ServerConfig::default(); 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 <PATH> "Config file")
.default_value(PathBuf::from(&defaults.data_dir).join("config").to_string_lossy().to_string());
let matches = Command::new("taskchampion-sync-server") let matches = Command::new("taskchampion-sync-server")
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.about("Server for TaskChampion") .about("Server for TaskChampion")
.arg( .arg(&config_arg)
arg!(-p --port <PORT> "Port on which to serve")
.help("Port on which to serve")
.value_parser(value_parser!(usize))
.default_value("8080"),
)
.arg( .arg(
arg!(-d --"data-dir" <DIR> "Directory in which to store data") arg!(-d --"data-dir" <DIR> "Directory in which to store data")
.value_parser(ValueParser::os_string()) .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!(<CLIENT_ID>)))
.subcommand(Command::new("remove").arg(arg!(<CLIENT_ID>)))
)
.arg(
arg!(-p --port <PORT> "Port on which to serve")
.value_parser(value_parser!(u16))
.default_value(defaults.port.to_string()),
) )
.arg( .arg(
arg!(--"snapshot-versions" <NUM> "Target number of versions between snapshots") arg!(--"snapshot-versions" <NUM> "Target number of versions between snapshots")
.value_parser(value_parser!(u32)) .value_parser(value_parser!(u32))
.default_value(default_snapshot_versions), .default_value(defaults.snapshot_versions.to_string()),
) )
.arg( .arg(
arg!(--"snapshot-days" <NUM> "Target number of days between snapshots") arg!(--"snapshot-days" <NUM> "Target number of days between snapshots")
.value_parser(value_parser!(i64)) .value_parser(value_parser!(i64))
.default_value(default_snapshot_days), .default_value(defaults.snapshot_days.to_string()),
) )
.get_matches(); .get_matches();
let data_dir: &OsString = matches.get_one("data-dir").unwrap(); let data_dir: &OsString = matches.get_one("data-dir").unwrap();
let port: usize = *matches.get_one("port").unwrap(); // TODO: let config_path: &String = matches.get_one("config").unwrap();
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::<String>("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_versions: u32 = *matches.get_one("snapshot-versions").unwrap();
let snapshot_days: i64 = *matches.get_one("snapshot-days").unwrap(); let snapshot_days: i64 = *matches.get_one("snapshot-days").unwrap();
let config = ServerConfig::from_args(snapshot_days, snapshot_versions)?; let data_dir_owned = data_dir.to_string_lossy().into();
let server = Server::new(config, Box::new(SqliteStorage::new(data_dir)?)); 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); log::warn!("Serving on port {}", port);
HttpServer::new(move || { HttpServer::new(move || {
@ -55,6 +94,10 @@ async fn main() -> anyhow::Result<()> {
.bind(format!("0.0.0.0:{}", port))? .bind(format!("0.0.0.0:{}", port))?
.run() .run()
.await?; .await?;
},
_ => unreachable!()
};
Ok(()) Ok(())
} }

View File

@ -24,6 +24,12 @@ pub struct ServerConfig {
/// Target number of versions between snapshots. /// Target number of versions between snapshots.
pub snapshot_versions: u32, pub snapshot_versions: u32,
pub port: u16,
pub data_dir: String,
pub create_client_on_request: bool,
} }
impl Default for ServerConfig { impl Default for ServerConfig {
@ -31,15 +37,21 @@ impl Default for ServerConfig {
ServerConfig { ServerConfig {
snapshot_days: 14, snapshot_days: 14,
snapshot_versions: 100, snapshot_versions: 100,
port: 8080,
data_dir: String::from("/var/lib/taskchampion-sync-server"),
create_client_on_request: true
} }
} }
} }
impl ServerConfig { impl ServerConfig {
pub fn from_args(snapshot_days: i64, snapshot_versions: u32) -> anyhow::Result<ServerConfig> { pub fn from_args(snapshot_days: i64, snapshot_versions: u32, port: u16, data_dir: String) -> anyhow::Result<ServerConfig> {
Ok(ServerConfig { Ok(ServerConfig {
snapshot_days, snapshot_days,
snapshot_versions, snapshot_versions,
port,
data_dir,
create_client_on_request: false
}) })
} }
} }

View File

@ -46,6 +46,10 @@ impl<'a> StorageTxn for InnerTxn<'a> {
Ok(self.0.clients.get(&client_id).cloned()) Ok(self.0.clients.get(&client_id).cloned())
} }
fn list_clients(&mut self) -> anyhow::Result<Vec<Uuid>> {
Ok(self.0.clients.keys().map(|key| key.clone()).collect())
}
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> { fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> {
if self.0.clients.get(&client_id).is_some() { if self.0.clients.get(&client_id).is_some() {
return Err(anyhow::anyhow!("Client {} already exists", client_id)); return Err(anyhow::anyhow!("Client {} already exists", client_id));

View File

@ -41,6 +41,9 @@ pub trait StorageTxn {
/// Get information about the given client /// Get information about the given client
fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>>; fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>>;
/// Get information about the given client
fn list_clients(&mut self) -> anyhow::Result<Vec<Uuid>>;
/// Create a new client with the given latest_version_id /// Create a new client with the given latest_version_id
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>; fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>;

View File

@ -166,6 +166,22 @@ impl StorageTxn for Txn {
Ok(result) Ok(result)
} }
fn list_clients(&mut self) -> anyhow::Result<Vec<Uuid>> {
let t = self.get_txn()?;
let mut stmt = t.prepare("SELECT client_id FROM clients")?;
let rows = stmt
.query_map([], |r| r.get::<usize, StoredUuid>(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<()> { fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> {
let t = self.get_txn()?; let t = self.get_txn()?;