Skip to main content

rustic_rs/commands/tui/
restore.rs

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