use crate::repository::{OpenRepo, get_grouped_snapshots};
use crate::{Application, RUSTIC_APP, RusticConfig, helpers::table_with_titles, status_err};
use abscissa_core::{Command, FrameworkError, Runnable};
use abscissa_core::{Shutdown, config::Override};
use anyhow::Result;
use conflate::Merge;
use jiff::Zoned;
use log::info;
use rustic_core::repofile::RusticTime;
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};
use crate::{commands::prune::PruneCmd, filtering::SnapshotFilter};
use rustic_core::{ForgetGroups, ForgetSnapshot, KeepOptions, SnapshotGroupCriterion};
#[derive(clap::Parser, Command, Debug)]
pub(super) struct ForgetCmd {
#[clap(value_name = "ID")]
ids: Vec<String>,
#[clap(long,value_parser = RusticTime::parse_system)]
pub forget_time: Option<Zoned>,
#[clap(long)]
json: bool,
#[clap(flatten)]
config: ForgetOptions,
#[clap(
flatten,
next_help_heading = "PRUNE OPTIONS (only when used with --prune)"
)]
prune_opts: PruneCmd,
}
impl Override<RusticConfig> for ForgetCmd {
fn override_config(&self, mut config: RusticConfig) -> Result<RusticConfig, FrameworkError> {
let mut self_config = self.config.clone();
self_config.merge(config.forget);
self_config.filter.merge(config.snapshot_filter.clone());
config.forget = self_config;
Ok(config)
}
}
#[serde_as]
#[derive(Clone, Default, Debug, clap::Parser, Serialize, Deserialize, Merge)]
#[serde(default, rename_all = "kebab-case")]
pub struct ForgetOptions {
#[clap(long, short = 'g', value_name = "CRITERION")]
#[serde_as(as = "Option<DisplayFromStr>")]
#[merge(strategy=conflate::option::overwrite_none)]
group_by: Option<SnapshotGroupCriterion>,
#[clap(long)]
#[merge(strategy=conflate::bool::overwrite_false)]
prune: bool,
#[clap(flatten, next_help_heading = "Snapshot filter options")]
#[serde(flatten)]
filter: SnapshotFilter,
#[clap(flatten, next_help_heading = "Retention options")]
#[serde(flatten)]
keep: KeepOptions,
}
impl Runnable for ForgetCmd {
fn run(&self) {
if let Err(err) = RUSTIC_APP
.config()
.repository
.run_open(|repo| self.inner_run(repo))
{
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
};
}
}
impl ForgetCmd {
fn inner_run(&self, repo: OpenRepo) -> Result<()> {
let config = RUSTIC_APP.config();
let group_by = config
.forget
.group_by
.or(config.global.group_by)
.unwrap_or_default();
if let Some(time) = &self.forget_time {
info!("using time: {time}");
}
let now = self.forget_time.clone().unwrap_or_else(Zoned::now);
let groups = if self.ids.is_empty() {
ForgetGroups::from_grouped_snapshots_with_retention(
get_grouped_snapshots(&repo, group_by, &[])?,
&config.forget.keep,
&now,
)?
} else {
ForgetGroups::from_snapshots(
repo.get_snapshots_from_strs(&self.ids, |sn| config.snapshot_filter.matches(sn))?,
&now,
)
};
if self.json {
let mut stdout = std::io::stdout();
serde_json::to_writer_pretty(&mut stdout, &groups)?;
} else {
print_groups(&groups);
}
let forget_snaps = groups.into_forget_ids();
match (forget_snaps.is_empty(), config.global.dry_run, self.json) {
(true, _, false) => info!("nothing to remove"),
(false, true, false) => {
info!("would have removed {} snapshots.", forget_snaps.len());
}
(false, false, _) => {
repo.delete_snapshots(&forget_snaps)?;
}
(_, _, true) => {}
}
if config.forget.prune {
let mut prune_opts = self.prune_opts.clone();
prune_opts.opts.ignore_snaps = forget_snaps;
prune_opts.run();
}
Ok(())
}
}
fn print_groups(groups: &ForgetGroups) {
let config = RUSTIC_APP.config();
for group in &groups.0 {
let mut table = table_with_titles([
"ID", "Time", "Host", "Label", "Tags", "Paths", "Action", "Reason",
]);
for ForgetSnapshot {
snapshot: sn,
keep,
reasons,
} in &group.items
{
let time = config.global.format_time(&sn.time).to_string();
let tags = sn.tags.formatln();
let paths = sn.paths.formatln();
let action = if *keep { "keep" } else { "remove" };
let reason = reasons.join("\n");
_ = table.add_row([
&sn.id.to_string(),
&time,
&sn.hostname,
&sn.label,
&tags,
&paths,
action,
&reason,
]);
}
if !group.group_key.is_empty() {
info!("snapshots for {}:\n{table}", group.group_key);
} else {
info!("snapshots:\n{table}");
}
}
}