use clap::Subcommand; use eyre::{Result, WrapErr}; use crate::atuin_client::{ database::Database, encryption, history::store::HistoryStore, record::{sqlite_store::SqliteStore, store::Store, sync}, settings::Settings, }; mod status; use crate::command::client::account; #[derive(Subcommand, Debug)] #[command(infer_subcommands = true)] pub(crate) enum Cmd { /// Sync with the configured server Sync { /// Force re-download everything #[arg(long, short)] force: bool, }, /// Login to the configured server Login(account::login::Cmd), /// Log out Logout, /// Register with the configured server Register(account::register::Cmd), /// Print the encryption key for transfer to another machine Key {}, /// Display the sync status Status, } impl Cmd { pub(crate) async fn run( self, settings: Settings, db: &impl Database, store: SqliteStore, ) -> Result<()> { match self { Self::Sync { force } => run(&settings, force, db, store).await, Self::Login(l) => l.run(&settings, &store).await, Self::Logout => account::logout::run().await, Self::Register(r) => r.run(&settings).await, Self::Status => status::run(&settings).await, Self::Key {} => { use crate::atuin_client::encryption::{encode_key, load_key}; let key = load_key(&settings).wrap_err("could not load encryption key")?; let encode = encode_key(&key).wrap_err("could not encode encryption key")?; println!("{encode}"); Ok(()) } } } } async fn run( settings: &Settings, force: bool, db: &impl Database, store: SqliteStore, ) -> Result<()> { let encryption_key: [u8; 32] = encryption::load_key(settings) .context("could not load encryption key")? .into(); let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); let (uploaded, downloaded) = sync::sync(settings, &store, &encryption_key) .await .map_err(crate::print_error::format_sync_error)?; crate::sync::build(settings, &store, db, Some(&downloaded)).await?; println!("{uploaded}/{} up/down to record store", downloaded.len()); let history_length = db.history_count(true).await?; let store_history_length = store.len_tag("history").await?; #[expect(clippy::cast_sign_loss)] if history_length as u64 > store_history_length { println!("{history_length} in history index, but {store_history_length} in history store"); println!("Running automatic history store init..."); // Internally we use the global filter mode, so this context is ignored. // don't recurse or loop here. history_store.init_store(db).await?; println!("Re-running sync due to new records locally"); // we'll want to run sync once more, as there will now be stuff to upload let (uploaded, downloaded) = sync::sync(settings, &store, &encryption_key) .await .map_err(crate::print_error::format_sync_error)?; crate::sync::build(settings, &store, db, Some(&downloaded)).await?; println!("{uploaded}/{} up/down to record store", downloaded.len()); } println!( "Sync complete! {} items in history database, force: {}", db.history_count(true).await?, force ); Ok(()) }