mirror of
				https://github.com/Dummi26/tuifile.git
				synced 2025-10-31 09:16:15 +01:00 
			
		
		
		
	updated color scheme, added threaded modes, added a way to set permission bits (chmod)
This commit is contained in:
		
							parent
							
								
									37557aa358
								
							
						
					
					
						commit
						71968f7f64
					
				
							
								
								
									
										35
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								README.md
									
									
									
									
									
								
							| @ -11,6 +11,7 @@ TuiFile can | |||||||
| - create new directories | - create new directories | ||||||
| - copy, move and delete | - copy, move and delete | ||||||
| - quickly open your `$TERM` and `$EDITOR` | - quickly open your `$TERM` and `$EDITOR` | ||||||
|  | - build the file list on a background thread to avoid blocking | ||||||
| - add more features (open an issue with ideas if you have any) | - add more features (open an issue with ideas if you have any) | ||||||
| 
 | 
 | ||||||
| ## Demo | ## Demo | ||||||
| @ -23,6 +24,7 @@ https://github.com/Dummi26/tuifile/assets/67615357/0b0553c9-72e5-4d38-8537-f6cc3 | |||||||
| 
 | 
 | ||||||
| ### Global | ### Global | ||||||
| 
 | 
 | ||||||
|  | Ctrl+C/D -> quit | ||||||
| Ctrl+Up/K -> previous | Ctrl+Up/K -> previous | ||||||
| Ctrl+Down/J -> next | Ctrl+Down/J -> next | ||||||
| Ctrl+Left/H -> close | Ctrl+Left/H -> close | ||||||
| @ -37,9 +39,12 @@ Ctrl+Right/L -> duplicate | |||||||
| - S -> Select or toggle current | - S -> Select or toggle current | ||||||
| - D -> Deselect all | - D -> Deselect all | ||||||
| - F -> focus Find/Filter bar | - F -> focus Find/Filter bar | ||||||
|  | - M -> set Mode bysed on Find/Filter bar | ||||||
| - N -> New directory (name taken from find/filter bar text) | - N -> New directory (name taken from find/filter bar text) | ||||||
| - C -> Copy selected to this directory | - C -> Copy selected to this directory | ||||||
| - R -> remove selected files and directories (not recursive: also requires selecting the directories content) | - R -> remove selected files and directories (not recursive: also requires selecting the directories content) | ||||||
|  | - P -> set Permissions (mode taken as base-8 number from find/filter bar text) | ||||||
|  | - O -> set Owner (and group - TODO!) | ||||||
| - 1-9 or 0 -> set recursive depth limit (0 = infinite) | - 1-9 or 0 -> set recursive depth limit (0 = infinite) | ||||||
| - T -> open terminal here | - T -> open terminal here | ||||||
| - E -> open this file in your editor | - E -> open this file in your editor | ||||||
| @ -50,3 +55,33 @@ Ctrl+Right/L -> duplicate | |||||||
| - Enter -> back & filter | - Enter -> back & filter | ||||||
| - Backspace -> delete | - Backspace -> delete | ||||||
| - type to enter search regex | - type to enter search regex | ||||||
|  | 
 | ||||||
|  | ## File List Modes | ||||||
|  | 
 | ||||||
|  | ### Blocking | ||||||
|  | 
 | ||||||
|  | This is the simplest mode. If listing all the files takes a long time, the program will be unresponsive. | ||||||
|  | 
 | ||||||
|  | To enable, type `b` into the filter bar, go back to files mode, and press `m`. | ||||||
|  | 
 | ||||||
|  | ### Threaded | ||||||
|  | 
 | ||||||
|  | To avoid blocking, this mode performs all filesystem operations in the background. | ||||||
|  | Can cause flickering and isn't as responsive on fast disks. | ||||||
|  | 
 | ||||||
|  | To enable, type `t` into the filter bar, go back to files mode, and press `m`. | ||||||
|  | 
 | ||||||
|  | ### Timeout | ||||||
|  | 
 | ||||||
|  | Like blocking, but after the timeout is reached, tuifile will stop adding more files to the list. | ||||||
|  | This means that file lists may be incomplete. | ||||||
|  | 
 | ||||||
|  | To enable, type `b<seconds>` into the filter bar, go back to files mode, and press `m`. | ||||||
|  | Replace `<seconds>` with a number like `1` or `0.3`. | ||||||
|  | 
 | ||||||
|  | ### TimeoutThenThreaded | ||||||
|  | 
 | ||||||
|  | Like blocking, but after the timeout is reached, tuifile will cancel the operation and restart it in threaded mode. | ||||||
|  | 
 | ||||||
|  | To enable, type `t<seconds>` into the filter bar, go back to files mode, and press `m`. | ||||||
|  | Replace `<seconds>` with a number like `1` or `0.3`. | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -3,10 +3,9 @@ mod tasks; | |||||||
| mod updates; | mod updates; | ||||||
| 
 | 
 | ||||||
