rustic_rs/commands/tui/
summary.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use anyhow::Result;
4use rustic_core::{
5 DataId, IndexedFull, Progress, Repository, TreeId,
6 repofile::{Metadata, Node, Tree},
7};
8
9use crate::{commands::ls::Summary, helpers::bytes_size_to_string};
10
11#[derive(Default)]
12pub struct SummaryMap(BTreeMap<TreeId, TreeSummary>);
13
14impl SummaryMap {
15 pub fn get(&self, id: &TreeId) -> Option<&TreeSummary> {
16 self.0.get(id)
17 }
18
19 pub fn compute<P, S: IndexedFull>(
20 &mut self,
21 repo: &Repository<P, S>,
22 id: TreeId,
23 p: &impl Progress,
24 ) -> Result<()> {
25 let _ = TreeSummary::from_tree(repo, id, &mut self.0, p)?;
26 Ok(())
27 }
28}
29
30#[derive(Default, Clone)]
31pub struct TreeSummary {
32 pub id_without_meta: TreeId,
33 pub summary: Summary,
34 blobs: BlobInfo,
35 subtrees: Vec<TreeId>,
36}
37
38impl TreeSummary {
39 fn update(&mut self, other: Self) {
40 self.summary += other.summary;
41 }
42
43 fn update_from_node(&mut self, node: &Node) {
44 for id in node.content.iter().flatten() {
45 _ = self.blobs.0.insert(*id);
46 }
47 self.summary.update(node);
48 }
49
50 pub fn from_tree<P, S>(
51 repo: &'_ Repository<P, S>,
52 id: TreeId,
53 summary_map: &mut BTreeMap<TreeId, Self>,
54 p: &impl Progress,
55 ) -> Result<Self>
56 where
57 S: IndexedFull,
58 {
59 if let Some(summary) = summary_map.get(&id) {
60 return Ok(summary.clone());
61 }
62
63 let mut summary = Self::default();
64
65 let tree = repo.get_tree(&id)?;
66 let mut tree_without_meta = Tree::default();
67 p.inc(1);
68 for node in &tree.nodes {
69 let mut node_without_meta = Node::new_node(
70 node.name().as_os_str(),
71 node.node_type.clone(),
72 Metadata::default(),
73 );
74 node_without_meta.content = node.content.clone();
75 summary.update_from_node(node);
76 if let Some(id) = node.subtree {
77 let subtree_summary = Self::from_tree(repo, id, summary_map, p)?;
78 node_without_meta.subtree = Some(subtree_summary.id_without_meta);
79 summary.update(subtree_summary);
80 summary.subtrees.push(id);
81 }
82 tree_without_meta.nodes.push(node_without_meta);
83 }
84 let (_, id_without_meta) = tree_without_meta.serialize()?;
85 summary.id_without_meta = id_without_meta;
86
87 _ = summary_map.insert(id, summary.clone());
88 Ok(summary)
89 }
90}
91
92#[derive(Default, Clone)]
93pub struct BlobInfo(BTreeSet<DataId>);
94
95impl BlobInfo {
96 pub fn as_ref(&self) -> BlobInfoRef<'_> {
97 BlobInfoRef(self.0.iter().collect())
98 }
99}
100
101#[derive(Default, Clone)]
102pub struct BlobInfoRef<'a>(BTreeSet<&'a DataId>);
103
104impl<'a> BlobInfoRef<'a> {
105 pub fn from_node_or_map(node: &'a Node, summary_map: &'a SummaryMap) -> Self {
106 node.subtree.as_ref().map_or_else(
107 || Self::from_node(node),
108 |id| Self::from_id(id, summary_map),
109 )
110 }
111 fn from_id(id: &'a TreeId, summary_map: &'a SummaryMap) -> Self {
112 summary_map.get(id).map_or_else(Self::default, |summary| {
113 let mut blobs = summary.blobs.as_ref();
114 for id in &summary.subtrees {
115 blobs.0.append(&mut Self::from_id(id, summary_map).0);
116 }
117 blobs
118 })
119 }
120 fn from_node(node: &'a Node) -> Self {
121 Self(node.content.iter().flatten().collect())
122 }
123
124 pub fn text_diff<P, S: IndexedFull>(
125 blobs1: &Option<Self>,
126 blobs2: &Option<Self>,
127 repo: &'a Repository<P, S>,
128 ) -> String {
129 if let (Some(blobs1), Some(blobs2)) = (blobs1, blobs2) {
130 blobs1
131 .0
132 .difference(&blobs2.0)
133 .map(|id| repo.get_index_entry(*id))
134 .try_fold(0u64, |sum, b| -> Result<_> {
135 Ok(sum + u64::from(b?.length))
136 })
137 .ok()
138 .map_or_else(|| "?".to_string(), bytes_size_to_string)
139 } else {
140 String::new()
141 }
142 }
143}