#[cfg(feature = "std")]
use alloc::borrow::ToOwned;
use alloc::rc::Rc;
use core::cell::RefCell;
#[cfg(unix)]
use std::os::unix::prelude::{AsRawFd, RawFd};
use std::{
fs::{self, remove_file, File, OpenOptions},
io::{Seek, Write},
path::{Path, PathBuf},
string::String,
};
use crate::Error;
pub const INPUTFILE_STD: &str = ".cur_input";
#[must_use]
pub fn get_unique_std_input_file() -> String {
format!("{}_{}", INPUTFILE_STD, std::process::id())
}
pub fn write_file_atomic<P>(path: P, bytes: &[u8]) -> Result<(), Error>
where
P: AsRef<Path>,
{
fn inner(path: &Path, bytes: &[u8]) -> Result<(), Error> {
let mut tmpfile_name = path.to_path_buf();
tmpfile_name.set_file_name(format!(
".{}.tmp",
tmpfile_name.file_name().unwrap().to_string_lossy()
));
let mut tmpfile = OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmpfile_name)?;
tmpfile.write_all(bytes)?;
fs::rename(&tmpfile_name, path)?;
Ok(())
}
inner(path.as_ref(), bytes)
}
#[cfg(feature = "std")]
#[derive(Debug)]
pub struct InputFile {
pub path: PathBuf,
pub file: File,
pub rc: Rc<RefCell<usize>>,
}
impl Eq for InputFile {}
impl PartialEq for InputFile {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Clone for InputFile {
fn clone(&self) -> Self {
{
let mut rc = self.rc.borrow_mut();
assert_ne!(*rc, usize::MAX, "InputFile rc overflow");
*rc += 1;
}
Self {
path: self.path.clone(),
file: self.file.try_clone().unwrap(),
rc: self.rc.clone(),
}
}
}
#[cfg(feature = "std")]
impl InputFile {
pub fn create<P>(filename: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let f = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(true)
.open(&filename)?;
Ok(Self {
path: filename.as_ref().to_owned(),
file: f,
rc: Rc::new(RefCell::new(1)),
})
}
#[must_use]
#[cfg(unix)]
pub fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
pub fn write_buf(&mut self, buf: &[u8]) -> Result<(), Error> {
self.rewind()?;
self.file.write_all(buf)?;
self.file.set_len(buf.len() as u64)?;
self.file.flush()?;
self.rewind()
}
#[inline]
pub fn rewind(&mut self) -> Result<(), Error> {
if let Err(err) = self.file.rewind() {
Err(err.into())
} else {
Ok(())
}
}
}
#[cfg(feature = "std")]
impl Drop for InputFile {
fn drop(&mut self) {
let mut rc = self.rc.borrow_mut();
assert_ne!(*rc, 0, "InputFile rc should never be 0");
*rc -= 1;
if *rc == 0 {
drop(remove_file(&self.path));
}
}
}
#[cfg(test)]
mod test {
use std::fs;
use crate::fs::{write_file_atomic, InputFile};
#[test]
fn test_atomic_file_write() {
let path = "test_atomic_file_write.tmp";
write_file_atomic(path, b"test").unwrap();
let content = fs::read_to_string(path).unwrap();
fs::remove_file(path).unwrap();
assert_eq!(content, "test");
}
#[test]
fn test_cloned_ref() {
let mut one = InputFile::create("test_cloned_ref.tmp").unwrap();
let two = one.clone();
one.write_buf("Welp".as_bytes()).unwrap();
drop(one);
assert_eq!("Welp", fs::read_to_string(two.path.as_path()).unwrap());
}
}