logisheets_controller/version_manager/
mod.rs

1pub mod ctx;
2pub mod diff;
3
4use std::collections::{HashMap, HashSet, VecDeque};
5
6use logisheets_base::{CellId, SheetId};
7
8use crate::{controller::status::Status, edit_action::PayloadsAction};
9
10use self::diff::{convert_payloads_to_sheet_diff, Diff, SheetDiff};
11
12const HISTORY_SIZE: usize = 50;
13
14/// VersionManager records the history of a workbook and the payloads. It can help
15/// users find out the minimal updates at a certain version.
16#[derive(Debug, Default)]
17pub struct VersionManager {
18    version: u32,
19    undo_stack: VecDeque<Status>,
20    redo_stack: VecDeque<Status>,
21    diff_undo_stack: VecDeque<HashMap<SheetId, SheetDiff>>,
22    diff_redo_stack: VecDeque<HashMap<SheetId, SheetDiff>>,
23    current_status: Status,
24    current_diffs: HashMap<SheetId, SheetDiff>,
25}
26
27impl VersionManager {
28    pub fn version(&self) -> u32 {
29        self.version
30    }
31
32    pub fn record(
33        &mut self,
34        mut status: Status,
35        processes: PayloadsAction,
36        updated_cells: HashSet<(SheetId, CellId)>,
37    ) {
38        let diffs = convert_payloads_to_sheet_diff(&mut status, processes, updated_cells);
39        self.add_status(status, diffs);
40        self.version += 1;
41    }
42
43    fn add_status(&mut self, current: Status, sheet_diff: HashMap<SheetId, SheetDiff>) {
44        self.redo_stack.clear();
45        self.diff_redo_stack.clear();
46
47        if self.undo_stack.len() >= HISTORY_SIZE {
48            self.undo_stack.pop_front();
49            self.diff_undo_stack.pop_front();
50        }
51
52        let mut current = current;
53        let mut sheet_diff = sheet_diff;
54
55        std::mem::swap(&mut current, &mut self.current_status);
56        std::mem::swap(&mut sheet_diff, &mut self.current_diffs);
57
58        self.undo_stack.push_back(current);
59        self.diff_undo_stack.push_back(sheet_diff);
60    }
61
62    pub fn undo(&mut self) -> Option<Status> {
63        let mut status = self.undo_stack.pop_back()?;
64        let mut payloads = self.diff_undo_stack.pop_back()?;
65
66        std::mem::swap(&mut status, &mut self.current_status);
67        std::mem::swap(&mut payloads, &mut self.current_diffs);
68
69        self.redo_stack.push_back(status.clone());
70        self.diff_redo_stack.push_back(payloads);
71
72        Some(status)
73    }
74
75    pub fn redo(&mut self) -> Option<Status> {
76        let mut status = self.redo_stack.pop_back()?;
77        let mut payloads = self.diff_redo_stack.pop_back()?;
78
79        std::mem::swap(&mut status, &mut self.current_status);
80        std::mem::swap(&mut payloads, &mut self.current_diffs);
81
82        self.undo_stack.push_back(status.clone());
83        self.diff_undo_stack.push_back(payloads);
84
85        Some(status)
86    }
87
88    // `None` means that users can not update the workbook to the latest one incremently.
89    pub fn get_sheet_diffs_from_version(&self, sheet: SheetId, version: u32) -> Option<SheetDiff> {
90        if version > self.version {
91            return None;
92        }
93
94        if self.version - HISTORY_SIZE as u32 >= version {
95            return None;
96        }
97
98        let start_idx = if self.version >= 50 {
99            HISTORY_SIZE - 1 + (version - self.version) as usize
100        } else {
101            version as usize
102        };
103
104        let mut result: HashSet<Diff> = HashSet::new();
105
106        for i in start_idx..self.undo_stack.len() {
107            let sheet_diffs = self.diff_undo_stack.get(i)?;
108            if let Some(diff) = sheet_diffs.get(&sheet) {
109                if diff.diff_unavailable() {
110                    return None;
111                }
112                result.extend(diff.data.clone())
113            }
114        }
115
116        Some(SheetDiff { data: result })
117    }
118}