Compare commits

...

2 Commits

Author SHA1 Message Date
270da18543 fix: emit download_progress events during direct stream downloads
When _try_direct_stream() succeeded, it streamed chunks directly via
requests.get() without firing any progress events. This caused the WebSocket
frontend to only see 'started' (0%) and 'completed' (100%) — no incremental
updates.

Add per-chunk progress events to _try_direct_stream that mirror yt-dlp's
hook format (downloaded_bytes, total_bytes, speed, eta, status), emitted at
~1% intervals. These flow through the existing download_progress handler chain
(SeriesApp → anime_service → progress_service → WebSocket → frontend).
2026-07-31 08:42:59 +02:00
163532b1ef fix: use data.key instead of data.data in series_updated handler
The websocket-client strips the outer {type, data, ...} wrapper before
emitting to handlers, so handlers receive the inner series data object
directly (e.g. {key, name, missing_episodes}) — not {type, data: {...}}.

The series_updated handler was checking data.data which always failed,
causing every update to fall back to a full loadSeries() call instead of
calling updateSingleSeries() directly. This prevented the missing
episodes count from updating in real-time after a download completed.

Fix: check data.key directly and pass data (not data.data) to
updateSingleSeries().
2026-07-31 08:37:44 +02:00
3 changed files with 44 additions and 5 deletions

View File

@@ -5,6 +5,7 @@ import logging
import os import os
import re import re
import shutil import shutil
import time
import threading import threading
from pathlib import Path from pathlib import Path
from urllib.parse import quote from urllib.parse import quote
@@ -383,6 +384,12 @@ class AniworldLoader(Loader):
"Direct stream download starting (type=%s)", "Direct stream download starting (type=%s)",
content_type content_type
) )
total_size = int(response.headers.get(
"Content-Length", 0
))
received = 0
last_emit = 0
start_time = time.monotonic()
with open(output_path, "wb") as fh: with open(output_path, "wb") as fh:
for chunk in response.iter_content(chunk_size=1024 * 1024): for chunk in response.iter_content(chunk_size=1024 * 1024):
if self._cancel_flag.is_set(): if self._cancel_flag.is_set():
@@ -391,7 +398,33 @@ class AniworldLoader(Loader):
) )
return False return False
if chunk: if chunk:
received += len(chunk)
fh.write(chunk) fh.write(chunk)
# Emit progress events at ~1% intervals
if total_size > 0:
pct = (received / total_size) * 100
if pct - last_emit >= 1.0 or received == total_size:
elapsed = time.monotonic() - start_time
speed_bps = (
received / elapsed
if elapsed > 0 else 0
)
eta = (
int((total_size - received) / speed_bps)
if speed_bps > 0 else None
)
self.events.download_progress({
"downloaded_bytes": received,
"total_bytes": total_size,
"speed": speed_bps,
"eta": eta,
"status": (
"finished"
if received >= total_size
else "downloading"
),
})
last_emit = pct
return True return True
except requests.RequestException as exc: except requests.RequestException as exc:
logger.warning("Direct stream download failed: %s", exc) logger.warning("Direct stream download failed: %s", exc)

View File

@@ -136,13 +136,16 @@ AniWorld.IndexSocketHandler = (function() {
// Series events // Series events
socket.on(WS_EVENTS.SERIES_UPDATED, function(data) { socket.on(WS_EVENTS.SERIES_UPDATED, function(data) {
console.log('Series updated:', data); console.log('Series updated:', data);
// Use the data directly to update the series instead of full refresh // NOTE: websocket-client.js strips the outer {type, data, ...} wrapper
if (data && data.data && AniWorld.SeriesManager && AniWorld.SeriesManager.updateSingleSeries) { // before emitting, so `data` here is the inner series data object
AniWorld.SeriesManager.updateSingleSeries(data.data); // (e.g. {key, name, missing_episodes, ...}) — NOT {type, data, ...}.
// AniWorld.SeriesManager.updateSingleSeries() expects this flat object.
if (data && data.key && AniWorld.SeriesManager && AniWorld.SeriesManager.updateSingleSeries) {
AniWorld.SeriesManager.updateSingleSeries(data);
} else { } else {
// Fallback to full reload if data is incomplete // Fallback to full reload if data is incomplete
console.warn('Incomplete series update data, falling back to full reload'); console.warn('Incomplete series update data, falling back to full reload', data);
if (AniWorld.SeriesManager && AniWorld.SeriesManager.loadSeries) { if (AniWorld.SeriesManager && AniWorld.SeriesManager.loadSeries) {
AniWorld.SeriesManager.loadSeries(); AniWorld.SeriesManager.loadSeries();
} }

3
uv.lock generated Normal file
View File

@@ -0,0 +1,3 @@
version = 1
revision = 3
requires-python = ">=3.12"