From 9597080825565b2ba8ea202b7027e91b02aaac7f Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 30 Jan 2024 13:41:01 +0000 Subject: [PATCH] feat: add store push (#1649) * feat: add store push * only push for the current host unless specified * tidy up * tidy up some more * sort features --- Cargo.lock | 1 + atuin-client/src/record/mod.rs | 1 + atuin/Cargo.toml | 1 + atuin/src/command/client/history.rs | 6 +- atuin/src/command/client/store.rs | 60 ++++---------------- atuin/src/command/client/store/push.rs | 68 +++++++++++++++++++++++ atuin/src/command/client/store/rebuild.rs | 52 +++++++++++++++++ atuin/tests/common/mod.rs | 5 +- atuin/tests/sync.rs | 9 --- atuin/tests/users.rs | 12 +--- 10 files changed, 143 insertions(+), 72 deletions(-) create mode 100644 atuin/src/command/client/store/push.rs create mode 100644 atuin/src/command/client/store/rebuild.rs diff --git a/Cargo.lock b/Cargo.lock index 688862e9..e7c52eef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,7 @@ dependencies = [ "tracing-subscriber", "tracing-tree", "unicode-width", + "uuid", "whoami", ] diff --git a/atuin-client/src/record/mod.rs b/atuin-client/src/record/mod.rs index 8bc816ae..c40fd395 100644 --- a/atuin-client/src/record/mod.rs +++ b/atuin-client/src/record/mod.rs @@ -1,5 +1,6 @@ pub mod encryption; pub mod sqlite_store; pub mod store; + #[cfg(feature = "sync")] pub mod sync; diff --git a/atuin/Cargo.toml b/atuin/Cargo.toml index 5da8d149..7664db5d 100644 --- a/atuin/Cargo.toml +++ b/atuin/Cargo.toml @@ -76,6 +76,7 @@ colored = "2.0.4" ratatui = "0.25" tracing = "0.1" cli-clipboard = { version = "0.4.0", optional = true } +uuid = { workspace = true } [dependencies.tracing-subscriber] diff --git a/atuin/src/command/client/history.rs b/atuin/src/command/client/history.rs index 18ae17cf..29a0bdf3 100644 --- a/atuin/src/command/client/history.rs +++ b/atuin/src/command/client/history.rs @@ -13,7 +13,7 @@ use atuin_client::{ database::{current_context, Database}, encryption, history::{store::HistoryStore, History}, - record::{self, sqlite_store::SqliteStore}, + record::sqlite_store::SqliteStore, settings::{ FilterMode::{Directory, Global, Session}, Settings, @@ -21,7 +21,8 @@ use atuin_client::{ }; #[cfg(feature = "sync")] -use atuin_client::sync; +use atuin_client::{record, sync}; + use log::{debug, warn}; use time::{macros::format_description, OffsetDateTime}; @@ -278,6 +279,7 @@ impl Cmd { Ok(()) } + #[allow(unused_variables)] async fn handle_end( db: &impl Database, store: SqliteStore, diff --git a/atuin/src/command/client/store.rs b/atuin/src/command/client/store.rs index 640a284b..0d132c50 100644 --- a/atuin/src/command/client/store.rs +++ b/atuin/src/command/client/store.rs @@ -1,65 +1,26 @@ -use clap::{Args, Subcommand}; -use eyre::{bail, Result}; +use clap::Subcommand; +use eyre::Result; use atuin_client::{ database::Database, - encryption, - history::store::HistoryStore, record::{sqlite_store::SqliteStore, store::Store}, settings::Settings, }; use time::OffsetDateTime; -#[derive(Args, Debug)] -pub struct Rebuild { - pub tag: String, -} +#[cfg(feature = "sync")] +mod push; -impl Rebuild { - pub async fn run( - &self, - settings: &Settings, - store: SqliteStore, - database: &dyn Database, - ) -> Result<()> { - // keep it as a string and not an enum atm - // would be super cool to build this dynamically in the future - // eg register handles for rebuilding various tags without having to make this part of the - // binary big - match self.tag.as_str() { - "history" => { - self.rebuild_history(settings, store.clone(), database) - .await?; - } - - tag => bail!("unknown tag: {tag}"), - } - - Ok(()) - } - - async fn rebuild_history( - &self, - settings: &Settings, - store: SqliteStore, - database: &dyn Database, - ) -> Result<()> { - let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); - - let host_id = Settings::host_id().expect("failed to get host_id"); - let history_store = HistoryStore::new(store, host_id, encryption_key); - - history_store.build(database).await?; - - Ok(()) - } -} +mod rebuild; #[derive(Subcommand, Debug)] #[command(infer_subcommands = true)] pub enum Cmd { Status, - Rebuild(Rebuild), + Rebuild(rebuild::Rebuild), + + #[cfg(feature = "sync")] + Push(push::Push), } impl Cmd { @@ -72,6 +33,9 @@ impl Cmd { match self { Self::Status => self.status(store).await, Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await, + + #[cfg(feature = "sync")] + Self::Push(push) => push.run(settings, store).await, } } diff --git a/atuin/src/command/client/store/push.rs b/atuin/src/command/client/store/push.rs new file mode 100644 index 00000000..a5bd2dd0 --- /dev/null +++ b/atuin/src/command/client/store/push.rs @@ -0,0 +1,68 @@ +use atuin_common::record::HostId; +use clap::Args; +use eyre::Result; +use uuid::Uuid; + +use atuin_client::{ + record::sync::Operation, + record::{sqlite_store::SqliteStore, sync}, + settings::Settings, +}; + +#[derive(Args, Debug)] +pub struct Push { + /// The tag to push (eg, 'history'). Defaults to all tags + #[arg(long, short)] + pub tag: Option, + + /// The host to push, in the form of a UUID host ID. Defaults to the current host. + #[arg(long)] + pub host: Option, +} + +impl Push { + pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { + let host_id = Settings::host_id().expect("failed to get host_id"); + // We can actually just use the existing diff/etc to push + // 1. Diff + // 2. Get operations + // 3. Filter operations by + // a) are they an upload op? + // b) are they for the host/tag we are pushing here? + let (diff, _) = sync::diff(settings, &store).await?; + let operations = sync::operations(diff, &store).await?; + + let operations = operations + .into_iter() + .filter(|op| match op { + // No noops or downloads thx + Operation::Noop { .. } | Operation::Download { .. } => false, + + // push, so yes plz to uploads! + Operation::Upload { host, tag, .. } => { + if let Some(h) = self.host { + if HostId(h) != *host { + return false; + } + } else if *host != host_id { + return false; + } + + if let Some(t) = self.tag.clone() { + if t != *tag { + return false; + } + } + + true + } + }) + .collect(); + + let (uploaded, _) = sync::sync_remote(operations, &store, settings).await?; + + println!("Uploaded {uploaded} records"); + + Ok(()) + } +} diff --git a/atuin/src/command/client/store/rebuild.rs b/atuin/src/command/client/store/rebuild.rs new file mode 100644 index 00000000..880647b4 --- /dev/null +++ b/atuin/src/command/client/store/rebuild.rs @@ -0,0 +1,52 @@ +use clap::Args; +use eyre::{bail, Result}; + +use atuin_client::{ + database::Database, encryption, history::store::HistoryStore, + record::sqlite_store::SqliteStore, settings::Settings, +}; + +#[derive(Args, Debug)] +pub struct Rebuild { + pub tag: String, +} + +impl Rebuild { + pub async fn run( + &self, + settings: &Settings, + store: SqliteStore, + database: &dyn Database, + ) -> Result<()> { + // keep it as a string and not an enum atm + // would be super cool to build this dynamically in the future + // eg register handles for rebuilding various tags without having to make this part of the + // binary big + match self.tag.as_str() { + "history" => { + self.rebuild_history(settings, store.clone(), database) + .await?; + } + + tag => bail!("unknown tag: {tag}"), + } + + Ok(()) + } + + async fn rebuild_history( + &self, + settings: &Settings, + store: SqliteStore, + database: &dyn Database, + ) -> Result<()> { + let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); + + let host_id = Settings::host_id().expect("failed to get host_id"); + let history_store = HistoryStore::new(store, host_id, encryption_key); + + history_store.build(database).await?; + + Ok(()) + } +} diff --git a/atuin/tests/common/mod.rs b/atuin/tests/common/mod.rs index bda6abd3..65679244 100644 --- a/atuin/tests/common/mod.rs +++ b/atuin/tests/common/mod.rs @@ -1,11 +1,10 @@ use std::{env, time::Duration}; use atuin_client::api_client; -use atuin_common::{api::AddHistoryRequest, utils::uuid_v7}; +use atuin_common::utils::uuid_v7; use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings}; use atuin_server_postgres::{Postgres, PostgresSettings}; use futures_util::TryFutureExt; -use time::OffsetDateTime; use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tracing::{dispatcher, Dispatch}; use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; @@ -80,6 +79,7 @@ pub async fn register_inner<'a>( api_client::Client::new(address, ®istration_response.session, 5, 30).unwrap() } +#[allow(dead_code)] pub async fn login(address: &str, username: String, password: String) -> api_client::Client<'_> { // registration works let login_respose = api_client::login( @@ -92,6 +92,7 @@ pub async fn login(address: &str, username: String, password: String) -> api_cli api_client::Client::new(address, &login_respose.session, 5, 30).unwrap() } +#[allow(dead_code)] pub async fn register(address: &str) -> api_client::Client<'_> { let username = uuid_v7().as_simple().to_string(); let password = uuid_v7().as_simple().to_string(); diff --git a/atuin/tests/sync.rs b/atuin/tests/sync.rs index 1835b20b..7e25d1c2 100644 --- a/atuin/tests/sync.rs +++ b/atuin/tests/sync.rs @@ -1,14 +1,5 @@ -use std::{env, time::Duration}; - -use atuin_client::api_client; use atuin_common::{api::AddHistoryRequest, utils::uuid_v7}; -use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings}; -use atuin_server_postgres::{Postgres, PostgresSettings}; -use futures_util::TryFutureExt; use time::OffsetDateTime; -use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; -use tracing::{dispatcher, Dispatch}; -use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; mod common; diff --git a/atuin/tests/users.rs b/atuin/tests/users.rs index 8580d64c..95fb533b 100644 --- a/atuin/tests/users.rs +++ b/atuin/tests/users.rs @@ -1,14 +1,4 @@ -use std::{env, time::Duration}; - -use atuin_client::api_client; -use atuin_common::{api::AddHistoryRequest, utils::uuid_v7}; -use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings}; -use atuin_server_postgres::{Postgres, PostgresSettings}; -use futures_util::TryFutureExt; -use time::OffsetDateTime; -use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; -use tracing::{dispatcher, Dispatch}; -use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; +use atuin_common::utils::uuid_v7; mod common;