use crate::atuin_common::record::HostId; use clap::Args; use eyre::{OptionExt, Result}; use uuid::Uuid; use crate::atuin_client::{ api_client::Client, encryption::load_key, record::sync::Operation, record::{sqlite_store::SqliteStore, sync}, settings::Settings, }; #[derive(Args, Debug)] pub(crate) struct Push { /// The tag to push (eg, 'history'). Defaults to all tags #[arg(long, short)] pub(crate) tag: Option, /// The host to push, in the form of a UUID host ID. Defaults to the current host. #[arg(long)] pub(crate) host: Option, /// Force push records /// This will override both host and tag, to be all hosts and all tags. First clear the remote store, then upload all of the /// local store #[arg(long, default_value = "false")] pub(crate) force: bool, /// Page Size /// How many records to upload at once. Defaults to 100 #[arg(long, default_value = "100")] pub(crate) page: u64, } impl Push { pub(crate) async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { let host_id = Settings::host_id().await?; if self.force { println!("Forcing remote store overwrite!"); println!("Clearing remote store"); let client = Client::new( &settings.sync.address, settings.network_connect_timeout, // we may be deleting a lot of data... so increase the // timeout settings.network_timeout * 10, settings.sync.user_id()?.ok_or_eyre("no sync user-id")?, ) .expect("failed to create client"); client.delete_store().await?; } // 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 client = sync::build_client(settings)?; let (diff, remote_index) = sync::diff(&client, &store).await?; // Skip on --force: that path intentionally replaces remote with local. if !self.force { let key: [u8; 32] = load_key(settings)?.into(); sync::check_encryption_key(&client, &remote_index, &key) .await .map_err(crate::print_error::format_sync_error)?; } let operations = sync::operations(diff, &store)?; 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 self.force { return true; } 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() && t != *tag { return false; } true } }) .collect(); let (uploaded, _) = sync::sync_remote(&client, operations, &store, self.page).await?; println!("Uploaded {uploaded} records"); Ok(()) } }