rustic_rs/commands/
restore.rs1use crate::{
4 Application, RUSTIC_APP, helpers::bytes_size_to_string, repository::CliIndexedRepo, status_err,
5};
6
7use abscissa_core::{Command, Runnable, Shutdown};
8use anyhow::Result;
9use log::info;
10
11use rustic_core::{LocalDestination, LsOptions, RestoreOptions};
12
13use crate::filtering::SnapshotFilter;
14
15#[allow(clippy::struct_excessive_bools)]
17#[derive(clap::Parser, Command, Debug)]
18pub(crate) struct RestoreCmd {
19 #[clap(value_name = "SNAPSHOT[:PATH]")]
21 snap: String,
22
23 #[clap(value_name = "DESTINATION")]
25 dest: String,
26
27 #[clap(flatten)]
29 opts: RestoreOptions,
30
31 #[clap(flatten)]
33 ls_opts: LsOptions,
34
35 #[clap(
37 flatten,
38 next_help_heading = "Snapshot filter options (when using latest)"
39 )]
40 filter: SnapshotFilter,
41}
42impl Runnable for RestoreCmd {
43 fn run(&self) {
44 if let Err(err) = RUSTIC_APP
45 .config()
46 .repository
47 .run_indexed(|repo| self.inner_run(repo))
48 {
49 status_err!("{}", err);
50 RUSTIC_APP.shutdown(Shutdown::Crash);
51 };
52 }
53}
54
55impl RestoreCmd {
56 fn inner_run(&self, repo: CliIndexedRepo) -> Result<()> {
57 let config = RUSTIC_APP.config();
58 let dry_run = config.global.dry_run;
59
60 let node =
61 repo.node_from_snapshot_path(&self.snap, |sn| config.snapshot_filter.matches(sn))?;
62
63 let mut ls_opts = self.ls_opts.clone();
65 ls_opts.recursive = true;
66 let ls = repo.ls(&node, &ls_opts)?;
67
68 let dest = LocalDestination::new(&self.dest, true, !node.is_dir())?;
69
70 let restore_infos = repo.prepare_restore(&self.opts, ls, &dest, dry_run)?;
71
72 let fs = restore_infos.stats.files;
73 println!(
74 "Files: {} to restore, {} unchanged, {} verified, {} to modify, {} additional",
75 fs.restore, fs.unchanged, fs.verified, fs.modify, fs.additional
76 );
77 let ds = restore_infos.stats.dirs;
78 println!(
79 "Dirs: {} to restore, {} to modify, {} additional",
80 ds.restore, ds.modify, ds.additional
81 );
82
83 info!(
84 "total restore size: {}",
85 bytes_size_to_string(restore_infos.restore_size)
86 );
87 if restore_infos.matched_size > 0 {
88 info!(
89 "using {} of existing file contents.",
90 bytes_size_to_string(restore_infos.matched_size)
91 );
92 }
93 if restore_infos.restore_size == 0 {
94 info!("all file contents are fine.");
95 }
96
97 if dry_run {
98 repo.warm_up(restore_infos.to_packs().into_iter())?;
99 } else {
100 let repo = repo.drop_data_from_index();
102
103 let ls = repo.ls(&node, &ls_opts)?;
104 repo.restore(restore_infos, &self.opts, ls, &dest)?;
105 println!("restore done.");
106 }
107
108 Ok(())
109 }
110}