1 use std::fs::{File, Metadata};
2 use std::io::prelude::*;
3 use std::path::PathBuf;
4 use std::time::{Duration, SystemTime, UNIX_EPOCH};
5 use std::{fs, thread};
6 
7 use crate::common::Common;
8 use crate::common_directory::Directories;
9 use crate::common_items::ExcludedItems;
10 use crate::common_messages::Messages;
11 use crate::common_traits::*;
12 use crossbeam_channel::Receiver;
13 use std::io::BufWriter;
14 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
15 use std::sync::Arc;
16 use std::thread::sleep;
17 
18 #[derive(Debug)]
19 pub struct ProgressData {
20     pub current_stage: u8,
21     pub max_stage: u8,
22     pub files_checked: usize,
23 }
24 
25 #[derive(Eq, PartialEq, Clone, Debug)]
26 pub enum DeleteMethod {
27     None,
28     Delete,
29 }
30 
31 #[derive(Clone)]
32 pub struct FileEntry {
33     pub path: PathBuf,
34     pub modified_date: u64,
35 }
36 
37 /// Info struck with helpful information's about results
38 #[derive(Default)]
39 pub struct Info {
40     pub number_of_temporary_files: usize,
41     pub number_of_removed_files: usize,
42     pub number_of_failed_to_remove_files: usize,
43 }
44 impl Info {
new() -> Self45     pub fn new() -> Self {
46         Default::default()
47     }
48 }
49 
50 /// Struct with required information's to work
51 pub struct Temporary {
52     text_messages: Messages,
53     information: Info,
54     temporary_files: Vec<FileEntry>,
55     directories: Directories,
56     excluded_items: ExcludedItems,
57     recursive_search: bool,
58     delete_method: DeleteMethod,
59     stopped_search: bool,
60 }
61 
62 impl Temporary {
new() -> Self63     pub fn new() -> Self {
64         Self {
65             text_messages: Messages::new(),
66             information: Info::new(),
67             recursive_search: true,
68             directories: Directories::new(),
69             excluded_items: ExcludedItems::new(),
70             delete_method: DeleteMethod::None,
71             temporary_files: vec![],
72             stopped_search: false,
73         }
74     }
75 
76     /// Finding temporary files, save results to internal struct variables
find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>)77     pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) {
78         self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
79         if !self.check_files(stop_receiver, progress_sender) {
80             self.stopped_search = true;
81             return;
82         }
83         self.delete_files();
84         self.debug_print();
85     }
get_stopped_search(&self) -> bool86     pub fn get_stopped_search(&self) -> bool {
87         self.stopped_search
88     }
89 
get_temporary_files(&self) -> &Vec<FileEntry>90     pub const fn get_temporary_files(&self) -> &Vec<FileEntry> {
91         &self.temporary_files
92     }
get_text_messages(&self) -> &Messages93     pub const fn get_text_messages(&self) -> &Messages {
94         &self.text_messages
95     }
96 
get_information(&self) -> &Info97     pub const fn get_information(&self) -> &Info {
98         &self.information
99     }
100 
set_delete_method(&mut self, delete_method: DeleteMethod)101     pub fn set_delete_method(&mut self, delete_method: DeleteMethod) {
102         self.delete_method = delete_method;
103     }
104 
set_recursive_search(&mut self, recursive_search: bool)105     pub fn set_recursive_search(&mut self, recursive_search: bool) {
106         self.recursive_search = recursive_search;
107     }
108 
set_included_directory(&mut self, included_directory: Vec<PathBuf>) -> bool109     pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>) -> bool {
110         self.directories.set_included_directory(included_directory, &mut self.text_messages)
111     }
112 
set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>)113     pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>) {
114         self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
115     }
116 
set_excluded_items(&mut self, excluded_items: Vec<String>)117     pub fn set_excluded_items(&mut self, excluded_items: Vec<String>) {
118         self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
119     }
120 
check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool121     fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
122         let start_time: SystemTime = SystemTime::now();
123         let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
124 
125         // Add root folders for finding
126         for id in &self.directories.included_directories {
127             folders_to_check.push(id.clone());
128         }
129 
130         //// PROGRESS THREAD START
131         const LOOP_DURATION: u32 = 200; //in ms
132         let progress_thread_run = Arc::new(AtomicBool::new(true));
133 
134         let atomic_file_counter = Arc::new(AtomicUsize::new(0));
135 
136         let progress_thread_handle;
137         if let Some(progress_sender) = progress_sender {
138             let progress_send = progress_sender.clone();
139             let progress_thread_run = progress_thread_run.clone();
140             let atomic_file_counter = atomic_file_counter.clone();
141             progress_thread_handle = thread::spawn(move || loop {
142                 progress_send
143                     .unbounded_send(ProgressData {
144                         current_stage: 0,
145                         max_stage: 0,
146                         files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize,
147                     })
148                     .unwrap();
149                 if !progress_thread_run.load(Ordering::Relaxed) {
150                     break;
151                 }
152                 sleep(Duration::from_millis(LOOP_DURATION as u64));
153             });
154         } else {
155             progress_thread_handle = thread::spawn(|| {});
156         }
157         //// PROGRESS THREAD END
158 
159         while !folders_to_check.is_empty() {
160             if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
161                 // End thread which send info to gui
162                 progress_thread_run.store(false, Ordering::Relaxed);
163                 progress_thread_handle.join().unwrap();
164                 return false;
165             }
166 
167             let current_folder = folders_to_check.pop().unwrap();
168             // Read current dir, if permission are denied just go to next
169             let read_dir = match fs::read_dir(&current_folder) {
170                 Ok(t) => t,
171                 Err(e) => {
172                     self.text_messages.warnings.push(format!("Cannot open dir {}, reason {}", current_folder.display(), e));
173                     continue;
174                 } // Permissions denied
175             };
176 
177             // Check every sub folder/file/link etc.
178             'dir: for entry in read_dir {
179                 let entry_data = match entry {
180                     Ok(t) => t,
181                     Err(e) => {
182                         self.text_messages.warnings.push(format!("Cannot read entry in dir {}, reason {}", current_folder.display(), e));
183                         continue;
184                     } //Permissions denied
185                 };
186                 let metadata: Metadata = match entry_data.metadata() {
187                     Ok(t) => t,
188                     Err(e) => {
189                         self.text_messages.warnings.push(format!("Cannot read metadata in dir {}, reason {}", current_folder.display(), e));
190                         continue;
191                     } //Permissions denied
192                 };
193                 if metadata.is_dir() {
194                     if !self.recursive_search {
195                         continue;
196                     }
197 
198                     let next_folder = current_folder.join(entry_data.file_name());
199                     if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) {
200                         continue 'dir;
201                     }
202 
203                     folders_to_check.push(next_folder);
204                 } else if metadata.is_file() {
205                     atomic_file_counter.fetch_add(1, Ordering::Relaxed);
206                     let file_name_lowercase: String = match entry_data.file_name().into_string() {
207                         Ok(t) => t,
208                         Err(_inspected) => {
209                             println!("File {:?} has not valid UTF-8 name", entry_data);
210                             continue 'dir;
211                         }
212                     }
213                     .to_lowercase();
214 
215                     // Temporary files which needs to have dot in name(not sure if exists without dot)
216                     let temporary_with_dot = ["#", "thumbs.db", ".bak", "~", ".tmp", ".temp", ".ds_store", ".crdownload", ".part", ".cache", ".dmp", ".download", ".partial"];
217 
218                     if !file_name_lowercase.contains('.') || !temporary_with_dot.iter().any(|f| file_name_lowercase.ends_with(f)) {
219                         continue 'dir;
220                     }
221                     // Checking files
222                     let current_file_name = current_folder.join(entry_data.file_name());
223                     if self.excluded_items.is_excluded(&current_file_name) {
224                         continue 'dir;
225                     }
226 
227                     // Creating new file entry
228                     let fe: FileEntry = FileEntry {
229                         path: current_file_name.clone(),
230                         modified_date: match metadata.modified() {
231                             Ok(t) => match t.duration_since(UNIX_EPOCH) {
232                                 Ok(d) => d.as_secs(),
233                                 Err(_inspected) => {
234                                     self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display()));
235                                     0
236                                 }
237                             },
238                             Err(e) => {
239                                 self.text_messages.warnings.push(format!("Unable to get modification date from file {}, reason {}", current_file_name.display(), e));
240                                 0
241                             } // Permissions Denied
242                         },
243                     };
244 
245                     // Adding files to Vector
246                     self.temporary_files.push(fe);
247                 }
248             }
249         }
250         // End thread which send info to gui
251         progress_thread_run.store(false, Ordering::Relaxed);
252         progress_thread_handle.join().unwrap();
253         self.information.number_of_temporary_files = self.temporary_files.len();
254 
255         Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string());
256         true
257     }
258 
259     /// Function to delete files, from filed Vector
delete_files(&mut self)260     fn delete_files(&mut self) {
261         let start_time: SystemTime = SystemTime::now();
262 
263         match self.delete_method {
264             DeleteMethod::Delete => {
265                 for file_entry in &self.temporary_files {
266                     if fs::remove_file(file_entry.path.clone()).is_err() {
267                         self.text_messages.warnings.push(file_entry.path.display().to_string());
268                     }
269                 }
270             }
271             DeleteMethod::None => {
272                 //Just do nothing
273             }
274         }
275 
276         Common::print_time(start_time, SystemTime::now(), "delete_files".to_string());
277     }
278 }
279 impl Default for Temporary {
default() -> Self280     fn default() -> Self {
281         Self::new()
282     }
283 }
284 
285 impl DebugPrint for Temporary {
286     #[allow(dead_code)]
287     #[allow(unreachable_code)]
debug_print(&self)288     fn debug_print(&self) {
289         #[cfg(not(debug_assertions))]
290         {
291             return;
292         }
293         println!("---------------DEBUG PRINT---------------");
294         println!("### Information's");
295 
296         println!("Errors size - {}", self.text_messages.errors.len());
297         println!("Warnings size - {}", self.text_messages.warnings.len());
298         println!("Messages size - {}", self.text_messages.messages.len());
299         println!("Number of removed files - {}", self.information.number_of_removed_files);
300         println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
301 
302         println!("### Other");
303 
304         println!("Temporary list size - {}", self.temporary_files.len());
305         println!("Excluded items - {:?}", self.excluded_items.items);
306         println!("Included directories - {:?}", self.directories.included_directories);
307         println!("Excluded directories - {:?}", self.directories.excluded_directories);
308         println!("Recursive search - {}", self.recursive_search.to_string());
309         println!("Delete Method - {:?}", self.delete_method);
310         println!("-----------------------------------------");
311     }
312 }
313 impl SaveResults for Temporary {
save_results_to_file(&mut self, file_name: &str) -> bool314     fn save_results_to_file(&mut self, file_name: &str) -> bool {
315         let start_time: SystemTime = SystemTime::now();
316         let file_name: String = match file_name {
317             "" => "results.txt".to_string(),
318             k => k.to_string(),
319         };
320 
321         let file_handler = match File::create(&file_name) {
322             Ok(t) => t,
323             Err(e) => {
324                 self.text_messages.errors.push(format!("Failed to create file {}, reason {}", file_name, e));
325                 return false;
326             }
327         };
328         let mut writer = BufWriter::new(file_handler);
329 
330         if let Err(e) = writeln!(
331             writer,
332             "Results of searching {:?} with excluded directories {:?} and excluded items {:?}",
333             self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items
334         ) {
335             self.text_messages.errors.push(format!("Failed to save results to file {}, reason {}", file_name, e));
336             return false;
337         }
338 
339         if !self.temporary_files.is_empty() {
340             writeln!(writer, "Found {} temporary files.", self.information.number_of_temporary_files).unwrap();
341             for file_entry in self.temporary_files.iter() {
342                 writeln!(writer, "{}", file_entry.path.display()).unwrap();
343             }
344         } else {
345             write!(writer, "Not found any temporary files.").unwrap();
346         }
347         Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string());
348         true
349     }
350 }
351 impl PrintResults for Temporary {
print_results(&self)352     fn print_results(&self) {
353         let start_time: SystemTime = SystemTime::now();
354         println!("Found {} temporary files.\n", self.information.number_of_temporary_files);
355         for file_entry in self.temporary_files.iter() {
356             println!("{}", file_entry.path.display());
357         }
358 
359         Common::print_time(start_time, SystemTime::now(), "print_entries".to_string());
360     }
361 }
362