diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c92585b..3958b49 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -605,6 +605,7 @@ dependencies = [ "diesel 2.1.4", "epub", "libcalibre", + "mobi", "regex", "serde", "serde_json", @@ -1084,6 +1085,70 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -2075,6 +2140,7 @@ dependencies = [ "diesel 2.1.4", "diesel_migrations", "epub", + "mobi", "regex", "serde", "serde_json", @@ -2287,6 +2353,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mobi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3f8e34216126be00a189105bda27462e1743d59cd4e15d6b99ff4af051b1b4" +dependencies = [ + "encoding", + "indexmap 1.9.3", + "thiserror", +] + [[package]] name = "native-tls" version = "0.2.11" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e9935a6..57e586b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ calibre-db = "0.1.0" chrono = { version = "0.4.31", features = ["serde"] } diesel = { version = "2.1.0", features = ["sqlite", "chrono", "returning_clauses_for_sqlite_3_35"] } epub = "2.1.1" +mobi = "0.8.0" libcalibre = { path = "./libcalibre" } regex = "1.10.2" serde = { version = "1.0", features = ["derive"] } diff --git a/src-tauri/libcalibre/Cargo.toml b/src-tauri/libcalibre/Cargo.toml index 85f8905..174a7df 100644 --- a/src-tauri/libcalibre/Cargo.toml +++ b/src-tauri/libcalibre/Cargo.toml @@ -10,6 +10,7 @@ chrono = { version = "0.4.31", features = ["serde"] } diesel = { version = "2.1.0", features = ["sqlite", "chrono", "returning_clauses_for_sqlite_3_35"] } diesel_migrations = { version = "2.1.0", features = ["sqlite"] } epub = "2.1.1" +mobi = "0.8.0" regex = "1.10.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/src-tauri/libcalibre/src/application/services/domain/file/service.rs b/src-tauri/libcalibre/src/application/services/domain/file/service.rs index 258faa9..7262599 100644 --- a/src-tauri/libcalibre/src/application/services/domain/file/service.rs +++ b/src-tauri/libcalibre/src/application/services/domain/file/service.rs @@ -2,6 +2,8 @@ use std::error::Error; use std::ffi::OsStr; use std::path::Path; +use mobi::Mobi; + use crate::application::services::domain::file::dto::{NewFileDto, UpdateFileDto}; use crate::domain::book_file::entity::{BookFile, NewBookFile, UpdateBookFile}; use crate::domain::book_file::repository::Repository as BookFileRepository; @@ -27,6 +29,16 @@ fn cover_data(path: &Path) -> Result>, Box> { let mut doc = epub::doc::EpubDoc::new(path)?; Ok(doc.get_cover().map(|(data, _id)| data)) } + Some(MIMETYPE::MOBI) => { + let m = Mobi::from_path(&path); + match m { + Err(_) => Err("Failed to read mobi file")?, + Ok(mobi) => { + let cover_data = mobi.image_records().last().map(|img| img.content.to_vec()); + Ok(cover_data) + } + } + } _ => Ok(None), } } diff --git a/src-tauri/libcalibre/src/mime_type.rs b/src-tauri/libcalibre/src/mime_type.rs index 851c442..6d7fa37 100644 --- a/src-tauri/libcalibre/src/mime_type.rs +++ b/src-tauri/libcalibre/src/mime_type.rs @@ -1,5 +1,6 @@ pub enum MIMETYPE { EPUB, + MOBI, UNKNOWN, } @@ -8,6 +9,7 @@ impl MIMETYPE { pub fn as_str(&self) -> &'static str { match *self { MIMETYPE::EPUB => "application/epub+zip", + MIMETYPE::MOBI => "application/x-mobipocket-ebook", MIMETYPE::UNKNOWN => "application/octet-stream", } } @@ -16,6 +18,7 @@ impl MIMETYPE { pub fn from_str(mimetype: &str) -> Option { match mimetype { "application/epub+zip" => Some(MIMETYPE::EPUB), + "application/x-mobipocket-ebook" => Some(MIMETYPE::MOBI), "application/octet-stream" => Some(MIMETYPE::UNKNOWN), _ => None, } @@ -24,6 +27,7 @@ impl MIMETYPE { pub fn to_file_extension(&self) -> &'static str { match *self { MIMETYPE::EPUB => "epub", + MIMETYPE::MOBI => "mobi", MIMETYPE::UNKNOWN => "", } } @@ -31,6 +35,7 @@ impl MIMETYPE { pub fn from_file_extension(extension: &str) -> Option { match extension.to_lowercase().as_str() { "epub" => Some(MIMETYPE::EPUB), + "mobi" => Some(MIMETYPE::MOBI), _ => None, } } diff --git a/src-tauri/src/libs/file_formats/mobi.rs b/src-tauri/src/libs/file_formats/mobi.rs new file mode 100644 index 0000000..456f33b --- /dev/null +++ b/src-tauri/src/libs/file_formats/mobi.rs @@ -0,0 +1,129 @@ +use std::path::Path; + +use chrono::{NaiveDate, NaiveDateTime}; +use mobi::{headers::Language, Mobi}; + +pub struct MobiMetadata { + pub title: String, + pub author: String, + pub contributor: String, + pub isbn: String, + pub publisher: String, + pub pub_date: Option, + pub cover_image_data: Option>, + pub language: String, + pub subjects: Vec, + + pub desc: String, +} + +fn language_to_string(lang: &Language) -> String { + match lang { + Language::Neutral => "Neutral".to_string(), + Language::Afrikaans => "Afrikaans".to_string(), + Language::Albanian => "Albanian".to_string(), + Language::Arabic => "Arabic".to_string(), + Language::Armenian => "Armenian".to_string(), + Language::Assamese => "Assamese".to_string(), + Language::Azeri => "Azeri".to_string(), + Language::Basque => "Basque".to_string(), + Language::Belarusian => "Belarusian".to_string(), + Language::Bengali => "Bengali".to_string(), + Language::Bulgarian => "Bulgarian".to_string(), + Language::Catalan => "Catalan".to_string(), + Language::Chinese => "Chinese".to_string(), + Language::Czech => "Czech".to_string(), + Language::Danish => "Danish".to_string(), + Language::Dutch => "Dutch".to_string(), + Language::English => "English".to_string(), + Language::Estonian => "Estonian".to_string(), + Language::Faeroese => "Faeroese".to_string(), + Language::Farsi => "Farsi".to_string(), + Language::Finnish => "Finnish".to_string(), + Language::French => "French".to_string(), + Language::Georgian => "Georgian".to_string(), + Language::German => "German".to_string(), + Language::Greek => "Greek".to_string(), + Language::Gujarati => "Gujarati".to_string(), + Language::Hebrew => "Hebrew".to_string(), + Language::Hindi => "Hindi".to_string(), + Language::Hungarian => "Hungarian".to_string(), + Language::Icelandic => "Icelandic".to_string(), + Language::Indonesian => "Indonesian".to_string(), + Language::Italian => "Italian".to_string(), + Language::Japanese => "Japanese".to_string(), + Language::Kannada => "Kannada".to_string(), + Language::Kazak => "Kazakh".to_string(), + Language::Konkani => "Konkani".to_string(), + Language::Korean => "Korean".to_string(), + Language::Latvian => "Latvian".to_string(), + Language::Lithuanian => "Lithuanian".to_string(), + Language::Macedonian => "Macedonian".to_string(), + Language::Malay => "Malay".to_string(), + Language::Malayalam => "Malayalam".to_string(), + Language::Maltese => "Maltese".to_string(), + Language::Marathi => "Marathi".to_string(), + Language::Nepali => "Nepali".to_string(), + Language::Norwegian => "Norwegian".to_string(), + Language::Oriya => "Oriya".to_string(), + Language::Polish => "Polish".to_string(), + Language::Portuguese => "Portuguese".to_string(), + Language::Punjabi => "Punjabi".to_string(), + Language::Rhaetoromanic => "Rhaetoromanic".to_string(), + Language::Romanian => "Romanian".to_string(), + Language::Russian => "Russian".to_string(), + Language::Sami => "Sami".to_string(), + Language::Sanskrit => "Sanskrit".to_string(), + Language::Serbian => "Serbian".to_string(), + Language::Slovak => "Slovak".to_string(), + Language::Slovenian => "Slovenian".to_string(), + Language::Sorbian => "Sorbian".to_string(), + Language::Spanish => "Spanish".to_string(), + Language::Sutu => "Sutu".to_string(), + Language::Swahili => "Swahili".to_string(), + Language::Swedish => "Swedish".to_string(), + Language::Tamil => "Tamil".to_string(), + Language::Tatar => "Tatar".to_string(), + Language::Telugu => "Telugu".to_string(), + Language::Thai => "Thai".to_string(), + Language::Tsonga => "Tsonga".to_string(), + Language::Tswana => "Tswana".to_string(), + Language::Turkish => "Turkish".to_string(), + Language::Ukrainian => "Ukrainian".to_string(), + Language::Urdu => "Urdu".to_string(), + Language::Uzbek => "Uzbek".to_string(), + Language::Vietnamese => "Vietnamese".to_string(), + Language::Xhosa => "Xhosa".to_string(), + Language::Zulu => "Zulu".to_string(), + Language::Unknown => "Unknown".to_string(), + } +} + +pub fn read_metadata(path: &Path) -> Option { + let m = Mobi::from_path(path); + + match m { + Err(_) => None, + Ok(m) => { + let date: Option = NaiveDateTime::parse_from_str( + &m.metadata.publish_date().unwrap_or_default(), + "%Y-%m-%dT%H:%M:%S%z", + ).map(|dt| dt.date()).ok(); + + let cover_image = m.image_records().last().map(|i| i.content.to_vec()); + + Some(MobiMetadata { + title: m.title(), + author: m.author().unwrap_or_default(), + publisher: m.publisher().unwrap_or_default(), + desc: m.description().unwrap_or_default(), + isbn: m.isbn().unwrap_or_default(), + pub_date: date, + contributor: m.contributor().unwrap_or_default(), + language: language_to_string(&m.language()), + subjects: m.metadata.subjects().unwrap_or_default(), + cover_image_data: cover_image, + }) + } + } +} diff --git a/src-tauri/src/libs/file_formats/mod.rs b/src-tauri/src/libs/file_formats/mod.rs index 272c6ad..b2da85e 100644 --- a/src-tauri/src/libs/file_formats/mod.rs +++ b/src-tauri/src/libs/file_formats/mod.rs @@ -6,6 +6,7 @@ use super::calibre::ImportableFile; use crate::book::{ImportableBookMetadata, ImportableBookType}; mod epub; +mod mobi; pub enum SupportedFormats { EPUB, @@ -77,6 +78,21 @@ pub fn get_importable_file_metadata(file: ImportableFile) -> Option None, }, + Some(SupportedFormats::MOBI) => match mobi::read_metadata(&file.path) { + Some(metadata) => Some(ImportableBookMetadata { + file_type: ImportableBookType::MOBI, + title: metadata.title, + author_names: Some(vec![metadata.author]), + identifier: None, + publisher: Some(metadata.publisher), + language: Some(metadata.language), + tags: metadata.subjects, + path: file.path, + publication_date: metadata.pub_date, + file_contains_cover: true, + }), + _ => None, + }, _ => None, } } diff --git a/src/bindings.ts b/src/bindings.ts index 1cda23f..e62be52 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -13,10 +13,10 @@ return await TAURI_INVOKE("plugin:tauri-specta|calibre_send_to_device", { librar async initClient(libraryPath: string) : Promise { return await TAURI_INVOKE("plugin:tauri-specta|init_client", { libraryPath }); }, -async getImportableFileMetadata(file: ImportableFile) : Promise { +async getImportableFileMetadata(file: ImportableFile) : Promise<{ file_type: ImportableBookType; title: string; author_names: string[] | null; identifier: string | null; publisher: string | null; language: string | null; tags: string[]; path: string; publication_date: string | null; file_contains_cover: boolean } | null> { return await TAURI_INVOKE("plugin:tauri-specta|get_importable_file_metadata", { file }); }, -async checkFileImportable(pathToFile: string) : Promise { +async checkFileImportable(pathToFile: string) : Promise<{ path: string } | null> { return await TAURI_INVOKE("plugin:tauri-specta|check_file_importable", { pathToFile }); }, async addBookToDbByMetadata(libraryPath: string, md: ImportableBookMetadata) : Promise { diff --git a/src/lib/library/addBook.ts b/src/lib/library/addBook.ts index 7efe5da..0c7add6 100644 --- a/src/lib/library/addBook.ts +++ b/src/lib/library/addBook.ts @@ -11,6 +11,10 @@ export const promptToAddBook = async (library: Library): Promise