diff --git a/core/src/filesystem/media/builder.rs b/core/src/filesystem/media/builder.rs index 73996e9dd..a7768f0d2 100644 --- a/core/src/filesystem/media/builder.rs +++ b/core/src/filesystem/media/builder.rs @@ -4,7 +4,7 @@ use prisma_client_rust::chrono::{DateTime, FixedOffset, Utc}; use crate::{ config::StumpConfig, - db::entity::{LibraryConfig, Media, MediaMetadata, Series}, + db::entity::{LibraryConfig, Media, Series}, filesystem::{ process, scanner::{CustomVisit, CustomVisitResult}, @@ -13,7 +13,7 @@ use crate::{ CoreError, CoreResult, }; -use super::{generate_hashes, process_metadata, ProcessedFileHashes}; +use super::{FileProcessorOptions, ProcessedFileHashes}; pub struct MediaBuilder { path: PathBuf, @@ -101,24 +101,26 @@ impl MediaBuilder { }) } - pub fn regen_hashes(&self) -> CoreResult { - Ok(generate_hashes( - self.path.clone(), - self.library_config.clone().into(), - )?) - } - - pub fn regen_meta(&self) -> CoreResult> { - Ok(process_metadata(self.path.clone())?) - } - pub fn custom_visit(self, config: CustomVisit) -> CoreResult { let mut result = CustomVisitResult::default(); + let processor_options = FileProcessorOptions { + convert_rar_to_zip: false, + delete_conversion_source: false, + generate_file_hashes: config.regen_hashes, + generate_koreader_hashes: config.regen_hashes, + process_metadata: config.regen_meta, + process_pages: config.regen_meta, + }; + let processed_file = + process(self.path.as_path(), processor_options, &self.config)?; if config.regen_hashes { - result.hashes = Some(self.regen_hashes()?); + result.hashes = Some(ProcessedFileHashes { + hash: processed_file.hash, + koreader_hash: processed_file.koreader_hash, + }); } if config.regen_meta { - result.meta = self.regen_meta()?.map(Box::new); + result.meta = processed_file.metadata.map(Box::new); } Ok(result) } @@ -239,8 +241,12 @@ mod tests { let builder = MediaBuilder::new(path, series_id, library_config, &StumpConfig::debug()); - let hashes = builder.regen_hashes(); - assert!(hashes.is_ok()); + let custom_visit = CustomVisit { + regen_hashes: true, + regen_meta: false, + }; + let hashes = builder.custom_visit(custom_visit).unwrap().hashes; + assert!(hashes.is_some()); assert!(hashes.unwrap().hash.is_some()); } } diff --git a/core/src/filesystem/media/format/epub.rs b/core/src/filesystem/media/format/epub.rs index ed358671d..2b2a7528a 100644 --- a/core/src/filesystem/media/format/epub.rs +++ b/core/src/filesystem/media/format/epub.rs @@ -89,72 +89,6 @@ impl FileProcessor for EpubProcessor { }) } - fn process_metadata(path: &str) -> Result, FileError> { - let epub_file = Self::open(path)?; - let embedded_metadata = MediaMetadata::from(epub_file.metadata); - - // try get opf file - let file_path = std::path::Path::new(path).with_extension("opf"); - if file_path.exists() { - // extract OPF data - let opf_string = std::fs::read_to_string(file_path)?; - let mut reader = Reader::from_str(opf_string.as_str()); - reader.config_mut().trim_text(true); - let mut current_tag = String::new(); - - let mut opf_metadata: HashMap> = HashMap::new(); - - while let Ok(event) = reader.read_event() { - match event { - Event::Start(ref e) | Event::Empty(ref e) => { - let tag_name = - String::from_utf8_lossy(e.name().as_ref()).to_string(); - - // normalize tags - current_tag = tag_name - .strip_prefix("dc:") - .unwrap_or(tag_name.as_str()) - .to_string(); - - if let Some(attr) = - e.attributes().filter_map(|a| a.ok()).find(|a| { - a.key.as_ref() == b"property" || a.key.as_ref() == b"name" - }) { - current_tag = format!( - "{}: {}", - current_tag, - String::from_utf8_lossy(&attr.value) - ); - } - }, - Event::Text(e) => { - if let Ok(text) = e.unescape() { - opf_metadata - .entry(current_tag.clone()) - .or_default() - .push(text.to_string()); - } - }, - Event::Eof => { - break; - }, - _ => {}, - } - } - - // merge opf and embedded, prioritizing opf - let opf_metadata = MediaMetadata::from(opf_metadata); - let mut combined_metadata = opf_metadata.clone(); - - combined_metadata.id = opf_metadata.id; - combined_metadata.merge(embedded_metadata); - - return Ok(Some(combined_metadata)); - } - - Ok(Some(embedded_metadata)) - } - fn process( path: &str, options: FileProcessorOptions, @@ -162,29 +96,37 @@ impl FileProcessor for EpubProcessor { ) -> Result { tracing::debug!(?path, "processing epub"); - let metadata = Self::process_metadata(path); + let mut processed_file = ProcessedFile { + path: PathBuf::from(path), + ..Default::default() + }; - let path_buf = PathBuf::from(path); - let epub_file = Self::open(path)?; + if options.process_pages || options.process_metadata { + let epub_file = Self::open(path)?; - let pages = epub_file.get_num_pages() as i32; - // Get metadata from epub file if process_metadata failed - let metadata = match metadata { - Ok(Some(m)) => m, - _ => MediaMetadata::from(epub_file.metadata), + if options.process_pages { + processed_file.pages = epub_file.get_num_pages() as i32; + } + // This still needs to be changed to take epub_file + if options.process_metadata { + processed_file.metadata = Self::process_metadata(path, epub_file)?; + } }; - let ProcessedFileHashes { - hash, - koreader_hash, - } = Self::generate_hashes(path, options)?; - Ok(ProcessedFile { - path: path_buf, - hash, - koreader_hash, - metadata: Some(metadata), - pages, - }) + if options.generate_file_hashes || options.generate_koreader_hashes { + let ProcessedFileHashes { + hash, + koreader_hash, + } = Self::generate_hashes(path, options)?; + if options.generate_file_hashes { + processed_file.hash = hash; + } + if options.generate_koreader_hashes { + processed_file.koreader_hash = koreader_hash; + } + }; + + Ok(processed_file) } fn get_page( @@ -255,6 +197,74 @@ impl EpubProcessor { EpubDoc::new(path).map_err(|e| FileError::EpubOpenError(e.to_string())) } + pub fn process_metadata( + path: &str, + epub_file: EpubDoc>, + ) -> Result, FileError> { + let embedded_metadata = MediaMetadata::from(epub_file.metadata); + + // try get opf file + let file_path = std::path::Path::new(path).with_extension("opf"); + if file_path.exists() { + // extract OPF data + let opf_string = std::fs::read_to_string(file_path)?; + let mut reader = Reader::from_str(opf_string.as_str()); + reader.config_mut().trim_text(true); + let mut current_tag = String::new(); + + let mut opf_metadata: HashMap> = HashMap::new(); + + while let Ok(event) = reader.read_event() { + match event { + Event::Start(ref e) | Event::Empty(ref e) => { + let tag_name = + String::from_utf8_lossy(e.name().as_ref()).to_string(); + + // normalize tags + current_tag = tag_name + .strip_prefix("dc:") + .unwrap_or(tag_name.as_str()) + .to_string(); + + if let Some(attr) = + e.attributes().filter_map(|a| a.ok()).find(|a| { + a.key.as_ref() == b"property" || a.key.as_ref() == b"name" + }) { + current_tag = format!( + "{}: {}", + current_tag, + String::from_utf8_lossy(&attr.value) + ); + } + }, + Event::Text(e) => { + if let Ok(text) = e.unescape() { + opf_metadata + .entry(current_tag.clone()) + .or_default() + .push(text.to_string()); + } + }, + Event::Eof => { + break; + }, + _ => {}, + } + } + + // merge opf and embedded, prioritizing opf + let opf_metadata = MediaMetadata::from(opf_metadata); + let mut combined_metadata = opf_metadata.clone(); + + combined_metadata.id = opf_metadata.id; + combined_metadata.merge(embedded_metadata); + + return Ok(Some(combined_metadata)); + } + + Ok(Some(embedded_metadata)) + } + fn get_cover_path(resources: &HashMap) -> Option { let search_result = resources .iter() @@ -692,20 +702,70 @@ mod tests { let processed_file = EpubProcessor::process( &path, FileProcessorOptions { - convert_rar_to_zip: false, - delete_conversion_source: false, ..Default::default() }, &config, ); assert!(processed_file.is_ok()); + let processed_file = processed_file.unwrap(); + assert!(processed_file.hash.is_none()); + assert!(processed_file.koreader_hash.is_none()); + assert!(processed_file.metadata.is_none()); + assert_eq!(processed_file.pages, 0); + + let processed_file = EpubProcessor::process( + &path, + FileProcessorOptions { + generate_file_hashes: true, + generate_koreader_hashes: true, + ..Default::default() + }, + &config, + ); + assert!(processed_file.is_ok()); + let processed_file = processed_file.unwrap(); + assert!(processed_file.hash.is_some()); + assert!(processed_file.koreader_hash.is_some()); + assert!(processed_file.metadata.is_none()); + assert_eq!(processed_file.pages, 0); + + let processed_file = EpubProcessor::process( + &path, + FileProcessorOptions { + process_metadata: true, + ..Default::default() + }, + &config, + ); + assert!(processed_file.is_ok()); + let processed_file = processed_file.unwrap(); + assert!(processed_file.hash.is_none()); + assert!(processed_file.koreader_hash.is_none()); + assert!(processed_file.metadata.is_some()); + assert_eq!(processed_file.pages, 0); + + let processed_file = EpubProcessor::process( + &path, + FileProcessorOptions { + process_pages: true, + ..Default::default() + }, + &config, + ); + assert!(processed_file.is_ok()); + let processed_file = processed_file.unwrap(); + assert!(processed_file.hash.is_none()); + assert!(processed_file.koreader_hash.is_none()); + assert!(processed_file.metadata.is_none()); + assert_ne!(processed_file.pages, 0); } #[test] fn test_process_metadata() { let path = get_test_epub_path(); + let epub_file = EpubProcessor::open(&path).unwrap(); - let processed_metadata = EpubProcessor::process_metadata(&path); + let processed_metadata = EpubProcessor::process_metadata(&path, epub_file); match processed_metadata { Ok(Some(metadata)) => { assert_eq!( diff --git a/core/src/filesystem/media/format/pdf.rs b/core/src/filesystem/media/format/pdf.rs index 384ba0b4d..72fcafdef 100644 --- a/core/src/filesystem/media/format/pdf.rs +++ b/core/src/filesystem/media/format/pdf.rs @@ -83,39 +83,44 @@ impl FileProcessor for PdfProcessor { }) } - fn process_metadata(path: &str) -> Result, FileError> { - let file = FileOptions::cached() - .parse_options(ParseOptions::tolerant()) - .open(path)?; - - Ok(file.trailer.info_dict.map(MediaMetadata::from)) - } - fn process( path: &str, options: FileProcessorOptions, _: &StumpConfig, ) -> Result { - let file = FileOptions::cached() - .parse_options(ParseOptions::tolerant()) - .open(path)?; - - let pages = file.pages().count() as i32; - // Note: The metadata is already parsed by the PDF library, so might as well use it - // PDF metadata is generally poop though - let metadata = file.trailer.info_dict.map(MediaMetadata::from); - let ProcessedFileHashes { - hash, - koreader_hash, - } = PdfProcessor::generate_hashes(path, options)?; - - Ok(ProcessedFile { + let mut processed_file = ProcessedFile { path: PathBuf::from(path), - hash, - koreader_hash, - metadata, - pages, - }) + ..Default::default() + }; + if options.process_pages || options.process_metadata { + let file = FileOptions::cached() + .parse_options(ParseOptions::tolerant()) + .open(path)?; + if options.process_pages { + processed_file.pages = file.pages().count() as i32; + } + // Note: The metadata is already parsed by the PDF library, so might as well use it + // PDF metadata is generally poop though + if options.process_metadata { + processed_file.metadata = file.trailer.info_dict.map(MediaMetadata::from); + } + } + + if options.generate_file_hashes || options.generate_koreader_hashes { + let ProcessedFileHashes { + hash, + koreader_hash, + } = PdfProcessor::generate_hashes(path, options)?; + + if options.generate_file_hashes { + processed_file.hash = hash; + } + if options.generate_koreader_hashes { + processed_file.koreader_hash = koreader_hash; + } + } + + Ok(processed_file) } // TODO: The decision to use PNG should be a configuration option diff --git a/core/src/filesystem/media/format/rar.rs b/core/src/filesystem/media/format/rar.rs index 4888ddb8e..94d4cdc63 100644 --- a/core/src/filesystem/media/format/rar.rs +++ b/core/src/filesystem/media/format/rar.rs @@ -9,7 +9,6 @@ use unrar::{Archive, CursorBeforeHeader, List, OpenArchive, Process, UnrarResult use crate::{ config::StumpConfig, - db::entity::MediaMetadata, filesystem::{ archive::create_zip_archive, content_type::ContentType, @@ -120,45 +119,16 @@ impl FileProcessor for RarProcessor { }) } - fn process_metadata(path: &str) -> Result, FileError> { - let mut archive = RarProcessor::open_for_processing(path)?; - let mut metadata_buf = None; - - while let Ok(Some(header)) = archive.read_header() { - let entry = header.entry(); - - if entry.is_directory() { - archive = header.skip()?; - continue; - } - - if entry.filename.is_hidden_file() { - archive = header.skip()?; - continue; - } - - if entry.filename.as_os_str() == "ComicInfo.xml" { - let (data, _) = header.read()?; - metadata_buf = Some(data); - break; - } else { - archive = header.skip()?; - } - } - - if let Some(buf) = metadata_buf { - let content_str = std::str::from_utf8(&buf)?; - Ok(metadata_from_buf(content_str)) - } else { - Ok(None) - } - } - fn process( path: &str, options: FileProcessorOptions, config: &StumpConfig, ) -> Result { + let mut processed_file = ProcessedFile { + path: PathBuf::from(path), + ..Default::default() + }; + if options.convert_rar_to_zip { let zip_path_buf = RarProcessor::to_zip( path, @@ -174,55 +144,67 @@ impl FileProcessor for RarProcessor { return ZipProcessor::process(zip_path, options, config); } - let ProcessedFileHashes { - hash, - koreader_hash, - } = RarProcessor::generate_hashes(path, options)?; + if options.generate_file_hashes || options.generate_koreader_hashes { + let ProcessedFileHashes { + hash, + koreader_hash, + } = RarProcessor::generate_hashes(path, options)?; + if options.generate_file_hashes { + processed_file.hash = hash; + } + if options.generate_koreader_hashes { + processed_file.koreader_hash = koreader_hash; + } + } - let mut archive = RarProcessor::open_for_processing(path)?; - let mut pages = 0; - let mut metadata_buf = None; + if options.process_pages || options.process_metadata { + let mut archive = RarProcessor::open_for_processing(path)?; + let mut pages = 0; + let mut metadata_buf = None; - while let Ok(Some(header)) = archive.read_header() { - let entry = header.entry(); + while let Ok(Some(header)) = archive.read_header() { + let entry = header.entry(); - if entry.is_directory() { - archive = header.skip()?; - continue; - } + if entry.is_directory() { + archive = header.skip()?; + continue; + } - if entry.filename.is_hidden_file() { - archive = header.skip()?; - continue; + if entry.filename.is_hidden_file() { + archive = header.skip()?; + continue; + } + + if entry.filename.as_os_str() == "ComicInfo.xml" + && options.process_metadata + { + let (data, rest) = header.read()?; + metadata_buf = Some(data); + archive = rest; + } else { + // If the entry is not an image then it cannot be a valid page + if entry.filename.is_img() { + pages += 1; + } + archive = header.skip()?; + } } - if entry.filename.as_os_str() == "ComicInfo.xml" && options.process_metadata { - let (data, rest) = header.read()?; - metadata_buf = Some(data); - archive = rest; + let metadata = if let Some(buf) = metadata_buf { + let content_str = std::str::from_utf8(&buf)?; + metadata_from_buf(content_str) } else { - // If the entry is not an image then it cannot be a valid page - if entry.filename.is_img() { - pages += 1; - } - archive = header.skip()?; + None + }; + if options.process_pages { + processed_file.pages = pages; + } + if options.process_metadata { + processed_file.metadata = metadata; } } - let metadata = if let Some(buf) = metadata_buf { - let content_str = std::str::from_utf8(&buf)?; - metadata_from_buf(content_str) - } else { - None - }; - - Ok(ProcessedFile { - path: PathBuf::from(path), - hash, - koreader_hash, - metadata, - pages, - }) + Ok(processed_file) } fn get_page( diff --git a/core/src/filesystem/media/format/zip.rs b/core/src/filesystem/media/format/zip.rs index 4b35185dc..f3c8f50dd 100644 --- a/core/src/filesystem/media/format/zip.rs +++ b/core/src/filesystem/media/format/zip.rs @@ -3,7 +3,6 @@ use tracing::{debug, error, trace}; use crate::{ config::StumpConfig, - db::entity::MediaMetadata, filesystem::{ content_type::ContentType, error::FileError, @@ -79,104 +78,78 @@ impl FileProcessor for ZipProcessor { }) } - fn process_metadata(path: &str) -> Result, FileError> { - let zip_file = File::open(path)?; - let mut archive = zip::ZipArchive::new(zip_file)?; - - let mut metadata = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - - if file.is_dir() { - trace!("Skipping directory"); - continue; - } - - let path_buf = file.enclosed_name().unwrap_or_else(|| { - tracing::warn!("Failed to get enclosed name for zip entry"); - PathBuf::from(file.name()) - }); - let path = path_buf.as_path(); - - if path.is_hidden_file() { - trace!(path = ?path, "Skipping hidden file"); - continue; - } - - let FileParts { file_name, .. } = path.file_parts(); - - if file_name == "ComicInfo.xml" { - trace!("Found ComicInfo.xml"); - let mut contents = Vec::new(); - file.read_to_end(&mut contents)?; - let contents = String::from_utf8_lossy(&contents).to_string(); - trace!(contents_len = contents.len(), "Read ComicInfo.xml"); - metadata = metadata_from_buf(&contents); - break; - } - } - - Ok(metadata) - } - fn process( path: &str, options: FileProcessorOptions, _: &StumpConfig, ) -> Result { - let zip_file = File::open(path)?; - let mut archive = zip::ZipArchive::new(zip_file)?; - - let mut metadata = None; - let mut pages = 0; - - let ProcessedFileHashes { - hash, - koreader_hash, - } = Self::generate_hashes(path, options)?; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - - if file.is_dir() { - trace!("Skipping directory"); - continue; + let mut processed_file = ProcessedFile { + path: PathBuf::from(path), + ..Default::default() + }; + + if options.generate_file_hashes || options.generate_koreader_hashes { + let ProcessedFileHashes { + hash, + koreader_hash, + } = Self::generate_hashes(path, options)?; + if options.generate_file_hashes { + processed_file.hash = hash; } - - let path_buf = file.enclosed_name().unwrap_or_else(|| { - tracing::warn!("Failed to get enclosed name for zip entry"); - PathBuf::from(file.name()) - }); - let path = path_buf.as_path(); - - if path.is_hidden_file() { - trace!(path = ?path, "Skipping hidden file"); - continue; + if options.generate_koreader_hashes { + processed_file.koreader_hash = koreader_hash; + } + }; + + if options.process_pages || options.process_metadata { + let zip_file = File::open(path)?; + let mut archive = zip::ZipArchive::new(zip_file)?; + + let mut metadata = None; + let mut pages = 0; + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + + if file.is_dir() { + trace!("Skipping directory"); + continue; + } + + let path_buf = file.enclosed_name().unwrap_or_else(|| { + tracing::warn!("Failed to get enclosed name for zip entry"); + PathBuf::from(file.name()) + }); + let path = path_buf.as_path(); + + if path.is_hidden_file() { + trace!(path = ?path, "Skipping hidden file"); + continue; + } + + let content_type = path.naive_content_type(); + let FileParts { file_name, .. } = path.file_parts(); + + if file_name == "ComicInfo.xml" && options.process_metadata { + trace!("Found ComicInfo.xml"); + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + let contents = String::from_utf8_lossy(&contents).to_string(); + trace!(contents_len = contents.len(), "Read ComicInfo.xml"); + metadata = metadata_from_buf(&contents); + } else if content_type.is_image() { + pages += 1; + } } - let content_type = path.naive_content_type(); - let FileParts { file_name, .. } = path.file_parts(); - - if file_name == "ComicInfo.xml" && options.process_metadata { - trace!("Found ComicInfo.xml"); - let mut contents = Vec::new(); - file.read_to_end(&mut contents)?; - let contents = String::from_utf8_lossy(&contents).to_string(); - trace!(contents_len = contents.len(), "Read ComicInfo.xml"); - metadata = metadata_from_buf(&contents); - } else if content_type.is_image() { - pages += 1; + if options.process_metadata { + processed_file.metadata = metadata; + } + if options.process_pages { + processed_file.pages = pages; } } - Ok(ProcessedFile { - path: PathBuf::from(path), - hash, - koreader_hash, - metadata, - pages, - }) + Ok(processed_file) } fn get_page( @@ -370,6 +343,7 @@ mod tests { FileProcessorOptions { convert_rar_to_zip: false, delete_conversion_source: false, + process_pages: true, ..Default::default() }, &config, diff --git a/core/src/filesystem/media/process.rs b/core/src/filesystem/media/process.rs index 7fb1d15f8..dbbcdd663 100644 --- a/core/src/filesystem/media/process.rs +++ b/core/src/filesystem/media/process.rs @@ -32,6 +32,8 @@ pub struct FileProcessorOptions { pub generate_file_hashes: bool, /// Whether to process metadata for the file pub process_metadata: bool, + /// Whether to process page count for the file + pub process_pages: bool, /// Whether to generate a hash for the file that is compatible with KOReader pub generate_koreader_hashes: bool, } @@ -44,6 +46,7 @@ impl From for FileProcessorOptions { generate_file_hashes: options.generate_file_hashes, generate_koreader_hashes: options.generate_koreader_hashes, process_metadata: options.process_metadata, + process_pages: true, } } } @@ -56,6 +59,7 @@ impl From<&LibraryConfig> for FileProcessorOptions { generate_file_hashes: options.generate_file_hashes, generate_koreader_hashes: options.generate_koreader_hashes, process_metadata: options.process_metadata, + process_pages: true, } } } @@ -91,8 +95,6 @@ pub trait FileProcessor { config: &StumpConfig, ) -> Result; - fn process_metadata(path: &str) -> Result, FileError>; - /// Get the bytes of a page of the file. fn get_page( path: &str, @@ -143,7 +145,7 @@ impl SeriesJson { /// Struct representing a processed file. This is the output of the `process` function /// on a `FileProcessor` implementation. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ProcessedFile { pub path: PathBuf, pub hash: Option, @@ -218,35 +220,30 @@ pub async fn process_async( #[tracing::instrument(err, fields(path = %path.as_ref().display()))] pub fn process_metadata( path: impl AsRef, + config: &StumpConfig, ) -> Result, FileError> { - let mime = ContentType::from_path(path.as_ref()).mime_type(); - - let path_str = path.as_ref().to_str().unwrap_or_default(); + let options = FileProcessorOptions { + process_metadata: true, + ..Default::default() + }; - match mime.as_str() { - "application/zip" | "application/vnd.comicbook+zip" => { - ZipProcessor::process_metadata(path_str) - }, - "application/vnd.rar" | "application/vnd.comicbook-rar" => { - RarProcessor::process_metadata(path_str) - }, - "application/epub+zip" => EpubProcessor::process_metadata(path_str), - "application/pdf" => PdfProcessor::process_metadata(path_str), - _ => Err(FileError::UnsupportedFileType(path_str.to_string())), - } + let path = path.as_ref().to_path_buf(); + Ok(process(path.as_path(), options, config)?.metadata) } #[tracing::instrument(err, fields(path = %path.as_ref().display()))] pub async fn process_metadata_async( path: impl AsRef, + config: &StumpConfig, ) -> Result, FileError> { let (tx, rx) = oneshot::channel(); let handle = spawn_blocking({ let path = path.as_ref().to_path_buf(); + let config = config.clone(); move || { - let send_result = tx.send(process_metadata(path.as_path())); + let send_result = tx.send(process_metadata(path.as_path(), &config)); tracing::trace!( is_err = send_result.is_err(), "Sending result of sync process_metadata" @@ -272,36 +269,30 @@ pub async fn process_metadata_async( pub fn generate_hashes( path: impl AsRef, options: FileProcessorOptions, + config: &StumpConfig, ) -> Result { - let path_str = path.as_ref().to_str().unwrap_or_default(); - - let mime = ContentType::from_path(path.as_ref()).mime_type(); - - match mime.as_str() { - "application/zip" | "application/vnd.comicbook+zip" => { - ZipProcessor::generate_hashes(path_str, options) - }, - "application/vnd.rar" | "application/vnd.comicbook-rar" => { - RarProcessor::generate_hashes(path_str, options) - }, - "application/epub+zip" => EpubProcessor::generate_hashes(path_str, options), - "application/pdf" => PdfProcessor::generate_hashes(path_str, options), - _ => Err(FileError::UnsupportedFileType(path_str.to_string())), - } + let path = path.as_ref().to_path_buf(); + let result = process(path.as_path(), options, config)?; + Ok(ProcessedFileHashes { + hash: result.hash, + koreader_hash: result.koreader_hash, + }) } #[tracing::instrument(err, fields(path = %path.as_ref().display()))] pub async fn generate_hashes_async( path: impl AsRef, options: FileProcessorOptions, + config: &StumpConfig, ) -> Result { let (tx, rx) = oneshot::channel(); let handle = spawn_blocking({ let path = path.as_ref().to_path_buf(); + let config = config.clone(); move || { - let send_result = tx.send(generate_hashes(path.as_path(), options)); + let send_result = tx.send(generate_hashes(path.as_path(), options, &config)); tracing::trace!( is_err = send_result.is_err(), "Sending result of sync generate_hashes" @@ -386,20 +377,17 @@ pub async fn get_page_async( /// Get the number of pages in a file. This will call the appropriate [`FileProcessor::get_page_count`] /// implementation based on the file's mime type, or return an error if the file type is not supported. -pub fn get_page_count(path: &str, config: &StumpConfig) -> Result { - let mime = ContentType::from_file(path).mime_type(); +pub fn get_page_count( + path: impl AsRef, + config: &StumpConfig, +) -> Result { + let options = FileProcessorOptions { + process_pages: true, + ..Default::default() + }; - match mime.as_str() { - "application/zip" | "application/vnd.comicbook+zip" => { - ZipProcessor::get_page_count(path, config) - }, - "application/vnd.rar" | "application/vnd.comicbook-rar" => { - RarProcessor::get_page_count(path, config) - }, - "application/epub+zip" => EpubProcessor::get_page_count(path, config), - "application/pdf" => PdfProcessor::get_page_count(path, config), - _ => Err(FileError::UnsupportedFileType(path.to_string())), - } + let path = path.as_ref().to_path_buf(); + Ok(process(path.as_path(), options, config)?.pages) } /// Get the number of pages in a file in the context of a spawned, blocking task. This will call the @@ -416,8 +404,7 @@ pub async fn get_page_count_async( let config = config.clone(); move || { - let send_result = - tx.send(get_page_count(path.to_str().unwrap_or_default(), &config)); + let send_result = tx.send(get_page_count(path, &config)); tracing::trace!( is_err = send_result.is_err(), "Sending result of sync get_page_count"