Skip to main content

rustic_rs/commands/
restore.rs

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