Skip to content

Commit 93a0d88

Browse files
committed
feat(interactive): show total diff when computing summary
1 parent 189b17c commit 93a0d88

File tree

3 files changed

+284
-80
lines changed

3 files changed

+284
-80
lines changed

src/commands/ls.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
//! `ls` subcommand
22
3-
use std::{
4-
ops::{Add, AddAssign},
5-
path::Path,
6-
};
3+
use std::{ops::AddAssign, path::Path};
74

85
#[cfg(feature = "tui")]
96
use crate::commands::tui;
@@ -12,6 +9,7 @@ use crate::{Application, RUSTIC_APP, repository::CliIndexedRepo, status_err};
129
use abscissa_core::{Command, Runnable, Shutdown};
1310
use anyhow::Result;
1411

12+
use derive_more::Add;
1513
use rustic_core::{
1614
LsOptions,
1715
repofile::{Node, NodeType},
@@ -82,7 +80,7 @@ impl Runnable for LsCmd {
8280
/// Summary of a ls command
8381
///
8482
/// This struct is used to print a summary of the ls command.
85-
#[derive(Default, Clone, Copy)]
83+
#[derive(Default, Clone, Copy, Add)]
8684
pub struct Summary {
8785
pub files: usize,
8886
pub size: u64,
@@ -95,17 +93,6 @@ impl AddAssign for Summary {
9593
}
9694
}
9795

98-
impl Add for Summary {
99-
type Output = Self;
100-
fn add(self, rhs: Self) -> Self::Output {
101-
Self {
102-
files: self.files + rhs.files,
103-
size: self.size + rhs.size,
104-
dirs: self.dirs + rhs.dirs,
105-
}
106-
}
107-
}
108-
10996
impl Summary {
11097
/// Update the summary with the node
11198
///
@@ -121,6 +108,12 @@ impl Summary {
121108
self.size += node.meta.size;
122109
}
123110
}
111+
112+
pub fn from_node(node: &Node) -> Self {
113+
let mut summary = Self::default();
114+
summary.update(node);
115+
summary
116+
}
124117
}
125118

126119
pub trait NodeLs {

src/commands/tui/diff.rs

Lines changed: 113 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ use crate::{
1717
commands::{
1818
diff::{DiffStatistics, NodeDiff},
1919
snapshots::fill_table,
20-
tui::{
21-
summary::BlobInfoRef,
22-
widgets::{
23-
Draw, PopUpPrompt, PopUpText, ProcessEvent, PromptResult, SelectTable, WithBlock,
24-
popup_prompt, popup_text,
25-
},
20+
tui::widgets::{
21+
Draw, PopUpPrompt, PopUpText, ProcessEvent, PromptResult, SelectTable, WithBlock,
22+
popup_prompt, popup_text,
2623
},
2724
},
2825
helpers::bytes_size_to_string,
@@ -51,7 +48,7 @@ Diff Commands:
5148
5249
m : toggle ignoring metadata
5350
d : toggle show only different entries
54-
s : compute information for (sub-)dirs
51+
s : compute information for (sub-)dirs and show totals
5552
I : show information about snapshots
5653
5754
General Commands:
@@ -64,7 +61,7 @@ General Commands:
6461
";
6562

6663
#[derive(Clone)]
67-
struct DiffNode(EitherOrBoth<Node>);
64+
pub struct DiffNode(pub EitherOrBoth<Node>);
6865

6966
impl DiffNode {
7067
fn only_subtrees(&self) -> Option<Self> {
@@ -87,6 +84,24 @@ impl DiffNode {
8784
fn name(&self) -> OsString {
8885
self.0.as_ref().reduce(|l, _| l).name()
8986
}
87+
88+
pub fn map<'a, F, T>(&'a self, f: F) -> EitherOrBoth<T>
89+
where
90+
F: Fn(&'a Node) -> T,
91+
{
92+
self.0.as_ref().map_any(&f, &f)
93+
}
94+
95+
pub fn try_map<'a, F, T>(&'a self, f: F) -> Result<EitherOrBoth<T>>
96+
where
97+
F: Fn(&'a Node) -> Result<T>,
98+
{
99+
Ok(match self.0.as_ref() {
100+
EitherOrBoth::Left(a) => EitherOrBoth::Left(f(a)?),
101+
EitherOrBoth::Right(b) => EitherOrBoth::Right(f(b)?),
102+
EitherOrBoth::Both(a, b) => EitherOrBoth::Both(f(a)?, f(b)?),
103+
})
104+
}
90105
}
91106

92107
#[derive(Default)]
@@ -234,45 +249,37 @@ impl<'a, P: ProgressBars, S: IndexedFull> Diff<'a, P, S> {
234249
}
235250

236251
fn ls_row(&self, node: &DiffNode, stat: &mut DiffStatistics) -> Vec<Text<'static>> {
237-
let node_info = |node: &Node| {
238-
let size = node.subtree.map_or(node.meta.size, |id| {
239-
self.summary_map
240-
.get(&id)
241-
.map_or(node.meta.size, |summary| summary.summary.size)
242-
});
243-
(
244-
bytes_size_to_string(size),
245-
node.meta.mtime.map_or_else(
246-
|| "?".to_string(),
247-
|t| format!("{}", t.format("%Y-%m-%d %H:%M:%S")),
248-
),
252+
let node_mtime = |node: &Node| {
253+
node.meta.mtime.map_or_else(
254+
|| "?".to_string(),
255+
|t| t.format("%Y-%m-%d %H:%M:%S").to_string(),
249256
)
250257
};
251258

252-
let (left, right) = node.0.as_ref().left_and_right();
253-
let left_blobs = left.map(|node| BlobInfoRef::from_node_or_map(node, &self.summary_map));
254-
let right_blobs = right.map(|node| BlobInfoRef::from_node_or_map(node, &self.summary_map));
255-
let left_only = BlobInfoRef::text_diff(&left_blobs, &right_blobs, self.repo);
256-
let right_only = BlobInfoRef::text_diff(&right_blobs, &left_blobs, self.repo);
259+
let statistics = self
260+
.summary_map
261+
.compute_diff_statistics(node, self.repo)
262+
.unwrap_or_default();
263+
264+
let (left, right) = statistics.stats.as_ref().left_and_right();
265+
266+
let left_size = left.map_or_else(String::new, |s| bytes_size_to_string(s.summary.size));
267+
let right_size = right.map_or_else(String::new, |s| bytes_size_to_string(s.summary.size));
268+
let left_only = left.map_or_else(String::new, |s| bytes_size_to_string(s.sizes.repo_size));
269+
let right_only =
270+
right.map_or_else(String::new, |s| bytes_size_to_string(s.sizes.repo_size));
257271

258272
let changed = self.node_changed(node);
259273
stat.apply(changed);
260274
let name = node.name();
261275
let name = format!("{changed} {}", name.to_string_lossy());
262-
let (left_size, left_mtime) = match &node.0 {
263-
EitherOrBoth::Left(node) | EitherOrBoth::Both(node, _) => node_info(node),
264-
_ => (String::new(), String::new()),
265-
};
266-
let (right_size, right_mtime) = match &node.0 {
267-
EitherOrBoth::Right(node) | EitherOrBoth::Both(_, node) => node_info(node),
268-
_ => (String::new(), String::new()),
269-
};
276+
let (left_mtime, right_mtime) = node.map(node_mtime).left_and_right();
270277
[
271278
name,
272-
left_mtime,
279+
left_mtime.unwrap_or_default(),
273280
left_size,
274281
left_only,
275-
right_mtime,
282+
right_mtime.unwrap_or_default(),
276283
right_size,
277284
right_only,
278285
]
@@ -381,7 +388,7 @@ impl<'a, P: ProgressBars, S: IndexedFull> Diff<'a, P, S> {
381388
Ok(())
382389
}
383390

384-
pub fn compute_summary(&mut self) -> Result<()> {
391+
pub fn compute_summary(&mut self) -> Result<PopUpTable> {
385392
let pb = self.repo.progress_bars();
386393
let p = pb.progress_counter("computing (sub)-dir information");
387394

@@ -399,7 +406,72 @@ impl<'a, P: ProgressBars, S: IndexedFull> Diff<'a, P, S> {
399406

400407
p.finish();
401408
self.update_table();
402-
Ok(())
409+
410+
// Compute total sizes diff
411+
let stats = self
412+
.summary_map
413+
.compute_diff_statistics(&self.node, self.repo)
414+
.unwrap_or_default();
415+
416+
fn row_map<'a, T>(
417+
title: &'static str,
418+
n: EitherOrBoth<T>,
419+
map: fn(T) -> String,
420+
) -> Vec<Text<'a>> {
421+
let (left, right) = n.left_and_right();
422+
vec![
423+
Text::from(title),
424+
Text::from(left.map_or_else(String::new, map)),
425+
Text::from(right.map_or_else(String::new, map)),
426+
]
427+
}
428+
429+
let row_bytes = |title, n: EitherOrBoth<u64>| row_map(title, n, bytes_size_to_string);
430+
let row_count = |title, n: EitherOrBoth<usize>| row_map(title, n, |n| n.to_string());
431+
432+
let mut rows = Vec::new();
433+
let title_left = if self.node.0.has_left() {
434+
format!("{}:{}", self.snapshot_left.id, self.path_left.display())
435+
} else {
436+
format!("({})", self.snapshot_left.id)
437+
};
438+
let title_right = if self.node.0.has_right() {
439+
format!("{}:{}", self.snapshot_right.id, self.path_right.display())
440+
} else {
441+
format!("({})", self.snapshot_right.id)
442+
};
443+
rows.push(vec![
444+
Text::from(""),
445+
Text::from(title_left),
446+
Text::from(title_right),
447+
]);
448+
rows.push(row_bytes("total Size", stats.summary().size()));
449+
rows.push(row_count("total files", stats.summary().files()));
450+
rows.push(row_count("total dirs", stats.summary().dirs()));
451+
rows.push(vec![Text::from(String::new()); 3]);
452+
rows.push(row_bytes(
453+
"exclusive size after deduplication",
454+
stats.sizes().dedup_size(),
455+
));
456+
rows.push(row_bytes(
457+
"shared size after deduplication",
458+
stats.both_sizes().dedup_size(),
459+
));
460+
rows.push(row_bytes(
461+
"total size after deduplication",
462+
stats.total_sizes().dedup_size(),
463+
));
464+
rows.push(vec![Text::from(String::new()); 3]);
465+
rows.push(row_bytes("exclusive RepoSize", stats.sizes().repo_size()));
466+
rows.push(row_bytes("shared RepoSize", stats.both_sizes().repo_size()));
467+
rows.push(row_bytes("total RepoSize", stats.total_sizes().repo_size()));
468+
rows.push(row_map(
469+
"compression ratio",
470+
stats.total_sizes().compression_ratio(),
471+
|r| format!("{r:.2}"),
472+
));
473+
474+
Ok(popup_table("diff total", rows))
403475
}
404476

405477
pub fn snapshot_details(&self) -> PopUpTable {
@@ -448,7 +520,10 @@ impl<'a, P: ProgressBars, S: IndexedFull> ProcessEvent for Diff<'a, P, S> {
448520
}
449521
Char('m') => self.toggle_ignore_metadata(),
450522
Char('d') => self.toggle_ignore_identical()?,
451-
Char('s') => self.compute_summary()?,
523+
Char('s') => {
524+
self.current_screen =
525+
CurrentScreen::SnapshotDetails(self.compute_summary()?);
526+
}
452527
Char('I') => {
453528
self.current_screen =
454529
CurrentScreen::SnapshotDetails(self.snapshot_details());

0 commit comments

Comments
 (0)