parallel_disk_usage/hardlink/hardlink_list/
reflection.rs

1use super::{HardlinkList, Value};
2use crate::{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 inode number is unique.
16/// * The internal list is always sorted by inode numbers.
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    /// Size of the file.
54    pub size: Size,
55    /// Total number of links of the file, both listed (in [`Self::paths`]) and unlisted.
56    pub links: u64,
57    /// Paths to the detected links of the file.
58    pub paths: LinkPathListReflection,
59}
60
61impl<Size> ReflectionEntry<Size> {
62    /// Create a new entry.
63    #[inline]
64    fn new(ino: InodeNumber, Value { size, links, paths }: Value<Size>) -> Self {
65        let paths = paths.into();
66        ReflectionEntry {
67            ino,
68            size,
69            links,
70            paths,
71        }
72    }
73
74    /// Dissolve [`ReflectionEntry`] into a pair of [`InodeNumber`] and [`Value`].
75    #[inline]
76    fn dissolve(self) -> (InodeNumber, Value<Size>) {
77        let ReflectionEntry {
78            ino,
79            size,
80            links,
81            paths,
82        } = self;
83        let paths = paths.into();
84        (ino, Value { size, links, paths })
85    }
86}
87
88impl<Size> From<Vec<ReflectionEntry<Size>>> for Reflection<Size> {
89    /// Sort the list by inode numbers, then create the reflection.
90    fn from(list: Vec<ReflectionEntry<Size>>) -> Self {
91        list.into_sorted_unstable_by_key(|entry| u64::from(entry.ino))
92            .pipe(Reflection)
93    }
94}
95
96impl<Size> From<HardlinkList<Size>> for Reflection<Size> {
97    fn from(HardlinkList(list): HardlinkList<Size>) -> Self {
98        list.into_iter()
99            .map(|(ino, value)| ReflectionEntry::new(ino, value))
100            .collect::<Vec<_>>()
101            .pipe(Reflection::from)
102    }
103}
104
105/// Error that occurs when an attempt to convert a [`Reflection`] into a
106/// [`HardlinkList`] fails.
107#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum ConversionError {
110    /// When the source has duplicated inode numbers.
111    #[display("Inode number {_0} is duplicated")]
112    DuplicatedInode(#[error(not(source))] InodeNumber),
113}
114
115impl<Size> TryFrom<Reflection<Size>> for HardlinkList<Size> {
116    type Error = ConversionError;
117    fn try_from(Reflection(entries): Reflection<Size>) -> Result<Self, Self::Error> {
118        let map = DashMap::with_capacity(entries.len());
119
120        for entry in entries {
121            let (ino, value) = entry.dissolve();
122            if map.insert(ino, value).is_some() {
123                return ino.pipe(ConversionError::DuplicatedInode).pipe(Err);
124            }
125        }
126
127        map.pipe(HardlinkList).pipe(Ok)
128    }
129}