summaryrefslogtreecommitdiffstats
path: root/src/walk.rs
blob: a5daac66682560d850b21a027fdfa7e25d69c286 (plain)
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
use std::collections::HashSet;
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::thread;

use crossbeam_channel as channel;

use rayon::prelude::*;

#[derive(Eq,PartialEq,Hash)]
struct UniqueID(u64, u64);

struct SizeEntry(Option<UniqueID>, u64);

fn walk(tx: channel::Sender<SizeEntry>, entries: &[PathBuf]) {
    entries.into_par_iter().for_each_with(tx, |tx_ref, entry| {
        if let Ok(metadata) = entry.symlink_metadata() {
            // If the entry has more than one hard link, generate
            // a unique ID consisting of device and inode in order
            // not to count this entry twice.
            let unique_id = if metadata.is_file() && metadata.nlink() > 1 {
                Some(UniqueID(metadata.dev(), metadata.ino()))
            } else {
                None
            };

            let size = metadata.len();

            tx_ref.send(SizeEntry(unique_id, size)).unwrap();

            if metadata.is_dir() {
                let mut children = vec![];
                for child_entry in fs::read_dir(entry).unwrap() {
                    if let Ok(child_entry) = child_entry {
                        children.push(child_entry.path());
                    }
                }

                walk(tx_ref.clone(), &children[..]);
            };
        } else {
            eprintln!("Could not get file metadata: '{}'", entry.to_string_lossy());
        };
    });
}

pub struct Walk<'a> {
    root_directories: &'a [PathBuf],
    threads: usize,
}

impl<'a> Walk<'a> {
    pub fn new(root_directories: &'a [PathBuf], threads: usize) -> Walk {
        Walk {
            root_directories,
            threads,
        }
    }

    pub fn run(&self) -> u64 {
        let (tx, rx) = channel::unbounded();

        let receiver_thread = thread::spawn(move || {
            let mut total = 0;
            let mut ids = HashSet::new();
            for SizeEntry(unique_id, size) in rx {
                if let Some(unique_id) = unique_id {
                    // Only count this entry if the ID has not been seen
                    if ids.insert(unique_id) {
                        total += size;
                    }
                } else {
                    total += size;
                }
            }

            total
        });

        let pool = rayon::ThreadPoolBuilder::new()
            .num_threads(self.threads)
            .build()
            .unwrap();
        pool.install(|| walk(tx, self.root_directories));

        receiver_thread.join().unwrap()
    }
}