use std::fs;
use std::io::{self, BufWriter, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use ld_lucivy::directory::error::{DeleteError, OpenReadError, OpenWriteError};
use ld_lucivy::directory::{
AntiCallToken, Directory, FileHandle, FileSlice, TerminatingWrite, WatchCallback,
WatchCallbackList, WatchHandle, WritePtr,
};
#[derive(Clone)]
pub struct StdFsDirectory {
root: PathBuf,
watch_router: Arc<RwLock<WatchCallbackList>>,
}
impl std::fmt::Debug for StdFsDirectory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "StdFsDirectory({:?})", self.root)
}
}
impl StdFsDirectory {
pub fn open(path: impl Into<PathBuf>) -> io::Result<Self> {
let root = path.into();
fs::create_dir_all(&root)?;
Ok(Self {
root,
watch_router: Arc::new(RwLock::new(WatchCallbackList::default())),
})
}
fn resolve(&self, path: &Path) -> PathBuf {
self.root.join(path)
}
}
struct FsWriter {
path: PathBuf,
buffer: Vec<u8>,
is_flushed: bool,
}
impl FsWriter {
fn new(path: PathBuf) -> Self {
Self {
path,
buffer: Vec::new(),
is_flushed: true,
}
}
}
impl Write for FsWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.is_flushed = false;
self.buffer.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.is_flushed = true;
fs::write(&self.path, &self.buffer)
}
}
impl TerminatingWrite for FsWriter {
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
self.flush()
}
}
impl Drop for FsWriter {
fn drop(&mut self) {
if !self.is_flushed {
eprintln!(
"Warning: FsWriter for {:?} dropped without flushing. Data may be lost.",
self.path
);
}
}
}
impl Directory for StdFsDirectory {
fn get_file_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>, OpenReadError> {
let file_slice = self.open_read(path)?;
Ok(Arc::new(file_slice))
}
fn open_read(&self, path: &Path) -> Result<FileSlice, OpenReadError> {
let full = self.resolve(path);
let data = fs::read(&full).map_err(|e| {
if e.kind() == io::ErrorKind::NotFound {
OpenReadError::FileDoesNotExist(full.clone())
} else {
OpenReadError::IoError {
io_error: Arc::new(e),
filepath: full.clone(),
}
}
})?;
Ok(FileSlice::from(data))
}
fn open_write(&self, path: &Path) -> Result<WritePtr, OpenWriteError> {
let full = self.resolve(path);
if full.exists() {
return Err(OpenWriteError::FileAlreadyExists(full));
}
if let Some(parent) = full.parent() {
fs::create_dir_all(parent).map_err(|e| OpenWriteError::IoError {
io_error: Arc::new(e),
filepath: full.clone(),
})?;
}
Ok(BufWriter::new(Box::new(FsWriter::new(full))))
}
fn delete(&self, path: &Path) -> Result<(), DeleteError> {
let full = self.resolve(path);
fs::remove_file(&full).map_err(|e| {
if e.kind() == io::ErrorKind::NotFound {
DeleteError::FileDoesNotExist(full)
} else {
DeleteError::IoError {
io_error: Arc::new(e),
filepath: full,
}
}
})
}
fn exists(&self, path: &Path) -> Result<bool, OpenReadError> {
Ok(self.resolve(path).exists())
}
fn atomic_read(&self, path: &Path) -> Result<Vec<u8>, OpenReadError> {
let full = self.resolve(path);
fs::read(&full).map_err(|e| {
if e.kind() == io::ErrorKind::NotFound {
OpenReadError::FileDoesNotExist(full.clone())
} else {
OpenReadError::IoError {
io_error: Arc::new(e),
filepath: full.clone(),
}
}
})
}
fn atomic_write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
let full = self.resolve(path);
if let Some(parent) = full.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&full, data)?;
if path == Path::new("meta.json") {
if let Ok(router) = self.watch_router.read() {
let _ = router.broadcast();
}
}
Ok(())
}
fn watch(&self, watch_callback: WatchCallback) -> ld_lucivy::Result<WatchHandle> {
Ok(self
.watch_router
.write()
.map_err(|_| {
ld_lucivy::LucivyError::SystemError("watch lock poisoned".to_string())
})?
.subscribe(watch_callback))
}
fn sync_directory(&self) -> io::Result<()> {
Ok(())
}
}