rustic_rs/commands/
restore.rs

1//! `restore` subcommand
2
3use 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/// `restore` subcommand
16#[allow(clippy::struct_excessive_bools)]
17#[derive(clap::Parser, Command, Debug)]
18pub(crate) struct RestoreCmd {
19    /// Snapshot/path to restore
20    #[clap(value_name = "SNAPSHOT[:PATH]")]
21    snap: String,
22
23    /// Restore destination
24    #[clap(value_name = "DESTINATION")]
25    dest: String,
26
27    /// Restore options
28    #[clap(flatten)]
29    opts: RestoreOptions,
30
31    /// List options
32    #[clap(flatten)]
33    ls_opts: LsOptions,
34
35    /// Snapshot filter options (when using latest)
36    #[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        // for restore, always recurse into tree
64        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            // save some memory
101            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}