Skip to content

Commit 97a2158

Browse files
committed
refactor: use general tree modifier in repair snapshots
1 parent 9975ab9 commit 97a2158

File tree

1 file changed

+111
-176
lines changed

1 file changed

+111
-176
lines changed

crates/core/src/commands/repair/snapshots.rs

Lines changed: 111 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22
use derive_setters::Setters;
33
use log::{info, warn};
44

5-
use std::collections::{BTreeMap, BTreeSet};
5+
use std::{
6+
collections::{BTreeMap, BTreeSet},
7+
path::PathBuf,
8+
};
69

710
use crate::{
8-
backend::{
9-
decrypt::{DecryptFullBackend, DecryptWriteBackend},
10-
node::NodeType,
11-
},
12-
blob::{
13-
BlobId, BlobType,
14-
packer::Packer,
15-
tree::{Tree, TreeId},
11+
backend::{decrypt::DecryptWriteBackend, node::NodeType},
12+
blob::tree::{
13+
Tree, TreeId,
14+
modify::{ModifierAction, ModifierChange, NodeAction, TreeAction, TreeModifier, Visitor},
1615
},
1716
error::{ErrorKind, RusticError, RusticResult},
18-
index::{ReadGlobalIndex, ReadIndex, indexer::Indexer},
17+
index::ReadGlobalIndex,
1918
progress::ProgressBars,
20-
repofile::{SnapshotFile, StringList, snapshotfile::SnapshotId},
19+
repofile::{Node, SnapshotFile, StringList, snapshotfile::SnapshotId},
2120
repository::{IndexedFull, IndexedTree, Repository},
2221
};
2322

@@ -60,19 +59,103 @@ impl Default for RepairSnapshotsOptions {
6059
}
6160
}
6261

63-
// TODO: add documentation
64-
#[derive(Clone, Copy)]
65-
pub(crate) enum Changed {
66-
This,
67-
SubTree,
68-
None,
62+
pub(crate) struct RepairState<'a, I: ReadGlobalIndex> {
63+
opts: &'a RepairSnapshotsOptions,
64+
index: &'a I,
65+
changed: BTreeMap<TreeId, TreeId>,
66+
unchanged: BTreeSet<TreeId>,
67+
delete: Vec<SnapshotId>,
6968
}
7069

71-
#[derive(Default)]
72-
pub(crate) struct RepairState {
73-
replaced: BTreeMap<TreeId, (Changed, TreeId)>,
74-
seen: BTreeSet<TreeId>,
75-
delete: Vec<SnapshotId>,
70+
impl<'a, I: ReadGlobalIndex> RepairState<'a, I> {
71+
fn new(opts: &'a RepairSnapshotsOptions, index: &'a I) -> Self {
72+
Self {
73+
opts,
74+
index,
75+
changed: BTreeMap::new(),
76+
unchanged: BTreeSet::new(),
77+
delete: Vec::new(),
78+
}
79+
}
80+
}
81+
82+
impl<I: ReadGlobalIndex> Visitor for RepairState<'_, I> {
83+
fn pre_process(&self, _path: &PathBuf, id: TreeId) -> ModifierAction {
84+
if self.unchanged.contains(&id) {
85+
ModifierAction::Change(ModifierChange::Unchanged)
86+
} else if let Some(r) = self.changed.get(&id) {
87+
ModifierAction::Change(ModifierChange::Changed(*r))
88+
} else {
89+
ModifierAction::Process(id)
90+
}
91+
}
92+
fn pre_process_tree(&mut self, tree: RusticResult<Tree>) -> RusticResult<TreeAction> {
93+
Ok(tree.map_or_else(
94+
|err| {
95+
warn!("{}", err.display_log()); // TODO: id in error message
96+
TreeAction::ProcessChangedTree(Tree::new())
97+
},
98+
TreeAction::ProcessUnchangedTree,
99+
))
100+
}
101+
102+
fn process_node(&mut self, _path: &PathBuf, mut node: Node, _id: TreeId) -> NodeAction {
103+
match node.node_type {
104+
NodeType::File => {
105+
let mut file_changed = false;
106+
let mut new_content = Vec::new();
107+
let mut new_size = 0;
108+
for blob in node.content.take().unwrap() {
109+
self.index.get_data(&blob).map_or_else(
110+
|| {
111+
file_changed = true;
112+
},
113+
|ie| {
114+
new_content.push(blob);
115+
new_size += u64::from(ie.data_length());
116+
},
117+
);
118+
}
119+
if file_changed {
120+
warn!("file {}: contents are missing", node.name);
121+
node.name += &self.opts.suffix;
122+
} else if new_size != node.meta.size {
123+
info!("file {}: corrected file size", node.name);
124+
}
125+
node.content = Some(new_content);
126+
node.meta.size = new_size;
127+
if file_changed {
128+
NodeAction::ChangedNode(node)
129+
} else {
130+
NodeAction::UnchangedNode(node)
131+
}
132+
}
133+
NodeType::Dir => {
134+
if let Some(subtree) = node.subtree {
135+
NodeAction::VisitTree(subtree, node)
136+
} else {
137+
NodeAction::CreateTree(node)
138+
}
139+
}
140+
_ => NodeAction::UnchangedNode(node), // Other types: no check needed
141+
}
142+
}
143+
fn post_process(
144+
&mut self,
145+
_path: PathBuf,
146+
id: TreeId,
147+
changed: bool,
148+
new_id: Option<TreeId>,
149+
_tree: &Tree,
150+
) {
151+
if changed {
152+
if let Some(new_id) = new_id {
153+
_ = self.changed.insert(id, new_id);
154+
}
155+
} else {
156+
_ = self.unchanged.insert(id);
157+
}
158+
}
76159
}
77160

