mod error;
mod id;
mod info;
mod options;
use log::{error, warn};
use nuts_backend::{Backend, ReceiveHeader, HEADER_MAX_SIZE};
use std::io::{self, ErrorKind, Read, Write};
use std::path::Path;
use std::{cmp, fs};
pub use error::Error;
pub use id::Id;
pub use info::Info;
pub use options::{CreateOptions, OpenOptions, Settings};
use crate::error::Result;
fn read_block(path: &Path, id: &Id, bsize: u32, buf: &mut [u8]) -> Result<usize> {
let path = id.to_pathbuf(path);
let mut fh = fs::OpenOptions::new().read(true).open(path)?;
let len = cmp::min(buf.len(), bsize as usize);
let target = &mut buf[..len];
fh.read_exact(target)?;
Ok(len)
}
fn write_block(
path: &Path,
id: &Id,
aquire: bool,
header: bool,
bsize: u32,
buf: &[u8],
) -> Result<usize> {
let path = id.to_pathbuf(path);
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
if aquire {
if path.exists() {
return Err(io::Error::new(
ErrorKind::Other,
format!("cannot aquire {}, already stored in {}", id, path.display()),
)
.into());
}
} else {
if !header && !path.is_file() {
return Err(io::Error::new(
ErrorKind::Other,
format!("cannot open {}, no related file {}", id, path.display()),
)
.into());
}
}
let tmp_path = path.with_extension("tmp");
let mut fh = fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp_path)?;
let len = cmp::min(buf.len(), bsize as usize);
let pad_len = bsize as usize - len;
fh.write_all(&buf[..len])?;
fh.write_all(&vec![0; pad_len])?;
fh.flush()?;
fs::rename(tmp_path, path)?;
Ok(len)
}
fn read_header(path: &Path, buf: &mut [u8]) -> Result<()> {
read_block(path, &Id::min(), HEADER_MAX_SIZE as u32, buf).map(|_| ())
}
fn write_header(path: &Path, bsize: u32, buf: &[u8]) -> Result<()> {
write_block(path, &Id::min(), false, true, bsize, buf).map(|_| ())
}
#[derive(Debug)]
pub struct DirectoryBackend<P: AsRef<Path>> {
bsize: u32,
path: P,
}
impl<P: AsRef<Path>> ReceiveHeader<Self> for DirectoryBackend<P> {
fn get_header_bytes(&mut self, bytes: &mut [u8; HEADER_MAX_SIZE]) -> Result<()> {
read_header(self.path.as_ref(), bytes)
}
}
impl<P: AsRef<Path>> Backend for DirectoryBackend<P> {
type Settings = Settings;
type Err = Error;
type Id = Id;
type Info = Info;
fn info(&self) -> Result<Info> {
Ok(Info { bsize: self.bsize })
}
fn block_size(&self) -> u32 {
self.bsize
}
fn aquire(&mut self, buf: &[u8]) -> Result<Self::Id> {
const MAX: u8 = 3;
for n in 0..MAX {
let id = Id::generate()?;
match write_block(self.path.as_ref(), &id, true, false, self.bsize, buf) {
Ok(_) => return Ok(id),
Err(Error::Io(err)) => {
if err.kind() == ErrorKind::AlreadyExists {
warn!("Id {} already exists try again ({}/{})", id, n + 1, MAX);
} else {
return Err(err.into());
}
}
Err(err) => return Err(err),
};
}
Err(Error::UniqueId)
}
fn release(&mut self, id: Self::Id) -> Result<()> {
let path = id.to_pathbuf(self.path.as_ref());
Ok(fs::remove_file(path)?)
}
fn read(&mut self, id: &Id, buf: &mut [u8]) -> Result<usize> {
read_block(self.path.as_ref(), id, self.bsize, buf)
}
fn write(&mut self, id: &Id, buf: &[u8]) -> Result<usize> {
write_block(self.path.as_ref(), id, false, false, self.bsize, buf)
}
fn write_header(&mut self, buf: &[u8; HEADER_MAX_SIZE]) -> Result<()> {
write_header(self.path.as_ref(), self.bsize, buf)
}
fn delete(self) {
if let Err(err) = fs::remove_dir_all(self.path) {
error!("failed to delete backend instance: {}", err);
}
}
}