rustic_rs/commands/tui/
restore.rs

1use anyhow::Result;
2use crossterm::event::{Event, KeyCode, KeyEventKind};
3use ratatui::prelude::*;
4use rustic_core::{
5    IndexedFull, LocalDestination, LsOptions, ProgressBars, Repository, RestoreOptions,
6    RestorePlan, repofile::Node,
7};
8
9use crate::{
10    commands::tui::widgets::{
11        Draw, PopUpInput, PopUpPrompt, PopUpText, ProcessEvent, PromptResult, TextInputResult,
12        popup_input, popup_prompt,
13    },
14    helpers::bytes_size_to_string,
15};
16
17use super::widgets::popup_text;
18
19// the states this screen can be in
20enum CurrentScreen {
21    GetDestination(PopUpInput),
22    PromptRestore(PopUpPrompt, Option<RestorePlan>),
23    RestoreDone(PopUpText),
24}
25
26pub(crate) struct Restore<'a, P, S> {
27    current_screen: CurrentScreen,
28    repo: &'a Repository<P, S>,
29    opts: RestoreOptions,
30    node: Node,
31    source: String,
32    dest: String,
33}
34
35impl<'a, P: ProgressBars, S: IndexedFull> Restore<'a, P, S> {
36    pub fn new(repo: &'a Repository<P, S>, node: Node, source: String, path: &str) -> Self {
37        let opts = RestoreOptions::default();
38        let title = format!("restore {source} to:");
39        let popup = popup_input(title, "enter restore destination", path, 1);
40        Self {
41            current_screen: CurrentScreen::GetDestination(popup),
42            node,
43            repo,
44            opts,
45            source,
46            dest: String::new(),
47        }
48    }
49
50    pub fn compute_plan(&mut self, mut dest: String, dry_run: bool) -> Result<RestorePlan> {
51        if dest.is_empty() {
52            dest = ".".to_string();
53        }
54        self.dest = dest;
55        let dest = LocalDestination::new(&self.dest, true, !self.node.is_dir())?;
56
57        // for restore, always recurse into tree
58        let mut ls_opts = LsOptions::default();
59        ls_opts.recursive = true;
60
61        let ls = self.repo.ls(&self.node, &ls_opts)?;
62
63        let plan = self.repo.prepare_restore(&self.opts, ls, &dest, dry_run)?;
64
65        Ok(plan)
66    }
67
68    // restore using the plan
69    //
70    // Note: This currently runs `prepare_restore` again and doesn't use `plan`
71    // TODO: Fix when restore is changed such that `prepare_restore` is always dry_run and all modification is done in `restore`
72    fn restore(&self, _plan: RestorePlan) -> Result<()> {
73        let dest = LocalDestination::new(&self.dest, true, !self.node.is_dir())?;
74
75        // for restore, always recurse into tree
76        let mut ls_opts = LsOptions::default();
77        ls_opts.recursive = true;
78
79        let ls = self.repo.ls(&self.node, &ls_opts)?;
80        let plan = self
81            .repo
82            .prepare_restore(&self.opts, ls.clone(), &dest, false)?;
83
84        // the actual restore
85        self.repo.restore(plan, &self.opts, ls, &dest)?;
86        Ok(())
87    }
88
89    pub fn input(&mut self, event: Event) -> Result<bool> {
90        use KeyCode::{Char, Enter, Esc};
91        match &mut self.current_screen {
92            CurrentScreen::GetDestination(prompt) => match prompt.input(event) {
93                TextInputResult::Cancel => return Ok(true),
94                TextInputResult::Input(input) => {
95                    let plan = self.compute_plan(input, true)?;
96                    let fs = plan.stats.files;
97                    let ds = plan.stats.dirs;
98                    let popup = popup_prompt(
99                        "restore information",
100                        Text::from(format!(
101                            r#"
102restoring from: {}
103restoring to: {}
104                            
105Files:  {} to restore, {} unchanged, {} verified, {} to modify, {} additional
106Dirs:   {} to restore, {} to modify, {} additional
107Total restore size: {}
108
109Do you want to proceed (y/n)?
110 "#,
111                            self.source,
112                            self.dest,
113                            fs.restore,
114                            fs.unchanged,
115                            fs.verified,
116                            fs.modify,
117                            fs.additional,
118                            ds.restore,
119                            ds.modify,
120                            ds.additional,
121                            bytes_size_to_string(plan.restore_size)
122                        )),
123                    );
124                    self.current_screen = CurrentScreen::PromptRestore(popup, Some(plan));
125                }
126                TextInputResult::None => {}
127            },
128            CurrentScreen::PromptRestore(prompt, plan) => match prompt.input(event) {
129                PromptResult::Ok => {
130                    let plan = plan.take().unwrap();
131                    self.restore(plan)?;
132                    self.current_screen = CurrentScreen::RestoreDone(popup_text(
133                        "restore done",
134                        format!("restored {} successfully to {}", self.source, self.dest).into(),
135                    ));
136                }
137                PromptResult::Cancel => return Ok(true),
138                PromptResult::None => {}
139            },
140            CurrentScreen::RestoreDone(_) => match event {
141                Event::Key(key) if key.kind == KeyEventKind::Press => {
142                    if matches!(key.code, Char('q' | ' ') | Esc | Enter) {
143                        return Ok(true);
144                    }
145                }
146                _ => {}
147            },
148        }
149        Ok(false)
150    }
151
152    pub fn draw(&mut self, area: Rect, f: &mut Frame<'_>) {
153        // draw popups
154        match &mut self.current_screen {
155            CurrentScreen::GetDestination(popup) => popup.draw(area, f),
156            CurrentScreen::PromptRestore(popup, _) => popup.draw(area, f),
157            CurrentScreen::RestoreDone(popup) => popup.draw(area, f),
158        }
159    }
160}