use atuin_client::database::Sqlite;
use atuin_client::history::store::HistoryStore;
use atuin_client::record::sqlite_store::SqliteStore;
use atuin_client::record::sync::{ClientSource, SyncEngine};
use atuin_client::settings::Settings;
use atuin_common::encryption::paseto_v4;
use atuin_domain::record::RecordTag;
use clap::Subcommand;
use easy_cast::Conv;
use eyre::{Result, WrapErr};
use tracing::instrument;
mod status;
use crate::command::client::account;
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub enum Cmd {
Sync {
#[arg(long, short)]
force: bool,
},
Login(account::login::Cmd),
Logout,
Register(account::register::Cmd),
Key {
#[arg(long)]
base64: bool,
},
Status,
}
impl Cmd {
#[instrument(level = "trace", skip_all, err)]
pub async fn run(self, settings: Settings, db: &Sqlite, 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, &store).await,
Self::Status => status::run(&settings).await,
Self::Key { base64 } => {
let key = paseto_v4::Key::try_load_from_path(&settings.key_path)
.wrap_err("could not load encryption key")?;
if base64 {
println!("{}", key.encode().dangerously_leak_secret());
} else {
println!("{}", key.try_mnemonic().context("invalid key")?);
}
Ok(())
}
}
}
}
#[instrument(level = "trace", skip_all, fields(force), err)]
async fn run(settings: &Settings, force: bool, db: &Sqlite, store: SqliteStore) -> Result<()> {
let encryption_key = paseto_v4::Key::try_load_from_path(&settings.key_path)
.context("could not load encryption key")?;
let host_id = Settings::host_id().await?;
let history_store = HistoryStore::new(store.clone(), host_id, encryption_key.clone());
let engine = SyncEngine::builder()
.store(store.clone())
.client_source(ClientSource::FromSettings {
settings,
caps: None,
})
.build()
.connect()
.await
.map_err(crate::print_error::format_sync_error)?;
let (uploaded, downloaded) = engine
.keyed(&encryption_key)
.sync()
.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(&RecordTag::History).await?;
if u64::conv(history_length) > store_history_length {
println!("{history_length} in history index, but {store_history_length} in history store");
println!("Running automatic history store init...");
history_store.init_store(db).await?;
println!("Re-running sync due to new records locally");
let (uploaded, downloaded) = engine
.keyed(&encryption_key)
.sync()
.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(())
}