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
This commit is contained in:
Ellie Huxtable 2024-01-30 13:41:01 +00:00 committed by GitHub
parent 366b8ea97b
commit 9597080825
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 143 additions and 72 deletions

1
Cargo.lock generated
View File

@ -202,6 +202,7 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
"tracing-tree", "tracing-tree",
"unicode-width", "unicode-width",
"uuid",
"whoami", "whoami",
] ]

View File

@ -1,5 +1,6 @@
pub mod encryption; pub mod encryption;
pub mod sqlite_store; pub mod sqlite_store;
pub mod store; pub mod store;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub mod sync; pub mod sync;

View File

@ -76,6 +76,7 @@ colored = "2.0.4"
ratatui = "0.25" ratatui = "0.25"
tracing = "0.1" tracing = "0.1"
cli-clipboard = { version = "0.4.0", optional = true } cli-clipboard = { version = "0.4.0", optional = true }
uuid = { workspace = true }
[dependencies.tracing-subscriber] [dependencies.tracing-subscriber]

View File

@ -13,7 +13,7 @@ use atuin_client::{
database::{current_context, Database}, database::{current_context, Database},
encryption, encryption,
history::{store::HistoryStore, History}, history::{store::HistoryStore, History},
record::{self, sqlite_store::SqliteStore}, record::sqlite_store::SqliteStore,
settings::{ settings::{
FilterMode::{Directory, Global, Session}, FilterMode::{Directory, Global, Session},
Settings, Settings,
@ -21,7 +21,8 @@ use atuin_client::{
}; };
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use atuin_client::sync; use atuin_client::{record, sync};
use log::{debug, warn}; use log::{debug, warn};
use time::{macros::format_description, OffsetDateTime}; use time::{macros::format_description, OffsetDateTime};
@ -278,6 +279,7 @@ impl Cmd {
Ok(()) Ok(())
} }
#[allow(unused_variables)]
async fn handle_end( async fn handle_end(
db: &impl Database, db: &impl Database,
store: SqliteStore, store: SqliteStore,

View File

@ -1,65 +1,26 @@
use clap::{Args, Subcommand}; use clap::Subcommand;
use eyre::{bail, Result}; use eyre::Result;
use atuin_client::{ use atuin_client::{
database::Database, database::Database,
encryption,
history::store::HistoryStore,
record::{sqlite_store::SqliteStore, store::Store}, record::{sqlite_store::SqliteStore, store::Store},
settings::Settings, settings::Settings,
}; };
use time::OffsetDateTime; use time::OffsetDateTime;
#[derive(Args, Debug)] #[cfg(feature = "sync")]
pub struct Rebuild { mod push;
pub tag: String,
}
impl Rebuild { mod 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(())
}
}
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)] #[command(infer_subcommands = true)]
pub enum Cmd { pub enum Cmd {
Status, Status,
Rebuild(Rebuild), Rebuild(rebuild::Rebuild),
#[cfg(feature = "sync")]
Push(push::Push),
} }
impl Cmd { impl Cmd {
@ -72,6 +33,9 @@ impl Cmd {
match self { match self {
Self::Status => self.status(store).await, Self::Status => self.status(store).await,
Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await, Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await,
#[cfg(feature = "sync")]
Self::Push(push) => push.run(settings, store).await,
} }
} }

View File

@ -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<String>,
/// The host to push, in the form of a UUID host ID. Defaults to the current host.
#[arg(long)]
pub host: Option<Uuid>,
}
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(())
}
}

View File

@ -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(())
}
}

View File

@ -1,11 +1,10 @@
use std::{env, time::Duration}; use std::{env, time::Duration};
use atuin_client::api_client; 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::{launch_with_tcp_listener, Settings as ServerSettings};
use atuin_server_postgres::{Postgres, PostgresSettings}; use atuin_server_postgres::{Postgres, PostgresSettings};
use futures_util::TryFutureExt; use futures_util::TryFutureExt;
use time::OffsetDateTime;
use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
use tracing::{dispatcher, Dispatch}; use tracing::{dispatcher, Dispatch};
use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
@ -80,6 +79,7 @@ pub async fn register_inner<'a>(
api_client::Client::new(address, &registration_response.session, 5, 30).unwrap() api_client::Client::new(address, &registration_response.session, 5, 30).unwrap()
} }
#[allow(dead_code)]
pub async fn login(address: &str, username: String, password: String) -> api_client::Client<'_> { pub async fn login(address: &str, username: String, password: String) -> api_client::Client<'_> {
// registration works // registration works
let login_respose = api_client::login( 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() api_client::Client::new(address, &login_respose.session, 5, 30).unwrap()
} }
#[allow(dead_code)]
pub async fn register(address: &str) -> api_client::Client<'_> { pub async fn register(address: &str) -> api_client::Client<'_> {
let username = uuid_v7().as_simple().to_string(); let username = uuid_v7().as_simple().to_string();
let password = uuid_v7().as_simple().to_string(); let password = uuid_v7().as_simple().to_string();

View File

@ -1,14 +1,5 @@
use std::{env, time::Duration};
use atuin_client::api_client;
use atuin_common::{api::AddHistoryRequest, utils::uuid_v7}; 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 time::OffsetDateTime;
use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
use tracing::{dispatcher, Dispatch};
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
mod common; mod common;

View File

@ -1,14 +1,4 @@
use std::{env, time::Duration}; use atuin_common::utils::uuid_v7;
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; mod common;