fix(performers): "Search more scenes" odpowiada od razu, wyszukiwanie mieli w tle
Zgloszenie 23c64a28: "Search more scenes button not working". Przycisk DZIALAL, tylko wygladal na zepsuty: endpoint byl synchroniczny i przy performerce z 871 scenami search po ~25 tubach trwal dluzej, niz telefon utrzymuje polaczenie. W logu zostaly 4 starty performer_driven bez ANI JEDNEJ domknietej odpowiedzi HTTP: kazdy tap konczyl sie "Refresh failed", uzytkownik klikal znowu i szukania mielily sie rownolegle, a wyniki i tak dopisywaly sie w tle, tylko bez zadnego sygnalu dla czlowieka. Endpoint startuje teraz watek i odpowiada od razu (`status="started"`). Rate-guard 60s bez zmian, `last_searched_at` idzie w gore PRZED startem watku, wiec chroni tez przed zdublowaniem samego startu. Watek ma wlasna obsluge bledow, bo nie ma juz requestu, ktoremu moglby oddac wyjatek. Mobile: przy `status="started"` komunikat "Searching... new finds appear within a minute or two" i odswiezenie listy po 60 s. Stare pola odpowiedzi zostaly, wiec klient sprzed OTA pokaze "0 new scenes", co jest znosne przez kilkuminutowe okno do publikacji. Zweryfikowane na produkcji: odpowiedz w 0,03 s, drugi strzal 429, watek tla skonczyl po 30 s i zalogowal wynik.
This commit is contained in:
parent
360af9fabc
commit
ccba96376c
4 changed files with 70 additions and 21 deletions
|
|
@ -9,6 +9,7 @@ Zwraca też scene_count żeby UI pokazywał "(123)" przy każdym tagu/performerz
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
|
@ -28,6 +29,8 @@ from app.models.studio import Studio
|
||||||
from app.models.tag import Tag
|
from app.models.tag import Tag
|
||||||
from app.verticals import VERTICALS
|
from app.verticals import VERTICALS
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(tags=["taxonomies"], dependencies=[Depends(require_api_key)])
|
router = APIRouter(tags=["taxonomies"], dependencies=[Depends(require_api_key)])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -440,6 +443,10 @@ class PerformerRefreshOut(BaseModel):
|
||||||
counters: dict[str, dict[str, int]]
|
counters: dict[str, dict[str, int]]
|
||||||
new_scenes: int
|
new_scenes: int
|
||||||
last_searched_at: str | None
|
last_searched_at: str | None
|
||||||
|
# "started" = wyszukiwanie ruszyło w tle i sceny dopiszą się za chwilę. Pole doszło
|
||||||
|
# przy przejściu na async (zgłoszenie 23c64a28); klient sprzed tej zmiany go nie
|
||||||
|
# czyta i pokaże "0 new scenes", co jest znośne przez okno do publikacji OTA.
|
||||||
|
status: str = "done"
|
||||||
|
|
||||||
|
|
||||||
class PerformerRescrapeOut(BaseModel):
|
class PerformerRescrapeOut(BaseModel):
|
||||||
|
|
@ -704,11 +711,18 @@ def refresh_performer(
|
||||||
performer_id: uuid.UUID,
|
performer_id: uuid.UUID,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
) -> PerformerRefreshOut:
|
) -> PerformerRefreshOut:
|
||||||
"""On-demand search across all tubes dla pojedynczego performera. Synchronous,
|
"""On-demand search across all tubes dla pojedynczego performera. Startuje w TLE
|
||||||
blokujemy aż search skończy. Mobile pokazuje spinner.
|
i odpowiada od razu (`status="started"`), a sceny dopisują się w miarę mielenia.
|
||||||
|
|
||||||
Rate-guard: jeśli refresh był < 60s temu, zwraca cached result (HTTP 429-style
|
Dlaczego nie synchronicznie, jak do 2026-08-26: search po ~25 tubach trwa dłużej,
|
||||||
detail). Continuous worker w tle też robi swoje, więc cache jest częsty.
|
niż klient jest gotów czekać, i przy rozbudowanych performerach telefon zrywał
|
||||||
|
połączenie. W logu zostawały starty `performer_driven` bez ANI JEDNEJ domkniętej
|
||||||
|
odpowiedzi (Cassie Del Isla, 871 scen, 4 zerwane requesty), użytkownik widział
|
||||||
|
"Refresh failed", klikał znowu i mieliliśmy to samo kilka razy równolegle
|
||||||
|
(zgłoszenie 23c64a28 "Search more scenes button not working").
|
||||||
|
|
||||||
|
Rate-guard bez zmian: refresh < 60s temu → 429. `last_searched_at` stawiamy PRZED
|
||||||
|
startem wątku, więc guard chroni też przed dublowaniem samego startu.
|
||||||
"""
|
"""
|
||||||
from datetime import UTC as _UTC, datetime as _dt, timedelta as _td
|
from datetime import UTC as _UTC, datetime as _dt, timedelta as _td
|
||||||
|
|
||||||
|
|
@ -724,27 +738,37 @@ def refresh_performer(
|
||||||
detail=f"recently searched {int(elapsed.total_seconds())}s ago, try in a bit",
|
detail=f"recently searched {int(elapsed.total_seconds())}s ago, try in a bit",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Lazy import, performer_driven ma ciężki connector tree
|
perf.last_searched_at = _dt.now(_UTC)
|
||||||
|
perf.search_run_count = (perf.search_run_count or 0) + 1
|
||||||
|
session.commit()
|
||||||
|
name = perf.canonical_name
|
||||||
|
searched_at = perf.last_searched_at.isoformat()
|
||||||
|
|
||||||
|
def _run_in_background() -> None:
|
||||||
|
# Własny import i własna obsługa błędów: wątek nie ma requestu, któremu mógłby
|
||||||
|
# oddać wyjątek, a `run_performer_driven` otwiera sesje DB samodzielnie.
|
||||||
|
try:
|
||||||
from app.scheduler.performer_driven import run_performer_driven
|
from app.scheduler.performer_driven import run_performer_driven
|
||||||
|
|
||||||
# NOTE: ten request blokuje request thread API na 30-90s (search across ~25 tubes).
|
|
||||||
# Akceptowalne dla self-hosted single-user. W razie potrzeby dorobić task queue.
|
|
||||||
counters_obj = run_performer_driven(
|
counters_obj = run_performer_driven(
|
||||||
performer_ids=[performer_id],
|
performer_ids=[performer_id],
|
||||||
top_n=0,
|
top_n=0,
|
||||||
per_performer_limit=200,
|
per_performer_limit=200,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update last_searched_at + counter (tak samo jak continuous worker)
|
|
||||||
perf.last_searched_at = _dt.now(_UTC)
|
|
||||||
perf.search_run_count = (perf.search_run_count or 0) + 1
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
new_total = sum(s.get("new", 0) for s in counters_obj.per_source.values())
|
new_total = sum(s.get("new", 0) for s in counters_obj.per_source.values())
|
||||||
|
log.info("refresh-performer %s (%s): tlo skonczone, new=%d", performer_id, name, new_total)
|
||||||
|
except Exception:
|
||||||
|
log.exception("refresh-performer %s (%s): tlo padlo", performer_id, name)
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
threading.Thread(target=_run_in_background, name=f"perf-refresh-{performer_id}", daemon=True).start()
|
||||||
|
|
||||||
return PerformerRefreshOut(
|
return PerformerRefreshOut(
|
||||||
performer_id=performer_id,
|
performer_id=performer_id,
|
||||||
canonical_name=perf.canonical_name,
|
canonical_name=name,
|
||||||
counters=counters_obj.per_source,
|
counters={},
|
||||||
new_scenes=new_total,
|
new_scenes=0,
|
||||||
last_searched_at=perf.last_searched_at.isoformat() if perf.last_searched_at else None,
|
last_searched_at=searched_at,
|
||||||
|
status="started",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,8 @@ export class GoonClient {
|
||||||
counters: Record<string, Record<string, number>>;
|
counters: Record<string, Record<string, number>>;
|
||||||
new_scenes: number;
|
new_scenes: number;
|
||||||
last_searched_at: string | null;
|
last_searched_at: string | null;
|
||||||
|
/** "started" = backend mieli w tle i sceny dopiszą się za chwilę (od 2026-08-26). */
|
||||||
|
status?: string;
|
||||||
}> {
|
}> {
|
||||||
return this.request(`/performers/${performerId}/refresh?pages=${pages}`, {
|
return this.request(`/performers/${performerId}/refresh?pages=${pages}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,14 @@ export type ChangelogEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
id: '2026-08-26a',
|
||||||
|
date: 'August 2026',
|
||||||
|
items: [
|
||||||
|
'"Search more scenes" on a performer now answers right away and keeps working in the background. It used to look broken on performers with lots of scenes: the search took longer than the phone keeps a connection open, so you saw "Refresh failed" even though the search actually ran. New finds show up within a minute or two.',
|
||||||
|
'Movie hosts that quietly moved to a new address (myvidplay, doply) play again. The app was being handed the old address and its pop-up blocker stopped the jump to the new one, which looked like a black screen.',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: '2026-08-24b',
|
id: '2026-08-24b',
|
||||||
date: 'August 2026',
|
date: 'August 2026',
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,21 @@ export function PerformerScenesScreen() {
|
||||||
const refreshMutation = useMutation({
|
const refreshMutation = useMutation({
|
||||||
mutationFn: () => client.refreshPerformer(id),
|
mutationFn: () => client.refreshPerformer(id),
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
// Od 2026-08-26 backend startuje wyszukiwanie w tle i odpowiada od razu.
|
||||||
|
// Poprzedni tryb (czekanie na wynik) trwał dłużej, niż telefon utrzymuje
|
||||||
|
// połączenie: przy rozbudowanych performerach każdy tap kończył się "Refresh
|
||||||
|
// failed", mimo że sceny i tak dopisywały się w tle (zgłoszenie 23c64a28).
|
||||||
|
// Listę odświeżamy z opóźnieniem, żeby pierwsze znaleziska były już zapisane.
|
||||||
|
if (data.status === 'started') {
|
||||||
|
Alert.alert(
|
||||||
|
'Searching…',
|
||||||
|
'Checking all sites for new scenes in the background. New finds appear within a minute or two; pull down to refresh.',
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['performer-scenes', id] });
|
||||||
|
}, 60_000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
queryClient.invalidateQueries({ queryKey: ['performer-scenes', id] });
|
queryClient.invalidateQueries({ queryKey: ['performer-scenes', id] });
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Refresh complete',
|
'Refresh complete',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue