libmactime2/bodyfile/
bodyfile_sorter.rs

1use crate::{MactimeError, Runnable, Sorter};
2use crate::{Joinable, RunOptions};
3use bitflags::bitflags;
4use bodyfile::Bodyfile3Line;
5use std::borrow::Borrow;
6use std::cmp::Ordering;
7use std::collections::{BTreeMap, HashSet};
8use std::fmt;
9use std::sync::Arc;
10use std::sync::mpsc::Receiver;
11use std::thread::JoinHandle;
12
13pub trait Mactime2Writer: Send {
14    fn write(&self, timestamp: &i64, entry: &ListEntry) {
15        println!("{}", self.fmt(timestamp, entry));
16    }
17    fn fmt(&self, timestamp: &i64, entry: &ListEntry) -> String;
18}
19
20#[derive(Default)]
21pub struct BodyfileSorter {
22    worker: Option<JoinHandle<Result<(),MactimeError>>>,
23    receiver: Option<Receiver<Bodyfile3Line>>,
24    output: Option<Box<dyn Mactime2Writer>>
25}
26
27bitflags! {
28    #[derive(PartialEq, Debug, Clone, Copy)]
29    pub struct MACBFlags: u8 {
30        const NONE = 0b00000000;
31        const M = 0b00000001;
32        const A = 0b00000010;
33        const C = 0b00000100;
34        const B = 0b00001000;
35    }
36}
37
38impl fmt::Display for MACBFlags {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        let m = if *self & Self::M == Self::M { 'm' } else { '.' };
41        let a = if *self & Self::A == Self::A { 'a' } else { '.' };
42        let c = if *self & Self::C == Self::C { 'c' } else { '.' };
43        let b = if *self & Self::B == Self::B { 'b' } else { '.' };
44        write!(f, "{}{}{}{}", m, a, c, b)
45    }
46}
47
48#[derive(Debug)]
49pub struct ListEntry {
50    pub flags: MACBFlags,
51    pub line: Arc<Bodyfile3Line>,
52}
53
54impl Eq for ListEntry {}
55impl PartialEq for ListEntry {
56    fn eq(&self, other: &Self) -> bool {
57        self.line.get_inode().eq(other.line.get_inode()) &&
58        self.line.get_name().eq(other.line.get_name())
59    }
60}
61impl PartialOrd for ListEntry {
62    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
63        Some(self.cmp(other))
64    }
65}
66
67impl Ord for ListEntry {
68    fn cmp(&self, other: &Self) -> Ordering {
69        match self.line.get_name().cmp(other.line.get_name()) {
70            Ordering::Equal => self.line.get_inode().cmp(other.line.get_inode()),
71            other => other
72        }
73    }
74}
75
76fn insert_timestamp(
77    entries: &mut BTreeMap<i64, Vec<ListEntry>>,
78    flag: MACBFlags,
79    line: Arc<Bodyfile3Line>,
80) {
81    let timestamp = if flag.contains(MACBFlags::M) {
82        line.get_mtime()
83    } else if flag.contains(MACBFlags::A) {
84        line.get_atime()
85    } else if flag.contains(MACBFlags::C) {
86        line.get_ctime()
87    } else if flag.contains(MACBFlags::B) {
88        line.get_crtime()
89    } else {
90        -1
91    };
92
93    match entries.get_mut(&timestamp) {
94        None => {
95            let mut entries_at_ts = Vec::new();
96            let entry = ListEntry {
97                flags: flag,
98                line,
99            };
100            entries_at_ts.push(entry);
101            entries.insert(timestamp, entries_at_ts);
102        }
103
104        Some(entries_at_ts) => {
105            let entry = ListEntry {
106                flags: flag,
107                line,
108            };
109            entries_at_ts.push(entry);
110        }
111    }
112}
113
114impl Runnable for BodyfileSorter {
115    fn run(&mut self) {
116        let receiver = self.receiver.take().expect("no receiver provided; please call with_receiver()");
117        let output = self.output.take().expect("no output provided; please call with_output()");
118        self.worker = Some(
119            std::thread::spawn(move || Self::worker(receiver, output)));
120    }
121}
122
123impl BodyfileSorter {
124    pub fn with_receiver(mut self, decoder: Receiver<Bodyfile3Line>, _: RunOptions) -> Self {
125        self.receiver = Some(decoder);
126        self
127    }
128
129    pub fn with_output(mut self, output: Box<dyn Mactime2Writer>) -> Self {
130        self.output = Some(output);
131        self
132    }
133
134    fn worker(decoder: Receiver<Bodyfile3Line>, output: Box<dyn Mactime2Writer>) -> Result<(), MactimeError> {
135        let mut entries: BTreeMap<i64, Vec<ListEntry>> = BTreeMap::new();
136        let mut names: HashSet<(String,String)> = HashSet::new();
137
138        loop {
139            let line = Arc::new(match decoder.recv() {
140                Err(_) => {
141                    break;
142                }
143                Ok(l) => l,
144            });
145
146            // each name && inode SHOULD occur only once
147            {
148                let bf: &Bodyfile3Line = line.borrow();
149                if names.contains(&(bf.get_inode().to_owned(), bf.get_name().to_owned())) {
150                    log::warn!("ambigious file name: '{}' and inode '{}'", bf.get_name(), bf.get_inode());
151                    //return Err(MactimeError::AmbiguousFilename(bf.get_name().to_owned()))
152                }
153                names.insert((bf.get_inode().to_owned(), bf.get_name().to_owned()));
154            } // delete the borrow to line
155
156            // we need *some* value in mactimes!
157            if line.get_mtime() == -1
158                && line.get_atime() == -1
159                && line.get_ctime() == -1
160                && line.get_crtime() == -1
161            {
162                insert_timestamp(&mut entries, MACBFlags::NONE, Arc::clone(&line));
163                continue;
164            }
165
166            let mut flags: [MACBFlags; 4] = [MACBFlags::NONE; 4];
167
168            if line.get_mtime() != -1 {
169                flags[0] |= MACBFlags::M;
170            }
171            if line.get_atime() != -1 {
172                if line.get_mtime() == line.get_atime() {
173                    flags[0] |= MACBFlags::A;
174                } else {
175                    flags[1] |= MACBFlags::A;
176                }
177            }
178            if line.get_ctime() != -1 {
179                if line.get_mtime() == line.get_ctime() {
180                    flags[0] |= MACBFlags::C;
181                } else if line.get_atime() == line.get_ctime() {
182                    flags[1] |= MACBFlags::C;
183                } else {
184                    flags[2] |= MACBFlags::C;
185                }
186            }
187            if line.get_crtime() != -1 {
188                if line.get_mtime() == line.get_crtime() {
189                    flags[0] |= MACBFlags::B;
190                } else if line.get_atime() == line.get_crtime() {
191                    flags[1] |= MACBFlags::B;
192                } else if line.get_ctime() == line.get_crtime() {
193                    flags[2] |= MACBFlags::B;
194                } else {
195                    flags[3] |= MACBFlags::B;
196                }
197            }
198            for flag in flags.iter() {
199                if flag != &MACBFlags::NONE {
200                    insert_timestamp(&mut entries, *flag, Arc::clone(&line));
201                }
202            }
203        }
204
205        for (ts, entries_at_ts) in entries.iter() {
206            for line in entries_at_ts {
207                output.write(ts, line);
208            }
209        }
210        Ok(())
211    }
212}
213
214impl Joinable<Result<(), MactimeError>> for BodyfileSorter {
215    fn join(&mut self) -> std::thread::Result<Result<(), MactimeError>> {
216        self.worker.take().unwrap().join()
217    }
218}
219
220impl Sorter<Result<(), MactimeError>> for BodyfileSorter {}