download the queue
This commit is contained in:
parent
eaf6bb9957
commit
33aeac0141
@ -1,62 +1,5 @@
|
||||
{
|
||||
"pending": [
|
||||
{
|
||||
"id": "467b39ac-48be-48c5-a04c-ce94b6f6c0d9",
|
||||
"serie_id": "highschool-dxd",
|
||||
"serie_name": "Highschool DxD",
|
||||
"episode": {
|
||||
"season": 3,
|
||||
"episode": 5,
|
||||
"title": null
|
||||
},
|
||||
"status": "pending",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.881950Z",
|
||||
"started_at": null,
|
||||
"completed_at": null,
|
||||
"progress": null,
|
||||
"error": null,
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
},
|
||||
{
|
||||
"id": "4c0b9bd0-52ad-4833-baf7-19e2c920f94f",
|
||||
"serie_id": "highschool-dxd",
|
||||
"serie_name": "Highschool DxD",
|
||||
"episode": {
|
||||
"season": 3,
|
||||
"episode": 6,
|
||||
"title": null
|
||||
},
|
||||
"status": "pending",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.881979Z",
|
||||
"started_at": null,
|
||||
"completed_at": null,
|
||||
"progress": null,
|
||||
"error": null,
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
},
|
||||
{
|
||||
"id": "fabf8a46-6588-4dff-959d-de5e2bf48e29",
|
||||
"serie_id": "highschool-dxd",
|
||||
"serie_name": "Highschool DxD",
|
||||
"episode": {
|
||||
"season": 3,
|
||||
"episode": 7,
|
||||
"title": null
|
||||
},
|
||||
"status": "pending",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.882013Z",
|
||||
"started_at": null,
|
||||
"completed_at": null,
|
||||
"progress": null,
|
||||
"error": null,
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
},
|
||||
{
|
||||
"id": "7a6a40ce-c5d9-4e7e-8f71-a08141e9ce6f",
|
||||
"serie_id": "highschool-dxd",
|
||||
@ -1500,10 +1443,7 @@
|
||||
"error": "Download failed",
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
}
|
||||
],
|
||||
"active": [],
|
||||
"failed": [
|
||||
},
|
||||
{
|
||||
"id": "55e21a17-ac91-4dfe-ba96-df5c4261a9a5",
|
||||
"serie_id": "highschool-dxd",
|
||||
@ -1513,7 +1453,7 @@
|
||||
"episode": 4,
|
||||
"title": null
|
||||
},
|
||||
"status": "failed",
|
||||
"status": "pending",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.881920Z",
|
||||
"started_at": "2025-11-01T15:04:26.488261Z",
|
||||
@ -1524,5 +1464,65 @@
|
||||
"source_url": null
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-11-01T15:04:43.998876+00:00"
|
||||
"active": [],
|
||||
"failed": [
|
||||
{
|
||||
"id": "467b39ac-48be-48c5-a04c-ce94b6f6c0d9",
|
||||
"serie_id": "highschool-dxd",
|
||||
"serie_name": "Highschool DxD",
|
||||
"episode": {
|
||||
"season": 3,
|
||||
"episode": 5,
|
||||
"title": null
|
||||
},
|
||||
"status": "failed",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.881950Z",
|
||||
"started_at": "2025-11-01T15:12:21.534113Z",
|
||||
"completed_at": "2025-11-01T15:12:30.481928Z",
|
||||
"progress": null,
|
||||
"error": "Download failed",
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
},
|
||||
{
|
||||
"id": "4c0b9bd0-52ad-4833-baf7-19e2c920f94f",
|
||||
"serie_id": "highschool-dxd",
|
||||
"serie_name": "Highschool DxD",
|
||||
"episode": {
|
||||
"season": 3,
|
||||
"episode": 6,
|
||||
"title": null
|
||||
},
|
||||
"status": "failed",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.881979Z",
|
||||
"started_at": "2025-11-01T15:12:31.484945Z",
|
||||
"completed_at": "2025-11-01T15:12:39.118803Z",
|
||||
"progress": null,
|
||||
"error": "Download failed",
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
},
|
||||
{
|
||||
"id": "fabf8a46-6588-4dff-959d-de5e2bf48e29",
|
||||
"serie_id": "highschool-dxd",
|
||||
"serie_name": "Highschool DxD",
|
||||
"episode": {
|
||||
"season": 3,
|
||||
"episode": 7,
|
||||
"title": null
|
||||
},
|
||||
"status": "failed",
|
||||
"priority": "normal",
|
||||
"added_at": "2025-11-01T14:30:56.882013Z",
|
||||
"started_at": "2025-11-01T15:12:40.122525Z",
|
||||
"completed_at": "2025-11-01T15:12:46.419411Z",
|
||||
"progress": null,
|
||||
"error": "Download failed",
|
||||
"retry_count": 0,
|
||||
"source_url": null
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-11-01T15:12:46.420241+00:00"
|
||||
}
|
||||
@ -280,25 +280,29 @@ async def start_queue(
|
||||
_: dict = Depends(require_auth),
|
||||
download_service: DownloadService = Depends(get_download_service),
|
||||
):
|
||||
"""Start the next download from pending queue.
|
||||
"""Start automatic queue processing.
|
||||
|
||||
Manually starts the first pending download in the queue. Only one download
|
||||
can be active at a time. If the queue is empty or a download is already
|
||||
active, an error is returned.
|
||||
Starts processing all pending downloads sequentially, one at a time.
|
||||
The queue will continue processing until all items are complete or
|
||||
the queue is manually stopped. Processing continues even if the browser
|
||||
is closed.
|
||||
|
||||
Only one download can be active at a time. If a download is already
|
||||
active or queue processing is running, an error is returned.
|
||||
|
||||
Requires authentication.
|
||||
|
||||
Returns:
|
||||
dict: Status message with started item ID
|
||||
dict: Status message confirming queue processing started
|
||||
|
||||
Raises:
|
||||
HTTPException: 401 if not authenticated, 400 if queue is empty or
|
||||
download already active, 500 on service error
|
||||
processing already active, 500 on service error
|
||||
"""
|
||||
try:
|
||||
item_id = await download_service.start_next_download()
|
||||
result = await download_service.start_queue_processing()
|
||||
|
||||
if item_id is None:
|
||||
if result is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No pending downloads in queue",
|
||||
@ -306,8 +310,7 @@ async def start_queue(
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Download started",
|
||||
"item_id": item_id,
|
||||
"message": "Queue processing started",
|
||||
}
|
||||
|
||||
except DownloadServiceError as e:
|
||||
@ -320,7 +323,7 @@ async def start_queue(
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to start download: {str(e)}",
|
||||
detail=f"Failed to start queue processing: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -356,20 +356,24 @@ class DownloadService:
|
||||
f"Failed to remove items: {str(e)}"
|
||||
) from e
|
||||
|
||||
async def start_next_download(self) -> Optional[str]:
|
||||
"""Manually start the next download from pending queue.
|
||||
async def start_queue_processing(self) -> Optional[str]:
|
||||
"""Start automatic queue processing of all pending downloads.
|
||||
|
||||
This will process all pending downloads one by one until the queue
|
||||
is empty or stopped. The processing continues even if the browser
|
||||
is closed.
|
||||
|
||||
Returns:
|
||||
Item ID of started download, or None if queue is empty
|
||||
Item ID of first started download, or None if queue is empty
|
||||
|
||||
Raises:
|
||||
DownloadServiceError: If a download is already active
|
||||
DownloadServiceError: If queue processing is already active
|
||||
"""
|
||||
try:
|
||||
# Check if download already active
|
||||
if self._active_download:
|
||||
raise DownloadServiceError(
|
||||
"A download is already in progress"
|
||||
"Queue processing is already active"
|
||||
)
|
||||
|
||||
# Check if queue is empty
|
||||
@ -377,42 +381,98 @@ class DownloadService:
|
||||
logger.info("No pending downloads to start")
|
||||
return None
|
||||
|
||||
# Get first item from queue
|
||||
item = self._pending_queue.popleft()
|
||||
del self._pending_items_by_id[item.id]
|
||||
|
||||
# Mark queue as running
|
||||
self._is_stopped = False
|
||||
|
||||
# Start download in background
|
||||
asyncio.create_task(self._process_download(item))
|
||||
# Start queue processing in background
|
||||
asyncio.create_task(self._process_queue())
|
||||
|
||||
logger.info(
|
||||
"Started download manually",
|
||||
item_id=item.id,
|
||||
serie=item.serie_name
|
||||
)
|
||||
logger.info("Queue processing started")
|
||||
|
||||
# Broadcast queue status update
|
||||
return "queue_started"
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to start queue processing", error=str(e))
|
||||
raise DownloadServiceError(
|
||||
f"Failed to start queue processing: {str(e)}"
|
||||
) from e
|
||||
|
||||
async def _process_queue(self) -> None:
|
||||
"""Process all items in the queue sequentially.
|
||||
|
||||
This runs continuously until the queue is empty or stopped.
|
||||
Each download is processed one at a time, and the next one starts
|
||||
automatically after the previous one completes.
|
||||
"""
|
||||
logger.info("Queue processor started")
|
||||
|
||||
while not self._is_stopped and len(self._pending_queue) > 0:
|
||||
try:
|
||||
# Get next item from queue
|
||||
item = self._pending_queue.popleft()
|
||||
del self._pending_items_by_id[item.id]
|
||||
|
||||
logger.info(
|
||||
"Processing next item from queue",
|
||||
item_id=item.id,
|
||||
serie=item.serie_name,
|
||||
remaining=len(self._pending_queue)
|
||||
)
|
||||
|
||||
# Broadcast queue status update
|
||||
queue_status = await self.get_queue_status()
|
||||
await self._broadcast_update(
|
||||
"download_started",
|
||||
{
|
||||
"item_id": item.id,
|
||||
"serie_name": item.serie_name,
|
||||
"season": item.episode.season,
|
||||
"episode": item.episode.episode,
|
||||
"queue_status": queue_status.model_dump(mode="json"),
|
||||
},
|
||||
)
|
||||
|
||||
# Process the download (this will wait until complete)
|
||||
await self._process_download(item)
|
||||
|
||||
# Small delay between downloads
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error in queue processing loop",
|
||||
error=str(e),
|
||||
exc_info=True
|
||||
)
|
||||
# Continue with next item even if one fails
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Queue processing completed
|
||||
self._is_stopped = True
|
||||
|
||||
if len(self._pending_queue) == 0:
|
||||
logger.info("Queue processing completed - all items processed")
|
||||
queue_status = await self.get_queue_status()
|
||||
await self._broadcast_update(
|
||||
"download_started",
|
||||
"queue_completed",
|
||||
{
|
||||
"item_id": item.id,
|
||||
"serie_name": item.serie_name,
|
||||
"season": item.episode.season,
|
||||
"episode": item.episode.episode,
|
||||
"message": "All downloads completed",
|
||||
"queue_status": queue_status.model_dump(mode="json"),
|
||||
},
|
||||
)
|
||||
|
||||
return item.id
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to start download", error=str(e))
|
||||
raise DownloadServiceError(
|
||||
f"Failed to start download: {str(e)}"
|
||||
) from e
|
||||
else:
|
||||
logger.info("Queue processing stopped by user")
|
||||
|
||||
async def start_next_download(self) -> Optional[str]:
|
||||
"""Legacy method - redirects to start_queue_processing.
|
||||
|
||||
Returns:
|
||||
Item ID of started download, or None if queue is empty
|
||||
|
||||
Raises:
|
||||
DownloadServiceError: If a download is already active
|
||||
"""
|
||||
return await self.start_queue_processing()
|
||||
|
||||
async def stop_downloads(self) -> None:
|
||||
"""Stop processing new downloads from queue.
|
||||
|
||||
@ -97,6 +97,11 @@ class QueueManager {
|
||||
this.showToast('All downloads completed!', 'success');
|
||||
this.loadQueueData(); // Refresh data
|
||||
});
|
||||
|
||||
this.socket.on('queue_completed', () => {
|
||||
this.showToast('All downloads completed!', 'success');
|
||||
this.loadQueueData(); // Refresh data
|
||||
});
|
||||
|
||||
this.socket.on('download_stop_requested', () => {
|
||||
this.showToast('Stopping downloads...', 'info');
|
||||
@ -592,7 +597,7 @@ class QueueManager {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
this.showToast('Download started', 'success');
|
||||
this.showToast('Queue processing started - all items will download automatically', 'success');
|
||||
|
||||
// Update UI
|
||||
document.getElementById('start-queue-btn').style.display = 'none';
|
||||
@ -601,11 +606,11 @@ class QueueManager {
|
||||
|
||||
this.loadQueueData(); // Refresh display
|
||||
} else {
|
||||
this.showToast(`Failed to start download: ${data.message || 'Unknown error'}`, 'error');
|
||||
this.showToast(`Failed to start queue: ${data.message || 'Unknown error'}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting download:', error);
|
||||
this.showToast('Failed to start download', 'error');
|
||||
console.error('Error starting queue:', error);
|
||||
this.showToast('Failed to start queue processing', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user