summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-02-02 15:18:50 +0000
committerEllie Huxtable <ellie@elliehuxtable.com>2024-02-02 18:01:09 +0000
commit374255dd5856465b30d4c90d5882b150fa6c47de (patch)
tree0c25941ba5250a898c58361573e71496c3761a86
parentc9a453289e2fea97b5ac17e265f99332edcdcd4c (diff)
feat: add `store pull`
This allows the user to 1. Specify that they want to sync, but ONLY pull new data 2. Specify that they wish to force pull, which will wipe the local store and download it from the remote With the other set of changes, this allows the user to perform sufficient maintenance to recovery from most errors I can think of right now.
-rw-r--r--atuin-client/src/record/sqlite_store.rs6
-rw-r--r--atuin-client/src/record/store.rs2
-rw-r--r--atuin/src/command/client/store.rs9
-rw-r--r--atuin/src/command/client/store/pull.rs86
4 files changed, 103 insertions, 0 deletions
diff --git a/atuin-client/src/record/sqlite_store.rs b/atuin-client/src/record/sqlite_store.rs
index 4e4a3756..e5e3b9cd 100644
--- a/atuin-client/src/record/sqlite_store.rs
+++ b/atuin-client/src/record/sqlite_store.rs
@@ -154,6 +154,12 @@ impl Store for SqliteStore {
Ok(())
}
+ async fn delete_all(&self) -> Result<()> {
+ sqlx::query("delete from store").execute(&self.pool).await?;
+
+ Ok(())
+ }
+
async fn last(&self, host: HostId, tag: &str) -> Result<Option<Record<EncryptedData>>> {
let res =
sqlx::query("select * from store where host=?1 and tag=?2 order by idx desc limit 1")
diff --git a/atuin-client/src/record/store.rs b/atuin-client/src/record/store.rs
index 1d812549..ae8ea356 100644
--- a/atuin-client/src/record/store.rs
+++ b/atuin-client/src/record/store.rs
@@ -21,7 +21,9 @@ pub trait Store {
) -> Result<()>;
async fn get(&self, id: RecordId) -> Result<Record<EncryptedData>>;
+
async fn delete(&self, id: RecordId) -> Result<()>;
+ async fn delete_all(&self) -> Result<()>;
async fn len(&self, host: HostId, tag: &str) -> Result<u64>;
async fn len_tag(&self, tag: &str) -> Result<u64>;
diff --git a/atuin/src/command/client/store.rs b/atuin/src/command/client/store.rs
index 16618b04..8e53954d 100644
--- a/atuin/src/command/client/store.rs
+++ b/atuin/src/command/client/store.rs
@@ -11,6 +11,9 @@ use time::OffsetDateTime;
#[cfg(feature = "sync")]
mod push;
+#[cfg(feature = "sync")]
+mod pull;
+
mod purge;
mod rebuild;
mod rekey;
@@ -27,6 +30,9 @@ pub enum Cmd {
#[cfg(feature = "sync")]
Push(push::Push),
+
+ #[cfg(feature = "sync")]
+ Pull(pull::Pull),
}
impl Cmd {
@@ -45,6 +51,9 @@ impl Cmd {
#[cfg(feature = "sync")]
Self::Push(push) => push.run(settings, store).await,
+
+ #[cfg(feature = "sync")]
+ Self::Pull(pull) => pull.run(settings, store, database).await,
}
}
diff --git a/atuin/src/command/client/store/pull.rs b/atuin/src/command/client/store/pull.rs
new file mode 100644
index 00000000..d920dd21
--- /dev/null
+++ b/atuin/src/command/client/store/pull.rs
@@ -0,0 +1,86 @@
+use clap::Args;
+use eyre::{Result, WrapErr};
+
+use atuin_client::{
+ database::Database,
+ encryption,
+ history::store::HistoryStore,
+ record::store::Store,
+ record::sync::Operation,
+ record::{sqlite_store::SqliteStore, sync},
+ settings::Settings,
+};
+
+#[derive(Args, Debug)]
+pub struct Pull {
+ /// The tag to push (eg, 'history'). Defaults to all tags
+ #[arg(long, short)]
+ pub tag: Option<String>,
+
+ /// Force push records
+ /// This will first wipe the local store, and then download all records from the remote
+ #[arg(long, default_value = "false")]
+ pub force: bool,
+}
+
+impl Pull {
+ pub async fn run(
+ &self,
+ settings: &Settings,
+ store: SqliteStore,
+ db: &dyn Database,
+ ) -> Result<()> {
+ if self.force {
+ println!("Forcing local overwrite!");
+ println!("Clearing local store");
+
+ store.delete_all().await?;
+ }
+
+ // We can actually just use the existing diff/etc to push
+ // 1. Diff
+ // 2. Get operations
+ // 3. Filter operations by
+ // a) are they a download op?
+ // b) are they for the host/tag we are pushing here?
+ let (diff, _) = sync::diff(settings, &store).await?;
+ let operations = sync::operations(diff, &store).await?;
+
+ let operations = operations
+ .into_iter()
+ .filter(|op| match op {
+ // No noops or downloads thx
+ Operation::Noop { .. } | Operation::Upload { .. } => false,
+
+ // pull, so yes plz to downloads!
+ Operation::Download { tag, .. } => {
+ if self.force {
+ return true;
+ }
+
+ if let Some(t) = self.tag.clone() {
+ if t != *tag {
+ return false;
+ }
+ }
+
+ true
+ }
+ })
+ .collect();
+
+ let (_, downloaded) = sync::sync_remote(operations, &store, settings).await?;
+
+ println!("Downloaded {} records", downloaded.len());
+
+ let encryption_key: [u8; 32] = encryption::load_key(settings)
+ .context("could not load encryption key")?
+ .into();
+
+ let host_id = Settings::host_id().expect("failed to get host_id");
+ let history_store = HistoryStore::new(store.clone(), host_id, encryption_key);
+ history_store.incremental_build(db, &downloaded).await?;
+
+ Ok(())
+ }
+}