use std::num::NonZeroU64;
use std::sync::Arc;
use atuin_client::database::Sqlite;
use atuin_client::record::sqlite_store::SqliteStore;
use atuin_client::record::sync::{ClientSource, Operation, SyncSession};
use atuin_client::settings::Settings;
use atuin_common::encryption::paseto_v4;
use atuin_domain::record::RecordTag;
use clap::Args;
use eyre::{Context as _, Result};
use crate::i18n::fl;
#[derive(Args, Debug)]
pub struct Pull {
#[arg(long, short, help = fl!("arg-store-tag"))]
pub tag: Option<RecordTag>,
#[arg(
long,
default_value = "false",
help = fl!("arg-store-pull-force"),
long_help = fl!("arg-store-pull-force", "long")
)]
pub force: bool,
#[arg(
long,
default_value = "100",
help = fl!("arg-store-pull-page"),
long_help = fl!("arg-store-pull-page", "long")
)]
pub page: NonZeroU64,
}
impl Pull {
pub async fn run(
&self,
settings: &Arc<Settings>,
store: SqliteStore,
db: &Sqlite,
) -> Result<()> {
if !self.force {
return self.pull(settings, store, db).await;
}
println!("Forcing local overwrite!");
println!("Clearing local store");
store.delete_all().await?;
let pulled = self.pull(settings, store, db).await;
let reset = super::reset_ai_sessions_after(settings).await;
match (pulled, reset) {
(Err(pull), Err(reset)) => {
eprintln!("{reset:?}");
Err(pull)
}
(pulled, reset) => pulled.and(reset),
}
}
async fn pull(&self, settings: &Arc<Settings>, store: SqliteStore, db: &Sqlite) -> Result<()> {
let key = paseto_v4::Key::try_load_from_path(&settings.key_path)
.context("could not load encryption key")?;
let session = SyncSession::builder()
.store(store.clone())
.client_source(ClientSource::FromSettings {
settings,
caps: None,
})
.build()
.connect()
.await?
.with_page_size(self.page);
let keyed = session.keyed(&key);
let (diff, remote_index) = session.diff().await?;
if !self.force
&& let Some(err) = keyed.key_valid_against(&remote_index).await
{
return Err(crate::print_error::format_sync_error(err));
}
let operations = SyncSession::operations(diff)?;
let operations = operations
.into_iter()
.filter(|op| match op {
Operation::Noop { .. } | Operation::Upload { .. } => false,
Operation::Download { series, .. } => {
if self.force {
return true;
}
if let Some(t) = self.tag.clone()
&& t != series.tag
{
return false;
}
true
}
})
.collect();
let (_, downloaded) = keyed.sync_remote(operations).await?;
println!("Downloaded {} records", downloaded.len());
if self.force {
crate::sync::build(settings, &store, db, Some(&downloaded)).await?;
} else {
crate::command::client::sync::build(settings, &store, db, &downloaded).await?;
}
Ok(())
}
}