use std::num::NonZeroU64;
use std::sync::Arc;
use atuin_client::api_client::Client;
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::{HostId, RecordTag};
use clap::Args;
use eyre::{Context as _, Result};
use uuid::Uuid;
use crate::i18n::fl;
#[derive(Args, Debug)]
pub struct Push {
#[arg(long, short, help = fl!("arg-store-tag"))]
pub tag: Option<RecordTag>,
#[arg(long, help = fl!("arg-store-push-host"))]
pub host: Option<Uuid>,
#[arg(
long,
default_value = "false",
help = fl!("arg-store-push-force"),
long_help = fl!("arg-store-push-force", "long")
)]
pub force: bool,
#[arg(
long,
default_value = "100",
help = fl!("arg-store-push-page"),
long_help = fl!("arg-store-push-page", "long")
)]
pub page: NonZeroU64,
}
impl Push {
pub async fn run(&self, settings: &Arc<Settings>, store: SqliteStore) -> Result<()> {
let host_id = settings.host_id().await?;
if self.force {
println!("Forcing remote store overwrite!");
println!("Clearing remote store");
let caps = atuin_client::api_client::caps_client(settings)?;
let client = Client::new(
settings.sync_address.clone(),
&settings.sync_auth_token().await?,
settings.network_connect_timeout,
settings.network_timeout * 10, &settings.extra_headers,
caps,
)
.expect("failed to create client");
client.delete_store().await?;
}
let key = paseto_v4::Key::try_load_from_path(&settings.key_path)
.context("could not load encryption key")?;
let session = SyncSession::builder()
.store(store)
.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::Download { .. } => false,
Operation::Upload { series, .. } => {
if self.force {
return true;
}
if let Some(h) = self.host {
if HostId(h) != series.host_id {
return false;
}
} else if series.host_id != host_id {
return false;
}
if let Some(t) = self.tag.clone()
&& t != series.tag
{
return false;
}
true
}
})
.collect();
let (uploaded, _) = keyed.sync_remote(operations).await?;
println!("Uploaded {uploaded} records");
Ok(())
}
}