added remove all item from queue

This commit is contained in:
Lukas 2025-11-01 18:09:23 +01:00
parent 4dba4db344
commit 18faf3fe91
8 changed files with 155 additions and 1473 deletions

File diff suppressed because it is too large Load Diff

View File

@ -232,6 +232,40 @@ async def clear_failed(
)
@router.delete("/pending", status_code=status.HTTP_200_OK)
async def clear_pending(
_: dict = Depends(require_auth),
download_service: DownloadService = Depends(get_download_service),
):
"""Clear all pending downloads from the queue.
Removes all pending download items from the queue. This is useful for
clearing the entire queue at once instead of removing items one by one.
Requires authentication.
Returns:
dict: Status message with count of cleared items
Raises:
HTTPException: 401 if not authenticated, 500 on service error
"""
try:
cleared_count = await download_service.clear_pending()
return {
"status": "success",
"message": f"Removed {cleared_count} pending item(s)",
"count": cleared_count,
}
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to clear pending items: {str(e)}",
)
@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def remove_from_queue(
item_id: str = Path(..., description="Download item ID to remove"),

View File

@ -27,9 +27,9 @@ class DownloadStatus(str, Enum):
class DownloadPriority(str, Enum):
"""Priority level for download queue items."""
LOW = "low"
NORMAL = "normal"
HIGH = "high"
LOW = "LOW"
NORMAL = "NORMAL"
HIGH = "HIGH"
class EpisodeIdentifier(BaseModel):
@ -175,9 +175,9 @@ class DownloadRequest(BaseModel):
@field_validator('priority', mode='before')
@classmethod
def normalize_priority(cls, v):
"""Normalize priority to lowercase for case-insensitive matching."""
"""Normalize priority to uppercase for case-insensitive matching."""
if isinstance(v, str):
return v.lower()
return v.upper()
return v

View File

@ -600,6 +600,34 @@ class DownloadService:
return count
async def clear_pending(self) -> int:
"""Clear all pending downloads from the queue.
Returns:
Number of items cleared
"""
count = len(self._pending_queue)
self._pending_queue.clear()
self._pending_items_by_id.clear()
logger.info("Cleared pending items", count=count)
# Save queue state
self._save_queue()
# Broadcast queue status update
if count > 0:
queue_status = await self.get_queue_status()
await self._broadcast_update(
"queue_status",
{
"action": "pending_cleared",
"cleared_count": count,
"queue_status": queue_status.model_dump(mode="json"),
},
)
return count
async def retry_failed(
self, item_ids: Optional[List[str]] = None
) -> List[str]:

View File

@ -142,6 +142,10 @@ class QueueManager {
this.clearQueue('failed');
});
document.getElementById('clear-pending-btn').addEventListener('click', () => {
this.clearQueue('pending');
});
document.getElementById('retry-all-btn').addEventListener('click', () => {
this.retryAllFailed();
});
@ -442,6 +446,14 @@ class QueueManager {
const hasFailed = (data.failed_downloads || []).length > 0;
const hasCompleted = (data.completed_downloads || []).length > 0;
console.log('Button states update:', {
hasPending,
pendingCount: (data.pending_queue || []).length,
hasActive,
hasFailed,
hasCompleted
});
// Enable start button only if there are pending items and no active downloads
document.getElementById('start-queue-btn').disabled = !hasPending || hasActive;
@ -458,17 +470,28 @@ class QueueManager {
document.getElementById('retry-all-btn').disabled = !hasFailed;
document.getElementById('clear-completed-btn').disabled = !hasCompleted;
document.getElementById('clear-failed-btn').disabled = !hasFailed;
// Update clear pending button if it exists
const clearPendingBtn = document.getElementById('clear-pending-btn');
if (clearPendingBtn) {
clearPendingBtn.disabled = !hasPending;
console.log('Clear pending button updated:', { disabled: !hasPending, hasPending });
} else {
console.error('Clear pending button not found in DOM');
}
}
async clearQueue(type) {
const titles = {
completed: 'Clear Completed Downloads',
failed: 'Clear Failed Downloads'
failed: 'Clear Failed Downloads',
pending: 'Remove All Pending Downloads'
};
const messages = {
completed: 'Are you sure you want to clear all completed downloads?',
failed: 'Are you sure you want to clear all failed downloads?'
failed: 'Are you sure you want to clear all failed downloads?',
pending: 'Are you sure you want to remove all pending downloads from the queue?'
};
const confirmed = await this.showConfirmModal(titles[type], messages[type]);
@ -495,6 +518,16 @@ class QueueManager {
this.showToast(`Cleared ${data.count} failed downloads`, 'success');
this.loadQueueData();
} else if (type === 'pending') {
const response = await this.makeAuthenticatedRequest('/api/queue/pending', {
method: 'DELETE'
});
if (!response) return;
const data = await response.json();
this.showToast(`Removed ${data.count} pending downloads`, 'success');
this.loadQueueData();
}
} catch (error) {

View File

@ -124,6 +124,10 @@
Download Queue (<span id="queue-count">0</span>)
</h2>
<div class="section-actions">
<button id="clear-pending-btn" class="btn btn-secondary" disabled>
<i class="fas fa-trash-alt"></i>
Remove All
</button>
<button id="start-queue-btn" class="btn btn-primary" disabled>
<i class="fas fa-play"></i>
Start

View File

@ -335,6 +335,22 @@ async def test_clear_completed(authenticated_client, mock_download_service):
mock_download_service.clear_completed.assert_called_once()
@pytest.mark.asyncio
async def test_clear_pending(authenticated_client, mock_download_service):
"""Test DELETE /api/queue/pending endpoint."""
mock_download_service.clear_pending = AsyncMock(return_value=3)
response = await authenticated_client.delete("/api/queue/pending")
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
assert data["count"] == 3
mock_download_service.clear_pending.assert_called_once()
@pytest.mark.asyncio
async def test_retry_failed(authenticated_client, mock_download_service):
"""Test POST /api/queue/retry endpoint."""

View File

@ -340,6 +340,37 @@ class TestQueueControl:
assert count == 1
assert len(download_service._completed_items) == 0
@pytest.mark.asyncio
async def test_clear_pending(self, download_service):
"""Test clearing all pending downloads from the queue."""
# Add multiple items to the queue
await download_service.add_to_queue(
serie_id="series-1",
serie_folder="test-series-1",
serie_name="Test Series 1",
episodes=[EpisodeIdentifier(season=1, episode=1)],
)
await download_service.add_to_queue(
serie_id="series-2",
serie_folder="test-series-2",
serie_name="Test Series 2",
episodes=[
EpisodeIdentifier(season=1, episode=2),
EpisodeIdentifier(season=1, episode=3),
],
)
# Verify items were added
assert len(download_service._pending_queue) == 3
# Clear pending queue
count = await download_service.clear_pending()
# Verify all pending items were cleared
assert count == 3
assert len(download_service._pending_queue) == 0
assert len(download_service._pending_items_by_id) == 0
class TestPersistence:
"""Test queue persistence functionality."""