use crate::page::Page;
use std::ffi::OsStr;
use std::fs::{read_dir, File};
use std::io::{self, Read};
use std::ops::Range;
use std::path::{is_separator, PathBuf};
use std::time::SystemTime;
pub trait Parser: Default + Sized {
type Error: std::error::Error;
fn from_extension(_extension: &OsStr) -> Option<Self> {
Some(Self::default())
}
fn parse<'a>(&self, data: &'a [u8]) -> Result<Page<'a>, Self::Error>;
}
pub struct Source<P: Parser> {
pub(crate) source: Range<usize>,
pub(crate) path: Range<usize>,
pub(crate) pages: Range<usize>,
pub(crate) subsections: Range<usize>,
pub(crate) is_section: bool,
pub(crate) parent: usize,
pub(crate) date: Option<SystemTime>,
pub(crate) to_load: Option<PathBuf>,
pub(crate) format: P,
}
pub struct Sources<P: Parser> {
pub(crate) data: Vec<u8>,
sources: Vec<Source<P>>,
}
impl<P: Parser> Source<P> {
#[inline]
fn new(
path: Range<usize>,
src: Range<usize>,
parent: usize,
date: Option<SystemTime>,
format: P,
) -> Self {
Self {
source: src,
path,
pages: 0..0,
subsections: 0..0,
is_section: false,
parent,
date,
to_load: None,
format,
}
}
#[inline]
fn empty(section: PathBuf, parent: usize) -> Self {
Self {
source: 0..0,
path: 0..0,
pages: 0..0,
subsections: 0..0,
is_section: true,
parent,
date: None,
to_load: Some(section),
format: P::default(),
}
}
}
impl<P: Parser> Sources<P> {
#[inline]
fn step(
&mut self,
index: usize,
path: PathBuf,
dirs: &mut Vec<PathBuf>,
content_dir: &str,
) -> Result<(), io::Error> {
let start = self.sources.len();
let mut index_file = None;
for (path, date, format) in read_dir(&path)?
.filter_map(Result::ok)
.filter(|entry| {
entry
.file_type()
.map(|ft| {
if ft.is_dir() {
dirs.push(entry.path());
false
} else {
ft.is_file()
}
})
.unwrap_or(false)
})
.map(|entry| {
let date = entry.metadata().and_then(|m| m.created()).ok();
(entry.path(), date)
})
.filter_map(|(path, date)| {
let ext = path.extension().unwrap_or_default();
let format = P::from_extension(ext)?;
if path.file_stem()? == "index" {
index_file = Some((path, date, format));
return None;
};
Some((path, date, format))
})
{
let start = self.data.len();
let read = File::open(&path)?.read_to_end(&mut self.data)?;
let mid = start + read;
let path = path.to_string_lossy();
let p = path.strip_prefix(content_dir).unwrap_or(&path);
let p = p.strip_prefix(is_separator).unwrap_or(p);
let ext_start = p.rfind('.').unwrap_or(p.len());
self.data.extend_from_slice(p[..ext_start].as_ref());
let end = self.data.len();
self.sources
.push(Source::new(mid..end, start..mid, index, date, format));
}
let end = self.sources.len();
for dir in dirs.drain(..) {
self.sources.push(Source::empty(dir, index));
}
let len = self.sources.len();
let source_start = self.data.len();
let read = if let Some((path, date, format)) = index_file {
self.sources[index].date = date;
self.sources[index].format = format;
File::open(path)?.read_to_end(&mut self.data)?
} else {
0
};
let mid = source_start + read;
let path = path.to_string_lossy();
let p = path.strip_prefix(content_dir).unwrap_or(&path);
let p = p.strip_prefix(is_separator).unwrap_or(p);
self.data.extend_from_slice(p.as_ref());
let source_end = self.data.len();
self.sources[index].path = mid..source_end;
self.sources[index].source = source_start..mid;
self.sources[index].pages = start..end;
if len > end {
self.sources[index].subsections = end..len;
}
Ok(())
}
pub fn load(dir: &str) -> Result<Self, io::Error> {
let mut sources = Self {
data: Vec::with_capacity(65536),
sources: Vec::with_capacity(64),
};
sources.sources.push(Source::empty(dir.into(), 0));
let mut dirs_buffer = Vec::new();
let mut i = 0;
while i < sources.sources.len() {
if let Some(path) = sources.sources[i].to_load.take() {
sources.step(i, path, &mut dirs_buffer, dir)?;
}
i += 1;
}
Ok(sources)
}
pub fn sources(&self) -> &[Source<P>] {
&self.sources
}
}