1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/// compute consolidated data for directories: modified date, size, and count.
/// A cache is used to avoid recomputing the same directories again and again.
/// On unix, hard links are checked to avoid counting twice an inode.

mod sum_computation;

use {
    crate::{
        app::*,
        task_sync::Dam,
    },
    ahash::AHashMap,
    once_cell::sync::Lazy,
    std::{
        ops::AddAssign,
        path::{Path, PathBuf},
        sync::Mutex,
    },
};

pub const DEFAULT_THREAD_COUNT: usize = 5;

static SUM_CACHE: Lazy<Mutex<AHashMap<PathBuf, FileSum>>> = Lazy::new(|| {
    Mutex::new(AHashMap::default())
});

pub fn clear_cache() {
    SUM_CACHE.lock().unwrap().clear();
}

/// Reduction of counts, dates and sizes on a file or directory
#[derive(Debug, Copy, Clone)]
pub struct FileSum {
    real_size: u64, // bytes, the space it takes on disk
    count: usize,   // number of files
    modified: u32,  // seconds from Epoch to last modification, or 0 if there was an error
    sparse: bool,   // only for non directories: tells whether the file is sparse
}

impl FileSum {
    pub fn new(
        real_size: u64,
        sparse: bool,
        count: usize,
        modified: u32,
    ) -> Self {
        Self { real_size, count, modified, sparse }
    }

    pub fn zero() -> Self {
        Self::new(0, false, 0, 0)
    }

    pub fn incr(&mut self) {
        self.count += 1;
    }

    /// return the sum of the given file, which is assumed
    /// to be a normal file (ie not a directory)
    pub fn from_file(path: &Path) -> Self {
        sum_computation::compute_file_sum(path)
    }

    /// Return the sum of the directory, either by computing it of by
    ///  fetching it from cache.
    /// If the lifetime expires before complete computation, None is returned.
    pub fn from_dir(path: &Path, dam: &Dam, con: &AppContext) -> Option<Self> {
        let mut sum_cache = SUM_CACHE.lock().unwrap();
        match sum_cache.get(path) {
            Some(sum) => Some(*sum),
            None => {
                let sum = time!(
                    "sum computation",
                    path,
                    sum_computation::compute_dir_sum(path, &mut sum_cache, dam, con),
                );
                if let Some(sum) = sum {
                    sum_cache.insert(PathBuf::from(path), sum);
                }
                sum
            }
        }
    }

    pub fn part_of_size(self, total: Self) -> f32 {
        if total.real_size == 0 {
            0.0
        } else {
            self.real_size as f32 / total.real_size as f32
        }
    }
    /// return the number of files (normally at least 1)
    pub fn to_count(self) -> usize {
        self.count
    }
    /// return the number of seconds from Epoch to last modification,
    /// or 0 if the computation failed
    pub fn to_seconds(self) -> u32 {
        self.modified
    }
    /// return the size in bytes
    pub fn to_size(self) -> u64 {
        self.real_size
    }
    pub fn to_valid_seconds(self) -> Option<i64> {
        if self.modified != 0 {
            Some(self.modified as i64)
        } else {
            None
        }
    }
    /// tell whether the file has holes (in which case the size displayed by
    /// other tools may be greater than the "real" one returned by broot).
    /// Not computed (return false) on windows or for directories.
    pub fn is_sparse(self) -> bool {
        self.sparse
    }
}

impl AddAssign for FileSum {
    #[allow(clippy::suspicious_op_assign_impl)]
    fn add_assign(&mut self, other: Self) {
        *self = Self::new(
            self.real_size + other.real_size,
            self.sparse | other.sparse,
            self.count + other.count,
            self.modified.max(other.modified),
        );
    }
}