|
@@ -358,11 +358,13 @@ void BibliothecaWindow::onAddBookClicked() {
|
|
|
}
|
|
}
|
|
|
if (files.empty()) return;
|
|
if (files.empty()) return;
|
|
|
|
|
|
|
|
- const size_t total_files = files.size();
|
|
|
|
|
-
|
|
|
|
|
// Hash and create Book objects off the GTK thread
|
|
// Hash and create Book objects off the GTK thread
|
|
|
- std::thread([this, files = std::move(files), total_files]() {
|
|
|
|
|
- std::vector<Book> imported;
|
|
|
|
|
|
|
+ std::thread([this, files = std::move(files)]() {
|
|
|
|
|
+ struct ImportedBook {
|
|
|
|
|
+ Book book;
|
|
|
|
|
+ bool is_duplicate;
|
|
|
|
|
+ };
|
|
|
|
|
+ std::vector<ImportedBook> imported;
|
|
|
std::vector<std::string> errors;
|
|
std::vector<std::string> errors;
|
|
|
imported.reserve(files.size());
|
|
imported.reserve(files.size());
|
|
|
|
|
|
|
@@ -370,7 +372,11 @@ void BibliothecaWindow::onAddBookClicked() {
|
|
|
try {
|
|
try {
|
|
|
const auto path = f->get_path();
|
|
const auto path = f->get_path();
|
|
|
const auto id = sha256_file(path);
|
|
const auto id = sha256_file(path);
|
|
|
- auto resOpt = import_book_assets(path, id); // may throw -> catch already present
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Check if this book already exists (by hash)
|
|
|
|
|
+ bool is_duplicate = m_bookList.findById(id).has_value();
|
|
|
|
|
+
|
|
|
|
|
+ auto resOpt = import_book_assets(path, id); // may throw -> catch already present
|
|
|
ImportResult res = resOpt.value_or(ImportResult{});
|
|
ImportResult res = resOpt.value_or(ImportResult{});
|
|
|
|
|
|
|
|
// Build the Book with extracted metadata
|
|
// Build the Book with extracted metadata
|
|
@@ -384,7 +390,7 @@ void BibliothecaWindow::onAddBookClicked() {
|
|
|
// Preload cover into texture (or defer to BookTile which already calls load_cover_from_disk)
|
|
// Preload cover into texture (or defer to BookTile which already calls load_cover_from_disk)
|
|
|
b.load_cover_from_disk();
|
|
b.load_cover_from_disk();
|
|
|
|
|
|
|
|
- imported.push_back(std::move(b));
|
|
|
|
|
|
|
+ imported.push_back({std::move(b), is_duplicate});
|
|
|
} catch (const std::exception& e) {
|
|
} catch (const std::exception& e) {
|
|
|
errors.push_back(Glib::path_get_basename(f->get_path()) + ": " + e.what());
|
|
errors.push_back(Glib::path_get_basename(f->get_path()) + ": " + e.what());
|
|
|
std::cerr << "Import failed: " << e.what() << "\n";
|
|
std::cerr << "Import failed: " << e.what() << "\n";
|
|
@@ -392,22 +398,38 @@ void BibliothecaWindow::onAddBookClicked() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Push to model on the GTK main loop
|
|
// Push to model on the GTK main loop
|
|
|
- Glib::signal_idle().connect_once([this, books = std::move(imported), errors = std::move(errors), total_files]() mutable {
|
|
|
|
|
- if (!books.empty()) {
|
|
|
|
|
- m_bookList.upsertMany(books);
|
|
|
|
|
|
|
+ Glib::signal_idle().connect_once([this, imported = std::move(imported), errors = std::move(errors)]() mutable {
|
|
|
|
|
+ std::vector<Book> books_to_add;
|
|
|
|
|
+ size_t new_count = 0;
|
|
|
|
|
+ size_t duplicate_count = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (auto& item : imported) {
|
|
|
|
|
+ if (item.is_duplicate) {
|
|
|
|
|
+ duplicate_count++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ new_count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ books_to_add.push_back(std::move(item.book));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!books_to_add.empty()) {
|
|
|
|
|
+ m_bookList.upsertMany(books_to_add);
|
|
|
updateVisibleView();
|
|
updateVisibleView();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Show toast with import results
|
|
// Show toast with import results
|
|
|
- const size_t success_count = books.size();
|
|
|
|
|
const size_t error_count = errors.size();
|
|
const size_t error_count = errors.size();
|
|
|
|
|
|
|
|
- if (error_count == 0 && success_count > 0) {
|
|
|
|
|
- showToast(Glib::ustring::compose("Added %1 book%2", success_count, success_count == 1 ? "" : "s"), false);
|
|
|
|
|
- } else if (success_count == 0 && error_count > 0) {
|
|
|
|
|
|
|
+ if (error_count == 0 && new_count > 0 && duplicate_count == 0) {
|
|
|
|
|
+ showToast(Glib::ustring::compose("Added %1 book%2", new_count, new_count == 1 ? "" : "s"), false);
|
|
|
|
|
+ } else if (error_count == 0 && new_count == 0 && duplicate_count > 0) {
|
|
|
|
|
+ showToast(Glib::ustring::compose("%1 book%2 already in library", duplicate_count, duplicate_count == 1 ? "" : "s"), false);
|
|
|
|
|
+ } else if (error_count == 0 && new_count > 0 && duplicate_count > 0) {
|
|
|
|
|
+ showToast(Glib::ustring::compose("Added %1 new, %2 duplicate%3", new_count, duplicate_count, duplicate_count == 1 ? "" : "s"), false);
|
|
|
|
|
+ } else if (new_count == 0 && duplicate_count == 0 && error_count > 0) {
|
|
|
showToast(Glib::ustring::compose("Failed to import %1 file%2", error_count, error_count == 1 ? "" : "s"), true);
|
|
showToast(Glib::ustring::compose("Failed to import %1 file%2", error_count, error_count == 1 ? "" : "s"), true);
|
|
|
- } else if (success_count > 0 && error_count > 0) {
|
|
|
|
|
- showToast(Glib::ustring::compose("Added %1, failed %2", success_count, error_count), true);
|
|
|
|
|
|
|
+ } else if ((new_count > 0 || duplicate_count > 0) && error_count > 0) {
|
|
|
|
|
+ showToast(Glib::ustring::compose("Added %1, %2 failed", new_count + duplicate_count, error_count), true);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}).detach();
|
|
}).detach();
|