booklist.md 3.6 KB

BookList Module Overview

BookList is the in-memory model of the library. It mirrors the contents of SQLite (via DatabaseManager), exposes thread-safe APIs to mutate the list, and emits granular signals so UI layers (e.g. BookShelf) can stay synchronized without polling.

Responsibilities

  • Load the full set of books from the database into a std::vector<Book>.
  • Provide CRUD helpers (upsert, removeById, upsertMany) that keep both the vector and the database in sync.
  • Broadcast change notifications (signalBookAdded, signalBookUpdated, etc.).
  • Offer _enqueue variants that marshal work onto the GTK main loop so helper threads can safely update the model.

Structure

BookList
 ├── DatabaseManager& m_database   // owned externally
 ├── std::vector<Book> m_books     // authoritative in-memory copy
 ├── std::mutex m_mutex            // guards access to m_books
 └── Signals (sigc::signal<...>)   // fine-grained notifications

Data flow

  1. Initial load: loadAll() grabs a copy of all rows via the database manager, swaps them into m_books, and emits signalReset() followed by signalBulkChanged() so views can rebuild.
  2. Insert/Update: upsert() writes the book to SQLite, then updates (or appends) the matching entry in m_books. Depending on whether the book was new, it emits signalBookAdded() or signalBookUpdated().
  3. Removal: removeById() deletes the record from the database; if the book existed in m_books it also erases it and emits signalBookRemoved().
  4. Bulk updates: upsertMany() writes each book to the database, swaps the vector entries, and finally emits signalBulkChanged() (useful after a batch import).

Thread-safety helpers

  • _enqueue variants (upsertEnqueue, removeByIdEnqueue, loadAllEnqueue) capture the arguments and schedule the corresponding method via Glib::signal_idle().connect_once. This allows worker threads (e.g. import jobs) to request updates without touching Gtk APIs off the main thread.
  • Internally, std::mutex m_mutex protects the vector so multiple calls don’t race even if they come from different threads.

Signals

Signal Emitted when …
signalBookAdded A new book (id not previously seen) is added.
signalBookUpdated An existing book’s metadata changes.
signalBookRemoved A book is erased by id.
signalReset The vector is cleared / reloaded wholesale.
signalBulkChanged Large batch operations mutate the list.

Consumers (e.g. BookShelf) typically connect to these to update UI widgets.

Extension points

  • Metadata fields: When new columns are added to Book, extend the CRUD methods and signal payloads accordingly.
  • Sorting / ordering: m_books preserves insertion order; consumers are free to sort if needed (e.g. BookShelf’s fuzzy filter reorders matches).
  • Persistence layer: swapping the backend (e.g. to a REST API) would mean replacing DatabaseManager calls but the public BookList API could remain stable.

Expected usage

Typical lifecycle inside the app:

BookList m_bookList(database);
m_bookList.loadAll();
m_bookList.signalBookAdded().connect(...);

Worker thread example:

std::thread([&list, book] {
  list.upsertEnqueue(book);  // safe from any thread
}).detach();

UI code listens to the signals to stay synchronized, while background import jobs call the _enqueue helpers to avoid GTK threading violations.