| use std::{ | use std::{ | ||||||
|     fs::{self, DirEntry, Metadata}, |     fs::{self, Metadata}, | ||||||
|     io::{self, StdoutLock}, |     io::{self, StdoutLock}, | ||||||
|     path::PathBuf, |     path::PathBuf, | ||||||
|     rc::Rc, |  | ||||||
|     sync::{Arc, Mutex}, |     sync::{Arc, Mutex}, | ||||||
|     thread::JoinHandle, |     thread::JoinHandle, | ||||||
| }; | }; | ||||||
| @ -46,6 +45,7 @@ fn main() -> io::Result<()> { | |||||||
|         terminal_command: std::env::var("TERM").unwrap_or("alacritty".to_string()), |         terminal_command: std::env::var("TERM").unwrap_or("alacritty".to_string()), | ||||||
|         editor_command: std::env::var("EDITOR").unwrap_or("nano".to_string()), |         editor_command: std::env::var("EDITOR").unwrap_or("nano".to_string()), | ||||||
|         live_search: !args.no_live_search, |         live_search: !args.no_live_search, | ||||||
|  |         info_what: vec![0, 1], | ||||||
|     }; |     }; | ||||||
|     if args.check { |     if args.check { | ||||||
|         eprintln!("Terminal: {}", share.terminal_command); |         eprintln!("Terminal: {}", share.terminal_command); | ||||||
| @ -111,11 +111,7 @@ fn main() -> io::Result<()> { | |||||||
|                                 .filter(|e| e.selected) |                                 .filter(|e| e.selected) | ||||||
|                                 .filter_map(|e| { |                                 .filter_map(|e| { | ||||||
|                                     Some(( |                                     Some(( | ||||||
|                                         e.entry |                                         e.path.strip_prefix(&v.current_dir).ok()?.to_owned(), | ||||||
|                                             .path() |  | ||||||
|                                             .strip_prefix(&v.current_dir) |  | ||||||
|                                             .ok()? |  | ||||||
|                                             .to_owned(), |  | ||||||
|                                         e.rel_depth == v.scan_files_max_depth, |                                         e.rel_depth == v.scan_files_max_depth, | ||||||
|                                     )) |                                     )) | ||||||
|                                 }) |                                 }) | ||||||
| @ -193,30 +189,39 @@ struct Share { | |||||||
|     live_search: bool, |     live_search: bool, | ||||||
|     terminal_command: String, |     terminal_command: String, | ||||||
|     editor_command: String, |     editor_command: String, | ||||||
|  |     /// 0: size
 | ||||||
|  |     /// 1: mode (permissions)
 | ||||||
|  |     info_what: Vec<u32>, | ||||||
| } | } | ||||||
| impl Share { | impl Share { | ||||||
|     fn check_bgtasks(&mut self) -> bool { |     /// returns Some if any task has finished.
 | ||||||
|  |     /// returns Some(true) if at least one of these tasks may have altered files.
 | ||||||
|  |     /// (this should trigger a rescan)
 | ||||||
|  |     fn check_bgtasks(&mut self) -> Option<bool> { | ||||||
|         for (i, task) in self.tasks.iter_mut().enumerate() { |         for (i, task) in self.tasks.iter_mut().enumerate() { | ||||||
|             if task.thread.is_finished() { |             if task.thread.is_finished() { | ||||||
|                 self.tasks.remove(i); |                 return Some(self.tasks.remove(i).rescan_after); | ||||||
|                 return true; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         false |         None | ||||||
|     } |     } | ||||||
| } | } | ||||||
| struct BackgroundTask { | struct BackgroundTask { | ||||||
|     status: Arc<Mutex<String>>, |     status: Arc<Mutex<String>>, | ||||||
|     thread: JoinHandle<Result<(), String>>, |     thread: JoinHandle<Result<(), String>>, | ||||||
|  |     rescan_after: bool, | ||||||
| } | } | ||||||
| impl BackgroundTask { | impl BackgroundTask { | ||||||
|     pub fn new( |     pub fn new( | ||||||
|  |         text: String, | ||||||
|         func: impl FnOnce(Arc<Mutex<String>>) -> Result<(), String> + Send + 'static, |         func: impl FnOnce(Arc<Mutex<String>>) -> Result<(), String> + Send + 'static, | ||||||
|  |         rescan_after: bool, | ||||||
|     ) -> Self { |     ) -> Self { | ||||||
|         let status = Arc::new(Mutex::new(String::new())); |         let status = Arc::new(Mutex::new(text)); | ||||||
|         Self { |         Self { | ||||||
|             status: Arc::clone(&status), |             status: Arc::clone(&status), | ||||||
|             thread: std::thread::spawn(move || func(status)), |             thread: std::thread::spawn(move || func(status)), | ||||||
|  |             rescan_after, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -226,6 +231,7 @@ struct TuiFile { | |||||||
|     current_dir: PathBuf, |     current_dir: PathBuf, | ||||||
|     dir_content: Vec<DirContent>, |     dir_content: Vec<DirContent>, | ||||||
|     dir_content_len: usize, |     dir_content_len: usize, | ||||||
|  |     dir_content_builder_task: Option<Arc<Mutex<Option<Result<Vec<DirContent>, String>>>>>, | ||||||
|     scroll: usize, |     scroll: usize, | ||||||
|     current_index: usize, |     current_index: usize, | ||||||
|     focus: Focus, |     focus: Focus, | ||||||
| @ -238,15 +244,39 @@ struct TuiFile { | |||||||
|     last_drawn_files_count: usize, |     last_drawn_files_count: usize, | ||||||
|     last_files_max_scroll: usize, |     last_files_max_scroll: usize, | ||||||
|     after_rescanning_files: Vec<Box<dyn FnOnce(&mut Self)>>, |     after_rescanning_files: Vec<Box<dyn FnOnce(&mut Self)>>, | ||||||
|  |     scan_files_mode: ScanFilesMode, | ||||||
|  | } | ||||||
|  | #[derive(Clone)] | ||||||
|  | enum ScanFilesMode { | ||||||
|  |     /// file-scanning blocks the main thread.
 | ||||||
|  |     /// prevents flickering.
 | ||||||
|  |     Blocking, | ||||||
|  |     /// file-scanning doesn't block the main thread.
 | ||||||
|  |     /// leads to flickering as the file list appears empty until the thread finishes.
 | ||||||
|  |     Threaded, | ||||||
|  |     /// file-scanning blocks the main thread for up to _ seconds.
 | ||||||
|  |     /// after the timeout is reached, file scanning is stopped.
 | ||||||
|  |     /// can lead to incomplete file lists.
 | ||||||
|  |     Timeout(f32), | ||||||
|  |     /// file-scanning blocks the main thread for up to _ seconds.
 | ||||||
|  |     /// after the timeout is reached, file-scanning will restart on a thread.
 | ||||||
|  |     /// prevents flickering but will scan the first files twice if the timeout is reached.
 | ||||||
|  |     TimeoutThenThreaded(f32), | ||||||
|  | } | ||||||
|  | impl Default for ScanFilesMode { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::Blocking | ||||||
|  |     } | ||||||
| } | } | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| struct DirContent { | struct DirContent { | ||||||
|     entry: Rc<DirEntry>, |     path: PathBuf, | ||||||
|     name: String, |     name: String, | ||||||
|     name_charlen: usize, |     name_charlen: usize, | ||||||
|     rel_depth: usize, |     rel_depth: usize, | ||||||
|     passes_filter: bool, |     passes_filter: bool, | ||||||
|     selected: bool, |     selected: bool, | ||||||
|  |     info: String, | ||||||
|     more: DirContentType, |     more: DirContentType, | ||||||
| } | } | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| @ -257,7 +287,6 @@ enum DirContentType { | |||||||
|         metadata: Metadata, |         metadata: Metadata, | ||||||
|     }, |     }, | ||||||
|     File { |     File { | ||||||
|         size: String, |  | ||||||
|         metadata: Metadata, |         metadata: Metadata, | ||||||
|     }, |     }, | ||||||
|     Symlink { |     Symlink { | ||||||
| @ -286,6 +315,7 @@ impl TuiFile { | |||||||
|             current_dir: self.current_dir.clone(), |             current_dir: self.current_dir.clone(), | ||||||
|             dir_content: self.dir_content.clone(), |             dir_content: self.dir_content.clone(), | ||||||
|             dir_content_len: self.dir_content_len, |             dir_content_len: self.dir_content_len, | ||||||
|  |             dir_content_builder_task: None, | ||||||
|             scroll: self.scroll, |             scroll: self.scroll, | ||||||
|             current_index: self.current_index, |             current_index: self.current_index, | ||||||
|             focus: self.focus.clone(), |             focus: self.focus.clone(), | ||||||
| @ -298,6 +328,7 @@ impl TuiFile { | |||||||
|             last_drawn_files_count: self.last_drawn_files_count, |             last_drawn_files_count: self.last_drawn_files_count, | ||||||
|             last_files_max_scroll: self.last_files_max_scroll, |             last_files_max_scroll: self.last_files_max_scroll, | ||||||
|             after_rescanning_files: vec![], |             after_rescanning_files: vec![], | ||||||
|  |             scan_files_mode: ScanFilesMode::default(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     pub fn new(current_dir: PathBuf) -> io::Result<Self> { |     pub fn new(current_dir: PathBuf) -> io::Result<Self> { | ||||||
| @ -310,6 +341,7 @@ impl TuiFile { | |||||||
|             current_dir, |             current_dir, | ||||||
|             dir_content: vec![], |             dir_content: vec![], | ||||||
|             dir_content_len: 0, |             dir_content_len: 0, | ||||||
|  |             dir_content_builder_task: None, | ||||||
|             scroll: 0, |             scroll: 0, | ||||||
|             current_index: 0, |             current_index: 0, | ||||||
|             focus: Focus::Files, |             focus: Focus::Files, | ||||||
| @ -322,6 +354,7 @@ impl TuiFile { | |||||||
|             last_drawn_files_count: 0, |             last_drawn_files_count: 0, | ||||||
|             last_files_max_scroll: 0, |             last_files_max_scroll: 0, | ||||||
|             after_rescanning_files: vec![], |             after_rescanning_files: vec![], | ||||||
|  |             scan_files_mode: ScanFilesMode::default(), | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|     fn set_current_index(&mut self, mut i: usize) { |     fn set_current_index(&mut self, mut i: usize) { | ||||||
|  | |||||||
							
								
								
									
										365
									
								
								src/run.rs
									
									
									
									
									
								
							
							
						
						
									
										365
									
								
								src/run.rs
									
									
									
									
									
								
							| @ -4,12 +4,16 @@ use crossterm::{cursor, queue, style, terminal, ExecutableCommand}; | |||||||
| use regex::RegexBuilder; | use regex::RegexBuilder; | ||||||
| 
 | 
 | ||||||
| use crate::updates::Updates; | use crate::updates::Updates; | ||||||
| use crate::{tasks, AppCmd, DirContent, DirContentType, Focus, Share}; | use crate::{ | ||||||
|  |     tasks, AppCmd, BackgroundTask, DirContent, DirContentType, Focus, ScanFilesMode, Share, | ||||||
|  | }; | ||||||
| use std::io::Write; | use std::io::Write; | ||||||
|  | use std::os::unix::prelude::PermissionsExt; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| use std::process::{Command, Stdio}; | use std::process::{Command, Stdio}; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| use std::time::Duration; | use std::sync::{Arc, Mutex}; | ||||||
|  | use std::time::{Duration, Instant}; | ||||||
| use std::{fs, io}; | use std::{fs, io}; | ||||||
| 
 | 
 | ||||||
| use crate::TuiFile; | use crate::TuiFile; | ||||||
| @ -33,58 +37,201 @@ impl TuiFile { | |||||||
|     } |     } | ||||||
|     pub fn run(&mut self, share: &mut Share) -> io::Result<AppCmd> { |     pub fn run(&mut self, share: &mut Share) -> io::Result<AppCmd> { | ||||||
|         loop { |         loop { | ||||||
|             if share.check_bgtasks() { |             if let Some(rescan) = share.check_bgtasks() { | ||||||
|  |                 if let Some(task) = &self.dir_content_builder_task { | ||||||
|  |                     if let Some(v) = { | ||||||
|  |                         let mut temp = task.lock().unwrap(); | ||||||
|  |                         temp.take() | ||||||
|  |                     } { | ||||||
|  |                         self.dir_content_builder_task = None; | ||||||
|  |                         after_rescanning_files(self, v); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if rescan { | ||||||
|                     return Ok(AppCmd::TaskFinished); |                     return Ok(AppCmd::TaskFinished); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             // rescan files if necessary
 |             // rescan files if necessary
 | ||||||
|  |             fn after_rescanning_files(s: &mut TuiFile, v: Result<Vec<DirContent>, String>) { | ||||||
|  |                 s.updates.request_rescanning_files_complete(); | ||||||
|  |                 s.updates.request_filter_files(); | ||||||
|  |                 match v { | ||||||
|  |                     Ok(v) => s.dir_content = v, | ||||||
|  |                     Err(err) => { | ||||||
|  |                         s.files_status_is_special = true; | ||||||
|  |                         s.files_status = err; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             if self.updates.rescan_files() { |             if self.updates.rescan_files() { | ||||||
|                 self.updates.dont_rescan_files(); |                 self.updates.dont_rescan_files(); | ||||||
|                 self.updates.request_filter_files(); |                 if self.dir_content_builder_task.is_none() { | ||||||
|                 self.files_status_is_special = false; |  | ||||||
|                     self.dir_content.clear(); |                     self.dir_content.clear(); | ||||||
|                 get_files(self, self.current_dir.clone(), 0); |                     self.files_status_is_special = false; | ||||||
|                 fn get_files(s: &mut TuiFile, dir: PathBuf, depth: usize) { |                     let (scan_dir_blocking, mut scan_dir_threaded, timeout) = | ||||||
|  |                         match self.scan_files_mode { | ||||||
|  |                             ScanFilesMode::Blocking => (true, false, None), | ||||||
|  |                             ScanFilesMode::Threaded => (false, true, None), | ||||||
|  |                             ScanFilesMode::Timeout(t) => (true, false, Some(t)), | ||||||
|  |                             ScanFilesMode::TimeoutThenThreaded(t) => (true, true, Some(t)), | ||||||
|  |                         }; | ||||||
|  |                     if scan_dir_blocking { | ||||||
|  |                         let v = get_files( | ||||||
|  |                             self.current_dir.clone(), | ||||||
|  |                             self.scan_files_max_depth, | ||||||
|  |                             &share.info_what, | ||||||
|  |                             timeout, | ||||||
|  |                         ); | ||||||
|  |                         if v.as_ref().is_ok_and(|v| v.1) { | ||||||
|  |                             // completed, no need for the threaded fallback.
 | ||||||
|  |                             scan_dir_threaded = false; | ||||||
|  |                         } | ||||||
|  |                         if !scan_dir_threaded { | ||||||
|  |                             after_rescanning_files(self, v.map(|v| v.0)); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     if scan_dir_threaded { | ||||||
|  |                         let dir = self.current_dir.clone(); | ||||||
|  |                         let max_depth = self.scan_files_max_depth; | ||||||
|  |                         let info_what = share.info_what.clone(); | ||||||
|  |                         let arc = Arc::new(Mutex::new(None)); | ||||||
|  |                         self.dir_content_builder_task = Some(Arc::clone(&arc)); | ||||||
|  |                         self.updates.request_redraw_filelist(); | ||||||
|  |                         self.updates.request_redraw_infobar(); | ||||||
|  |                         share.tasks.push(BackgroundTask::new( | ||||||
|  |                             "listing files...".to_string(), | ||||||
|  |                             move |_status| { | ||||||
|  |                                 let v = get_files(dir, max_depth, &info_what, None).map(|v| v.0); | ||||||
|  |                                 *arc.lock().unwrap() = Some(v); | ||||||
|  |                                 Ok(()) | ||||||
|  |                             }, | ||||||
|  |                             false, | ||||||
|  |                         )); | ||||||
|  |                     } | ||||||
|  |                     fn get_files( | ||||||
|  |                         dir: PathBuf, | ||||||
|  |                         max_depth: usize, | ||||||
|  |                         info_what: &Vec<u32>, | ||||||
|  |                         timeout: Option<f32>, | ||||||
|  |                     ) -> Result<(Vec<DirContent>, bool), String> { | ||||||
|  |                         let mut o = vec![]; | ||||||
|  |                         let completed = get_files( | ||||||
|  |                             &mut o, | ||||||
|  |                             dir, | ||||||
|  |                             0, | ||||||
|  |                             max_depth, | ||||||
|  |                             info_what, | ||||||
|  |                             timeout.map(|v| (Instant::now(), v)), | ||||||
|  |                         )?; | ||||||
|  |                         // table-style
 | ||||||
|  |                         let mut lengths = vec![]; | ||||||
|  |                         for e in o.iter() { | ||||||
|  |                             for (i, line) in e.info.lines().enumerate() { | ||||||
|  |                                 if i >= lengths.len() { | ||||||
|  |                                     lengths.push(0); | ||||||
|  |                                 } | ||||||
|  |                                 if line.len() > lengths[i] { | ||||||
|  |                                     lengths[i] = line.len(); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         for e in o.iter_mut() { | ||||||
|  |                             let src = std::mem::replace(&mut e.info, String::new()); | ||||||
|  |                             for (i, line) in src.lines().enumerate() { | ||||||
|  |                                 let rem = lengths[i] - line.len(); | ||||||
|  |                                 if line.starts_with('<') { | ||||||
|  |                                     e.info.push_str(&line[1..]); | ||||||
|  |                                     for _ in 0..rem { | ||||||
|  |                                         e.info.push(' '); | ||||||
|  |                                     } | ||||||
|  |                                 } else if line.starts_with('>') { | ||||||
|  |                                     for _ in 0..rem { | ||||||
|  |                                         e.info.push(' '); | ||||||
|  |                                     } | ||||||
|  |                                     e.info.push_str(&line[1..]); | ||||||
|  |                                 } else { | ||||||
|  |                                     let r = rem / 2; | ||||||
|  |                                     for _ in 0..r { | ||||||
|  |                                         e.info.push(' '); | ||||||
|  |                                     } | ||||||
|  |                                     e.info.push_str(&line[1..]); | ||||||
|  |                                     for _ in 0..(rem - r) { | ||||||
|  |                                         e.info.push(' '); | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         fn get_files( | ||||||
|  |                             dir_content: &mut Vec<DirContent>, | ||||||
|  |                             dir: PathBuf, | ||||||
|  |                             depth: usize, | ||||||
|  |                             max_depth: usize, | ||||||
|  |                             info_what: &Vec<u32>, | ||||||
|  |                             time_limit: Option<(Instant, f32)>, | ||||||
|  |                         ) -> Result<bool, String> { | ||||||
|                             match fs::read_dir(&dir) { |                             match fs::read_dir(&dir) { | ||||||
|                                 Err(e) => { |                                 Err(e) => { | ||||||
|                                     if depth == 0 { |                                     if depth == 0 { | ||||||
|                                 s.dir_content = vec![]; |                                         return Err(format!("{e}")); | ||||||
|                                 s.files_status = format!("{e}"); |  | ||||||
|                                 s.files_status_is_special = true; |  | ||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|                                 Ok(files) => { |                                 Ok(files) => { | ||||||
|                                     for entry in files { |                                     for entry in files { | ||||||
|                                         if let Ok(entry) = entry { |                                         if let Ok(entry) = entry { | ||||||
|                                     let mut name = entry.file_name().to_string_lossy().into_owned(); |                                             let mut name = | ||||||
|  |                                                 entry.file_name().to_string_lossy().into_owned(); | ||||||
|                                             let metadata = entry.metadata(); |                                             let metadata = entry.metadata(); | ||||||
|                                             let p = entry.path(); |                                             let p = entry.path(); | ||||||
|                                     let more = match metadata { |                                             let info = if let Ok(metadata) = &metadata { | ||||||
|                                         Err(e) => DirContentType::Err(e.to_string()), |                                                 // in each line:
 | ||||||
|                                         Ok(metadata) => { |                                                 // first char:
 | ||||||
|                                             if metadata.is_symlink() { |                                                 // < left-aligned
 | ||||||
|                                                 DirContentType::Symlink { metadata } |                                                 // > right-aligned
 | ||||||
|                                             } else if metadata.is_file() { |                                                 // anything else -> centered
 | ||||||
|                                                 DirContentType::File { |                                                 // sep. line: "< | "
 | ||||||
|                                                     size: { |                                                 let mut info = String::new(); | ||||||
|  |                                                 for info_what in info_what { | ||||||
|  |                                                     match info_what { | ||||||
|  |                                                         0 => { | ||||||
|                                                             let mut bytes = metadata.len(); |                                                             let mut bytes = metadata.len(); | ||||||
|                                                             let mut i = 0; |                                                             let mut i = 0; | ||||||
|                                                             loop { |                                                             loop { | ||||||
|                                                                 if bytes < 1024 |                                                                 if bytes < 1024 | ||||||
|                                                                     || i + 1 >= BYTE_UNITS.len() |                                                                     || i + 1 >= BYTE_UNITS.len() | ||||||
|                                                                 { |                                                                 { | ||||||
|                                                                 break format!( |                                                                     info.push_str(&format!( | ||||||
|                                                                     "{bytes}{}", |                                                                         "< | \n>{bytes}\n>{}\n", | ||||||
|                                                                         BYTE_UNITS[i] |                                                                         BYTE_UNITS[i] | ||||||
|                                                                 ); |                                                                     )); | ||||||
|  |                                                                     break; | ||||||
|                                                                 } else { |                                                                 } else { | ||||||
|                                                                     i += 1; |                                                                     i += 1; | ||||||
|                                                                     // divide by 1024 but cooler
 |                                                                     // divide by 1024 but cooler
 | ||||||
|                                                                     bytes >>= 10; |                                                                     bytes >>= 10; | ||||||
|                                                                 } |                                                                 } | ||||||
|                                                             } |                                                             } | ||||||
|                                                     }, |  | ||||||
|                                                     metadata, |  | ||||||
|                                                         } |                                                         } | ||||||
|  |                                                         1 => { | ||||||
|  |                                                             info.push_str(&format!( | ||||||
|  |                                                                 "< | \n>{:03o}\n", | ||||||
|  |                                                                 metadata.permissions().mode() | ||||||
|  |                                                                     & 0o777, | ||||||
|  |                                                             )); | ||||||
|  |                                                         } | ||||||
|  |                                                         _ => {} | ||||||
|  |                                                     } | ||||||
|  |                                                 } | ||||||
|  |                                                 info | ||||||
|  |                                             } else { | ||||||
|  |                                                 String::new() | ||||||
|  |                                             }; | ||||||
|  |                                             let more = match metadata { | ||||||
|  |                                                 Err(e) => DirContentType::Err(e.to_string()), | ||||||
|  |                                                 Ok(metadata) => { | ||||||
|  |                                                     if metadata.is_symlink() { | ||||||
|  |                                                         DirContentType::Symlink { metadata } | ||||||
|  |                                                     } else if metadata.is_file() { | ||||||
|  |                                                         DirContentType::File { metadata } | ||||||
|                                                     } else if metadata.is_dir() { |                                                     } else if metadata.is_dir() { | ||||||
|                                                         DirContentType::Dir { metadata } |                                                         DirContentType::Dir { metadata } | ||||||
|                                                     } else { |                                                     } else { | ||||||
| @ -97,23 +244,43 @@ impl TuiFile { | |||||||
|                                             if let DirContentType::Dir { .. } = more { |                                             if let DirContentType::Dir { .. } = more { | ||||||
|                                                 name.push('/'); |                                                 name.push('/'); | ||||||
|                                             } |                                             } | ||||||
|                                     s.dir_content.push(DirContent { |                                             dir_content.push(DirContent { | ||||||
|                                         entry: Rc::new(entry), |                                                 path: entry.path(), | ||||||
|                                                 name_charlen: name.chars().count(), |                                                 name_charlen: name.chars().count(), | ||||||
|                                                 name, |                                                 name, | ||||||
|                                                 rel_depth: depth, |                                                 rel_depth: depth, | ||||||
|                                                 passes_filter: true, |                                                 passes_filter: true, | ||||||
|                                                 selected: false, |                                                 selected: false, | ||||||
|  |                                                 info, | ||||||
|                                                 more, |                                                 more, | ||||||
|                                             }); |                                             }); | ||||||
|                                     if depth < s.scan_files_max_depth { |                                             if let Some((since, max)) = time_limit { | ||||||
|                                         get_files(s, p, depth + 1); |                                                 if since.elapsed().as_secs_f32() > max { | ||||||
|  |                                                     return Ok(false); | ||||||
|  |                                                 } | ||||||
|  |                                             } | ||||||
|  |                                             if depth < max_depth { | ||||||
|  |                                                 get_files( | ||||||
|  |                                                     dir_content, | ||||||
|  |                                                     p, | ||||||
|  |                                                     depth + 1, | ||||||
|  |                                                     max_depth, | ||||||
|  |                                                     info_what, | ||||||
|  |                                                     time_limit, | ||||||
|  |                                                 ); | ||||||
|                                             } |                                             } | ||||||
|                                         } |                                         } | ||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|  |                             Ok(true) | ||||||
|                         } |                         } | ||||||
|  |                         Ok((o, completed)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if self.updates.rescanning_files_complete() { | ||||||
|  |                 self.updates.dont_rescanning_files_complete(); | ||||||
|                 if self.current_index >= self.dir_content.len() { |                 if self.current_index >= self.dir_content.len() { | ||||||
|                     self.current_index = self.dir_content.len().saturating_sub(1); |                     self.current_index = self.dir_content.len().saturating_sub(1); | ||||||
|                 } |                 } | ||||||
| @ -129,6 +296,7 @@ impl TuiFile { | |||||||
|                     self.search_text.clear(); |                     self.search_text.clear(); | ||||||
|                     self.search_regex = None; |                     self.search_regex = None; | ||||||
|                     self.updates.request_redraw_searchbar(); |                     self.updates.request_redraw_searchbar(); | ||||||
|  |                     self.updates.request_filter_files(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if self.updates.filter_files() { |             if self.updates.filter_files() { | ||||||
| @ -226,17 +394,26 @@ impl TuiFile { | |||||||
|                             cursor::MoveTo(0, 0), |                             cursor::MoveTo(0, 0), | ||||||
|                             style::PrintStyledContent( |                             style::PrintStyledContent( | ||||||
|                                 pathstring |                                 pathstring | ||||||
|                                     .with(Color::Cyan) |                                     .green() | ||||||
|  |                                     .underlined() | ||||||
|  |                                     .bold() | ||||||
|                                     .attribute(Attribute::Underlined) |                                     .attribute(Attribute::Underlined) | ||||||
|                             ) |                             ) | ||||||
|                         )?; |                         )?; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if self.updates.redraw_filelist() { |                 if self.updates.redraw_filebar() || self.updates.redraw_filelist() { | ||||||
|                     self.updates.dont_redraw_filelist(); |                     self.updates.request_redraw_filebar(); | ||||||
|  |                     self.updates.dont_redraw_filebar(); | ||||||
|                     self.updates.request_move_cursor(); |                     self.updates.request_move_cursor(); | ||||||
|                     self.last_drawn_files_height = share.size.1.saturating_sub(3) as _; |                     self.last_drawn_files_height = share.size.1.saturating_sub(3) as _; | ||||||
|                     let mut status = format!(" {}", self.files_status); |                     let mut status = match self.scan_files_mode { | ||||||
|  |                         ScanFilesMode::Blocking => " ".to_string(), | ||||||
|  |                         ScanFilesMode::Threaded => " (t) ".to_string(), | ||||||
|  |                         ScanFilesMode::Timeout(secs) => format!(" ({secs}s) "), | ||||||
|  |                         ScanFilesMode::TimeoutThenThreaded(secs) => format!(" ({secs}s -> t) "), | ||||||
|  |                     }; | ||||||
|  |                     status.push_str(&self.files_status); | ||||||
|                     while status.len() < share.size.0 as usize { |                     while status.len() < share.size.0 as usize { | ||||||
|                         status.push(' '); |                         status.push(' '); | ||||||
|                     } |                     } | ||||||
| @ -245,6 +422,8 @@ impl TuiFile { | |||||||
|                         cursor::MoveTo(0, 1), |                         cursor::MoveTo(0, 1), | ||||||
|                         style::PrintStyledContent(status.attribute(Attribute::Italic)), |                         style::PrintStyledContent(status.attribute(Attribute::Italic)), | ||||||
|                     )?; |                     )?; | ||||||
|  |                     if self.updates.redraw_filelist() { | ||||||
|  |                         self.updates.dont_redraw_filelist(); | ||||||
|                         self.last_files_max_scroll = self |                         self.last_files_max_scroll = self | ||||||
|                             .dir_content_len |                             .dir_content_len | ||||||
|                             .saturating_sub(self.last_drawn_files_height); |                             .saturating_sub(self.last_drawn_files_height); | ||||||
| @ -309,35 +488,11 @@ impl TuiFile { | |||||||
|                                     text.push(endchar); |                                     text.push(endchar); | ||||||
|                                     vec![text.red()] |                                     vec![text.red()] | ||||||
|                                 } |                                 } | ||||||
|                             DirContentType::Dir { metadata: _ } => { |                                 DirContentType::File { metadata } | ||||||
|                                 let filenamelen = share.size.0 as usize - 2 - text_charlen; |                                 | DirContentType::Dir { metadata } | ||||||
|                                 if entry.name_charlen < filenamelen { |                                 | DirContentType::Symlink { metadata } => { | ||||||
|                                     text.push_str(&entry.name); |  | ||||||
|                                     for _ in 0..(filenamelen - entry.name_charlen) { |  | ||||||
|                                         text.push(' '); |  | ||||||
|                                     } |  | ||||||
|                                 } else if entry.name_charlen == filenamelen { |  | ||||||
|                                     text.push_str(&entry.name); |  | ||||||
|                                 } else { |  | ||||||
|                                     // the new length is the old length minus the combined length of the characters we want to cut off
 |  | ||||||
|                                     let i = entry.name.len() |  | ||||||
|                                         - entry |  | ||||||
|                                             .name |  | ||||||
|                                             .chars() |  | ||||||
|                                             .rev() |  | ||||||
|                                             .take(entry.name_charlen - filenamelen) |  | ||||||
|                                             .map(|char| char.len_utf8()) |  | ||||||
|                                             .sum::<usize>(); |  | ||||||
|                                     text.push_str(&entry.name[0..i.saturating_sub(3)]); |  | ||||||
|                                     text.push_str("..."); |  | ||||||
|                                 } |  | ||||||
|                                 text.push(' '); |  | ||||||
|                                 text.push(endchar); |  | ||||||
|                                 vec![text.stylize()] |  | ||||||
|                             } |  | ||||||
|                             DirContentType::File { size, metadata: _ } => { |  | ||||||
|                                     let filenamelen = |                                     let filenamelen = | ||||||
|                                     share.size.0 as usize - 3 - text_charlen - size.chars().count(); |                                         share.size.0 as usize - 2 - text_charlen - entry.info.len(); | ||||||
|                                     if entry.name_charlen < filenamelen { |                                     if entry.name_charlen < filenamelen { | ||||||
|                                         text.push_str(&entry.name); |                                         text.push_str(&entry.name); | ||||||
|                                         for _ in 0..(filenamelen - entry.name_charlen) { |                                         for _ in 0..(filenamelen - entry.name_charlen) { | ||||||
| @ -358,37 +513,15 @@ impl TuiFile { | |||||||
|                                         text.push_str(&entry.name[0..i.saturating_sub(3)]); |                                         text.push_str(&entry.name[0..i.saturating_sub(3)]); | ||||||
|                                         text.push_str("..."); |                                         text.push_str("..."); | ||||||
|                                     } |                                     } | ||||||
|                                 text.push(' '); |                                     text.push_str(&entry.info); | ||||||
|                                 text.push_str(&size); |  | ||||||
|                                     text.push(' '); |                                     text.push(' '); | ||||||
|                                     text.push(endchar); |                                     text.push(endchar); | ||||||
|                                 vec![text.stylize()] |                                     vec![match entry.more { | ||||||
|                             } |                                         DirContentType::File { .. } => text.blue(), | ||||||
|                             DirContentType::Symlink { metadata: _ } => { |                                         DirContentType::Dir { .. } => text.yellow(), | ||||||
|                                 let filenamelen = share.size.0 as usize - 2 - text_charlen; |                                         DirContentType::Symlink { .. } => text.grey(), | ||||||
|                                 if entry.name_charlen < filenamelen { |                                         DirContentType::Err { .. } => text.red(), | ||||||
|                                     text.push_str(&entry.name); |                                     }] | ||||||
|                                     for _ in 0..(filenamelen - entry.name_charlen) { |  | ||||||
|                                         text.push(' '); |  | ||||||
|                                     } |  | ||||||
|                                 } else if entry.name_charlen == filenamelen { |  | ||||||
|                                     text.push_str(&entry.name); |  | ||||||
|                                 } else { |  | ||||||
|                                     // the new length is the old length minus the combined length of the characters we want to cut off
 |  | ||||||
|                                     let i = entry.name.len() |  | ||||||
|                                         - entry |  | ||||||
|                                             .name |  | ||||||
|                                             .chars() |  | ||||||
|                                             .rev() |  | ||||||
|                                             .take(entry.name_charlen - filenamelen) |  | ||||||
|                                             .map(|char| char.len_utf8()) |  | ||||||
|                                             .sum::<usize>(); |  | ||||||
|                                     text.push_str(&entry.name[0..i.saturating_sub(3)]); |  | ||||||
|                                     text.push_str("..."); |  | ||||||
|                                 } |  | ||||||
|                                 text.push(' '); |  | ||||||
|                                 text.push(endchar); |  | ||||||
|                                 vec![text.italic()] |  | ||||||
|                                 } |                                 } | ||||||
|                             }; |                             }; | ||||||
|                             queue!(share.stdout, cursor::MoveToNextLine(1))?; |                             queue!(share.stdout, cursor::MoveToNextLine(1))?; | ||||||
| @ -410,6 +543,7 @@ impl TuiFile { | |||||||
|                             )?; |                             )?; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|                 if self.updates.redraw_searchbar() { |                 if self.updates.redraw_searchbar() { | ||||||
|                     self.updates.dont_redraw_searchbar(); |                     self.updates.dont_redraw_searchbar(); | ||||||
|                     self.updates.request_move_cursor(); |                     self.updates.request_move_cursor(); | ||||||
| @ -426,7 +560,7 @@ impl TuiFile { | |||||||
|                         share.stdout, |                         share.stdout, | ||||||
|                         cursor::MoveTo(0, share.size.1 - 1), |                         cursor::MoveTo(0, share.size.1 - 1), | ||||||
|                         style::PrintStyledContent(text.underlined()) |                         style::PrintStyledContent(text.underlined()) | ||||||
|                     ); |                     )?; | ||||||
|                 } |                 } | ||||||
|                 if self.updates.move_cursor() { |                 if self.updates.move_cursor() { | ||||||
|                     self.updates.dont_move_cursor(); |                     self.updates.dont_move_cursor(); | ||||||
| @ -474,6 +608,10 @@ impl TuiFile { | |||||||
|                     }, |                     }, | ||||||
|                     Event::Key(e) => match (&self.focus, e.code) { |                     Event::Key(e) => match (&self.focus, e.code) { | ||||||
|                         // - - - Global - - -
 |                         // - - - Global - - -
 | ||||||
|  |                         // Ctrl+C/D -> Quit
 | ||||||
|  |                         (_, KeyCode::Char('c' | 'd')) if e.modifiers == KeyModifiers::CONTROL => { | ||||||
|  |                             return Ok(AppCmd::Quit); | ||||||
|  |                         } | ||||||
|                         // Ctrl+Left/H -> Close
 |                         // Ctrl+Left/H -> Close
 | ||||||
|                         (_, KeyCode::Left | KeyCode::Char('h')) |                         (_, KeyCode::Left | KeyCode::Char('h')) | ||||||
|                             if e.modifiers == KeyModifiers::CONTROL => |                             if e.modifiers == KeyModifiers::CONTROL => | ||||||
| @ -526,7 +664,7 @@ impl TuiFile { | |||||||
|                         (Focus::Files, KeyCode::Right | KeyCode::Char('l')) => { |                         (Focus::Files, KeyCode::Right | KeyCode::Char('l')) => { | ||||||
|                             // descend into directory
 |                             // descend into directory
 | ||||||
|                             if let Some(entry) = self.dir_content.get(self.current_index) { |                             if let Some(entry) = self.dir_content.get(self.current_index) { | ||||||
|                                 self.current_dir = entry.entry.path(); |                                 self.current_dir = entry.path.clone(); | ||||||
|                                 self.updates = u32::MAX; |                                 self.updates = u32::MAX; | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| @ -559,6 +697,25 @@ impl TuiFile { | |||||||
|                             self.focus = Focus::SearchBar; |                             self.focus = Focus::SearchBar; | ||||||
|                             self.updates.request_move_cursor(); |                             self.updates.request_move_cursor(); | ||||||
|                         } |                         } | ||||||
|  |                         // M -> toggle threaded mode based on searchbar
 | ||||||
|  |                         (_, KeyCode::Char('m')) => { | ||||||
|  |                             self.updates.request_reset_search(); | ||||||
|  |                             self.updates.request_redraw_filebar(); | ||||||
|  |                             if self.search_text == "b" { | ||||||
|  |                                 self.scan_files_mode = ScanFilesMode::Blocking; | ||||||
|  |                             } else if self.search_text == "t" { | ||||||
|  |                                 self.scan_files_mode = ScanFilesMode::Threaded; | ||||||
|  |                             } else if self.search_text.starts_with("b") { | ||||||
|  |                                 if let Ok(timeout) = self.search_text[1..].parse() { | ||||||
|  |                                     self.scan_files_mode = ScanFilesMode::Timeout(timeout); | ||||||
|  |                                 } | ||||||
|  |                             } else if self.search_text.starts_with("t") { | ||||||
|  |                                 if let Ok(timeout) = self.search_text[1..].parse() { | ||||||
|  |                                     self.scan_files_mode = | ||||||
|  |                                         ScanFilesMode::TimeoutThenThreaded(timeout); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|                         // N -> New Directory
 |                         // N -> New Directory
 | ||||||
|                         (Focus::Files, KeyCode::Char('n')) => { |                         (Focus::Files, KeyCode::Char('n')) => { | ||||||
|                             let dir = self.current_dir.join(&self.search_text); |                             let dir = self.current_dir.join(&self.search_text); | ||||||
| @ -573,7 +730,7 @@ impl TuiFile { | |||||||
|                         (Focus::Files, KeyCode::Char('c')) => { |                         (Focus::Files, KeyCode::Char('c')) => { | ||||||
|                             if let Some(e) = self.dir_content.get(self.current_index) { |                             if let Some(e) = self.dir_content.get(self.current_index) { | ||||||
|                                 if let DirContentType::Dir { .. } = e.more { |                                 if let DirContentType::Dir { .. } = e.more { | ||||||
|                                     return Ok(AppCmd::CopyTo(e.entry.path())); |                                     return Ok(AppCmd::CopyTo(e.path.clone())); | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| @ -584,10 +741,30 @@ impl TuiFile { | |||||||
|                                 .iter() |                                 .iter() | ||||||
|                                 .rev() |                                 .rev() | ||||||
|                                 .filter(|e| e.selected) |                                 .filter(|e| e.selected) | ||||||
|                                 .map(|e| e.entry.path()) |                                 .map(|e| e.path.clone()) | ||||||
|                                 .collect(); |                                 .collect(); | ||||||
|                             tasks::task_del(paths, share); |                             tasks::task_del(paths, share); | ||||||
|                         } |                         } | ||||||
|  |                         // P -> Permissions
 | ||||||
|  |                         (Focus::Files, KeyCode::Char('p')) => { | ||||||
|  |                             self.updates.request_reset_search(); | ||||||
|  |                             if let Ok(mode) = u32::from_str_radix(&self.search_text, 8) { | ||||||
|  |                                 let paths = self | ||||||
|  |                                     .dir_content | ||||||
|  |                                     .iter() | ||||||
|  |                                     .rev() | ||||||
|  |                                     .filter(|e| e.selected) | ||||||
|  |                                     .map(|e| e.path.clone()) | ||||||
|  |                                     .collect(); | ||||||
|  |                                 self.updates.request_redraw_infobar(); | ||||||
|  |                                 tasks::task_chmod(paths, mode, share); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         // O -> Owner (and group)
 | ||||||
|  |                         (Focus::Files, KeyCode::Char('o')) => { | ||||||
|  |                             self.updates.request_reset_search(); | ||||||
|  |                             // TODO!
 | ||||||
|  |                         } | ||||||
|                         // T -> Open Terminal
 |                         // T -> Open Terminal
 | ||||||
|                         (Focus::Files, KeyCode::Char('t')) => 'term: { |                         (Focus::Files, KeyCode::Char('t')) => 'term: { | ||||||
|                             Command::new(&share.terminal_command) |                             Command::new(&share.terminal_command) | ||||||
| @ -600,7 +777,7 @@ impl TuiFile { | |||||||
|                         (Focus::Files, KeyCode::Char('e')) => { |                         (Focus::Files, KeyCode::Char('e')) => { | ||||||
|                             Self::term_reset(share)?; |                             Self::term_reset(share)?; | ||||||
|                             if let Some(entry) = self.dir_content.get(self.current_index) { |                             if let Some(entry) = self.dir_content.get(self.current_index) { | ||||||
|                                 let entry_path = entry.entry.path(); |                                 let entry_path = entry.path.clone(); | ||||||
|                                 Command::new(&share.editor_command) |                                 Command::new(&share.editor_command) | ||||||
|                                     .arg(&entry_path) |                                     .arg(&entry_path) | ||||||
|                                     .current_dir(&self.current_dir) |                                     .current_dir(&self.current_dir) | ||||||
|  | |||||||
							
								
								
									
										39
									
								
								src/tasks.rs
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								src/tasks.rs
									
									
									
									
									
								
							| @ -1,7 +1,9 @@ | |||||||
| use std::{ | use std::{ | ||||||
|     collections::HashSet, |     collections::HashSet, | ||||||
|     fs, io, |     fs, io, | ||||||
|  |     os::unix::prelude::PermissionsExt, | ||||||
|     path::{Path, PathBuf}, |     path::{Path, PathBuf}, | ||||||
|  |     time::Duration, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::{BackgroundTask, Share}; | use crate::{BackgroundTask, Share}; | ||||||
| @ -11,7 +13,9 @@ pub(crate) fn task_copy( | |||||||
|     target: PathBuf, |     target: PathBuf, | ||||||
|     share: &mut Share, |     share: &mut Share, | ||||||
| ) { | ) { | ||||||
|     share.tasks.push(BackgroundTask::new(move |status| { |     share.tasks.push(BackgroundTask::new( | ||||||
|  |         "cp".to_string(), | ||||||
|  |         move |status| { | ||||||
|             let mut total: usize = src.iter().map(|v| v.1.len()).sum(); |             let mut total: usize = src.iter().map(|v| v.1.len()).sum(); | ||||||
|             for (parent, rel_paths) in src { |             for (parent, rel_paths) in src { | ||||||
|                 let mut created: HashSet<PathBuf> = HashSet::new(); |                 let mut created: HashSet<PathBuf> = HashSet::new(); | ||||||
| @ -49,7 +53,9 @@ pub(crate) fn task_copy( | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             Ok(()) |             Ok(()) | ||||||
|     })); |         }, | ||||||
|  |         true, | ||||||
|  |     )); | ||||||
| } | } | ||||||
| fn copy_dir( | fn copy_dir( | ||||||
|     file_from: impl AsRef<Path>, |     file_from: impl AsRef<Path>, | ||||||
| @ -76,10 +82,13 @@ fn copy_dir( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub(crate) fn task_del(paths: Vec<PathBuf>, share: &mut Share) { | pub(crate) fn task_del(paths: Vec<PathBuf>, share: &mut Share) { | ||||||
|     share.tasks.push(BackgroundTask::new(move |status| { |     let mut total: usize = paths.len(); | ||||||
|         let total: usize = paths.len(); |     share.tasks.push(BackgroundTask::new( | ||||||
|  |         format!("rm {total}"), | ||||||
|  |         move |status| { | ||||||
|             for path in paths { |             for path in paths { | ||||||
|                 { |                 { | ||||||
|  |                     total -= 1; | ||||||
|                     let s = format!("rm {total}"); |                     let s = format!("rm {total}"); | ||||||
|                     *status.lock().unwrap() = s; |                     *status.lock().unwrap() = s; | ||||||
|                 } |                 } | ||||||
| @ -90,5 +99,25 @@ pub(crate) fn task_del(paths: Vec<PathBuf>, share: &mut Share) { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             Ok(()) |             Ok(()) | ||||||
|     })); |         }, | ||||||
|  |         true, | ||||||
|  |     )); | ||||||
|  | } | ||||||
|  | pub(crate) fn task_chmod(paths: Vec<PathBuf>, mode: u32, share: &mut Share) { | ||||||
|  |     let mut total = paths.len(); | ||||||
|  |     share.tasks.push(BackgroundTask::new( | ||||||
|  |         format!("chmod {total}"), | ||||||
|  |         move |status| { | ||||||
|  |             for path in paths { | ||||||
|  |                 { | ||||||
|  |                     total -= 1; | ||||||
|  |                     let s = format!("chmod {total}"); | ||||||
|  |                     *status.lock().unwrap() = s; | ||||||
|  |                 } | ||||||
|  |                 fs::set_permissions(path, fs::Permissions::from_mode(mode)); | ||||||
|  |             } | ||||||
|  |             Ok(()) | ||||||
|  |         }, | ||||||
|  |         true, | ||||||
|  |     )); | ||||||
| } | } | ||||||
|  | |||||||
| @ -40,6 +40,14 @@ pub(crate) trait Updates { | |||||||
|     fn reset_search(&self) -> bool; |     fn reset_search(&self) -> bool; | ||||||
|     fn dont_reset_search(&mut self); |     fn dont_reset_search(&mut self); | ||||||
|     fn request_reset_search(&mut self); |     fn request_reset_search(&mut self); | ||||||
|  | 
 | ||||||
|  |     fn rescanning_files_complete(&self) -> bool; | ||||||
|  |     fn dont_rescanning_files_complete(&mut self); | ||||||
|  |     fn request_rescanning_files_complete(&mut self); | ||||||
|  | 
 | ||||||
|  |     fn redraw_filebar(&self) -> bool; | ||||||
|  |     fn dont_redraw_filebar(&mut self); | ||||||
|  |     fn request_redraw_filebar(&mut self); | ||||||
| } | } | ||||||
| impl Updates for u32 { | impl Updates for u32 { | ||||||
|     fn rescan_files(&self) -> bool { |     fn rescan_files(&self) -> bool { | ||||||
| @ -123,4 +131,22 @@ impl Updates for u32 { | |||||||
|     fn request_reset_search(&mut self) { |     fn request_reset_search(&mut self) { | ||||||
|         *self |= 0b100000000; |         *self |= 0b100000000; | ||||||
|     } |     } | ||||||
|  |     fn rescanning_files_complete(&self) -> bool { | ||||||
|  |         0 != self & 0b1000000000 | ||||||
|  |     } | ||||||
|  |     fn dont_rescanning_files_complete(&mut self) { | ||||||
|  |         *self ^= 0b1000000000; | ||||||
|  |     } | ||||||
|  |     fn request_rescanning_files_complete(&mut self) { | ||||||
|  |         *self |= 0b1000000000; | ||||||
|  |     } | ||||||
|  |     fn redraw_filebar(&self) -> bool { | ||||||
|  |         0 != self & 0b10000000000 | ||||||
|  |     } | ||||||
|  |     fn dont_redraw_filebar(&mut self) { | ||||||
|  |         *self ^= 0b10000000000; | ||||||
|  |     } | ||||||
|  |     fn request_redraw_filebar(&mut self) { | ||||||
|  |         *self |= 0b10000000000; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Mark
						Mark