changxi 0.3.0

TUI EPUB Reader
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)?;

        // Strip anchor from href if present
        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)
        }
    }
}