rustic_rs/commands/tui/
summary.rs

1use 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}