fm/modes/menu/
bulkrename.rs1use std::io::{BufRead, Write};
2use std::path::{Path, PathBuf};
3use std::sync::{mpsc::Sender, Arc};
4use std::thread;
5use std::time::{Duration, SystemTime};
6
7use anyhow::{anyhow, Result};
8
9use crate::common::{random_name, rename_filename, TMP_FOLDER_PATH};
10use crate::event::FmEvents;
11use crate::{log_info, log_line};
12
13type OptionVecPathBuf = Option<Vec<PathBuf>>;
14
15struct BulkExecutor {
16 index: usize,
17 original_filepath: Vec<PathBuf>,
18 temp_file: PathBuf,
19 new_filenames: Vec<String>,
20 parent_dir: String,
21}
22
23impl BulkExecutor {
24 fn new(original_filepath: Vec<PathBuf>, parent_dir: &str) -> Self {
25 let temp_file = generate_random_filepath();
26 Self {
27 index: 0,
28 original_filepath,
29 temp_file,
30 new_filenames: vec![],
31 parent_dir: parent_dir.to_owned(),
32 }
33 }
34
35 fn ask_filenames(self) -> Result<Self> {
36 create_random_file(&self.temp_file)?;
37 log_info!("created {temp_file}", temp_file = self.temp_file.display());
38 self.write_original_names()?;
39 Ok(self)
43 }
44
45 fn watch_modification_in_thread(&self, fm_sender: Arc<Sender<FmEvents>>) -> Result<()> {
46 let original_modification = get_modified_date(&self.temp_file)?;
47 let filepath = self.temp_file.to_owned();
48 thread::spawn(move || {
49 loop {
50 if is_file_modified(&filepath, original_modification).unwrap_or(true) {
51 break;
52 }
53 thread::sleep(Duration::from_millis(100));
54 }
55 fm_sender.send(FmEvents::BulkExecute).unwrap_or_default();
56 });
57 Ok(())
58 }
59 fn get_new_names(&mut self) -> Result<()> {
60 self.new_filenames = get_new_filenames(&self.temp_file)?;
61 Ok(())
62 }
63
64 fn write_original_names(&self) -> Result<()> {
65 let mut file = std::fs::File::create(&self.temp_file)?;
66 log_info!("created {temp_file}", temp_file = self.temp_file.display());
67
68 for path in &self.original_filepath {
69 let Some(os_filename) = path.file_name() else {
70 return Ok(());
71 };
72 let Some(filename) = os_filename.to_str() else {
73 return Ok(());
74 };
75 file.write_all(filename.as_bytes())?;
76 file.write_all(b"\n")?;
77 }
78 Ok(())
79 }
80
81 fn execute(&self) -> Result<(OptionVecPathBuf, OptionVecPathBuf)> {
82 let paths = self.rename_create();
83 self.del_temporary_file()?;
84 paths
85 }
86
87 fn rename_create(&self) -> Result<(OptionVecPathBuf, OptionVecPathBuf)> {
88 let renamed_paths = self.rename_all(&self.new_filenames)?;
89 let created_paths = self.create_all_files(&self.new_filenames)?;
90 Ok((renamed_paths, created_paths))
91 }
92
93 fn rename_all(&self, new_filenames: &[String]) -> Result<OptionVecPathBuf> {
94 let mut paths = vec![];
95 for (path, filename) in self.original_filepath.iter().zip(new_filenames.iter()) {
96 match rename_filename(path, filename) {
97 Ok(path) => paths.push(path),
98 Err(error) => log_info!(
99 "Error renaming {path} to {filename}. Error: {error:?}",
100 path = path.display()
101 ),
102 }
103 }
104 log_line!("Bulk renamed {len} files", len = paths.len());
105 Ok(Some(paths))
106 }
107
108 fn create_all_files(&self, new_filenames: &[String]) -> Result<OptionVecPathBuf> {
109 let mut paths = vec![];
110 for filename in new_filenames.iter().skip(self.original_filepath.len()) {
111 let Some(path) = self.create_file(filename)? else {
112 continue;
113 };
114 paths.push(path)
115 }
116 log_line!("Bulk created {len} files", len = paths.len());
117 Ok(Some(paths))
118 }
119
120 fn create_file(&self, filename: &str) -> Result<Option<PathBuf>> {
121 let mut new_path = std::path::PathBuf::from(&self.parent_dir);
122 if !filename.ends_with('/') {
123 new_path.push(filename);
124 let Some(parent) = new_path.parent() else {
125 return Ok(None);
126 };
127 log_info!("Bulk new files. Creating parent: {}", parent.display());
128 if std::fs::create_dir_all(parent).is_err() {
129 return Ok(None);
130 };
131 log_info!("creating: {new_path:?}");
132 std::fs::File::create(&new_path)?;
133 log_line!("Bulk created {new_path}", new_path = new_path.display());
134 } else {
135 new_path.push(filename);
136 log_info!("Bulk creating dir: {}", new_path.display());
137 std::fs::create_dir_all(&new_path)?;
138 log_line!("Bulk created {new_path}", new_path = new_path.display());
139 }
140 Ok(Some(new_path))
141 }
142
143 fn del_temporary_file(&self) -> Result<()> {
144 std::fs::remove_file(&self.temp_file)?;
145 Ok(())
146 }
147
148 fn len(&self) -> usize {
149 self.new_filenames.len()
150 }
151
152 fn next(&mut self) {
153 self.index = (self.index + 1) % self.len()
154 }
155
156 fn prev(&mut self) {
157 if self.index > 0 {
158 self.index -= 1
159 } else {
160 self.index = self.len() - 1
161 }
162 }
163
164 fn set_index(&mut self, index: usize) {
166 if index < self.len() {
167 self.index = index;
168 }
169 }
170
171 fn style(&self, index: usize, style: &ratatui::style::Style) -> ratatui::style::Style {
173 let mut style = *style;
174 if index == self.index {
175 style.add_modifier |= ratatui::style::Modifier::REVERSED;
176 }
177 style
178 }
179}
180
181fn generate_random_filepath() -> PathBuf {
182 let mut filepath = PathBuf::from(&TMP_FOLDER_PATH);
183 filepath.push(random_name());
184 filepath
185}
186
187fn create_random_file(temp_file: &Path) -> Result<()> {
188 std::fs::File::create(temp_file)?;
189 Ok(())
190}
191
192fn get_modified_date(filepath: &Path) -> Result<SystemTime> {
193 Ok(std::fs::metadata(filepath)?.modified()?)
194}
195
196fn is_file_modified(path: &Path, original_modification: std::time::SystemTime) -> Result<bool> {
197 Ok(get_modified_date(path)? > original_modification)
198}
199
200fn get_new_filenames(temp_file: &Path) -> Result<Vec<String>> {
201 let file = std::fs::File::open(temp_file)?;
202 let reader = std::io::BufReader::new(file);
203
204 let new_names: Vec<String> = reader
205 .lines()
206 .map_while(Result::ok)
207 .map(|line| line.trim().to_owned())
208 .filter(|line| !line.is_empty())
209 .collect();
210 Ok(new_names)
211}
212
213#[derive(Default)]
225pub struct Bulk {
226 bulk: Option<BulkExecutor>,
227}
228
229impl Bulk {
230 pub fn reset(&mut self) {
232 self.bulk = None;
233 }
234
235 pub fn ask_filenames(
246 &mut self,
247 flagged_in_current_dir: Vec<PathBuf>,
248 current_tab_path_str: &str,
249 ) -> Result<()> {
250 self.bulk =
251 Some(BulkExecutor::new(flagged_in_current_dir, current_tab_path_str).ask_filenames()?);
252 Ok(())
253 }
254
255 pub fn watch_in_thread(&mut self, fm_sender: Arc<Sender<FmEvents>>) -> Result<()> {
256 if let Some(bulk) = &self.bulk { bulk.watch_modification_in_thread(fm_sender)? }
257 Ok(())
258 }
259
260 pub fn get_new_names(&mut self) -> Result<()> {
261 if let Some(bulk) = &mut self.bulk {
262 bulk.get_new_names()?;
263 }
264 Ok(())
265 }
266
267 pub fn format_confirmation(&self) -> Vec<String> {
269 if let Some(bulk) = &self.bulk {
270 let mut lines: Vec<String> = bulk
271 .original_filepath
272 .iter()
273 .zip(bulk.new_filenames.iter())
274 .map(|(original, new)| {
275 format!("RENAME: {original} -> {new}", original = original.display())
276 })
277 .collect();
278 for new in bulk.new_filenames.iter().skip(bulk.original_filepath.len()) {
279 lines.push(format!("CREATE: {new}"));
280 }
281 lines
282 } else {
283 vec![]
284 }
285 }
286
287 pub fn execute(&mut self) -> Result<(OptionVecPathBuf, OptionVecPathBuf)> {
294 let Some(bulk) = &mut self.bulk else {
295 return Err(anyhow!("bulk shouldn't be None"));
296 };
297 let ret = bulk.execute();
298 self.reset();
299 ret
300 }
301
302 pub fn temp_file(&self) -> Option<PathBuf> {
305 self.bulk.as_ref().map(|bulk| bulk.temp_file.to_owned())
306 }
307
308 pub fn is_empty(&self) -> bool {
309 self.len() == 0
310 }
311
312 pub fn len(&self) -> usize {
313 let Some(bulk) = &self.bulk else {
314 return 0;
315 };
316 bulk.len()
317 }
318
319 pub fn index(&self) -> usize {
320 let Some(bulk) = &self.bulk else {
321 return 0;
322 };
323 bulk.index
324 }
325
326 pub fn next(&mut self) {
327 let Some(bulk) = &mut self.bulk else {
328 return;
329 };
330 bulk.next()
331 }
332
333 pub fn prev(&mut self) {
334 let Some(bulk) = &mut self.bulk else {
335 return;
336 };
337 bulk.prev()
338 }
339
340 pub fn set_index(&mut self, index: usize) {
342 let Some(bulk) = &mut self.bulk else {
343 return;
344 };
345 bulk.set_index(index)
346 }
347
348 pub fn style(&self, index: usize, style: &ratatui::style::Style) -> ratatui::style::Style {
349 let Some(bulk) = &self.bulk else {
350 return ratatui::style::Style::default();
351 };
352 bulk.style(index, style)
353 }
354}