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