parallel_disk_usage/hardlink/hardlink_list/
summary.rs1use super::{HardlinkList, Reflection, iter::Item as IterItem, reflection::ReflectionEntry};
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!(
93 "Impossible! Total of nlink ({links}) is less than detected paths ({paths}). Something must have gone wrong!"
94 );
95 }
96 }
97 }
98 summary
99 }
100}
101
102impl<Size: size::Size> HardlinkList<Size> {
103 pub fn summarize(&self) -> Summary<Size> {
105 self.iter().summarize_hardlinks()
106 }
107}
108
109impl<Size: size::Size> SummarizeHardlinks<Size> for &HardlinkList<Size> {
110 type Summary = Summary<Size>;
111 fn summarize_hardlinks(self) -> Self::Summary {
112 self.summarize()
113 }
114}
115
116impl<Size: size::Size> Reflection<Size> {
117 pub fn summarize(&self) -> Summary<Size> {
119 self.iter().summarize_hardlinks()
120 }
121}
122
123impl<Size: size::Size> SummarizeHardlinks<Size> for &Reflection<Size> {
124 type Summary = Summary<Size>;
125 fn summarize_hardlinks(self) -> Self::Summary {
126 self.summarize()
127 }
128}
129
130#[derive(Debug, Clone, Copy)]
132pub struct SummaryDisplay<'a, Size: size::Size> {
133 format: Size::DisplayFormat,
134 summary: &'a Summary<Size>,
135}
136
137impl<Size: size::Size> Display for SummaryDisplay<'_, Size> {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 let SummaryDisplay { format, summary } = self;
140 let Summary {
141 inodes,
142 exclusive_inodes,
143 all_links,
144 detected_links,
145 exclusive_links,
146 shared_size,
147 exclusive_shared_size,
148 } = summary;
149
150 let shared_size = shared_size.display(*format);
151 let exclusive_shared_size = exclusive_shared_size.display(*format);
152
153 macro_rules! ln {
154 ($($args:tt)*) => {
155 writeln!(f, $($args)*)
156 };
157 }
158
159 if inodes == &0 {
160 return ln!("There are no hardlinks.");
161 }
162
163 write!(f, "Hardlinks detected! ")?;
164 if exclusive_inodes == inodes {
165 ln!("No files have links outside this tree")?;
166 ln!("* Number of shared inodes: {inodes}")?;
167 ln!("* Total number of links: {all_links}")?;
168 ln!("* Total shared size: {shared_size}")?;
169 } else if exclusive_inodes == &0 {
170 ln!("All hardlinks within this tree have links without")?;
171 ln!("* Number of shared inodes: {inodes}")?;
172 ln!("* Total number of links: {all_links} total, {detected_links} detected")?;
173 ln!("* Total shared size: {shared_size}")?;
174 } else {
175 ln!("Some files have links outside this tree")?;
176 ln!("* Number of shared inodes: {inodes} total, {exclusive_inodes} exclusive")?;
177 ln!(
178 "* Total number of links: {all_links} total, {detected_links} detected, {exclusive_links} exclusive"
179 )?;
180 ln!("* Total shared size: {shared_size} total, {exclusive_shared_size} exclusive")?;
181 }
182
183 Ok(())
184 }
185}
186
187impl<Size: size::Size> Summary<Size> {
188 #[inline]
190 pub fn display(&self, format: Size::DisplayFormat) -> SummaryDisplay<'_, Size> {
191 SummaryDisplay {
192 format,
193 summary: self,
194 }
195 }
196}
197
198impl<Size: Copy> SummarizeHardlinks<Size> for ReflectionEntry<Size> {
199 type Summary = SingleInodeSummary<Size>;
200 fn summarize_hardlinks(self) -> Self::Summary {
201 (&self).summarize_hardlinks()
202 }
203}
204
205impl<Size: Copy> SummarizeHardlinks<Size> for &ReflectionEntry<Size> {
206 type Summary = SingleInodeSummary<Size>;
207 fn summarize_hardlinks(self) -> Self::Summary {
208 SingleInodeSummary {
209 links: self.links,
210 paths: self.paths.len(),
211 size: self.size,
212 }
213 }
214}
215
216impl<'a, Size: Copy> SummarizeHardlinks<Size> for IterItem<'a, Size> {
217 type Summary = SingleInodeSummary<Size>;
218 fn summarize_hardlinks(self) -> Self::Summary {
219 (&self).summarize_hardlinks()
220 }
221}
222
223impl<'a, Size: Copy> SummarizeHardlinks<Size> for &IterItem<'a, Size> {
224 type Summary = SingleInodeSummary<Size>;
225 fn summarize_hardlinks(self) -> Self::Summary {
226 SingleInodeSummary {
227 links: self.links(),
228 paths: self.paths().len(),
229 size: *self.size(),
230 }
231 }
232}