Compare commits
8 Commits
d3cbb60c00
...
v1.5.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 46e8b2c9eb | |||
| ec24325036 | |||
| 4ec95d8ba9 | |||
| d52b9a55f4 | |||
| 12681720e9 | |||
| 084488a88c | |||
| 270da18543 | |||
| 163532b1ef |
@@ -1 +1 @@
|
||||
v1.5.2
|
||||
v1.5.5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aniworld-web",
|
||||
"version": "1.5.2",
|
||||
"version": "1.5.5",
|
||||
"description": "Aniworld Anime Download Manager - Web Frontend",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -134,7 +134,7 @@ class SerieList:
|
||||
"""Return all series that still contain missing episodes."""
|
||||
return [
|
||||
anime for anime in self.keyDict.values()
|
||||
if anime.episodeDict
|
||||
if getattr(anime, 'episodeDict', None)
|
||||
]
|
||||
|
||||
def get_missing_episodes(self) -> List[AnimeSeries]:
|
||||
|
||||
@@ -205,6 +205,8 @@ class AnimeSeries(Base, TimestampMixin):
|
||||
try:
|
||||
if self.episodes:
|
||||
for ep in self.episodes:
|
||||
if ep.is_downloaded:
|
||||
continue
|
||||
season = ep.season or 1
|
||||
if season not in episode_dict:
|
||||
episode_dict[season] = []
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
@@ -383,6 +384,12 @@ class AniworldLoader(Loader):
|
||||
"Direct stream download starting (type=%s)",
|
||||
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:
|
||||
for chunk in response.iter_content(chunk_size=1024 * 1024):
|
||||
if self._cancel_flag.is_set():
|
||||
@@ -391,7 +398,33 @@ class AniworldLoader(Loader):
|
||||
)
|
||||
return False
|
||||
if chunk:
|
||||
received += len(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
|
||||
except requests.RequestException as exc:
|
||||
logger.warning("Direct stream download failed: %s", exc)
|
||||
|
||||
@@ -162,17 +162,18 @@ class ImageLoadingService:
|
||||
for i in range(0, len(series_list), self.BATCH_SIZE):
|
||||
batch = series_list[i : i + self.BATCH_SIZE]
|
||||
|
||||
tasks = [
|
||||
self.load_series_images(
|
||||
# Process each series sequentially to avoid concurrent use of the
|
||||
# same AsyncSession (SQLAlchemy async sessions are not thread-safe
|
||||
# for concurrent operations). BATCH_SIZE still paces TMDB requests.
|
||||
results: List[Dict[str, Any] | Exception] = []
|
||||
for series in batch:
|
||||
result = await self.load_series_images(
|
||||
key=series["key"],
|
||||
folder=series["folder"],
|
||||
anime_directory=anime_directory,
|
||||
db=db,
|
||||
)
|
||||
for series in batch
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
results.append(result)
|
||||
|
||||
for series, result in zip(batch, results):
|
||||
if isinstance(result, Exception):
|
||||
|
||||
@@ -136,13 +136,16 @@ AniWorld.IndexSocketHandler = (function() {
|
||||
// Series events
|
||||
socket.on(WS_EVENTS.SERIES_UPDATED, function(data) {
|
||||
console.log('Series updated:', data);
|
||||
|
||||
// Use the data directly to update the series instead of full refresh
|
||||
if (data && data.data && AniWorld.SeriesManager && AniWorld.SeriesManager.updateSingleSeries) {
|
||||
AniWorld.SeriesManager.updateSingleSeries(data.data);
|
||||
|
||||
// NOTE: websocket-client.js strips the outer {type, data, ...} wrapper
|
||||
// before emitting, so `data` here is the inner series data object
|
||||
// (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 {
|
||||
// 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) {
|
||||
AniWorld.SeriesManager.loadSeries();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user