Commit graph

4 commits

Author SHA1 Message Date
vrubelroman
10c16ba2cb Fix DownloadButton reverting to "Скачать" after completion
The button disabled its own status polling once a download reached a
terminal state and fell back to the video prop it was first rendered with.
On the video detail page that prop only refreshes on a full page reload
(only ['feed'] was invalidated, not ['video', id]), so the button showed
"Скачать" again right after a real completion until the user refreshed.

Keep the polling query enabled permanently once a download starts tracking
(refetchInterval alone already stops the ticking on a terminal status) and
use its cache as the source of truth instead of the prop, so the button
reflects the fetched status directly rather than depending on an unrelated
query being invalidated and refetched in time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 19:37:33 +00:00
vrubelroman
e333296170 Fix download status flapping: only the 'completed' event is terminal
Production logs showed a job oscillating downloading -> completed ->
downloading -> completed -> postprocessing -> completed. Cause: yt-dlp
reports status='finished' via progress-hook ticks once per stream when
downloading separate video+audio for muxing (video lands, audio is still
in flight), and MeTube forwards that through 'updated' Socket.IO events
too -- we were mapping any status='finished' to our "completed", regardless
of which event carried it.

Only the dedicated 'completed' event (and the 'done' bucket of MeTube's
/history, for startup reconciliation) is now treated as authoritative for
terminal status; a transient 'finished' arriving via 'added'/'updated'
(or history's queue/pending) is a no-op for status, matching the
progress_percent update it also carries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 19:09:18 +00:00
vrubelroman
fe16c08daa Implement Phase 6: MeTube download integration
- MeTubeClient encapsulates all MeTube HTTP/Socket.IO calls (verified against
  the real MeTube source: /add returns no job id, GET /history gives a queue
  snapshot for reconciliation, filenames arrive already relative, percent is
  a 0-100 float)
- download_jobs table + service: request/dedup active downloads, apply live
  Socket.IO events (added/updated/completed/canceled/cleared) matched by
  canonical YouTube URL, safe relative-path -> public media URL construction
- Reconciliation on startup against MeTube's live queue/done state (section 19):
  non-terminal jobs recovered where possible, else marked "unknown"; already
  completed jobs are left untouched
- POST/GET /api/videos/{id}/download(-status), recheck-local; feed/video
  detail now report real local availability instead of a stub
- Frontend: download button with live status polling (queued/downloading %/
  postprocessing/completed/failed+retry), local <video> playback with
  YouTube fallback on playback error
- health.py now delegates to MeTubeClient (single place for MeTube calls)

26 new backend tests (63 total). Verified live: Socket.IO connects
successfully to the real MeTube instance on deploy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 18:56:17 +00:00
vrubelroman
0ed20bb838 Implement Phases 1-5: skeleton, OAuth, categories, video sync/feed, playback
- FastAPI + PostgreSQL + Alembic + React/Vite skeleton, Docker Compose, healthcheck
- Google OAuth (single allowed account), encrypted refresh token storage
- Subscriptions sync with pagination, uploads playlist batch fetch
- Categories CRUD, many-to-many channel assignment, category filtering
- Video sync (playlistItems + videos.list batching), cached feed with cursor
  pagination, background scheduler (APScheduler)
- Video detail page with YouTube embed player
- SPA fallback routing, optimistic UI updates, client-side query caching

40 backend tests covering OAuth allow-list, sync idempotency, cascade deletes,
cursor pagination, and category filtering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 18:44:30 +00:00