use crate::wrapper_names_old_and_new::{MESSAGE_PREFIX, RenamedName};
use crate::rename_that_never_replaces::{rename_noreplace, rename_unsupported};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Look {
OnlyEarlier,
Both,
Settled,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Placement {
pub path: PathBuf,
pub notice: Option<String>,
}
fn exists(path: &Path) -> bool {
return std::fs::symlink_metadata(path).is_ok();
}
pub fn look(parent: &Path, name: RenamedName) -> Look {
let current_there: bool = exists(parent.join(name.current).as_path());
let earlier_there: bool = exists(parent.join(name.earlier).as_path());
if current_there && earlier_there {
return Look::Both;
}
if earlier_there {
return Look::OnlyEarlier;
}
return Look::Settled;
}
fn both_notice(parent: &Path, name: RenamedName) -> String {
return format!(
"{}: {} and {} both exist in {}; {} is used and {} was left as it is. Move what you still need from {} into {}, \
then remove {}.\n",
MESSAGE_PREFIX.current,
name.earlier,
name.current,
parent.display(),
name.current,
name.earlier,
name.earlier,
name.current,
name.earlier
);
}
fn unmovable_notice(parent: &Path, name: RenamedName, error: &std::io::Error) -> String {
return format!(
"{}: {} in {} could not be renamed to {} ({error}), so it is used under its earlier name this time and the rename is \
tried again by the next command.\n",
MESSAGE_PREFIX.current,
name.earlier,
parent.display(),
name.current
);
}
pub fn carry_out(looked: Look, parent: &Path, name: RenamedName, rename: fn(&Path, &Path) -> std::io::Result<()>) -> Placement {
let current: PathBuf = parent.join(name.current);
let earlier: PathBuf = parent.join(name.earlier);
match looked {
Look::Settled => return Placement { path: current, notice: None },
Look::Both => {
return Placement { path: current, notice: Some(both_notice(parent, name)) };
}
Look::OnlyEarlier => {}
}
match rename(earlier.as_path(), current.as_path()) {
Ok(()) => return Placement { path: current, notice: None },
Err(error) => {
if error.kind() == std::io::ErrorKind::NotFound && exists(current.as_path()) {
return Placement { path: current, notice: None };
}
if error.kind() == std::io::ErrorKind::AlreadyExists {
return Placement { path: current, notice: Some(both_notice(parent, name)) };
}
let reported: std::io::Error = if rename_unsupported(&error) {
std::io::Error::new(error.kind(), "this filesystem cannot rename without replacing")
} else {
error
};
return Placement { path: earlier, notice: Some(unmovable_notice(parent, name, &reported)) };
}
}
}
pub fn place_kept_name(parent: &Path, name: RenamedName) -> Placement {
return carry_out(look(parent, name), parent, name, rename_noreplace);
}
pub fn existing_kept_name(parent: &Path, name: RenamedName) -> PathBuf {
if look(parent, name) == Look::OnlyEarlier {
return parent.join(name.earlier);
}
return parent.join(name.current);
}
static REPORTED: AtomicU64 = AtomicU64::new(0);
fn report_bit(name: RenamedName) -> u64 {
let mut sum: u64 = 0;
for byte in name.current.bytes() {
sum = sum.wrapping_mul(31).wrapping_add(u64::from(byte));
}
return 1u64 << (sum % 64);
}
pub fn kept_name_for_use(parent: &Path, name: RenamedName) -> PathBuf {
let placed: Placement = place_kept_name(parent, name);
if let Some(text) = placed.notice {
let bit: u64 = report_bit(name);
let before: u64 = REPORTED.fetch_or(bit, Ordering::Relaxed);
if before & bit == 0 {
let _written = std::io::stderr().write_all(text.as_bytes());
}
}
return placed.path;
}
#[cfg(test)]
#[path = "earlier_name_moved_to_current_in_place_tests.rs"]
mod tests;