Cache Goodreads data and slow down bulk requests

This commit is contained in:
SirBlob committed 2026-11-01 21:50:01 -05:00
1 parent af73bb5b26
commit 7ef3a29ece
6 files changed
+162 -13

No files matched your search

+49 -5
View File
@@ -1,3 +1,5 @@
import time
from rich.markup import escape
from textual import on, work
from textual.app import ComposeResult
@@ -6,11 +8,14 @@ from textual.containers import Horizontal, VerticalScroll
from textual.widgets import DataTable, Static
import books
import goodreads
import goodreads_series
from tui.content_pane import ContentPane
from tui.dialogs import ConfirmDialog, FormDialog, FormField
from tui.series_picker import SeriesPicker
REQUEST_DELAY_SECONDS = 3
def book_details(book: dict) -> str:
rows = [
@@ -40,6 +45,7 @@ class BooksPane(ContentPane):
Binding("d", "delete", "Delete"),
Binding("u", "refresh", "Refresh"),
Binding("U", "refresh_all", "Refresh all"),
Binding("C", "cache_covers", "Cache covers"),
Binding("r", "reload", "Reload"),
]
@@ -151,19 +157,48 @@ class BooksPane(ContentPane):
self.notify("Refreshing every book from Goodreads...")
self.refresh_every_book()
def report_blocked(self, error: Exception):
self.app.call_from_thread(self.notify, str(error), title="Stopped", severity="error", timeout=15)
@work(thread=True, exclusive=True, group="refresh")
def refresh_every_book(self):
failures = 0
refreshed = 0
all_books = books.load_books()
for book in all_books:
for index, book in enumerate(all_books):
if index:
time.sleep(REQUEST_DELAY_SECONDS)
try:
books.refresh_book(book["goodreadsId"])
except goodreads.BlockedError as error:
self.report_blocked(error)
break
except (OSError, ValueError) as error:
failures += 1
self.app.call_from_thread(self.notify, f"{book['title']}: {error}", title="Skipped", severity="warning")
continue
refreshed += 1
self.app.call_from_thread(lambda: self.reload(self.selected_key()))
summary = f"Refreshed {len(all_books) - failures} of {len(all_books)} books"
self.app.call_from_thread(self.notify, summary)
self.app.call_from_thread(self.notify, f"Refreshed {refreshed} of {len(all_books)} books")
def action_cache_covers(self):
missing = books.books_missing_local_covers()
if not missing:
self.notify("Every cover is already saved in static/covers/books")
return
self.notify(f"Saving {len(missing)} covers...")
self.cache_missing_covers(missing)
@work(thread=True, exclusive=True, group="covers")
def cache_missing_covers(self, missing: list[dict]):
saved = 0
for book in missing:
try:
books.cache_book_cover(book["goodreadsId"])
except (OSError, ValueError) as error:
self.app.call_from_thread(self.notify, f"{book['title']}: {error}", title="Skipped", severity="warning")
continue
saved += 1
self.app.call_from_thread(lambda: self.reload(self.selected_key()))
self.app.call_from_thread(self.notify, f"Saved {saved} of {len(missing)} covers")
def action_add_series(self):
fields = [FormField("link", "Goodreads series link", placeholder="https://www.goodreads.com/series/92037-wings-of-fire", required=True)]
@@ -185,12 +220,21 @@ class BooksPane(ContentPane):
@work(thread=True, exclusive=True, group="series")
def add_series_books(self, series: dict, choices: dict[str, str]):
added = 0
already_listed = set(books.library_shelves())
fetched_any = False
for entry in series["books"]:
shelf = choices.get(entry["goodreadsId"])
if not shelf:
continue
needs_fetch = entry["goodreadsId"] not in already_listed
if needs_fetch and fetched_any:
time.sleep(REQUEST_DELAY_SECONDS)
fetched_any = fetched_any or needs_fetch
try:
book = books.add_series_book(series, entry, shelf)
except goodreads.BlockedError as error:
self.report_blocked(error)
break
except (OSError, ValueError) as error:
self.app.call_from_thread(self.notify, f"{entry['title']}: {error}", title="Skipped", severity="warning")
continue