diff --git a/Cargo.lock b/Cargo.lock index 2a3bfc4..679cb60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,7 +148,7 @@ dependencies = [ [[package]] name = "pbdbfixer" -version = "0.3.1" +version = "0.4.0" dependencies = [ "rusqlite", "xml-rs", diff --git a/Cargo.toml b/Cargo.toml index 5733d89..8700c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pbdbfixer" -version = "0.3.1" +version = "0.4.0" authors = ["Martin Brodbeck "] edition = "2018" diff --git a/src/main.rs b/src/main.rs index b3730ce..1bb944d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ mod pocketbook; use rusqlite::{named_params, Connection, Result, Transaction, NO_PARAMS}; -use std::io::BufReader; use std::{collections::HashMap, fs::File}; use std::{error::Error, io::Read}; +use std::{io::BufReader, usize}; use xml::reader::{EventReader, ParserConfig, XmlEvent}; use zip::{read::ZipFile, ZipArchive}; @@ -299,12 +299,48 @@ fn get_attribute_creator(opf: ZipFile) -> Option { None } +fn get_attribute_genre(opf: ZipFile) -> Option { + let parser = ParserConfig::new() + .trim_whitespace(true) + .ignore_comments(true) + .coalesce_characters(true) + .create_reader(opf); + + let mut genre_found = false; + + for e in parser { + match e { + Ok(XmlEvent::StartElement { name, .. }) if name.local_name == "subject" => { + genre_found = true; + } + Ok(XmlEvent::Characters(value)) => { + if genre_found { + return Some(value); + } + } + Ok(XmlEvent::StartElement { .. }) => { + if genre_found == true { + genre_found = false; + } + } + Err(e) => { + println!("{}", e); + break; + } + _ => {} + } + } + + None +} + struct BookEntry { id: i32, filepath: String, author: String, firstauthor: String, has_drm: bool, + genre: String, } fn get_epubs_from_database(tx: &Transaction) -> Vec { @@ -313,11 +349,15 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec { let mut stmt = tx .prepare( r" - SELECT books.id, folders.name, files.filename, books.firstauthor, books.author + SELECT books.id, folders.name, files.filename, books.firstauthor, books.author, genres.name FROM books_impl books JOIN files ON books.id = files.book_id JOIN folders ON folders.id = files.folder_id + LEFT OUTER JOIN booktogenre btg + ON books.id = btg.bookid + LEFT OUTER JOIN genres + ON genres.id = btg.genreid WHERE files.storageid = 1 AND books.ext = 'epub' ORDER BY books.id", ) @@ -336,6 +376,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec { "/mnt/ext1/Digital Editions" => true, _ => false, }; + let genre: String = row.get(5).unwrap_or_default(); let entry = BookEntry { id: book_id, @@ -343,6 +384,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec { firstauthor, author, has_drm, + genre, }; book_entries.push(entry); @@ -401,6 +443,7 @@ struct Statistics { authors_fixed: i32, ghost_books_cleaned: usize, drm_skipped: usize, + genres_fixed: usize, } fn fix_db_entries(tx: &Transaction, book_entries: &Vec) -> Statistics { @@ -408,6 +451,7 @@ fn fix_db_entries(tx: &Transaction, book_entries: &Vec) -> Statistics authors_fixed: 0, ghost_books_cleaned: 0, drm_skipped: 0, + genres_fixed: 0, }; for entry in book_entries { @@ -453,6 +497,31 @@ fn fix_db_entries(tx: &Transaction, book_entries: &Vec) -> Statistics stat.authors_fixed = stat.authors_fixed + 1; } } + // genreā€¦ + if entry.genre.is_empty() { + let opf = archive.by_name(opf_file.as_str()).unwrap(); + if let Some(genre) = get_attribute_genre(opf) { + let mut stmt = tx + .prepare( + r#"INSERT INTO genres (name) SELECT :genre ON CONFLICT DO NOTHING"#, + ) + .unwrap(); + stmt.execute_named(named_params![":genre": &genre]).unwrap(); + let mut stmt = tx + .prepare( + r#" + INSERT INTO booktogenre (bookid, genreid) + VALUES (:bookid, + (SELECT id FROM genres WHERE name = :genre) + ) + ON CONFLICT DO NOTHING"#, + ) + .unwrap(); + stmt.execute_named(named_params![":bookid": &entry.id, ":genre": &genre]) + .unwrap(); + stat.genres_fixed = stat.genres_fixed + 1; + } + } } } @@ -517,9 +586,13 @@ fn main() { pocketbook::Icon::Info, &format!( "Authors fixed: {}\n\ - Books skipped (DRM): {}\n\ + Genres fixed: {}\n\ + Books skipped (DRM): {}\n\ Books cleaned from DB: {}", - &stat.authors_fixed, &stat.drm_skipped, &stat.ghost_books_cleaned + &stat.authors_fixed, + &stat.genres_fixed, + &stat.drm_skipped, + &stat.ghost_books_cleaned ), &["OK"], ); @@ -527,9 +600,10 @@ fn main() { } else { println!( "Authors fixed: {}\n\ - Books skipped (DRM): {}\n\ + Genres fixed: {}\n\ + Books skipped (DRM): {}\n\ Books cleaned from DB: {}", - &stat.authors_fixed, &stat.drm_skipped, &stat.ghost_books_cleaned + &stat.authors_fixed, &stat.genres_fixed, &stat.drm_skipped, &stat.ghost_books_cleaned ); } }