# 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`. - 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 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: ```cpp BookList m_bookList(database); m_bookList.loadAll(); m_bookList.signalBookAdded().connect(...); ``` Worker thread example: ```cpp 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.