From 5b0f4043edca7450c5c5415ff727dd2639e797ca Mon Sep 17 00:00:00 2001 From: Dymas Date: Sat, 23 May 2026 19:14:34 +0200 Subject: [PATCH] Revert "Backfill watchlist alternative titles lazily" This reverts commit 8aa490adf8287afe4651c7cc7b5ff99f3f0f090c. --- CHANGELOG.md | 4 -- README.md | 3 +- VERSION | 2 +- app.py | 48 ------------------------ app_support.py | 2 +- http_handler.py | 10 ----- test_app.py | 21 ----------- watchlist_page.py | 93 ++++++++--------------------------------------- 8 files changed, 18 insertions(+), 165 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67950bb..cf2d538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## 0.37.2 - 2026-05-23 - -- Added lazy watchlist alt-title backfill for visible cards, so older entries can fetch and show the new `Alternative titles` button without requiring a manual refresh first. - ## 0.37.1 - 2026-05-23 - Fixed AllManga alternative title parsing to read the visible `span.altnames` markup and changed watchlist cards to reveal those titles only when the new `Alternative titles` button is pressed. diff --git a/README.md b/README.md index bdefc29..c33bbd1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Small local web UI for a system-wide `ani-cli` install. -Current version: `0.37.2` +Current version: `0.37.1` ## Project Layout @@ -244,7 +244,6 @@ Title behavior: - Watchlist refreshes also try to pull alternative English and synonym titles from the AllManga show page. - Watchlist cards show an `Alternative titles` button when names are available. - Pressing that button expands the stored alternative titles on demand. -- Visible watchlist cards also try to backfill missing alternative titles automatically, so older entries do not need a manual refresh just to get the button. - Missing covers are warmed in small batches. - Failed lookups back off before retrying. - You can force `Reload` or use `Upload` when no thumbnail exists. diff --git a/VERSION b/VERSION index 8570a3a..9b1bb85 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.37.2 +0.37.1 diff --git a/app.py b/app.py index 599bc2f..3d869e7 100644 --- a/app.py +++ b/app.py @@ -1875,54 +1875,6 @@ class WatchlistStore: continue return {"items": updated} - def ensure_alt_titles(self, show_id, force=False): - item = self.get(show_id) - if not force and (item.get("alt_titles") or item.get("english_title")): - return item - parsed = fetch_allmanga_alternative_titles(item["show_id"], item.get("title"), timeout=self._refresh_request_timeout()) - updated_at = now_iso() - alt_titles_json = json.dumps(unique_title_values(parsed.get("alt_titles") or [], item.get("title"))) - with self.lock, self._connect() as conn: - conn.execute( - """ - UPDATE watchlist - SET english_title = ?, alt_titles_json = ?, updated_at = ? - WHERE show_id = ? - """, - ( - parsed.get("english_title") or None, - alt_titles_json, - updated_at, - item["show_id"], - ), - ) - return self.get(show_id) - - def ensure_alt_titles_many(self, show_ids, limit=6, force=False): - normalized = [] - for show_id in show_ids: - text = str(show_id or "").strip() - if text and text not in normalized: - normalized.append(text) - updated = [] - for show_id in normalized[: max(1, min(int(limit or 6), 10))]: - try: - item = self.get(show_id) - except KeyError: - continue - if not force and (item.get("alt_titles") or item.get("english_title")): - updated.append(item) - continue - try: - updated.append(self.ensure_alt_titles(show_id, force=force)) - except Exception as exc: - debug_log("watchlist.alt_titles.ensure_failed", show_id=show_id, error=exc) - try: - updated.append(self.get(show_id)) - except KeyError: - continue - return {"items": updated} - def add_to_watchlist(payload): ensure_runtime() diff --git a/app_support.py b/app_support.py index ed9eef5..ece506c 100644 --- a/app_support.py +++ b/app_support.py @@ -19,7 +19,7 @@ from uuid import uuid4 PROJECT_ROOT = Path(__file__).resolve().parent ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" APP_NAME = "ani-cli-web" -VERSION = "0.37.2" +VERSION = "0.37.1" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/http_handler.py b/http_handler.py index d624acf..d600c88 100644 --- a/http_handler.py +++ b/http_handler.py @@ -499,16 +499,6 @@ class Handler(BaseHTTPRequestHandler): force=Handler.optional_bool_field(self, payload, "force", default=False), ) ) - elif parsed.path == "/api/watchlist/ensure-alt-titles": - runtime = Handler._runtime(self) - payload = Handler.require_json_object(self, self.body_json()) - self.json( - runtime["watchlist"].ensure_alt_titles_many( - Handler.optional_list_field(self, payload, "show_ids") or [], - limit=payload.get("limit", 6), - force=Handler.optional_bool_field(self, payload, "force", default=False), - ) - ) elif parsed.path == "/api/watchlist/update-status": Handler._runtime(self) payload = Handler.require_fields(self, self.body_json(), "show_id") diff --git a/test_app.py b/test_app.py index 2ec4bc5..58d72fd 100644 --- a/test_app.py +++ b/test_app.py @@ -866,16 +866,6 @@ class WatchlistCompletionTests(unittest.TestCase): self.assertEqual(item["english_title"], "Example Show English") self.assertEqual(item["display_alt_titles"], ["Example Show English", "Sample Alias"]) - def test_ensure_alt_titles_backfills_existing_item(self): - self.seed_watchlist_item() - with mock.patch.object( - APP, "fetch_allmanga_alternative_titles", return_value={"english_title": "Example Show English", "alt_titles": ["Example Show English", "Sample Alias"]} - ): - item = APP.WATCHLIST.ensure_alt_titles("show-1") - - self.assertEqual(item["english_title"], "Example Show English") - self.assertEqual(item["display_alt_titles"], ["Example Show English", "Sample Alias"]) - def test_list_filters_by_category_and_reports_category_counts(self): self.seed_watchlist_item(show_id="show-1", title="Watching Show", category="watching", status="queued") self.seed_watchlist_item(show_id="show-2", title="Finished Show", category="finished", downloaded=True) @@ -1671,15 +1661,6 @@ class HandlerRouteTests(unittest.TestCase): self.assertEqual(handler.json_payload, payload) self.assertEqual(handler.json_status, HTTPStatus.CREATED) - def test_watchlist_ensure_alt_titles_post_returns_items(self): - body = b'{"show_ids":["show-1"],"limit":1}' - handler = DummyHandler("/api/watchlist/ensure-alt-titles", body=body, content_length=len(body)) - payload = {"items": [{"show_id": "show-1", "display_alt_titles": ["Example Show English"]}]} - with mock.patch.object(APP.WATCHLIST, "ensure_alt_titles_many", return_value=payload): - APP.Handler.do_POST(handler) - self.assertEqual(handler.json_payload, payload) - self.assertEqual(handler.json_status, HTTPStatus.OK) - def test_generic_handler_exception_skips_traceback_when_debug_disabled(self): handler = DummyHandler("/api/config") with mock.patch.object(http_handler, "debug_enabled", return_value=False), mock.patch.object( @@ -1769,8 +1750,6 @@ class TemplateHelperTests(unittest.TestCase): self.assertIn('class="alt-titles"', APP.WATCHLIST_HTML) self.assertIn('Alternative titles', APP.WATCHLIST_HTML) self.assertIn('altTitlesNode.classList.toggle("visible")', APP.WATCHLIST_HTML) - self.assertIn('api("/api/watchlist/ensure-alt-titles"', APP.WATCHLIST_HTML) - self.assertIn("altTitlePending: new Set()", APP.WATCHLIST_HTML) def test_watchlist_thumbnail_warmup_skips_repeated_attempts(self): self.assertIn("thumbnailWarmupPending: new Set()", APP.WATCHLIST_HTML) diff --git a/watchlist_page.py b/watchlist_page.py index da7717a..73cdccf 100644 --- a/watchlist_page.py +++ b/watchlist_page.py @@ -674,8 +674,6 @@ WATCHLIST_HTML = r""" watchlistPerPage: 30, queuedCount: 0, watchlistCategory: "watching", - altTitleTokens: {}, - altTitlePending: new Set(), thumbnailNonce: Date.now(), thumbnailWarmupTokens: {}, thumbnailWarmupPending: new Set(), @@ -865,44 +863,6 @@ WATCHLIST_HTML = r""" .find((card) => card.dataset.showId === showId) || null; } - function altTitleToken(item) { - return [ - item.english_title || "", - ...(item.display_alt_titles || []), - ].join("|"); - } - - function applyAltTitles(card, item) { - if (!card) return; - const altTitlesNode = card.querySelector(".alt-titles"); - const oldToggle = card.querySelector(".alt-toggle"); - if (!altTitlesNode || !oldToggle) return; - const altTitles = item.display_alt_titles || []; - const wasVisible = altTitlesNode.classList.contains("visible"); - altTitlesNode.classList.remove("visible"); - altTitlesNode.textContent = ""; - altTitlesNode.hidden = true; - const newToggle = oldToggle.cloneNode(true); - oldToggle.replaceWith(newToggle); - newToggle.hidden = true; - newToggle.textContent = "Alternative titles"; - newToggle.setAttribute("aria-expanded", "false"); - if (!altTitles.length) return; - altTitlesNode.hidden = false; - altTitlesNode.textContent = altTitles.join(" · "); - newToggle.hidden = false; - if (wasVisible) { - altTitlesNode.classList.add("visible"); - newToggle.textContent = "Hide alternative titles"; - newToggle.setAttribute("aria-expanded", "true"); - } - newToggle.addEventListener("click", () => { - const visible = altTitlesNode.classList.toggle("visible"); - newToggle.setAttribute("aria-expanded", visible ? "true" : "false"); - newToggle.textContent = visible ? "Hide alternative titles" : "Alternative titles"; - }); - } - function thumbnailWarmupToken(item) { return [ item.thumbnail_ready ? "1" : "0", @@ -949,42 +909,6 @@ WATCHLIST_HTML = r""" } } - async function hydrateAltTitlesOnce(items) { - const missing = []; - for (const item of items) { - const token = altTitleToken(item); - if ((item.display_alt_titles || []).length) { - state.altTitlePending.delete(item.show_id); - state.altTitleTokens[item.show_id] = token; - continue; - } - if (state.altTitlePending.has(item.show_id)) continue; - if (state.altTitleTokens[item.show_id] === token) continue; - state.altTitlePending.add(item.show_id); - state.altTitleTokens[item.show_id] = token; - missing.push(item.show_id); - if (missing.length >= 6) break; - } - if (!missing.length) return; - try { - const data = await api("/api/watchlist/ensure-alt-titles", { - method: "POST", - body: JSON.stringify({ show_ids: missing, limit: 6, force: false }) - }); - for (const item of data.items || []) { - state.altTitleTokens[item.show_id] = altTitleToken(item); - const card = findCardByShowId(item.show_id); - if (!card) continue; - applyAltTitles(card, item); - } - } catch (_error) { - } finally { - for (const showId of missing) { - state.altTitlePending.delete(showId); - } - } - } - async function reloadWatchlistThumbnail(showId, title) { const card = findCardByShowId(showId); if (card) setThumbnailReloading(card, true); @@ -1117,7 +1041,21 @@ WATCHLIST_HTML = r""" titleLink.rel = "noopener noreferrer"; titleLink.textContent = item.title; titleNode.replaceChildren(titleLink); - applyAltTitles(card, item); + const altToggle = card.querySelector(".alt-toggle"); + const altTitlesNode = card.querySelector(".alt-titles"); + const altTitles = item.display_alt_titles || []; + if (altTitles.length) { + altToggle.hidden = false; + altTitlesNode.hidden = false; + altTitlesNode.textContent = altTitles.join(" · "); + altToggle.setAttribute("aria-expanded", "false"); + altToggle.addEventListener("click", () => { + const visible = altTitlesNode.classList.toggle("visible"); + altToggle.setAttribute("aria-expanded", visible ? "true" : "false"); + altToggle.textContent = visible ? "Hide alternative titles" : "Alternative titles"; + }); + altToggle.textContent = "Alternative titles"; + } const finishCheck = card.querySelector(".finish-check"); if (finishCheck && item.finished_badge) { finishCheck.title = item.finished_badge === "downloaded" @@ -1176,7 +1114,6 @@ WATCHLIST_HTML = r""" applyThumbnail(card, item, false); } renderPager(); - window.setTimeout(() => hydrateAltTitlesOnce(items), 120); window.setTimeout(() => hydrateThumbnailsOnce(items), 150); }