Wait only 5 seconds for CWA on the story page

A job still waits for the full timeout.
This commit is contained in:
tim
2026-10-02 23:37:33 +02:00
parent e196d1688e
commit 90457610f6
4 changed files with 21 additions and 5 deletions
+5 -4
View File
@@ -80,8 +80,9 @@ class CwaClient:
raise CwaError("CWA address is not set. Set CWA_URL.")
# hrefs from the feed are absolute paths, and include any path prefix of CWA.
url = urljoin(self.base_url, href)
kwargs.setdefault("timeout", self.timeout)
try:
response = self.session.get(url, auth=auth or self.auth, timeout=self.timeout, **kwargs)
response = self.session.get(url, auth=auth or self.auth, **kwargs)
except requests.RequestException as e:
raise CwaError(f"Cannot reach CWA at {self.base_url}: {e}") from e
if response.status_code == 401:
@@ -153,8 +154,8 @@ class CwaClient:
return []
return self._feed("opds/search", params={"query": query.strip()})[0]
def get_book(self, book_uuid: str) -> dict[str, Any] | None:
"""A book by its calibre uuid, or None if CWA has no such book."""
def get_book(self, book_uuid: str, timeout: int | None = None) -> dict[str, Any] | None:
"""A book by its calibre uuid, or None if CWA has no such book. `timeout` is for a page that waits."""
# The uuid can come from a request. Other characters could change the path.
if not re.fullmatch(r"[0-9A-Za-z-]+", book_uuid):
return None
@@ -165,7 +166,7 @@ class CwaClient:
return book
except calibre_db.DatabaseUnavailable as e:
logger.warning(f"Cannot read the calibre library database. Trying CWA: {e}")
response = self._get(f"ajax/book/{book_uuid}")
response = self._get(f"ajax/book/{book_uuid}", timeout=timeout or self.timeout)
if not response.text.strip():
return None
try:
+4 -1
View File
@@ -202,6 +202,9 @@ def list_stories() -> list[dict[str, Any]]:
return stories
# The seconds that the story page waits for CWA. A job waits for the full timeout of the client.
PAGE_TIMEOUT = 5
# Only raster images. An SVG cover could hold a script that runs on Bindery's origin.
COVER_TYPES = frozenset({"image/jpeg", "image/png", "image/gif", "image/webp"})
@@ -222,7 +225,7 @@ def story_details(story_id: int) -> dict[str, Any]:
book, library_error = None, None
if story["calibre_uuid"] and _can_match_books():
try:
book = cwa().get_book(story["calibre_uuid"])
book = cwa().get_book(story["calibre_uuid"], timeout=PAGE_TIMEOUT)
except CwaError as e:
library_error = str(e)
story["recheck_days"] = store.get_setting(story_id, "recheck_days")
+1
View File
@@ -99,6 +99,7 @@ def test_library_search_link_import_and_unlink(client, story, monkeypatch):
assert client.get("/api/library/search?q=Title").json[0]["tracked_as"] == "Story"
cwa.get_book.side_effect = CwaError("offline")
assert client.get(f"/api/stories/{sid}").json["library_error"] == "offline"
cwa.get_book.assert_called_with("book-7", timeout=5) # the page does not wait long for CWA
cwa.get_book.side_effect = None
cwa.get_book.return_value = None
assert client.post(f"/api/stories/{sid}/link", json={"uuid": "missing"}, headers=H).status_code == 404
+11
View File
@@ -75,6 +75,17 @@ def test_get_book(fake):
assert c.get_book("nope") is None
def test_get_book_can_wait_for_a_shorter_time(fake, monkeypatch):
c = client(fake)
get = c.session.get
timeouts = []
monkeypatch.setattr(c.session, "get", lambda url, **kw: timeouts.append(kw["timeout"]) or get(url, **kw))
c.get_book("uuid-2", timeout=5)
c.get_book("uuid-2")
c.search("Story")
assert timeouts == [5, 60, 60]
@pytest.mark.parametrize("book_uuid", ["../opds", "uuid-2?x=1", "uuid-2#x", "uuid%2F2", ""])
def test_get_book_refuses_a_uuid_that_changes_the_path(book_uuid):
# Nothing listens on this port, so a request would raise CwaError.