fix missing series information
This commit is contained in:
parent
7d8ab91d85
commit
a52b8a8288
5 changed files with 114 additions and 7 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -148,7 +148,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pbdbfixer"
|
name = "pbdbfixer"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pbdbfixer"
|
name = "pbdbfixer"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
authors = ["Martin Brodbeck <martin@brodbeck-online.de>"]
|
authors = ["Martin Brodbeck <martin@brodbeck-online.de>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,12 @@ The app tries to fix the following issues in the database
|
||||||
- Correction of wrong author entries (books_impl table)
|
- Correction of wrong author entries (books_impl table)
|
||||||
- Removing deleted e-books from the database (various tables)
|
- Removing deleted e-books from the database (various tables)
|
||||||
- Add missing genre if present in epub (genre and booktogenre tables)
|
- Add missing genre if present in epub (genre and booktogenre tables)
|
||||||
|
- Add missing series information (books_impl table)
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
This program is tested on a PocketBook
|
This program is tested on a PocketBook
|
||||||
- *Touch HD 3* (software version 6.1.900)
|
- *Touch HD 3* (software version 6.1.900)
|
||||||
|
- *Inkpad 3 Pro* (software version 6.0.1067)
|
||||||
- *Touch Lux 4* (software version 6.0.1118)
|
- *Touch Lux 4* (software version 6.0.1118)
|
||||||
|
|
||||||
It might work with other PocketBook devices/software versions. Please tell me if it works for you (and do make a backup of the explorer-3.db file before trying!).
|
It might work with other PocketBook devices/software versions. Please tell me if it works for you (and do make a backup of the explorer-3.db file before trying!).
|
||||||
|
|
91
src/epub.rs
91
src/epub.rs
|
@ -13,10 +13,26 @@ pub struct Author {
|
||||||
pub firstauthor: String,
|
pub firstauthor: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Series {
|
||||||
|
pub name: String,
|
||||||
|
pub index: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Series {
|
||||||
|
fn new() -> Self {
|
||||||
|
Series {
|
||||||
|
name: String::new(),
|
||||||
|
index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EpubMetadata {
|
pub struct EpubMetadata {
|
||||||
pub authors: Vec<Author>,
|
pub authors: Vec<Author>,
|
||||||
pub genre: String,
|
pub genre: String,
|
||||||
|
pub series: Series,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EpubMetadata {
|
impl EpubMetadata {
|
||||||
|
@ -24,6 +40,7 @@ impl EpubMetadata {
|
||||||
EpubMetadata {
|
EpubMetadata {
|
||||||
authors: Vec::new(),
|
authors: Vec::new(),
|
||||||
genre: String::new(),
|
genre: String::new(),
|
||||||
|
series: Series::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +104,8 @@ pub fn get_epub_metadata(filename: &str) -> Option<EpubMetadata> {
|
||||||
let mut file_as_found = false;
|
let mut file_as_found = false;
|
||||||
let mut role_found = false;
|
let mut role_found = false;
|
||||||
let mut genre_found = false;
|
let mut genre_found = false;
|
||||||
|
let mut series_found = false;
|
||||||
|
let mut series_index_found = false;
|
||||||
let mut is_epub3 = false;
|
let mut is_epub3 = false;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -185,8 +204,63 @@ pub fn get_epub_metadata(filename: &str) -> Option<EpubMetadata> {
|
||||||
}) {
|
}) {
|
||||||
curr_id = String::from_utf8(refines.unwrap().value.to_vec()).unwrap();
|
curr_id = String::from_utf8(refines.unwrap().value.to_vec()).unwrap();
|
||||||
role_found = true;
|
role_found = true;
|
||||||
|
} else if e.attributes().any(|attr| {
|
||||||
|
attr.as_ref().unwrap().key == b"property"
|
||||||
|
&& attr.as_ref().unwrap().value.ends_with(b"group-position")
|
||||||
|
}) {
|
||||||
|
series_index_found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if e.attributes().any(|attr| {
|
||||||
|
attr.as_ref().unwrap().key == b"property"
|
||||||
|
&& attr
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.value
|
||||||
|
.ends_with(b"belongs-to-collection")
|
||||||
|
}) {
|
||||||
|
series_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Event::Empty(ref e)) if e.local_name() == b"meta" && !is_epub3 => {
|
||||||
|
if e.attributes().any(|attr| {
|
||||||
|
attr.as_ref().unwrap().key == b"name"
|
||||||
|
&& attr
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.unescaped_value()
|
||||||
|
.unwrap()
|
||||||
|
.ends_with(b"series")
|
||||||
|
}) {
|
||||||
|
epub_meta.series.name = e
|
||||||
|
.attributes()
|
||||||
|
.filter(|attr| attr.as_ref().unwrap().key == b"content")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.unescape_and_decode_value(&reader)
|
||||||
|
.unwrap_or_default();
|
||||||
|
} else if e.attributes().any(|attr| {
|
||||||
|
attr.as_ref().unwrap().key == b"name"
|
||||||
|
&& attr
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.unescaped_value()
|
||||||
|
.unwrap()
|
||||||
|
.ends_with(b"series_index")
|
||||||
|
}) {
|
||||||
|
let index_float = e
|
||||||
|
.attributes()
|
||||||
|
.filter(|attr| attr.as_ref().unwrap().key == b"content")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.unescape_and_decode_value(&reader)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.parse::<f32>()
|
||||||
|
.unwrap_or_default();
|
||||||
|
epub_meta.series.index = index_float as i32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Event::Text(ref e)) if file_as_found && is_epub3 => {
|
Ok(Event::Text(ref e)) if file_as_found && is_epub3 => {
|
||||||
let entry = xml_authors.entry(curr_id.clone()).or_insert(XmlAut::new());
|
let entry = xml_authors.entry(curr_id.clone()).or_insert(XmlAut::new());
|
||||||
|
@ -200,6 +274,19 @@ pub fn get_epub_metadata(filename: &str) -> Option<EpubMetadata> {
|
||||||
|
|
||||||
role_found = false;
|
role_found = false;
|
||||||
}
|
}
|
||||||
|
Ok(Event::Text(ref e)) if series_found && is_epub3 => {
|
||||||
|
epub_meta.series.name = String::from_utf8(e.to_vec()).unwrap();
|
||||||
|
|
||||||
|
series_found = false;
|
||||||
|
}
|
||||||
|
Ok(Event::Text(ref e)) if series_index_found && is_epub3 => {
|
||||||
|
epub_meta.series.index = String::from_utf8(e.to_vec())
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
series_index_found = false;
|
||||||
|
}
|
||||||
Ok(Event::Start(ref e)) if e.local_name() == b"subject" => {
|
Ok(Event::Start(ref e)) if e.local_name() == b"subject" => {
|
||||||
genre_found = true;
|
genre_found = true;
|
||||||
}
|
}
|
||||||
|
@ -212,8 +299,6 @@ pub fn get_epub_metadata(filename: &str) -> Option<EpubMetadata> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//println!("Meta: {:?}", &xml_authors);
|
|
||||||
|
|
||||||
epub_meta.authors = xml_authors
|
epub_meta.authors = xml_authors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|&(_, ref xml_author)| &xml_author.role == "aut" && &xml_author.name.len() > &0)
|
.filter(|&(_, ref xml_author)| &xml_author.role == "aut" && &xml_author.name.len() > &0)
|
||||||
|
@ -223,7 +308,5 @@ pub fn get_epub_metadata(filename: &str) -> Option<EpubMetadata> {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
//println!("Meta: {:?}", &epub_meta);
|
|
||||||
|
|
||||||
Some(epub_meta)
|
Some(epub_meta)
|
||||||
}
|
}
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -12,6 +12,7 @@ struct BookEntry {
|
||||||
has_drm: bool,
|
has_drm: bool,
|
||||||
genre: String,
|
genre: String,
|
||||||
first_author_letter: String,
|
first_author_letter: String,
|
||||||
|
series: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
|
fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
|
||||||
|
@ -21,7 +22,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
|
||||||
.prepare(
|
.prepare(
|
||||||
r#"
|
r#"
|
||||||
SELECT books.id, folders.name, files.filename, books.firstauthor,
|
SELECT books.id, folders.name, files.filename, books.firstauthor,
|
||||||
books.author, genres.name, first_author_letter
|
books.author, genres.name, first_author_letter, series
|
||||||
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
|
||||||
|
@ -50,6 +51,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
|
||||||
};
|
};
|
||||||
let genre: String = row.get(5).unwrap_or_default();
|
let genre: String = row.get(5).unwrap_or_default();
|
||||||
let first_author_letter = row.get(6).unwrap_or_default();
|
let first_author_letter = row.get(6).unwrap_or_default();
|
||||||
|
let series: String = row.get(7).unwrap_or_default();
|
||||||
|
|
||||||
let entry = BookEntry {
|
let entry = BookEntry {
|
||||||
id: book_id,
|
id: book_id,
|
||||||
|
@ -59,6 +61,7 @@ fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
|
||||||
has_drm,
|
has_drm,
|
||||||
genre,
|
genre,
|
||||||
first_author_letter,
|
first_author_letter,
|
||||||
|
series,
|
||||||
};
|
};
|
||||||
|
|
||||||
book_entries.push(entry);
|
book_entries.push(entry);
|
||||||
|
@ -119,6 +122,7 @@ struct Statistics {
|
||||||
drm_skipped: usize,
|
drm_skipped: usize,
|
||||||
genres_fixed: usize,
|
genres_fixed: usize,
|
||||||
sorting_fixed: usize,
|
sorting_fixed: usize,
|
||||||
|
series_fixed: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Statistics {
|
impl Statistics {
|
||||||
|
@ -127,6 +131,7 @@ impl Statistics {
|
||||||
|| &self.genres_fixed > &0
|
|| &self.genres_fixed > &0
|
||||||
|| &self.ghost_books_cleaned > &0
|
|| &self.ghost_books_cleaned > &0
|
||||||
|| &self.sorting_fixed > &0
|
|| &self.sorting_fixed > &0
|
||||||
|
|| &self.series_fixed > &0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +142,7 @@ fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics
|
||||||
drm_skipped: 0,
|
drm_skipped: 0,
|
||||||
genres_fixed: 0,
|
genres_fixed: 0,
|
||||||
sorting_fixed: 0,
|
sorting_fixed: 0,
|
||||||
|
series_fixed: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in book_entries {
|
for entry in book_entries {
|
||||||
|
@ -224,6 +230,18 @@ fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stat.genres_fixed = stat.genres_fixed + 1;
|
stat.genres_fixed = stat.genres_fixed + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix series…
|
||||||
|
if !epub_metadata.series.name.is_empty() && entry.series.is_empty() {
|
||||||
|
let mut stmt = tx
|
||||||
|
.prepare("UPDATE books_impl SET series = :series, numinseries = :series_index WHERE id = :book_id")
|
||||||
|
.unwrap();
|
||||||
|
stmt.execute_named(
|
||||||
|
named_params![":series": &epub_metadata.series.name, ":series_index": &epub_metadata.series.index, ":book_id": entry.id],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
stat.series_fixed = stat.series_fixed + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,11 +308,13 @@ fn main() {
|
||||||
"Authors fixed: {}\n\
|
"Authors fixed: {}\n\
|
||||||
Sorting fixed: {}\n\
|
Sorting fixed: {}\n\
|
||||||
Genres fixed: {}\n\
|
Genres fixed: {}\n\
|
||||||
|
Series fixed: {}\n\
|
||||||
Books skipped (DRM): {}\n\
|
Books skipped (DRM): {}\n\
|
||||||
Books cleaned from DB: {}",
|
Books cleaned from DB: {}",
|
||||||
&stat.authors_fixed,
|
&stat.authors_fixed,
|
||||||
&stat.sorting_fixed,
|
&stat.sorting_fixed,
|
||||||
&stat.genres_fixed,
|
&stat.genres_fixed,
|
||||||
|
&stat.series_fixed,
|
||||||
&stat.drm_skipped,
|
&stat.drm_skipped,
|
||||||
&stat.ghost_books_cleaned
|
&stat.ghost_books_cleaned
|
||||||
),
|
),
|
||||||
|
@ -306,11 +326,13 @@ fn main() {
|
||||||
"Authors fixed: {}\n\
|
"Authors fixed: {}\n\
|
||||||
Sorting fixed: {}\n\
|
Sorting fixed: {}\n\
|
||||||
Genres fixed: {}\n\
|
Genres fixed: {}\n\
|
||||||
|
Series fixed: {}\n\
|
||||||
Books skipped (DRM): {}\n\
|
Books skipped (DRM): {}\n\
|
||||||
Books cleaned from DB: {}",
|
Books cleaned from DB: {}",
|
||||||
&stat.authors_fixed,
|
&stat.authors_fixed,
|
||||||
&stat.sorting_fixed,
|
&stat.sorting_fixed,
|
||||||
&stat.genres_fixed,
|
&stat.genres_fixed,
|
||||||
|
&stat.series_fixed,
|
||||||
&stat.drm_skipped,
|
&stat.drm_skipped,
|
||||||
&stat.ghost_books_cleaned
|
&stat.ghost_books_cleaned
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue