libmactime2/bodyfile/
bodyfile_sorter.rs1use 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(×tamp) {
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 {
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 }
153 names.insert((bf.get_inode().to_owned(), bf.get_name().to_owned()));
154 } 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 {}