use epub::doc::EpubDoc; use mime::Mime; use pile_io::SyncReadBridge; use std::sync::{Arc, OnceLock}; use tracing::trace; use crate::{ extract::traits::ExtractState, value::{Item, PileValue}, }; pub struct EpubCoverExtractor { item: Item, output: OnceLock)>>, } impl EpubCoverExtractor { pub fn new(item: &Item) -> Self { Self { item: item.clone(), output: OnceLock::new(), } } async fn get_inner(&self) -> Result)>, std::io::Error> { if let Some(x) = self.output.get() { return Ok(x.as_ref()); } let reader = SyncReadBridge::new_current(self.item.read().await?); let result = tokio::task::spawn_blocking(move || { let mut doc = EpubDoc::from_reader(reader)?; let cover_id = match doc.get_cover_id() { Ok(id) => id, Err(_) => return Ok::<_, anyhow::Error>(None), }; let mime: Mime = doc .resources .get(&cover_id) .and_then(|(_, mime_str)| mime_str.parse().ok()) .unwrap_or(mime::IMAGE_JPEG); let bytes = doc.get_cover()?; Ok(Some((mime, bytes))) }) .await?; let result = match result { Ok(x) => x, Err(error) => match error.downcast::() { Ok(x) => return Err(x), Err(error) => { trace!(message = "Could not extract epub cover", ?error, key = ?self.item.key()); None } }, }; Ok(self.output.get_or_init(|| result).as_ref()) } pub async fn get(&self, state: &ExtractState) -> Result, std::io::Error> { if !state.ignore_mime && self.item.mime().essence_str() != "application/epub+zip" { return Ok(None); } Ok(self .get_inner() .await? .map(|(mime, bytes)| PileValue::Blob { mime: mime.clone(), bytes: Arc::new(bytes.clone()), })) } }