use crate::core::models::Chapter;
use crate::core::parser::{ChapterInfo, EpubParser, Parser};
use crate::core::reader::Reader;
use crate::error::EpubError;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::sync::{Arc, Mutex};
use zip::ZipArchive;
pub struct EpubReader {
archive: Arc<Mutex<ZipArchive<File>>>,
chapter_info: Vec<ChapterInfo>,
images: HashMap<String, String>,
#[allow(dead_code)]
opf_path: String,
title: String,
author: String,
cover_id: Option<String>,
}
impl EpubReader {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, EpubError> {
let file = File::open(path)?;
let mut archive = ZipArchive::new(file)?;
let parser = EpubParser;
let opf_path = parser.find_opf_path(&mut archive)?;
let (title, author, chapter_info, images, cover_id) =
parser.parse_opf(&mut archive, &opf_path)?;
Ok(EpubReader {
archive: Arc::new(Mutex::new(archive)),
chapter_info,
images,
opf_path,
title,
author,
cover_id,
})
}
pub fn get_image(&self, id: &str) -> Result<Vec<u8>, EpubError> {
let href = self
.images
.get(id)
.ok_or_else(|| EpubError::ChapterNotFound(format!("Image not found: {}", id)))?;
let mut archive = self.archive.lock().map_err(|_| EpubError::CacheLockError)?;
let mut file = archive.by_name(href)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(data)
}
}
impl Reader for EpubReader {
fn title(&self) -> &str {
&self.title
}
fn author(&self) -> &str {
&self.author
}
fn chapter_count(&self) -> usize {
self.chapter_info.len()
}
fn get_chapter(&self, index: usize) -> Result<Chapter, EpubError> {
if index >= self.chapter_info.len() {
return Err(EpubError::InvalidChapterIndex(index));
}
let info = &self.chapter_info[index];
let mut archive = self.archive.lock().map_err(|_| EpubError::CacheLockError)?;
let file_path = info.href.split('#').next().unwrap_or(&info.href);
let mut file = archive.by_name(file_path)?;
let mut html = String::new();
file.read_to_string(&mut html)?;
let parser = EpubParser;
let (parsed_title, elements) = parser.parse_chapter_content(&html, index, file_path);
let title = if !info.title.is_empty() {
info.title.clone()
} else {
parsed_title
};
Ok(Chapter::new(info.id.clone(), title, elements))
}
fn get_chapters_info(&self) -> Vec<ChapterInfo> {
self.chapter_info.clone()
}
fn get_image_by_path(&self, path: &str) -> Result<Vec<u8>, EpubError> {
let mut archive = self.archive.lock().map_err(|_| EpubError::CacheLockError)?;
let mut file = archive.by_name(path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(data)
}
fn cover_image(&self) -> Result<Option<Vec<u8>>, EpubError> {
if let Some(ref id) = self.cover_id {
Ok(Some(self.get_image(id)?))
} else {
Ok(None)
}
}
}