//! Archive fetcher. use std::fs::File; use std::io::BufReader; use std::io::ErrorKind as IoErrorKind; use std::io::Read; use std::io::Seek; use std::path::Path; use std::sync::Mutex; use rayon::prelude::*; use zip::read::ZipArchive; use zip::result::ZipError; use super::parser::parse; use super::story::Story; use crate::archive::AUTHORS; use crate::archive::TAGS; use crate::error::Error; use crate::error::Result; pub struct Fetcher { archive: Mutex>, index: Vec, } impl Fetcher> { pub fn new(path: impl AsRef) -> Result { use IoErrorKind::*; let file = File::open(path).map_err(|e| match e.kind() { NotFound => Error::archive("File not found"), _ => Error::archive("Could not open file"), })?; Self::with_reader(BufReader::new(file)) } } impl Fetcher { pub fn with_reader(reader: T) -> Result { let mut handle = Self::open(reader)?; let index = Self::load(&mut handle)?; let archive = Mutex::new(handle); Ok(Self { archive, index }) } fn open(archive: T) -> Result> { use ZipError::*; ZipArchive::new(archive).map_err(|e| match e { InvalidArchive(e) => Error::archive(e), UnsupportedArchive(e) => Error::archive(e), _ => Error::archive("Unknown ZIP-file issue"), }) } fn load(archive: &mut ZipArchive) -> Result> { use ZipError::*; let file = archive.by_name("index.json").map_err(|e| match e { FileNotFound => Error::archive("Missing story index"), _ => Error::archive("Could not open story index"), })?; let reader = BufReader::with_capacity(1048576, file); let result = parse(reader).map_err(Error::index); AUTHORS.clear(); TAGS.clear(); result } pub fn fetch(&self, key: i32) -> Option<&Story> { match self.index.binary_search_by_key(&key, |story| story.id) { Ok(i) => self.index.get(i), Err(_) => None, } } pub fn read(&self, story: &Story) -> Result> { use ZipError::*; let path = &story.archive.path; let Ok(mut archive) = self.archive.lock() else { return Err(Error::archive("Could not acquire fetcher lock")); }; let mut file = archive.by_name(path).map_err(|e| match e { FileNotFound => Error::archive("Missing story data"), _ => Error::archive("Could not open story data"), })?; let size = file.size() as usize; let mut buf = Vec::with_capacity(size); let Ok(_) = file.read_to_end(&mut buf) else { return Err(Error::archive("Could not read story data")); }; Ok(buf) } pub fn iter(&self) -> impl Iterator { self.index.iter() } pub fn filter(&self, function: &F) -> Vec<&Story> where F: Sync + Fn(&Story) -> bool, { self.index.par_iter().filter(|s| function(s)).collect() } }