Skip to content

Commit a91e2f8

Browse files
committed
feat(api): summarize HardlinkList
1 parent 1215edf commit a91e2f8

3 files changed

Lines changed: 174 additions & 17 deletions

File tree

src/app/sub/unix_ext.rs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,9 @@ where
1818
report: Self::Report,
1919
bytes_format: Size::DisplayFormat,
2020
) -> Result<(), RuntimeError> {
21-
let (inodes, links, size): (usize, usize, Size) = report
22-
.iter()
23-
.filter_map(|values| {
24-
let size = values.size();
25-
let links = values.links().len();
26-
(links > 1).then_some(())?;
27-
Some((*size, links))
28-
})
29-
.fold(
30-
(0, 0, Size::default()),
31-
|(inodes, total_links, total_size), (size, links)| {
32-
(inodes + 1, total_links + links, total_size + size)
33-
},
34-
);
35-
if inodes > 0 {
36-
let size = size.display(bytes_format);
37-
println!("Detected {links} hardlinks for {inodes} unique files (total: {size})");
21+
let summary = report.summarize();
22+
if summary.inodes > 0 {
23+
println!("{}", summary.display(bytes_format));
3824
}
3925
Ok(())
4026
}

src/hardlink/hardlink_list.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
pub mod reflection;
2+
pub mod summary;
23

34
pub use reflection::Reflection;
5+
pub use summary::Summary;
46

57
pub use Reflection as HardlinkListReflection;
8+
pub use Summary as SharedLinkSummary;
69

710
use crate::{hardlink::LinkPathList, inode::InodeNumber, size};
811
use dashmap::{iter::Iter as DashIter, mapref::multiple::RefMulti, DashMap};
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use super::{reflection::ReflectionEntry, HardlinkList, IterItem, Reflection};
2+
use crate::size;
3+
use derive_more::{Add, AddAssign, Sum};
4+
use std::fmt::{self, Display};
5+
6+
#[cfg(feature = "json")]
7+
use serde::{Deserialize, Serialize};
8+
9+
/// Summary from [`HardlinkList`] or [`Reflection`].
10+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Add, AddAssign, Sum)]
11+
#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
12+
#[non_exhaustive]
13+
pub struct Summary<Size> {
14+
/// Number of unique files, each with more than 1 links.
15+
pub inodes: usize,
16+
/// Total number of links of all the unique files as counted by [`Summary::inodes`].
17+
pub links: usize,
18+
/// Total size of all the unique files.
19+
pub size: Size,
20+
}
21+
22+
impl<Size> Summary<Size> {
23+
/// Create a new summary.
24+
pub fn new(inodes: usize, links: usize, size: Size) -> Self {
25+
Summary {
26+
inodes,
27+
links,
28+
size,
29+
}
30+
}
31+
}
32+
33+
/// Ability to summarize into a [`Summary`].
34+
pub trait SummarizeHardlinks<Size>: Sized {
35+
/// Summarize into a summary of shared links and size.
36+
fn summarize_hardlinks(self) -> Summary<Size>;
37+
}
38+
39+
/// Summary of a single unique file.
40+
#[derive(Debug, Clone, Copy)]
41+
pub struct SingleInodeSummary<Size> {
42+
/// Number of detected links to the file.
43+
links: usize,
44+
/// Size of the file.
45+
size: Size,
46+
}
47+
48+
impl<Size, Iter> SummarizeHardlinks<Size> for Iter
49+
where
50+
Size: size::Size,
51+
Iter: IntoIterator,
52+
Iter::Item: Into<SingleInodeSummary<Size>>,
53+
{
54+
fn summarize_hardlinks(self) -> Summary<Size> {
55+
let mut summary = Summary::default();
56+
for item in self {
57+
let SingleInodeSummary { links, size } = item.into();
58+
if links <= 1 {
59+
continue;
60+
}
61+
summary.inodes += 1;
62+
summary.links += links;
63+
summary.size += size;
64+
}
65+
summary
66+
}
67+
}
68+
69+
/// Summarize an iterator.
70+
impl<Size, Item> FromIterator<Item> for Summary<Size>
71+
where
72+
Size: size::Size,
73+
Item: Into<SingleInodeSummary<Size>>,
74+
{
75+
/// Create a summary of shared links and size from an iterator.
76+
fn from_iter<Iter: IntoIterator<Item = Item>>(iter: Iter) -> Self {
77+
iter.summarize_hardlinks()
78+
}
79+
}
80+
81+
impl<Size: size::Size> HardlinkList<Size> {
82+
/// Create summary for the shared links and size.
83+
pub fn summarize(&self) -> Summary<Size> {
84+
self.iter().summarize_hardlinks()
85+
}
86+
}
87+
88+
impl<Size: size::Size> SummarizeHardlinks<Size> for &HardlinkList<Size> {
89+
fn summarize_hardlinks(self) -> Summary<Size> {
90+
self.summarize()
91+
}
92+
}
93+
94+
impl<Size: size::Size> Reflection<Size> {
95+
/// Create summary for the shared links and size.
96+
pub fn summarize(&self) -> Summary<Size> {
97+
self.iter().summarize_hardlinks()
98+
}
99+
}
100+
101+
impl<Size: size::Size> SummarizeHardlinks<Size> for &Reflection<Size> {
102+
fn summarize_hardlinks(self) -> Summary<Size> {
103+
self.summarize()
104+
}
105+
}
106+
107+
/// Return type of [`Summary::display`] which implements [`Display`].
108+
#[derive(Debug, Clone, Copy)]
109+
pub struct SummaryDisplay<'a, Size: size::Size> {
110+
format: Size::DisplayFormat,
111+
summary: &'a Summary<Size>,
112+
}
113+
114+
impl<Size: size::Size> Display for SummaryDisplay<'_, Size> {
115+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116+
let SummaryDisplay { format, summary } = self;
117+
let Summary {
118+
inodes,
119+
links,
120+
size,
121+
} = summary;
122+
let size = size.display(*format);
123+
write!(
124+
f,
125+
"Detected {links} hardlinks for {inodes} unique files (total: {size})"
126+
)
127+
}
128+
}
129+
130+
impl<Size: size::Size> Summary<Size> {
131+
/// Turns this [`Summary`] into something [displayable](Display).
132+
pub fn display(&self, format: Size::DisplayFormat) -> SummaryDisplay<Size> {
133+
SummaryDisplay {
134+
format,
135+
summary: self,
136+
}
137+
}
138+
}
139+
140+
impl<Size: Copy> From<ReflectionEntry<Size>> for SingleInodeSummary<Size> {
141+
fn from(reflection: ReflectionEntry<Size>) -> Self {
142+
(&reflection).into()
143+
}
144+
}
145+
146+
impl<'r, Size: Copy> From<&'r ReflectionEntry<Size>> for SingleInodeSummary<Size> {
147+
fn from(reflection: &'r ReflectionEntry<Size>) -> Self {
148+
SingleInodeSummary {
149+
links: reflection.links.len(),
150+
size: reflection.size,
151+
}
152+
}
153+
}
154+
155+
impl<'a, Size: Copy> From<IterItem<'a, Size>> for SingleInodeSummary<Size> {
156+
fn from(value: IterItem<'a, Size>) -> Self {
157+
(&value).into()
158+
}
159+
}
160+
161+
impl<'r, 'a, Size: Copy> From<&'r IterItem<'a, Size>> for SingleInodeSummary<Size> {
162+
fn from(value: &'r IterItem<'a, Size>) -> Self {
163+
SingleInodeSummary {
164+
links: value.links().len(),
165+
size: *value.size(),
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)