parallel_disk_usage/hardlink/hardlink_list/
summary.rs1use super::{iter::Item as IterItem, reflection::ReflectionEntry, HardlinkList, Reflection};
2use crate::size;
3use derive_more::{Add, AddAssign, Sum};
4use derive_setters::Setters;
5use std::{
6 cmp::Ordering,
7 fmt::{self, Display},
8};
9
10#[cfg(feature = "json")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Default, Setters, Clone, Copy, PartialEq, Eq, Add, AddAssign, Sum)]
15#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
16#[setters(prefix = "with_")]
17#[non_exhaustive]
18pub struct Summary<Size> {
19 pub inodes: usize,
21
22 pub exclusive_inodes: usize,
26
27 pub all_links: u64,
29
30 pub detected_links: usize,
34
35 pub exclusive_links: usize,
39
40 pub shared_size: Size,
42
43 pub exclusive_shared_size: Size,
47}
48
49pub trait SummarizeHardlinks<Size>: Sized {
51 type Summary;
53 fn summarize_hardlinks(self) -> Self::Summary;
55}
56
57#[derive(Debug, Clone, Copy)]
59pub struct SingleInodeSummary<Size> {
60 links: u64,
62 paths: usize,
64 size: Size,
66}
67
68impl<Size, Iter> SummarizeHardlinks<Size> for Iter
69where
70 Size: size::Size,
71 Iter: IntoIterator,
72 Iter::Item: SummarizeHardlinks<Size>,
73 <Iter::Item as SummarizeHardlinks<Size>>::Summary: Into<SingleInodeSummary<Size>>,
74{
75 type Summary = Summary<Size>;
76 fn summarize_hardlinks(self) -> Self::Summary {
77 let mut summary = Summary::default();
78 for item in self {
79 let SingleInodeSummary { links, paths, size } = item.summarize_hardlinks().into();
80 summary.inodes += 1;
81 summary.all_links += links;
82 summary.detected_links += paths;
83 summary.shared_size += size;
84 match links.cmp(&(paths as u64)) {
85 Ordering::Greater => {}
86 Ordering::Equal => {
87 summary.exclusive_inodes += 1;
88 summary.exclusive_links += paths; summary.exclusive_shared_size += size;
90 }
91 Ordering::Less => {
92 panic!("Impossible! Total of nlink ({links}) is less than detected paths ({paths}). Something must have gone wrong!");
93 }
94 }
95 }
96 summary
97 }
98}
99
100impl<Size: size::Size> HardlinkList<Size> {
101 pub fn summarize(&self) -> Summary<Size> {
103 self.iter().summarize_hardlinks()
104 }
105}
106
107impl<Size: size::Size> SummarizeHardlinks<Size> for &HardlinkList<Size> {
108 type Summary = Summary<Size>;
109 fn summarize_hardlinks(self) -> Self::Summary {
110 self.summarize()
111 }
112}
113
114impl<Size: size::Size> Reflection<Size> {
115 pub fn summarize(&self) -> Summary<Size> {
117 self.iter().summarize_hardlinks()
118 }
119}
120
121impl<Size: size::Size> SummarizeHardlinks<Size> for &Reflection<Size> {
122 type Summary = Summary<Size>;
123 fn summarize_hardlinks(self) -> Self::Summary {
124 self.summarize()
125 }
126}
127
128#[derive(Debug, Clone, Copy)]
130pub struct SummaryDisplay<'a, Size: size::Size> {
131 format: Size::DisplayFormat,
132 summary: &'a Summary<Size>,
133}
134
135impl<Size: size::Size> Display for SummaryDisplay<'_, Size> {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 let SummaryDisplay { format, summary } = self;
138 let Summary {
139 inodes,
140 exclusive_inodes,
141 all_links,
142 detected_links,
143 exclusive_links,
144 shared_size,
145 exclusive_shared_size,
146 } = summary;
147
148 let shared_size = shared_size.display(*format);
149 let exclusive_shared_size = exclusive_shared_size.display(*format);
150
151 macro_rules! ln {
152 ($($args:tt)*) => {
153 writeln!(f, $($args)*)
154 };
155 }
156
157 if inodes == &0 {
158 return ln!("There are no hardlinks.");
159 }
160
161 write!(f, "Hardlinks detected! ")?;
162 if exclusive_inodes == inodes {
163 ln!("No files have links outside this tree")?;
164 ln!("* Number of shared inodes: {inodes}")?;
165 ln!("* Total number of links: {all_links}")?;
166 ln!("* Total shared size: {shared_size}")?;
167 } else if exclusive_inodes == &0 {
168 ln!("All hardlinks within this tree have links without")?;
169 ln!("* Number of shared inodes: {inodes}")?;
170 ln!("* Total number of links: {all_links} total, {detected_links} detected")?;
171 ln!("* Total shared size: {shared_size}")?;
172 } else {
173 ln!("Some files have links outside this tree")?;
174 ln!("* Number of shared inodes: {inodes} total, {exclusive_inodes} exclusive")?;
175 ln!("* Total number of links: {all_links} total, {detected_links} detected, {exclusive_links} exclusive")?;
176 ln!("* Total shared size: {shared_size} total, {exclusive_shared_size} exclusive")?;
177 }
178
179 Ok(())
180 }
181}
182
183impl<Size: size::Size> Summary<Size> {
184 #[inline]
186 pub fn display(&self, format: Size::DisplayFormat) -> SummaryDisplay<'_, Size> {
187 SummaryDisplay {
188 format,
189 summary: self,
190 }
191 }
192}
193
194impl<Size: Copy> SummarizeHardlinks<Size> for ReflectionEntry<Size> {
195 type Summary = SingleInodeSummary<Size>;
196 fn summarize_hardlinks(self) -> Self::Summary {
197 (&self).summarize_hardlinks()
198 }
199}
200
201impl<Size: Copy> SummarizeHardlinks<Size> for &ReflectionEntry<Size> {
202 type Summary = SingleInodeSummary<Size>;
203 fn summarize_hardlinks(self) -> Self::Summary {
204 SingleInodeSummary {
205 links: self.links,
206 paths: self.paths.len(),
207 size: self.size,
208 }
209 }
210}
211
212impl<'a, Size: Copy> SummarizeHardlinks<Size> for IterItem<'a, Size> {
213 type Summary = SingleInodeSummary<Size>;
214 fn summarize_hardlinks(self) -> Self::Summary {
215 (&self).summarize_hardlinks()
216 }
217}
218
219impl<'a, Size: Copy> SummarizeHardlinks<Size> for &IterItem<'a, Size> {
220 type Summary = SingleInodeSummary<Size>;
221 fn summarize_hardlinks(self) -> Self::Summary {
222 SingleInodeSummary {
223 links: self.links(),
224 paths: self.paths().len(),
225 size: *self.size(),
226 }
227 }
228}