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.
std::vector<Book>.upsert, removeById, upsertMany) that keep both the
vector and the database in sync.signalBookAdded, signalBookUpdated, etc.)._enqueue variants that marshal work onto the GTK main loop so helper
threads can safely update the model.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
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.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().removeById() deletes the record from the database; if the book
existed in m_books it also erases it and emits signalBookRemoved().upsertMany() writes each book to the database, swaps the
vector entries, and finally emits signalBulkChanged() (useful after a batch
import)._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.std::mutex m_mutex protects the vector so multiple calls don’t
race even if they come from different threads.| 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.
Book, extend the CRUD
methods and signal payloads accordingly.m_books preserves insertion order; consumers are free
to sort if needed (e.g. BookShelf’s fuzzy filter reorders matches).DatabaseManager calls but the public BookList API could remain
stable.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.