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(¤t_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(¤t_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