mod handle;
mod lock;
use std::{
cmp::Ordering,
fs::{File, Metadata},
hash::{Hash, Hasher},
io::{self, Read, Seek, Write},
path::Path,
};
use handle::PortableFileHandle;
use lock::Lock;
type Arc<T> = std::sync::Arc<T>;
#[derive(Debug)]
struct FileImpl {
file: File,
state_lock: Lock<()>,
}
#[derive(Debug, Clone)]
pub struct CloneableFile {
inner: Arc<FileImpl>,
}
impl CloneableFile {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<CloneableFile> {
File::open(path).map(CloneableFile::from)
}
fn with_lock<T>(&self, func: impl FnOnce(&File) -> T) -> T {
lock::acquire(&self.inner.state_lock, |_| func(&self.inner.file))
}
pub fn create<P: AsRef<Path>>(path: P) -> io::Result<CloneableFile> {
File::create(path).map(CloneableFile::from)
}
pub fn metadata(&self) -> io::Result<Metadata> {
self.with_lock(|f| f.metadata())
}
pub fn set_len(&self, size: u64) -> io::Result<()> {
self.with_lock(|f| f.set_len(size))
}
pub fn try_clone(&self) -> io::Result<CloneableFile> {
self.with_lock(|f| f.try_clone().map(CloneableFile::from))
}
fn portable_fd(&self) -> PortableFileHandle {
handle::portable_file_handle(&self.inner.file)
}
}
impl From<File> for CloneableFile {
fn from(file: File) -> Self {
CloneableFile {
inner: Arc::new(FileImpl {
file,
state_lock: Lock::new(()),
}),
}
}
}
impl Read for CloneableFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.with_lock(|mut f| f.read(buf))
}
}
impl Read for &CloneableFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.with_lock(|mut f| f.read(buf))
}
}
impl Write for CloneableFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.with_lock(|mut f| f.write(buf))
}
fn flush(&mut self) -> io::Result<()> {
self.with_lock(|mut f| f.flush())
}
}
impl Write for &CloneableFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.with_lock(|mut f| f.write(buf))
}
fn flush(&mut self) -> io::Result<()> {
self.with_lock(|mut f| f.flush())
}
}
impl Seek for CloneableFile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.with_lock(|mut f| f.seek(pos))
}
}
impl Seek for &CloneableFile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.with_lock(|mut f| f.seek(pos))
}
}
impl PartialEq for CloneableFile {
fn eq(&self, other: &Self) -> bool {
self.portable_fd().eq(&other.portable_fd())
}
}
impl Eq for CloneableFile {}
impl Hash for CloneableFile {
fn hash<H: Hasher>(&self, state: &mut H) {
self.portable_fd().hash(state)
}
}
impl PartialOrd for CloneableFile {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.portable_fd().partial_cmp(&other.portable_fd())
}
}
impl Ord for CloneableFile {
fn cmp(&self, other: &Self) -> Ordering {
self.portable_fd().cmp(&other.portable_fd())
}
}
#[cfg(test)]
mod tests {
use std::{
cmp::Ordering,
collections::{BTreeMap, HashMap},
};
use super::*;
fn is_read_write_seek<T: io::Read + io::Write + io::Seek>(_: T) {}
fn is_send_sync<T: Send + Sync>(_: T) {}
fn cloneable_tempfile() -> io::Result<CloneableFile> {
tempfile::tempfile().map(CloneableFile::from)
}
#[test]
fn cloneable_file_is_usable() -> io::Result<()> {
let f = cloneable_tempfile()?;
let f2 = f.clone();
is_read_write_seek(f);
is_read_write_seek(&f2);
is_send_sync(f2);
is_send_sync(tempfile::tempfile()?);
Ok(())
}
#[test]
fn equality() -> io::Result<()> {
let f1 = cloneable_tempfile()?;
let f2 = cloneable_tempfile()?;
assert_ne!(&f1, &f2);
assert_eq!(&f2, &f2);
assert_eq!(&f1, &f1);
Ok(())
}
#[test]
fn ordering() -> io::Result<()> {
let f1 = cloneable_tempfile()?;
let f2 = cloneable_tempfile()?;
assert!(matches!(f1.cmp(&f2), Ordering::Greater | Ordering::Less));
Ok(())
}
#[test]
fn do_ops_with_lock_acquired() -> io::Result<()> {
let f = cloneable_tempfile()?;
f.with_lock(|mut f| {
f.write_all(b"Hello,")?;
f.write_all(b"qq world!\n")?;
f.sync_all()?;
Ok(())
})
}
#[test]
fn can_use_with_btreemap() -> io::Result<()> {
let mut map = BTreeMap::new();
let f1 = cloneable_tempfile()?;
let f2 = cloneable_tempfile()?;
map.insert(f1.clone(), 1);
map.insert(f2, 2);
assert_eq!(map.len(), 2);
assert_eq!(map[&f1], 1);
Ok(())
}
#[test]
fn can_use_with_hashmap() -> io::Result<()> {
let mut map = HashMap::new();
let f1 = cloneable_tempfile()?;
let f2 = cloneable_tempfile()?;
map.insert(f1.clone(), 1);
map.insert(f2, 2);
assert_eq!(map.len(), 2);
assert_eq!(map[&f1], 1);
Ok(())
}
}