use std::sync::Arc;
use atuin_client::database::Sqlite;
use atuin_client::record::sqlite_store::SqliteStore;
use atuin_client::settings::Settings;
use atuin_common::time::{OffsetDateTimeExt, UtcOffsetExt};
use atuin_domain::record::RecordSeriesKey;
use clap::Subcommand;
use eyre::{Result, WrapErr as _};
use itertools::Itertools;
use time::OffsetDateTime;
use tracing::instrument;
#[cfg(feature = "daemon")]
use crate::command::client::daemon;
use crate::i18n::fl;
#[cfg(feature = "sync")]
mod push;
#[cfg(feature = "sync")]
mod pull;
mod purge;
mod rebuild;
mod rekey;
mod verify;
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub enum Cmd {
#[command(about = fl!("cmd-store-status"))]
Status,
#[command(about = fl!("cmd-store-rebuild"))]
Rebuild(rebuild::Rebuild),
#[command(about = fl!("cmd-store-rekey"))]
Rekey(rekey::Rekey),
#[command(about = fl!("cmd-store-compact"))]
Compact,
#[command(about = fl!("cmd-store-purge"))]
Purge(purge::Purge),
#[command(about = fl!("cmd-store-verify"))]
Verify(verify::Verify),
#[command(about = fl!("cmd-store-push"))]
#[cfg(feature = "sync")]
Push(push::Push),
#[command(about = fl!("cmd-store-pull"))]
#[cfg(feature = "sync")]
Pull(pull::Pull),
}
pub async fn invalidate_ai_sessions(settings: &Settings) {
if let Err(err) = atuin_client::ai_session::invalidate_sidecar(settings).await {
eprintln!("Failed to schedule a rebuild of the threads index: {err}");
}
}
#[cfg_attr(
not(feature = "daemon"),
expect(clippy::unused_async, reason = "only the daemon rebuilds")
)]
pub async fn reset_ai_sessions(settings: &Settings) -> Result<()> {
#[cfg(feature = "daemon")]
{
if !settings.daemon.enabled {
eyre::bail!(
"Threads need the daemon: enable it (`enabled = true` under `[daemon]` in the \
config) to rebuild the threads index"
);
}
daemon::rebuild_ai_sessions(settings)
.await
.wrap_err("the daemon could not rebuild the threads index")
}
#[cfg(not(feature = "daemon"))]
{
let _ = settings;
eyre::bail!("Threads need the daemon, which this build of atuin does not include");
}
}
pub async fn reset_ai_sessions_after(settings: &Settings) -> Result<()> {
if !atuin_client::ai_session::sidecar_path(settings).exists() {
return Ok(());
}
reset_ai_sessions(settings).await.wrap_err(
"the records changed, but the threads index was not rebuilt and still lists what they \
held: run `atuin store rebuild threads` once the daemon is enabled and running",
)
}
impl Cmd {
#[instrument(level = "trace", skip_all, err)]
pub async fn run(
&self,
settings: &Arc<Settings>,
database: &Sqlite,
store: SqliteStore,
) -> Result<()> {
match self {
Self::Status => self.status(settings, store).await,
Self::Rebuild(rebuild) => rebuild.run(settings, store, database).await,
Self::Rekey(rekey) => rekey.run(settings, store).await,
Self::Compact => {
#[cfg(feature = "daemon")]
let rewritten = if settings.daemon.enabled {
daemon::compact_store(settings).await?
} else {
store.compact().await?
};
#[cfg(not(feature = "daemon"))]
let rewritten = store.compact().await?;
println!("Rewrote {rewritten} records");
Ok(())
}
Self::Verify(verify) => verify.run(settings, store).await,
Self::Purge(purge) => purge.run(settings, store).await,
#[cfg(feature = "sync")]
Self::Push(push) => push.run(settings, store).await,
#[cfg(feature = "sync")]
Self::Pull(pull) => pull.run(settings, store, database).await,
}
}
pub async fn status(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
let host_id = settings.host_id().await?;
let offset = time::UtcOffset::local_or_utc();
let status = store.status().await?;
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 series = RecordSeriesKey::new(*host, tag.clone());
let first = store.first(&series).await?;
let last = store.last(&series).await?;
println!("\t\tidx: {idx}");
if let Some(first) = first {
println!("\t\tfirst: {}", first.id.0.as_hyphenated());
let time =
OffsetDateTime::from_unix_nanos_u64(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_nanos_u64(last.timestamp).to_offset(offset);
println!("\t\t\tcreated: {time}");
}
}
println!();
}
Ok(())
}
}