PbDbFixer/src/main.rs

279 lines
8.5 KiB
Rust
Raw Normal View History

2021-02-11 21:58:10 +01:00
mod epub;
2021-01-29 12:47:33 +01:00
mod pocketbook;
2021-02-11 21:58:10 +01:00
use rusqlite::{named_params, Connection, Transaction, NO_PARAMS};
use std::usize;
2021-02-10 10:31:44 +01:00
2021-01-28 17:52:09 +01:00
struct BookEntry {
id: i32,
filepath: String,
2021-02-01 19:39:36 +01:00
author: String,
firstauthor: String,
has_drm: bool,
2021-02-10 10:31:44 +01:00
genre: String,
2021-01-28 17:52:09 +01:00
}
2021-02-01 14:33:00 +01:00
fn get_epubs_from_database(tx: &Transaction) -> Vec<BookEntry> {
let mut book_entries = Vec::new();
let mut stmt = tx
.prepare(
r"
2021-02-10 10:31:44 +01:00
SELECT books.id, folders.name, files.filename, books.firstauthor, books.author, genres.name
2021-02-01 14:33:00 +01:00
FROM books_impl books JOIN files
ON books.id = files.book_id
JOIN folders
ON folders.id = files.folder_id
2021-02-10 10:31:44 +01:00
LEFT OUTER JOIN booktogenre btg
ON books.id = btg.bookid
LEFT OUTER JOIN genres
ON genres.id = btg.genreid
2021-02-01 14:33:00 +01:00
WHERE files.storageid = 1 AND books.ext = 'epub'
ORDER BY books.id",
)
.unwrap();
2021-01-29 13:51:36 +01:00
2021-01-30 17:59:00 +01:00
let mut rows = stmt.query(NO_PARAMS).unwrap();
while let Some(row) = rows.next().unwrap() {
let book_id: i32 = row.get(0).unwrap();
let prefix: String = row.get(1).unwrap();
let filename: String = row.get(2).unwrap();
let filepath = format!("{}/{}", prefix, filename);
2021-02-01 19:39:36 +01:00
let firstauthor: String = row.get(3).unwrap();
let author: String = row.get(4).unwrap();
let has_drm = match prefix.as_str() {
"/mnt/ext1/Digital Editions" => true,
_ => false,
};
2021-02-10 10:31:44 +01:00
let genre: String = row.get(5).unwrap_or_default();
2021-02-01 14:33:00 +01:00
let entry = BookEntry {
2021-01-30 17:59:00 +01:00
id: book_id,
filepath,
2021-02-01 19:39:36 +01:00
firstauthor,
author,
has_drm,
2021-02-10 10:31:44 +01:00
genre,
2021-02-01 14:33:00 +01:00
};
book_entries.push(entry);
2021-01-30 17:59:00 +01:00
}
2021-01-28 17:52:09 +01:00
2021-02-01 14:33:00 +01:00
book_entries
}
2021-02-01 19:39:36 +01:00
fn remove_ghost_books_from_db(tx: &Transaction) -> usize {
let mut stmt = tx
.prepare(
r"
DELETE FROM books_impl
WHERE id IN (
SELECT books.id
FROM books_impl books
LEFT OUTER JOIN files
ON books.id = files.book_id
WHERE files.filename is NULL
)",
)
.unwrap();
let num = stmt.execute(NO_PARAMS).unwrap();
tx.execute(
r"DELETE FROM books_settings WHERE bookid NOT IN ( SELECT id FROM books_impl )",
NO_PARAMS,
)
.unwrap();
tx.execute(
r"DELETE FROM books_uids WHERE book_id NOT IN ( SELECT id FROM books_impl )",
NO_PARAMS,
)
.unwrap();
tx.execute(
r"DELETE FROM bookshelfs_books WHERE bookid NOT IN ( SELECT id FROM books_impl )",
NO_PARAMS,
)
.unwrap();
tx.execute(
r"DELETE FROM booktogenre WHERE bookid NOT IN ( SELECT id FROM books_impl )",
NO_PARAMS,
)
.unwrap();
tx.execute(
r"DELETE FROM social WHERE bookid NOT IN ( SELECT id FROM books_impl )",
NO_PARAMS,
)
.unwrap();
num
}
2021-02-01 14:33:00 +01:00
struct Statistics {
authors_fixed: i32,
2021-02-01 19:39:36 +01:00
ghost_books_cleaned: usize,
drm_skipped: usize,
2021-02-10 10:31:44 +01:00
genres_fixed: usize,
2021-02-01 14:33:00 +01:00
}
fn fix_db_entries(tx: &Transaction, book_entries: &Vec<BookEntry>) -> Statistics {
2021-02-01 19:39:36 +01:00
let mut stat = Statistics {
authors_fixed: 0,
ghost_books_cleaned: 0,
drm_skipped: 0,
2021-02-10 10:31:44 +01:00
genres_fixed: 0,
2021-02-01 19:39:36 +01:00
};
2021-02-01 14:33:00 +01:00
for entry in book_entries {
if entry.has_drm {
stat.drm_skipped = stat.drm_skipped + 1;
continue;
}
2021-02-11 21:58:10 +01:00
if let Some(epub_metadata) = epub::get_epub_metadata(&entry.filepath) {
let authors = epub_metadata
.authors
.iter()
.filter(|aut| aut.firstauthor.len() > 0)
.collect::<Vec<_>>();
// Fix firstauthor…
let firstauthors = authors
.iter()
.map(|aut| aut.firstauthor.clone())
.collect::<Vec<_>>();
if !firstauthors.iter().all(|s| entry.firstauthor.contains(s)) {
let mut stmt = tx
.prepare("UPDATE books_impl SET firstauthor = :file_as WHERE id = :book_id")
.unwrap();
stmt.execute_named(
named_params![":file_as": firstauthors.join(" & "), ":book_id": entry.id],
)
.unwrap();
stat.authors_fixed = stat.authors_fixed + 1;
}
// Fix author names…
let authornames = authors
.iter()
.map(|aut| aut.name.clone())
.collect::<Vec<_>>();
if !authornames.iter().all(|s| entry.author.contains(s)) {
let mut stmt = tx
.prepare("UPDATE books_impl SET author = :authors WHERE id = :book_id")
.unwrap();
stmt.execute_named(
named_params![":authors": authornames.join(", "), ":book_id": entry.id],
)
.unwrap();
stat.authors_fixed = stat.authors_fixed + 1;
}
if entry.genre.is_empty() && epub_metadata.genre.len() > 0 {
let mut stmt = tx
.prepare(r#"INSERT INTO genres (name) SELECT :genre ON CONFLICT DO NOTHING"#)
.unwrap();
stmt.execute_named(named_params![":genre": &epub_metadata.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": &epub_metadata.genre],
)
.unwrap();
stat.genres_fixed = stat.genres_fixed + 1;
2021-02-10 10:31:44 +01:00
}
2021-01-28 17:52:09 +01:00
}
}
2021-01-30 17:59:00 +01:00
2021-02-01 19:39:36 +01:00
// ghost books
let num = remove_ghost_books_from_db(tx);
stat.ghost_books_cleaned = num;
2021-02-01 14:33:00 +01:00
stat
2021-01-30 17:59:00 +01:00
}
fn main() {
2021-02-02 09:52:50 +01:00
if cfg!(target_arch = "arm") {
let res = pocketbook::dialog(
pocketbook::Icon::None,
"PocketBook has sometimes problems parsing metadata.\n\
This app tries to fix some of these issues.\n\
(Note: The database file explore-3.db will be altered!)\n\
\n\
Please be patient - this might take a while.\n\
You will see a blank screen during the process.\n\
\n\
Proceed?",
&["Cancel", "Yes"],
);
if res == 1 {
return;
}
}
2021-01-30 17:59:00 +01:00
let mut conn = Connection::open("/mnt/ext1/system/explorer-3/explorer-3.db").unwrap();
2021-02-01 19:39:36 +01:00
conn.execute("PRAGMA foreign_keys = 0", NO_PARAMS).unwrap();
2021-01-30 17:59:00 +01:00
let tx = conn.transaction().unwrap();
2021-02-01 14:33:00 +01:00
let book_entries = get_epubs_from_database(&tx);
let stat = fix_db_entries(&tx, &book_entries);
2021-01-28 17:52:09 +01:00
tx.commit().unwrap();
2021-01-29 12:47:33 +01:00
if cfg!(target_arch = "arm") {
2021-02-01 14:33:00 +01:00
if stat.authors_fixed == 0 {
2021-02-02 20:11:13 +01:00
if stat.drm_skipped == 0 {
pocketbook::dialog(
pocketbook::Icon::Info,
"The database seems to be ok.\n\
Nothing had to be fixed.",
&["OK"],
);
} else {
pocketbook::dialog(
pocketbook::Icon::Info,
&format!(
"The database seems to be ok.\n\
Nothing had to be fixed.\n\
(Books skipped (DRM): {})",
&stat.drm_skipped
),
&["OK"],
);
}
2021-01-29 12:47:33 +01:00
} else {
pocketbook::dialog(
pocketbook::Icon::Info,
2021-02-01 19:39:36 +01:00
&format!(
"Authors fixed: {}\n\
2021-02-10 10:31:44 +01:00
Genres fixed: {}\n\
Books skipped (DRM): {}\n\
Books cleaned from DB: {}",
2021-02-10 10:31:44 +01:00
&stat.authors_fixed,
&stat.genres_fixed,
&stat.drm_skipped,
&stat.ghost_books_cleaned
2021-02-01 19:39:36 +01:00
),
2021-02-02 09:52:50 +01:00
&["OK"],
2021-01-29 12:47:33 +01:00
);
}
2021-02-01 14:33:00 +01:00
} else {
2021-02-01 19:39:36 +01:00
println!(
"Authors fixed: {}\n\
2021-02-10 10:31:44 +01:00
Genres fixed: {}\n\
Books skipped (DRM): {}\n\
Books cleaned from DB: {}",
2021-02-10 10:31:44 +01:00
&stat.authors_fixed, &stat.genres_fixed, &stat.drm_skipped, &stat.ghost_books_cleaned
2021-02-01 19:39:36 +01:00
);
2021-01-29 12:47:33 +01:00
}
2021-01-28 17:52:09 +01:00
}