Revert "Backfill watchlist alternative titles lazily"
This reverts commit 8aa490adf8.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
-21
@@ -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)
|
||||
|
||||
+15
-78
@@ -674,8 +674,6 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
watchlistPerPage: 30,
|
||||
queuedCount: 0,
|
||||
watchlistCategory: "watching",
|
||||
altTitleTokens: {},
|
||||
altTitlePending: new Set(),
|
||||
thumbnailNonce: Date.now(),
|
||||
thumbnailWarmupTokens: {},
|
||||
thumbnailWarmupPending: new Set(),
|
||||
@@ -865,44 +863,6 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
.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"""<!doctype html>
|
||||
}
|
||||
}
|
||||
|
||||
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"""<!doctype html>
|
||||
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"""<!doctype html>
|
||||
applyThumbnail(card, item, false);
|
||||
}
|
||||
renderPager();
|
||||
window.setTimeout(() => hydrateAltTitlesOnce(items), 120);
|
||||
window.setTimeout(() => hydrateThumbnailsOnce(items), 150);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user