Skip to main content

parallel_disk_usage/hardlink/hardlink_list/
summary.rs

1use 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/// Summary from [`HardlinkList`] or [`Reflection`].
14#[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    /// Number of shared inodes, each with more than 1 links (i.e. `nlink > 1`).
20    pub inodes: usize,
21
22    /// Number of [shared inodes](Self::inodes) that don't have links outside the measured tree.
23    ///
24    /// This number is expected to be less than or equal to [`Self::inodes`].
25    pub exclusive_inodes: usize,
26
27    /// Totality of the numbers of links of all [shared inodes](Self::inodes).
28    pub all_links: u64,
29
30    /// Total number of links of [shared inodes](Self::inodes) that were detected within the measured tree.
31    ///
32    /// This number is expected to be less than or equal to [`Self::all_links`].
33    pub detected_links: usize,
34
35    /// Total number of links of [shared inodes](Self::inodes) that don't have links outside the measured tree.
36    ///
37    /// This number is expected to be less than or equal to [`Self::detected_links`].
38    pub exclusive_links: usize,
39
40    /// Totality of the sizes of all [shared inodes](Self::inodes).
41    pub shared_size: Size,
42
43    /// Totality of the sizes of all [shared inodes](Self::inodes) that don't have links outside the measured tree.
44    ///
45    /// This number is expected to be less than or equal to [`Self::all_links`].
46    pub exclusive_shared_size: Size,
47}
48
49/// Ability to summarize into a [`Summary`].
50pub trait SummarizeHardlinks<Size>: Sized {
51    /// The result of [`SummarizeHardlinks::summarize_hardlinks`].
52    type Summary;
53    /// Summarize into a summary of shared links and size.
54    fn summarize_hardlinks(self) -> Self::Summary;
55}
56
57/// Summary of a single unique file.
58#[derive(Debug, Clone, Copy)]
59pub struct SingleInodeSummary<Size> {
60    /// Total number of all links to the file.
61    links: u64,
62    /// Number of detected links to the file.
63    paths: usize,
64    /// Size of the file.
65    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; // `links` and `paths` are both fine, but `paths` doesn't require type cast
89                    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    /// Create summary for the shared links and size.
104    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    /// Create summary for the shared links and size.
118    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/// Return type of [`Summary::display`] which implements [`Display`].
131#[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    /// Turns this [`Summary`] into something [displayable](Display).
189    #[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}