use std::{
collections::HashMap,
fmt::Debug,
io::{Read, Seek, Write},
path::{Path, PathBuf},
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
use parking_lot::{RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard};
use crate::error::Error;
pub mod any;
pub mod fs;
pub mod memory;
pub trait ManagedFile: File {
type Manager: FileManager<File = Self>;
}
pub trait File: Debug + Send + Sync + Seek + Read + Write + 'static {
fn id(&self) -> Option<u64>;
fn path(&self) -> &Path;
fn length(&self) -> Result<u64, Error>;
fn close(self) -> Result<(), Error>;
}
pub trait ManagedFileOpener<File>
where
File: ManagedFile,
{
fn open_for_read(&self, path: impl AsRef<Path> + Send, id: Option<u64>) -> Result<File, Error>;
fn open_for_append(
&self,
path: impl AsRef<Path> + Send,
id: Option<u64>,
) -> Result<File, Error>;
}
pub trait FileManager:
ManagedFileOpener<Self::File> + Default + Clone + Debug + Send + Sync + 'static
{
type File: ManagedFile<Manager = Self>;
type FileHandle: OpenableFile<Self::File> + OperableFile<Self::File>;
fn read(&self, path: impl AsRef<Path>) -> Result<Self::FileHandle, Error>;
fn append(&self, path: impl AsRef<Path>) -> Result<Self::FileHandle, Error>;
fn file_length(&self, path: impl AsRef<Path>) -> Result<u64, Error>;
fn exists(&self, path: impl AsRef<Path>) -> Result<bool, Error>;
fn close_handles<F: FnOnce(u64)>(&self, path: impl AsRef<Path>, publish_callback: F);
fn delete(&self, path: impl AsRef<Path>) -> Result<bool, Error>;
fn delete_directory(&self, path: impl AsRef<Path>) -> Result<(), Error>;
}
pub trait OpenableFile<F: ManagedFile>: Debug + Sized + Send + Sync {
fn id(&self) -> Option<u64>;
fn replace_with<C: FnOnce(u64)>(
self,
replacement: F,
manager: &F::Manager,
publish_callback: C,
) -> Result<Self, Error>;
fn close(self) -> Result<(), Error>;
}
pub trait OperableFile<File>
where
File: ManagedFile,
{
fn execute<Output, Op: FileOp<Output>>(&mut self, operator: Op) -> Output;
}
pub trait FileOp<Output> {
fn execute(self, file: &mut dyn File) -> Output;
}
#[derive(Default, Clone, Debug)]
pub struct PathIds {
file_id_counter: Arc<AtomicU64>,
file_ids: Arc<RwLock<HashMap<PathBuf, u64>>>,
}
impl PathIds {
fn file_id_for_path(&self, path: &Path, insert_if_not_found: bool) -> Option<u64> {
let file_ids = self.file_ids.upgradable_read();
if let Some(id) = file_ids.get(path) {
Some(*id)
} else if insert_if_not_found {
let mut file_ids = RwLockUpgradableReadGuard::upgrade(file_ids);
Some(
*file_ids
.entry(path.to_path_buf())
.or_insert_with(|| self.file_id_counter.fetch_add(1, Ordering::SeqCst)),
)
} else {
None
}
}
fn remove_file_id_for_path(&self, path: &Path) -> Option<u64> {
let mut file_ids = self.file_ids.write();
file_ids.remove(path)
}
fn recreate_file_id_for_path(&self, path: &Path) -> Option<RecreatedFile<'_>> {
let mut file_ids = self.file_ids.write();
let new_id = self.file_id_counter.fetch_add(1, Ordering::SeqCst);
file_ids
.insert(path.to_path_buf(), new_id)
.map(|old_id| RecreatedFile {
previous_id: old_id,
new_id,
_guard: file_ids,
})
}
fn remove_file_ids_for_path_prefix(&self, path: &Path) -> Vec<u64> {
let mut file_ids = self.file_ids.write();
let mut ids_to_remove = Vec::new();
let mut paths_to_remove = Vec::new();
for (file, id) in file_ids.iter() {
if file.starts_with(path) {
paths_to_remove.push(file.clone());
ids_to_remove.push(*id);
}
}
for path in paths_to_remove {
file_ids.remove(&path);
}
ids_to_remove
}
}
pub struct RecreatedFile<'a> {
pub previous_id: u64,
pub new_id: u64,
_guard: RwLockWriteGuard<'a, HashMap<PathBuf, u64>>,
}