rustic_rs/commands/
restore.rs1use crate::{
4 Application, RUSTIC_APP, helpers::bytes_size_to_string, repository::IndexedRepo, status_err,
5};
6
7use abscissa_core::{Command, Runnable, Shutdown};
8use anyhow::Result;
9use log::{debug, 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]")]
23 snap: String,
24
25 #[clap(value_name = "DESTINATION")]
27 dest: String,
28
29 #[clap(flatten)]
31 opts: RestoreOptions,
32
33 #[clap(flatten)]
35 ls_opts: LsOptions,
36
37 #[clap(
39 flatten,
40 next_help_heading = "Snapshot filter options (when using latest)"
41 )]
42 filter: SnapshotFilter,
43}
44impl Runnable for RestoreCmd {
45 fn run(&self) {
46 if let Err(err) = RUSTIC_APP
47 .config()
48 .repository
49 .run_indexed(|repo| self.inner_run(repo))
50 {
51 status_err!("{}", err);
52 RUSTIC_APP.shutdown(Shutdown::Crash);
53 };
54 }
55}
56
57impl RestoreCmd {
58 fn inner_run(&self, repo: IndexedRepo) -> Result<()> {
59 let config = RUSTIC_APP.config();
60 let dry_run = config.global.dry_run;
61
62 let node =
63 repo.node_from_snapshot_path(&self.snap, |sn| config.snapshot_filter.matches(sn))?;
64
65 let mut ls_opts = self.ls_opts.clone();
67 ls_opts.recursive = true;
68 let ls = repo.ls(&node, &ls_opts)?;
69
70 let dest = LocalDestination::new(&self.dest, true, !node.is_dir())?;
71
72 let restore_infos = repo.prepare_restore(&self.opts, ls, &dest, dry_run)?;
73
74 let fs = restore_infos.stats.files;
75 println!(
76 "Files: {} to restore, {} unchanged, {} verified, {} to modify, {} additional",
77 fs.restore, fs.unchanged, fs.verified, fs.modify, fs.additional
78 );
79 let ds = restore_infos.stats.dirs;
80 println!(
81 "Dirs: {} to restore, {} to modify, {} additional",
82 ds.restore, ds.modify, ds.additional
83 );
84
85 info!(
86 "total restore size: {}",
87 bytes_size_to_string(restore_infos.restore_size)
88 );
89 if restore_infos.matched_size > 0 {
90 info!(
91 "using {} of existing file contents.",
92 bytes_size_to_string(restore_infos.matched_size)
93 );
94 }
95 if restore_infos.restore_size == 0 {
96 info!("all file contents are fine.");
97 }
98
99 if dry_run && config.global.dry_run_warmup {
100 repo.warm_up(restore_infos.to_packs().into_iter())?;
101 } else if !dry_run && !config.global.dry_run_warmup {
102 let repo = repo.drop_data_from_index();
104
105 let ls = repo.ls(&node, &ls_opts)?;
106 repo.restore(restore_infos, &self.opts, ls, &dest)?;
107 println!("restore done.");
108 } else {
109 debug!(
110 "--dry-run is without warmup, --dry-run --dry-run-warmup also issues the warmup script."
111 );
112 }
113
114 Ok(())
115 }
116}