fix genres

This commit is contained in:
Martin Brodbeck 2021-02-10 10:31:44 +01:00
parent 77d87c751d
commit e4f6efe12a
3 changed files with 82 additions and 8 deletions

2
Cargo.lock generated
View File

@ -148,7 +148,7 @@ dependencies = [
[[package]] [[package]]
name = "pbdbfixer" name = "pbdbfixer"
version = "0.3.1" version = "0.4.0"
dependencies = [ dependencies = [
"rusqlite", "rusqlite",
"xml-rs", "xml-rs",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pbdbfixer" name = "pbdbfixer"
version = "0.3.1" version = "0.4.0"
authors = ["Martin Brodbeck <martin@brodbeck-online.de>"] authors = ["Martin Brodbeck <martin@brodbeck-online.de>"]
edition = "2018" edition = "2018"

View File

@ -1,9 +1,9 @@
mod pocketbook; mod pocketbook;
use rusqlite::{named_params, Connection, Result, Transaction, NO_PARAMS}; use rusqlite::{named_params, Connection, Result, Transaction, NO_PARAMS};
use std::io::BufReader;
use std::{collections::HashMap, fs::File}; use std::{collections::HashMap, fs::File};
use std::{error::Error, io::Read}; use std::{error::Error, io::Read};
use std::{io::BufReader, usize};
use xml::reader::{EventReader, ParserConfig, XmlEvent}; use xml::reader::{EventReader, ParserConfig, XmlEvent};
use zip::{read::ZipFile, ZipArchive}; use zip::{read::ZipFile, ZipArchive};
@ -299,12 +299,48 @@ fn get_attribute_creator(opf: ZipFile) -> Option<String> {
None None
} }
fn get_attribute_genre(opf: ZipFile) -> Option<String> {
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 { struct BookEntry {
id: i32, id: i32,
filepath: String, filepath: String,
author: String, author: String,
firstauthor: String, firstauthor: String,
has_drm: bool, has_drm: bool,
genre: String,
} }
fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> { fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
@ -313,11 +349,15 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
let mut stmt = tx let mut stmt = tx
.prepare( .prepare(
r" 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 FROM books_impl books JOIN files
ON books.id = files.book_id ON books.id = files.book_id
JOIN folders JOIN folders
ON folders.id = files.folder_id 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' WHERE files.storageid = 1 AND books.ext = 'epub'
ORDER BY books.id", ORDER BY books.id",
) )
@ -336,6 +376,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
"/mnt/ext1/Digital Editions" => true, "/mnt/ext1/Digital Editions" => true,
_ => false, _ => false,
}; };
let genre: String = row.get(5).unwrap_or_default();
let entry = BookEntry { let entry = BookEntry {
id: book_id, id: book_id,
@ -343,6 +384,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
firstauthor, firstauthor,
author, author,
has_drm, has_drm,
genre,
}; };
book_entries.push(entry); book_entries.push(entry);
@ -401,6 +443,7 @@ struct Statistics {
authors_fixed: i32, authors_fixed: i32,
ghost_books_cleaned: usize, ghost_books_cleaned: usize,
drm_skipped: usize, drm_skipped: usize,
genres_fixed: usize,
} }
fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics { fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics {
@ -408,6 +451,7 @@ fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics
authors_fixed: 0, authors_fixed: 0,
ghost_books_cleaned: 0, ghost_books_cleaned: 0,
drm_skipped: 0, drm_skipped: 0,
genres_fixed: 0,
}; };
for entry in book_entries { for entry in book_entries {
@ -453,6 +497,31 @@ fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics
stat.authors_fixed = stat.authors_fixed + 1; 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, pocketbook::Icon::Info,
&format!( &format!(
"Authors fixed: {}\n\ "Authors fixed: {}\n\
Books skipped (DRM): {}\n\ Genres fixed: {}\n\
Books skipped (DRM): {}\n\
Books cleaned from DB: {}", 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"], &["OK"],
); );
@ -527,9 +600,10 @@ fn main() {
} else { } else {
println!( println!(
"Authors fixed: {}\n\ "Authors fixed: {}\n\
Books skipped (DRM): {}\n\ Genres fixed: {}\n\
Books skipped (DRM): {}\n\
Books cleaned from DB: {}", 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
); );
} }
} }