aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client/store.rs
blob: bc57488d4d2ce9ac7e7c52c9bee6348cfc862945 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use clap::Subcommand;
use eyre::Result;

use crate::atuin_client::{
    database::ClientSqlite, record::sqlite_store::SqliteStore, settings::Settings,
};
use itertools::Itertools;
use time::{OffsetDateTime, UtcOffset};

mod pull;
mod purge;
mod push;
mod rebuild;
mod rekey;
mod verify;

#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub(crate) enum Cmd {
    /// Print the current status of the record store
    Status,

    /// Rebuild a store (eg atuin store rebuild history)
    Rebuild(rebuild::Rebuild),

    /// Re-encrypt the store with a new key (potential for data loss!)
    Rekey(rekey::Rekey),

    /// Delete all records in the store that cannot be decrypted with the current key
    Purge(purge::Purge),

    /// Verify that all records in the store can be decrypted with the current key
    Verify(verify::Verify),

    /// Push all records to the remote sync server (one way sync)
    Push(push::Push),

    /// Pull records from the remote sync server (one way sync)
    Pull(pull::Pull),
}

impl Cmd {
    pub(crate) async fn run(
        &self,
        settings: &Settings,
        database: &ClientSqlite,
        store: SqliteStore,
    ) -> Result<()> {
        match self {
            Self::Status => self.status(store).await,
            Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await,
            Self::Rekey(rekey) => rekey.run(settings, store).await,
            Self::Verify(verify) => verify.run(settings, store).await,
            Self::Purge(purge) => purge.run(settings, store).await,
            Self::Push(push) => push.run(settings, store).await,
            Self::Pull(pull) => pull.run(settings, store, database).await,
        }
    }

    pub(crate) async fn status(&self, store: SqliteStore) -> Result<()> {
        let host_id = Settings::host_id().await?;
        let offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);

        let status = store.status().await?;

        // TODO: should probs build some data structure and then pretty-print it or smth
        for (host, st) in status.hosts.iter().sorted_by_key(|(h, _)| *h) {
            let host_string = if host == &host_id {
                format!("host: {} <- CURRENT HOST", host.0.as_hyphenated())
            } else {
                format!("host: {}", host.0.as_hyphenated())
            };

            println!("{host_string}");

            for (tag, idx) in st.iter().sorted_by_key(|(tag, _)| *tag) {
                println!("\tstore: {tag}");

                let first = store.first(*host, tag).await?;
                let last = store.last(*host, tag).await?;

                println!("\t\tidx: {idx}");

                if let Some(first) = first {
                    println!("\t\tfirst: {}", first.id.0.as_hyphenated());

                    let time =
                        OffsetDateTime::from_unix_timestamp_nanos(i128::from(first.timestamp))?
                            .to_offset(offset);
                    println!("\t\t\tcreated: {time}");
                }

                if let Some(last) = last {
                    println!("\t\tlast: {}", last.id.0.as_hyphenated());

                    let time =
                        OffsetDateTime::from_unix_timestamp_nanos(i128::from(last.timestamp))?
                            .to_offset(offset);
                    println!("\t\t\tcreated: {time}");
                }
            }

            println!();
        }

        Ok(())
    }
}