78161
/// Runs the `repair snapshots` command
@@ -104,37 +187,21 @@ pub(crate) fn repair_snapshots<P: ProgressBars, S: IndexedFull>(
104187
));
105188
}
106189

107-
let mut state = RepairState::default();
108-
109-
let indexer = Indexer::new(be.clone()).into_shared();
110-
let mut packer = Packer::new(
111-
be.clone(),
112-
BlobType::Tree,
113-
indexer.clone(),
114-
config_file,
115-
repo.index().total_size(BlobType::Tree),
116-
)?;
190+
let mut state = RepairState::new(opts, repo.index());
191+
let modifier = TreeModifier::new(be, repo.index(), config_file, dry_run)?;
117192

118193
for mut snap in snapshots {
119194
let snap_id = snap.id;
120195
info!("processing snapshot {snap_id}");
121-
match repair_tree(
122-
repo.dbe(),
123-
opts,
124-
repo.index(),
125-
&mut packer,
126-
Some(snap.tree),
127-
&mut state,
128-
dry_run,
129-
)? {
130-
(Changed::None, _) => {
196+
match modifier.modify_tree(PathBuf::new(), snap.tree, &mut state)? {
197+
ModifierChange::Unchanged => {
131198
info!("snapshot {snap_id} is ok.");
132199
}
133-
(Changed::This, _) => {
200+
ModifierChange::Removed => {
134201
warn!("snapshot {snap_id}: root tree is damaged -> marking for deletion!");
135202
state.delete.push(snap_id);
136203
}
137-
(Changed::SubTree, id) => {
204+
ModifierChange::Changed(id) => {
138205
// change snapshot tree
139206
if snap.original.is_none() {
140207
snap.original = Some(snap.id);
@@ -152,11 +219,6 @@ pub(crate) fn repair_snapshots<P: ProgressBars, S: IndexedFull>(
152219
}
153220
}
154221

155-
if !dry_run {
156-
_ = packer.finalize()?;
157-
indexer.write().unwrap().finalize()?;
158-
}
159-
160222
if opts.delete {
161223
if dry_run {
162224
info!("would have removed {} snapshots.", state.delete.len());
@@ -171,130 +233,3 @@ pub(crate) fn repair_snapshots<P: ProgressBars, S: IndexedFull>(
171233

172234
Ok(())
173235
}
174-
175-
/// Repairs a tree
176-
///
177-
/// # Type Parameters
178-
///
179-
/// * `BE` - The type of the backend.
180-
///
181-
/// # Arguments
182-
///
183-
/// * `be` - The backend to use
184-
/// * `opts` - The repair options to use
185-
/// * `packer` - The packer to use
186-
/// * `id` - The id of the tree to repair
187-
/// * `replaced` - A map of already replaced trees
188-
/// * `seen` - A set of already seen trees
189-
/// * `dry_run` - Whether to actually modify the repository or just print what would be done
190-
///
191-
/// # Returns
192-
///
193-
/// A tuple containing the change status and the id of the repaired tree
194-
pub(crate) fn repair_tree<BE: DecryptWriteBackend>(
195-
be: &impl DecryptFullBackend,
196-
opts: &RepairSnapshotsOptions,
197-
index: &impl ReadGlobalIndex,
198-
packer: &mut Packer<BE>,
199-
id: Option<TreeId>,
200-
state: &mut RepairState,
201-
dry_run: bool,
202-
) -> RusticResult<(Changed, TreeId)> {
203-
let (tree, changed) = match id {
204-
None => (Tree::new(), Changed::This),
205-
Some(id) => {
206-
if state.seen.contains(&id) {
207-
return Ok((Changed::None, id));
208-
}
209-
if let Some(r) = state.replaced.get(&id) {
210-
return Ok(*r);
211-
}
212-
213-
let (tree, mut changed) = Tree::from_backend(be, index, id).map_or_else(
214-
|err| {
215-
warn!("tree {id} could not be loaded: {}", err.display_log());
216-
(Tree::new(), Changed::This)
217-
},
218-
|tree| (tree, Changed::None),
219-
);
220-
221-
let mut new_tree = Tree::new();
222-
223-
for mut node in tree {
224-
match node.node_type {
225-
NodeType::File => {
226-
let mut file_changed = false;
227-
let mut new_content = Vec::new();
228-
let mut new_size = 0;
229-
for blob in node.content.take().unwrap() {
230-
index.get_data(&blob).map_or_else(
231-
|| {
232-
file_changed = true;
233-
},
234-
|ie| {
235-
new_content.push(blob);
236-
new_size += u64::from(ie.data_length());
237-
},
238-
);
239-
}
240-
if file_changed {
241-
warn!("file {}: contents are missing", node.name);
242-
node.name += &opts.suffix;
243-
changed = Changed::SubTree;
244-
} else if new_size != node.meta.size {
245-
info!("file {}: corrected file size", node.name);
246-
changed = Changed::SubTree;
247-
}
248-
node.content = Some(new_content);
249-
node.meta.size = new_size;
250-
}
251-
NodeType::Dir => {
252-
let (c, tree_id) =
253-
repair_tree(be, opts, index, packer, node.subtree, state, dry_run)?;
254-
match c {
255-
Changed::None => {}
256-
Changed::This => {
257-
warn!("dir {}: tree is missing", node.name);
258-
node.subtree = Some(tree_id);
259-
node.name += &opts.suffix;
260-
changed = Changed::SubTree;
261-
}
262-
Changed::SubTree => {
263-
node.subtree = Some(tree_id);
264-
changed = Changed::SubTree;
265-
}
266-
}
267-
}
268-
_ => {} // Other types: no check needed
269-
}
270-
new_tree.add(node);
271-
}
272-
if matches!(changed, Changed::None) {
273-
_ = state.seen.insert(id);
274-
}
275-
(new_tree, changed)
276-
}
277-
};
278-
279-
match (id, changed) {
280-
(None, Changed::None) => panic!("this should not happen!"),
281-
(Some(id), Changed::None) => Ok((Changed::None, id)),
282-
(_, c) => {
283-
// the tree has been changed => save it
284-
let (chunk, new_id) = tree.serialize().map_err(|err| {
285-
RusticError::with_source(ErrorKind::Internal, "Failed to serialize tree.", err)
286-
.ask_report()
287-
})?;
288-
289-
if !index.has_tree(&new_id) && !dry_run {
290-
packer.add(chunk.into(), BlobId::from(*new_id))?;
291-
}
292-
293-
if let Some(id) = id {
294-
_ = state.replaced.insert(id, (c, new_id));
295-
}
296-
297-
Ok((c, new_id))
298-
}
299-
}
300-
}

0 commit comments

Comments
 (0)