diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs index cfc26b5a8..88e7a76f5 100644 --- a/cli/src/cmd/add.rs +++ b/cli/src/cmd/add.rs @@ -38,7 +38,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { let t = command - .get_replica() + .get_replica()? .new_task(Status::Pending, self.description.clone()) .unwrap(); println!("added task {}", t.get_uuid()); diff --git a/cli/src/cmd/gc.rs b/cli/src/cmd/gc.rs index d8948a715..15bdd288b 100644 --- a/cli/src/cmd/gc.rs +++ b/cli/src/cmd/gc.rs @@ -20,7 +20,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - command.get_replica().gc()?; + command.get_replica()?.gc()?; println!("garbage collected."); Ok(()) } diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs index cdc39cc11..f55667dfb 100644 --- a/cli/src/cmd/info.rs +++ b/cli/src/cmd/info.rs @@ -30,7 +30,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let uuid = task.get_uuid(); diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index d29227c92..72ee206e0 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -23,7 +23,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); diff --git a/cli/src/cmd/modify.rs b/cli/src/cmd/modify.rs index 487084a53..662941e34 100644 --- a/cli/src/cmd/modify.rs +++ b/cli/src/cmd/modify.rs @@ -36,7 +36,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let mut task = task.into_mut(&mut replica); diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs index b55c044d0..f35756b8b 100644 --- a/cli/src/cmd/pending.rs +++ b/cli/src/cmd/pending.rs @@ -25,7 +25,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let working_set = command.get_replica().working_set().unwrap(); + let working_set = command.get_replica()?.working_set().unwrap(); let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 474bca280..bcd6fbf0b 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -2,7 +2,7 @@ use clap::Arg; use failure::{format_err, Fallible}; use std::env; use std::ffi::OsString; -use taskchampion::{server, taskstorage, Replica, Task, Uuid}; +use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("task") @@ -46,20 +46,29 @@ impl CommandInvocation { // -- utilities for command invocations - pub(super) fn get_replica(&self) -> Replica { + pub(super) fn get_replica(&self) -> Fallible { // temporarily use $TASK_DB to locate the taskdb let taskdb_dir = env::var_os("TASK_DB").unwrap_or_else(|| OsString::from("/tmp/tasks")); - Replica::new(Box::new(taskstorage::KVStorage::new(taskdb_dir).unwrap())) + let replica_config = ReplicaConfig { + taskdb_dir: taskdb_dir.into(), + }; + Ok(Replica::from_config(replica_config)?) } - pub(super) fn get_server(&self) -> Fallible { + pub(super) fn get_server(&self) -> Fallible> { // temporarily use $SYNC_SERVER_ORIGIN for the sync server - let sync_server_origin = env::var_os("SYNC_SERVER_ORIGIN") + let origin = env::var_os("SYNC_SERVER_ORIGIN") .map(|osstr| osstr.into_string().unwrap()) .unwrap_or_else(|| String::from("http://localhost:8080")); - Ok(server::RemoteServer::new( - sync_server_origin, - Uuid::parse_str("d5b55cbd-9a82-4860-9a39-41b67893b22f").unwrap(), - )) + let client_id = env::var_os("SYNC_SERVER_CLIENT_ID") + .ok_or_else(|| format_err!("SYNC_SERVER_CLIENT_ID not set"))?; + let client_id = client_id + .into_string() + .map_err(|_| format_err!("SYNC_SERVER_CLIENT_ID is not a valid UUID"))?; + let client_id = Uuid::parse_str(&client_id)?; + Ok(server::from_config(ServerConfig::Remote { + origin, + client_id, + })?) } } diff --git a/cli/src/cmd/sync.rs b/cli/src/cmd/sync.rs index a17920436..37c3a76d6 100644 --- a/cli/src/cmd/sync.rs +++ b/cli/src/cmd/sync.rs @@ -21,7 +21,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let mut server = command.get_server()?; replica.sync(&mut server)?; Ok(()) diff --git a/docs/src/usage.md b/docs/src/usage.md index b16c724e1..47ab9388b 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -12,6 +12,7 @@ Note that the `task` interface does not match that of TaskWarrior. Temporarily, configuration is by environment variables. The directory containing the replica's task data is given by `TASK_DB`, defaulting to `/tmp/tasks`. the origin of the sync server is given by `SYNC_SERVER_ORIGIN`, defaulting to `http://localhost:8080`. +The client ID to use with the sync server is givne by `SYNC_SERVER_CLIENT_ID` (with no default). ## `taskchampion-sync-server` diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs new file mode 100644 index 000000000..8a8ee5bcd --- /dev/null +++ b/taskchampion/src/config.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use uuid::Uuid; + +/// The configuration required for a replica. Use with [`crate::Replica::from_config`]. +pub struct ReplicaConfig { + /// Path containing the task DB. + pub taskdb_dir: PathBuf, +} + +/// The configuration for a replica's access to a sync server. Use with +/// [`crate::server::from_config`]. +pub enum ServerConfig { + /// A local task database, for situations with a single replica. + Local { + /// Path containing the server's DB + server_dir: PathBuf, + }, + /// A remote taskchampion-sync-server instance + Remote { + /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` + origin: String, + + /// Client ID to identify this replica to the server + client_id: Uuid, + }, +} diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 932dbdeef..aa2c8bf99 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -23,6 +23,7 @@ for more information about the design and usage of the tool. */ +mod config; mod errors; mod replica; pub mod server; @@ -31,6 +32,7 @@ mod taskdb; pub mod taskstorage; mod utils; +pub use config::{ReplicaConfig, ServerConfig}; pub use replica::Replica; pub use task::Priority; pub use task::Status; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index c33d31028..525be9888 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,8 +1,9 @@ +use crate::config::ReplicaConfig; use crate::errors::Error; use crate::server::Server; use crate::task::{Status, Task}; use crate::taskdb::TaskDB; -use crate::taskstorage::{Operation, TaskMap, TaskStorage}; +use crate::taskstorage::{KVStorage, Operation, TaskMap, TaskStorage}; use chrono::Utc; use failure::Fallible; use std::collections::HashMap; @@ -21,6 +22,11 @@ impl Replica { } } + pub fn from_config(config: ReplicaConfig) -> Fallible { + let storage = Box::new(KVStorage::new(config.taskdb_dir)?); + Ok(Replica::new(storage)) + } + #[cfg(test)] pub fn new_inmemory() -> Replica { Replica::new(Box::new(crate::taskstorage::InMemoryStorage::new())) @@ -140,7 +146,7 @@ impl Replica { } /// Synchronize this replica against the given server. - pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { self.taskdb.sync(server) } diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 89775acf1..9cc4b2b28 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -25,7 +25,7 @@ pub struct LocalServer<'t> { impl<'t> LocalServer<'t> { /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new(directory: &Path) -> Fallible { + pub fn new>(directory: P) -> Fallible> { let mut config = Config::default(directory); config.bucket("versions", None); config.bucket("numbers", None); diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 78c49c6f6..341428130 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,3 +1,6 @@ +use crate::ServerConfig; +use failure::Fallible; + #[cfg(test)] pub(crate) mod test; @@ -8,3 +11,12 @@ mod types; pub use local::LocalServer; pub use remote::RemoteServer; pub use types::*; + +pub fn from_config(config: ServerConfig) -> Fallible> { + Ok(match config { + ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), + ServerConfig::Remote { origin, client_id } => { + Box::new(RemoteServer::new(origin, client_id)) + } + }) +} diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index eb69e35ae..4cdb927ca 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -164,7 +164,7 @@ impl TaskDB { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between @@ -542,7 +542,7 @@ mod tests { #[test] fn test_sync() { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut db1 = newdb(); db1.sync(&mut server).unwrap(); @@ -602,7 +602,7 @@ mod tests { #[test] fn test_sync_create_delete() { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut db1 = newdb(); db1.sync(&mut server).unwrap(); @@ -692,7 +692,7 @@ mod tests { // and delete operations that results in a task existing in one TaskDB but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut dbs = [newdb(), newdb(), newdb()]; for (action, db) in action_sequence { diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 4ddd7df75..b5f94c8ff 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -2,11 +2,13 @@ use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; +#[cfg(test)] mod inmemory; mod kv; mod operation; pub use self::kv::KVStorage; +#[cfg(test)] pub use inmemory::InMemoryStorage; pub use operation::Operation;