use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};
use std::sync::{mpsc::Sender, Arc};
use std::thread;
use std::time::{Duration, SystemTime};
use anyhow::{anyhow, Result};
use crate::common::{random_name, rename_filename, TMP_FOLDER_PATH};
use crate::event::FmEvents;
use crate::{log_info, log_line};
type OptionVecPathBuf = Option<Vec<PathBuf>>;
type OptionVecPairPathBuf = Option<Vec<(PathBuf, PathBuf)>>;
struct BulkExecutor {
index: usize,
original_filepath: Vec<PathBuf>,
temp_file: PathBuf,
new_filenames: Vec<String>,
parent_dir: String,
}
impl BulkExecutor {
fn new(original_filepath: Vec<PathBuf>, parent_dir: &str) -> Self {
let temp_file = generate_random_filepath();
Self {
index: 0,
original_filepath,
temp_file,
new_filenames: vec![],
parent_dir: parent_dir.to_owned(),
}
}
fn ask_filenames(self) -> Result<Self> {
create_random_file(&self.temp_file)?;
log_info!("created {temp_file}", temp_file = self.temp_file.display());
self.write_original_names()?;
Ok(self)
}
fn watch_modification_in_thread(&self, fm_sender: Arc<Sender<FmEvents>>) -> Result<()> {
let original_modification = get_modified_date(&self.temp_file)?;
let filepath = self.temp_file.to_owned();
thread::spawn(move || {
loop {
if is_file_modified(&filepath, original_modification).unwrap_or(true) {
break;
}
thread::sleep(Duration::from_millis(100));
}
fm_sender.send(FmEvents::BulkExecute).unwrap_or_default();
});
Ok(())
}
fn get_new_names(&mut self) -> Result<()> {
self.new_filenames = get_new_filenames(&self.temp_file)?;
Ok(())
}
fn write_original_names(&self) -> Result<()> {
let mut file = std::fs::File::create(&self.temp_file)?;
log_info!("created {temp_file}", temp_file = self.temp_file.display());
for path in &self.original_filepath {
let Some(os_filename) = path.file_name() else {
return Ok(());
};
let Some(filename) = os_filename.to_str() else {
return Ok(());
};
file.write_all(filename.as_bytes())?;
file.write_all(b"\n")?;
}
Ok(())
}
fn execute(&self) -> Result<(OptionVecPairPathBuf, OptionVecPathBuf)> {
let paths = self.rename_create();
self.del_temporary_file()?;
paths
}
fn rename_create(&self) -> Result<(OptionVecPairPathBuf, OptionVecPathBuf)> {
let updated_paths = self.rename_all(&self.new_filenames)?;
let created_paths = self.create_all_files(&self.new_filenames)?;
Ok((updated_paths, created_paths))
}
fn rename_all(&self, new_filenames: &[String]) -> Result<OptionVecPairPathBuf> {
let mut updated_paths = vec![];
for (old_path, filename) in self.original_filepath.iter().zip(new_filenames.iter()) {
match rename_filename(old_path, filename) {
Ok(new_path) => updated_paths.push((old_path.to_owned(), new_path)),
Err(error) => log_info!(
"Error renaming {old_path} to {filename}. Error: {error:?}",
old_path = old_path.display()
),
}
}
log_line!("Bulk renamed {len} files", len = updated_paths.len());
Ok(Some(updated_paths))
}
fn create_all_files(&self, new_filenames: &[String]) -> Result<OptionVecPathBuf> {
let mut paths = vec![];
for filename in new_filenames.iter().skip(self.original_filepath.len()) {
let Some(path) = self.create_file(filename)? else {
continue;
};
paths.push(path)
}
log_line!("Bulk created {len} files", len = paths.len());
Ok(Some(paths))
}
fn create_file(&self, filename: &str) -> Result<Option<PathBuf>> {
let mut new_path = std::path::PathBuf::from(&self.parent_dir);
if !filename.ends_with('/') {
new_path.push(filename);
let Some(parent) = new_path.parent() else {
return Ok(None);
};
log_info!("Bulk new files. Creating parent: {}", parent.display());
if std::fs::create_dir_all(parent).is_err() {
return Ok(None);
};
log_info!("creating: {new_path:?}");
std::fs::File::create(&new_path)?;
log_line!("Bulk created {new_path}", new_path = new_path.display());
} else {
new_path.push(filename);
log_info!("Bulk creating dir: {}", new_path.display());
std::fs::create_dir_all(&new_path)?;
log_line!("Bulk created {new_path}", new_path = new_path.display());
}
Ok(Some(new_path))
}
fn del_temporary_file(&self) -> Result<()> {
std::fs::remove_file(&self.temp_file)?;
Ok(())
}
fn len(&self) -> usize {
self.new_filenames.len()
}
fn next(&mut self) {
self.index = (self.index + 1) % self.len()
}
fn prev(&mut self) {
if self.index > 0 {
self.index -= 1
} else {
self.index = self.len() - 1
}
}
fn set_index(&mut self, index: usize) {
if index < self.len() {
self.index = index;
}
}
fn style(&self, index: usize, style: &ratatui::style::Style) -> ratatui::style::Style {
let mut style = *style;
if index == self.index {
style.add_modifier |= ratatui::style::Modifier::REVERSED;
}
style
}
}
fn generate_random_filepath() -> PathBuf {
let mut filepath = PathBuf::from(&TMP_FOLDER_PATH);
filepath.push(random_name());
filepath
}
fn create_random_file(temp_file: &Path) -> Result<()> {
std::fs::File::create(temp_file)?;
Ok(())
}
fn get_modified_date(filepath: &Path) -> Result<SystemTime> {
Ok(std::fs::metadata(filepath)?.modified()?)
}
fn is_file_modified(path: &Path, original_modification: std::time::SystemTime) -> Result<bool> {
Ok(get_modified_date(path)? > original_modification)
}
fn get_new_filenames(temp_file: &Path) -> Result<Vec<String>> {
let file = std::fs::File::open(temp_file)?;
let reader = std::io::BufReader::new(file);
let new_names: Vec<String> = reader
.lines()
.map_while(Result::ok)
.map(|line| line.trim().to_owned())
.filter(|line| !line.is_empty())
.collect();
Ok(new_names)
}
#[derive(Default)]
pub struct Bulk {
bulk: Option<BulkExecutor>,
}
impl Bulk {
pub fn reset(&mut self) {
self.bulk = None;
}
pub fn ask_filenames(
&mut self,
flagged_in_current_dir: Vec<PathBuf>,
current_tab_path_str: &str,
) -> Result<()> {
self.bulk =
Some(BulkExecutor::new(flagged_in_current_dir, current_tab_path_str).ask_filenames()?);
Ok(())
}
pub fn watch_in_thread(&mut self, fm_sender: Arc<Sender<FmEvents>>) -> Result<()> {
if let Some(bulk) = &self.bulk {
bulk.watch_modification_in_thread(fm_sender)?
}
Ok(())
}
pub fn get_new_names(&mut self) -> Result<()> {
if let Some(bulk) = &mut self.bulk {
bulk.get_new_names()?;
}
Ok(())
}
pub fn format_confirmation(&self) -> Vec<String> {
if let Some(bulk) = &self.bulk {
let mut lines: Vec<String> = bulk
.original_filepath
.iter()
.zip(bulk.new_filenames.iter())
.map(|(original, new)| {
format!("RENAME: {original} -> {new}", original = original.display())
})
.collect();
for new in bulk.new_filenames.iter().skip(bulk.original_filepath.len()) {
lines.push(format!("CREATE: {new}"));
}
lines
} else {
vec![]
}
}
pub fn execute(&mut self) -> Result<(OptionVecPairPathBuf, OptionVecPathBuf)> {
let Some(bulk) = &mut self.bulk else {
return Err(anyhow!("bulk shouldn't be None"));
};
let ret = bulk.execute();
self.reset();
ret
}
pub fn temp_file(&self) -> Option<PathBuf> {
self.bulk.as_ref().map(|bulk| bulk.temp_file.to_owned())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
let Some(bulk) = &self.bulk else {
return 0;
};
bulk.len()
}
pub fn index(&self) -> usize {
let Some(bulk) = &self.bulk else {
return 0;
};
bulk.index
}
pub fn next(&mut self) {
let Some(bulk) = &mut self.bulk else {
return;
};
bulk.next()
}
pub fn prev(&mut self) {
let Some(bulk) = &mut self.bulk else {
return;
};
bulk.prev()
}
pub fn set_index(&mut self, index: usize) {
let Some(bulk) = &mut self.bulk else {
return;
};
bulk.set_index(index)
}
pub fn style(&self, index: usize, style: &ratatui::style::Style) -> ratatui::style::Style {
let Some(bulk) = &self.bulk else {
return ratatui::style::Style::default();
};
bulk.style(index, style)
}
}