fix genres
This commit is contained in:
parent
77d87c751d
commit
e4f6efe12a
3 changed files with 82 additions and 8 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
86
src/main.rs
86
src/main.rs
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue