feat: Android download page, analysis retry and a collapsible search
ci / api / api (push) Successful in 37s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 46s
ci / db / db (push) Failing after 14s
ci / recommender / recommender (push) Successful in 51s
ci / ts / ts (packages + web) (push) Successful in 57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m2s

- /android is a public page with the latest APK, the server address to copy
  and three install steps; linked from the sign-in screen and the profile menu
- failed audio analysis can be retried from the library page, which also shows
  the most common error; the analyzer no longer burns attempts on files that
  are missing because the disk is not mounted
- the top bar search folds into a round button and slides open on click, "/"
  or Ctrl+K

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 23:55:51 +03:00
1 parent 2d49cb3f19
commit fda6ed48ab
27 files changed
+757 -277

No files matched your search

@@ -20,6 +20,7 @@ log = logging.getLogger(__name__)
MAX_ATTEMPTS = 3
MAX_DURATION_S = 15 * 60
IDLE_SLEEP_S = 10.0
MISSING_SLEEP_S = 60.0 # файлы пропали целиком — ждём, пока смонтируют диск
@dataclass(frozen=True)
@@ -168,6 +169,18 @@ class Worker:
)
conn.commit()
def postpone(self, conn: psycopg.Connection[Any], track_ids: list[str]) -> None:
"""Файла нет на диске (не смонтирован?) — обратно в очередь, попытка не засчитывается."""
conn.execute(
"""
UPDATE analysis_jobs SET status = 'queued', attempts = greatest(attempts - 1, 0),
error = 'file not found — is the disk mounted?', updated_at = now()
WHERE track_id = ANY(%s::uuid[])
""",
(track_ids,),
)
conn.commit()
def requeue_stale(self, conn: psycopg.Connection[Any]) -> None:
"""Задачи, брошенные упавшим воркером, и признаки старой версии анализатора — снова в очередь."""
conn.execute(
@@ -189,6 +202,16 @@ class Worker:
self.requeue_stale(conn)
while True:
jobs = self.claim(conn, self.workers * 2)
missing = [j for j in jobs if not Path(j.path).exists()]
if missing:
self.postpone(conn, [j.track_id for j in missing])
jobs = [j for j in jobs if j not in missing]
if not jobs:
log.warning(
"analysis: %d files not found, waiting — is the disk mounted?", len(missing)
)
time.sleep(MISSING_SLEEP_S)
continue
if not jobs:
time.sleep(IDLE_SLEEP_S)
continue