#[cfg(test)]
mod tests;
use log::{debug, error, warn};
use nuts_backend::Backend;
use std::cmp;
use std::convert::{TryFrom, TryInto};
use crate::entry::{populate_mode_api, populate_tstamp_api, Inner};
use crate::error::{ArchiveResult, Error};
use crate::id::Id;
use crate::pager::Pager;
use crate::tree::Tree;
pub enum Entry<'a, B: Backend> {
File(FileEntry<'a, B>),
Directory(DirectoryEntry<'a, B>),
Symlink(SymlinkEntry<'a, B>),
}
impl<'a, B: Backend> Entry<'a, B> {
pub fn next(self) -> Option<ArchiveResult<Entry<'a, B>, B>> {
match self.into_inner_entry().next() {
Some(Ok(entry)) => Some(entry.try_into()),
Some(Err(err)) => Some(Err(err)),
None => None,
}
}
pub fn name(&self) -> &str {
&self.inner_entry().inner.name
}
pub fn size(&self) -> u64 {
self.inner_entry().inner.size
}
populate_mode_api!();
populate_tstamp_api!();
pub fn is_file(&self) -> bool {
match self {
Self::File(_) => true,
Self::Directory(_) => false,
Self::Symlink(_) => false,
}
}
pub fn as_file(&self) -> Option<&FileEntry<'a, B>> {
match self {
Self::File(value) => Some(value),
Self::Directory(_) => None,
Self::Symlink(_) => None,
}
}
pub fn as_file_mut(&mut self) -> Option<&mut FileEntry<'a, B>> {
match self {
Entry::File(value) => Some(value),
Entry::Directory(_) => None,
Entry::Symlink(_) => None,
}
}
pub fn into_file(self) -> Option<FileEntry<'a, B>> {
match self {
Self::File(value) => Some(value),
Self::Directory(_) => None,
Self::Symlink(_) => None,
}
}
pub fn is_directory(&self) -> bool {
match self {
Self::File(_) => false,
Self::Directory(_) => true,
Self::Symlink(_) => false,
}
}
pub fn as_directory(&self) -> Option<&DirectoryEntry<'a, B>> {
match self {
Self::File(_) => None,
Self::Directory(value) => Some(value),
Self::Symlink(_) => None,
}
}
pub fn into_directory(self) -> Option<DirectoryEntry<'a, B>> {
match self {
Self::File(_) => None,
Self::Directory(value) => Some(value),
Self::Symlink(_) => None,
}
}
pub fn is_symlink(&self) -> bool {
match self {
Self::File(_) => false,
Self::Directory(_) => false,
Self::Symlink(_) => true,
}
}
pub fn as_symlink(&self) -> Option<&SymlinkEntry<'a, B>> {
match self {
Self::File(_) => None,
Self::Directory(_) => None,
Self::Symlink(value) => Some(value),
}
}
pub fn into_symlink(self) -> Option<SymlinkEntry<'a, B>> {
match self {
Self::File(_) => None,
Self::Directory(_) => None,
Self::Symlink(value) => Some(value),
}
}
fn inner_entry(&'a self) -> &InnerEntry<'a, B> {
match self {
Self::File(inner) => &inner.0,
Self::Directory(inner) => &inner.0,
Self::Symlink(inner) => &inner.shared,
}
}
fn into_inner_entry(self) -> InnerEntry<'a, B> {
match self {
Self::File(inner) => inner.0,
Self::Directory(inner) => inner.0,
Self::Symlink(inner) => inner.shared,
}
}
fn inner(&self) -> &Inner {
&self.inner_entry().inner
}
}
impl<'a, B: Backend> TryFrom<InnerEntry<'a, B>> for Entry<'a, B> {
type Error = Error<B>;
fn try_from(src: InnerEntry<'a, B>) -> ArchiveResult<Self, B> {
if src.inner.mode.is_file() {
Ok(Self::File(FileEntry(src)))
} else if src.inner.mode.is_directory() {
Ok(Self::Directory(DirectoryEntry(src)))
} else if src.inner.mode.is_symlink() {
Ok(Self::Symlink(SymlinkEntry::new(src)?))
} else {
error!(
"could not detect entry type at {} from mode {:?}",
src.idx, src.inner.mode
);
match src.tree.lookup(src.pager, src.idx) {
Some(Ok(id)) => Err(Error::InvalidType(Some(id.as_ref().clone()))),
Some(Err(err)) => {
error!("id lookup failed for idx {}", src.idx);
Err(err)
}
None => {
warn!("no id found at idx {}", src.idx);
Err(Error::InvalidType(None))
}
}
}
}
}
pub struct FileEntry<'a, B: Backend>(InnerEntry<'a, B>);
impl<'a, B: Backend> FileEntry<'a, B> {
pub fn name(&self) -> &str {
&self.0.inner.name
}
pub fn size(&self) -> u64 {
self.0.inner.size
}
populate_mode_api!();
populate_tstamp_api!();
pub fn read(&mut self, buf: &mut [u8]) -> ArchiveResult<usize, B> {
self.0.read(buf)
}
pub fn read_all(&mut self, mut buf: &mut [u8]) -> ArchiveResult<(), B> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
}
Err(e) => return Err(e),
}
}
if !buf.is_empty() {
Err(Error::UnexpectedEof)
} else {
Ok(())
}
}
pub fn read_vec(&mut self) -> ArchiveResult<Vec<u8>, B> {
let mut vec = vec![0; self.0.inner.size as usize];
self.read_all(&mut vec).map(|()| vec)
}
fn inner(&self) -> &Inner {
&self.0.inner
}
}
pub struct DirectoryEntry<'a, B: Backend>(InnerEntry<'a, B>);
impl<'a, B: Backend> DirectoryEntry<'a, B> {
pub fn name(&self) -> &str {
&self.0.inner.name
}
populate_mode_api!();
populate_tstamp_api!();
fn inner(&self) -> &Inner {
&self.0.inner
}
}
pub struct SymlinkEntry<'a, B: Backend> {
shared: InnerEntry<'a, B>,
target: String,
}
impl<'a, B: Backend> SymlinkEntry<'a, B> {
fn new(mut shared: InnerEntry<'a, B>) -> ArchiveResult<SymlinkEntry<'a, B>, B> {
let target = Self::read_target(&mut shared)?;
Ok(SymlinkEntry { shared, target })
}
pub fn name(&self) -> &str {
&self.shared.inner.name
}
pub fn target(&self) -> &str {
&self.target
}
populate_mode_api!();
populate_tstamp_api!();
fn read_target(shared: &mut InnerEntry<'a, B>) -> ArchiveResult<String, B> {
const CHUNK: usize = 64;
let mut vec = vec![];
let mut nbytes = 0;
loop {
vec.resize(vec.len() + CHUNK, 0);
let n = shared.read(&mut vec[nbytes..nbytes + CHUNK])?;
nbytes += n;
vec.resize(nbytes, 0);
if n == 0 {
break;
}
}
Ok(String::from_utf8_lossy(&vec).to_string())
}
fn inner(&self) -> &Inner {
&self.shared.inner
}
}
pub struct InnerEntry<'a, B: Backend> {
pager: &'a mut Pager<B>,
tree: &'a mut Tree<B>,
inner: Inner,
idx: usize,
rcache: Vec<u8>,
ridx: usize,
}
impl<'a, B: Backend> InnerEntry<'a, B> {
pub fn load(
pager: &'a mut Pager<B>,
tree: &'a mut Tree<B>,
idx: usize,
id: &Id<B>,
) -> ArchiveResult<InnerEntry<'a, B>, B> {
let inner = Inner::load(pager, id)?;
Ok(InnerEntry {
pager,
tree,
inner,
idx,
rcache: vec![],
ridx: 0,
})
}
pub fn first(
pager: &'a mut Pager<B>,
tree: &'a mut Tree<B>,
) -> Option<ArchiveResult<InnerEntry<'a, B>, B>> {
match tree.lookup(pager, 0) {
Some(Ok(id)) => {
debug!("lookup first at {}: {}", 0, id);
let id = id.clone();
Some(Self::load(pager, tree, 0, &id))
}
Some(Err(err)) => {
error!("lookup first at {}: {}", 0, err);
Some(Err(err))
}
None => {
debug!("lookup first at {}: none", 0);
None
}
}
}
fn next(self) -> Option<ArchiveResult<InnerEntry<'a, B>, B>> {
let content_blocks = self.content_blocks() as usize;
let next_idx = self.idx + content_blocks + 1;
debug!(
"next_idx={} (idx={}, size={}, content_blocks={})",
next_idx, self.idx, self.inner.size, content_blocks
);
match self.tree.lookup(self.pager, next_idx) {
Some(Ok(id)) => {
debug!("lookup next at {}: {}", next_idx, id);
let id = id.clone();
Some(Self::load(self.pager, self.tree, next_idx, &id))
}
Some(Err(err)) => {
error!("lookup next at {}: {}", next_idx, err);
Some(Err(err))
}
None => {
debug!("lookup next at {}: none", next_idx);
None
}
}
}
fn read(&mut self, buf: &mut [u8]) -> ArchiveResult<usize, B> {
if self.rcache.is_empty() {
let blocks = self.content_blocks();
debug!("fill cache: idx={}, blocks={}", self.ridx, blocks);
if self.ridx >= blocks as usize {
return Ok(0);
}
let block_size = self.pager.block_size() as usize;
let remaining = self.inner.size as usize - self.ridx * block_size;
let cache_size = cmp::min(remaining, block_size);
debug!(
"fill cache: remaining={}, cache_size={}",
remaining, cache_size
);
self.rcache.resize(cache_size, 0);
let idx = self.idx + self.ridx + 1;
match self.tree.lookup(self.pager, idx) {
Some(Ok(id)) => {
let n = self.pager.read(id, self.rcache.as_mut_slice())?;
assert_eq!(n, cache_size);
self.ridx += 1;
}
Some(Err(err)) => return Err(err),
None => {
warn!("premature end of archive, no block at {}", idx);
return Ok(0);
}
};
}
let len = cmp::min(self.rcache.len(), buf.len());
self.rcache
.drain(..len)
.enumerate()
.for_each(|(i, n)| buf[i] = n);
Ok(len)
}
fn content_blocks(&self) -> u64 {
let block_size = self.pager.block_size() as u64;
if self.inner.size % block_size == 0 {
self.inner.size / block_size
} else {
self.inner.size / block_size + 1
}
}
}