Skip to main content

parallel_disk_usage/hardlink/hardlink_list/
reflection.rs

1use super::{HardlinkList, InodeKey, Value};
2use crate::{device::DeviceNumber, hardlink::LinkPathListReflection, inode::InodeNumber};
3use dashmap::DashMap;
4use derive_more::{Display, Error, Into, IntoIterator};
5use into_sorted::IntoSortedUnstable;
6use pipe_trait::Pipe;
7
8#[cfg(feature = "json")]
9use serde::{Deserialize, Serialize};
10
11/// Intermediate format used for construction and inspection of [`HardlinkList`]'s
12/// internal content.
13///
14/// **Guarantees:**
15/// * Every pair of an inode number and a device number is unique.
16/// * The internal list is always sorted by pairs of an inode number and a device number.
17///
18/// **Equality:** `Reflection` implements `PartialEq` and `Eq` traits.
19///
20/// **Serialization and deserialization:** _(feature: `json`)_ `Reflection` implements
21/// `Serialize` and `Deserialize` traits, this allows functions in `serde_json` to convert
22/// a `Reflection` into/from JSON.
23#[derive(Debug, Clone, PartialEq, Eq, Into, IntoIterator)]
24#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
25pub struct Reflection<Size>(Vec<ReflectionEntry<Size>>);
26
27impl<Size> Reflection<Size> {
28    /// Get the number of entries inside the reflection.
29    #[inline]
30    pub fn len(&self) -> usize {
31        self.0.len()
32    }
33
34    /// Check whether the reflection has any entry.
35    #[inline]
36    pub fn is_empty(&self) -> bool {
37        self.0.is_empty()
38    }
39
40    /// Iterate over the entries.
41    #[inline]
42    pub fn iter(&self) -> impl Iterator<Item = &ReflectionEntry<Size>> + Clone {
43        self.0.iter()
44    }
45}
46
47/// An entry in [`Reflection`].
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
50pub struct ReflectionEntry<Size> {
51    /// The inode number of the file.
52    pub ino: InodeNumber,
53    /// Device number of the filesystem the inode belongs to.
54    pub dev: DeviceNumber,
55    /// Size of the file.
56    pub size: Size,
57    /// Total number of links of the file, both listed (in [`Self::paths`]) and unlisted.
58    pub links: u64,
59    /// Paths to the detected links of the file.
60    pub paths: LinkPathListReflection,
61}
62
63impl<Size> ReflectionEntry<Size> {
64    /// Create a new entry.
65    #[inline]
66    fn new(InodeKey { ino, dev }: InodeKey, Value { size, links, paths }: Value<Size>) -> Self {
67        let paths = paths.into();
68        ReflectionEntry {
69            ino,
70            dev,
71            size,
72            links,
73            paths,
74        }
75    }
76
77    /// Dissolve [`ReflectionEntry`] into a pair of [`InodeKey`] and [`Value`].
78    #[inline]
79    fn dissolve(self) -> (InodeKey, Value<Size>) {
80        let ReflectionEntry {
81            ino,
82            dev,
83            size,
84            links,
85            paths,
86        } = self;
87        let paths = paths.into();
88        (InodeKey { ino, dev }, Value { size, links, paths })
89    }
90
91    /// Sorting key to be used in the "sort by key" family of functions.
92    ///
93    /// Sort by the inode number first, then by the device number.
94    ///
95    /// This function returns a pair of 2 `u64`s instead of a pair of 2 wrapper
96    /// types because we prefer them not to have to implement `Ord`.
97    #[inline]
98    fn sorting_key(&self) -> (u64, u64) {
99        (u64::from(self.ino), u64::from(self.dev))
100    }
101}
102
103impl<Size> From<Vec<ReflectionEntry<Size>>> for Reflection<Size> {
104    /// Sort the list by inode numbers and device numbers, then create the reflection.
105    fn from(list: Vec<ReflectionEntry<Size>>) -> Self {
106        list.into_sorted_unstable_by_key(ReflectionEntry::sorting_key)
107            .pipe(Reflection)
108    }
109}
110
111impl<Size> From<HardlinkList<Size>> for Reflection<Size> {
112    fn from(HardlinkList(list): HardlinkList<Size>) -> Self {
113        list.into_iter()
114            .map(|(key, value)| ReflectionEntry::new(key, value))
115            .collect::<Vec<_>>()
116            .pipe(Reflection::from)
117    }
118}
119
120/// Error that occurs when an attempt to convert a [`Reflection`] into a
121/// [`HardlinkList`] fails.
122#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq)]
123#[non_exhaustive]
124pub enum ConversionError {
125    /// When the source has a duplicated `(inode, device)` pair.
126    #[display("Inode {_0} on device {_1} is duplicated")]
127    DuplicatedInode(InodeNumber, DeviceNumber),
128}
129
130impl ConversionError {
131    /// Convenient function to convert an [`InodeKey`] into a [`ConversionError::DuplicatedInode`].
132    ///
133    /// We don't embed [`InodeKey`] directly into [`ConversionError::DuplicatedInode`] because of
134    /// their difference in visibility: One is private, the other public.
135    fn duplicated_inode(InodeKey { ino, dev }: InodeKey) -> Self {
136        ConversionError::DuplicatedInode(ino, dev)
137    }
138}
139
140impl<Size> TryFrom<Reflection<Size>> for HardlinkList<Size> {
141    type Error = ConversionError;
142    fn try_from(Reflection(entries): Reflection<Size>) -> Result<Self, Self::Error> {
143        let map = DashMap::with_capacity(entries.len());
144
145        for entry in entries {
146            let (key, value) = entry.dissolve();
147            if map.insert(key, value).is_some() {
148                return key.pipe(ConversionError::duplicated_inode).pipe(Err);
149            }
150        }
151
152        map.pipe(HardlinkList).pipe(Ok)
153    }
154}