added remove all item from queue
This commit is contained in:
parent
4dba4db344
commit
18faf3fe91
File diff suppressed because it is too large
Load Diff
@ -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)
|
@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def remove_from_queue(
|
async def remove_from_queue(
|
||||||
item_id: str = Path(..., description="Download item ID to remove"),
|
item_id: str = Path(..., description="Download item ID to remove"),
|
||||||
|
|||||||
@ -27,9 +27,9 @@ class DownloadStatus(str, Enum):
|
|||||||
class DownloadPriority(str, Enum):
|
class DownloadPriority(str, Enum):
|
||||||
"""Priority level for download queue items."""
|
"""Priority level for download queue items."""
|
||||||
|
|
||||||
LOW = "low"
|
LOW = "LOW"
|
||||||
NORMAL = "normal"
|
NORMAL = "NORMAL"
|
||||||
HIGH = "high"
|
HIGH = "HIGH"
|
||||||
|
|
||||||
|
|
||||||
class EpisodeIdentifier(BaseModel):
|
class EpisodeIdentifier(BaseModel):
|
||||||
@ -175,9 +175,9 @@ class DownloadRequest(BaseModel):
|
|||||||
@field_validator('priority', mode='before')
|
@field_validator('priority', mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
def normalize_priority(cls, v):
|
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):
|
if isinstance(v, str):
|
||||||
return v.lower()
|
return v.upper()
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -600,6 +600,34 @@ class DownloadService:
|
|||||||
|
|
||||||
return count
|
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(
|
async def retry_failed(
|
||||||
self, item_ids: Optional[List[str]] = None
|
self, item_ids: Optional[List[str]] = None
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
|
|||||||
@ -142,6 +142,10 @@ class QueueManager {
|
|||||||
this.clearQueue('failed');
|
this.clearQueue('failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('clear-pending-btn').addEventListener('click', () => {
|
||||||
|
this.clearQueue('pending');
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('retry-all-btn').addEventListener('click', () => {
|
document.getElementById('retry-all-btn').addEventListener('click', () => {
|
||||||
this.retryAllFailed();
|
this.retryAllFailed();
|
||||||
});
|
});
|
||||||
@ -442,6 +446,14 @@ class QueueManager {
|
|||||||
const hasFailed = (data.failed_downloads || []).length > 0;
|
const hasFailed = (data.failed_downloads || []).length > 0;
|
||||||
const hasCompleted = (data.completed_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
|
// Enable start button only if there are pending items and no active downloads
|
||||||
document.getElementById('start-queue-btn').disabled = !hasPending || hasActive;
|
document.getElementById('start-queue-btn').disabled = !hasPending || hasActive;
|
||||||
|
|
||||||
@ -458,17 +470,28 @@ class QueueManager {
|
|||||||
document.getElementById('retry-all-btn').disabled = !hasFailed;
|
document.getElementById('retry-all-btn').disabled = !hasFailed;
|
||||||
document.getElementById('clear-completed-btn').disabled = !hasCompleted;
|
document.getElementById('clear-completed-btn').disabled = !hasCompleted;
|
||||||
document.getElementById('clear-failed-btn').disabled = !hasFailed;
|
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) {
|
async clearQueue(type) {
|
||||||
const titles = {
|
const titles = {
|
||||||
completed: 'Clear Completed Downloads',
|
completed: 'Clear Completed Downloads',
|
||||||
failed: 'Clear Failed Downloads'
|
failed: 'Clear Failed Downloads',
|
||||||
|
pending: 'Remove All Pending Downloads'
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
completed: 'Are you sure you want to clear all completed downloads?',
|
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]);
|
const confirmed = await this.showConfirmModal(titles[type], messages[type]);
|
||||||
@ -495,6 +518,16 @@ class QueueManager {
|
|||||||
|
|
||||||
this.showToast(`Cleared ${data.count} failed downloads`, 'success');
|
this.showToast(`Cleared ${data.count} failed downloads`, 'success');
|
||||||
this.loadQueueData();
|
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) {
|
} catch (error) {
|
||||||
|
|||||||
@ -124,6 +124,10 @@
|
|||||||
Download Queue (<span id="queue-count">0</span>)
|
Download Queue (<span id="queue-count">0</span>)
|
||||||
</h2>
|
</h2>
|
||||||
<div class="section-actions">
|
<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>
|
<button id="start-queue-btn" class="btn btn-primary" disabled>
|
||||||
<i class="fas fa-play"></i>
|
<i class="fas fa-play"></i>
|
||||||
Start
|
Start
|
||||||
|
|||||||
@ -335,6 +335,22 @@ async def test_clear_completed(authenticated_client, mock_download_service):
|
|||||||
mock_download_service.clear_completed.assert_called_once()
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_retry_failed(authenticated_client, mock_download_service):
|
async def test_retry_failed(authenticated_client, mock_download_service):
|
||||||
"""Test POST /api/queue/retry endpoint."""
|
"""Test POST /api/queue/retry endpoint."""
|
||||||
|
|||||||
@ -340,6 +340,37 @@ class TestQueueControl:
|
|||||||
assert count == 1
|
assert count == 1
|
||||||
assert len(download_service._completed_items) == 0
|
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:
|
class TestPersistence:
|
||||||
"""Test queue persistence functionality."""
|
"""Test queue persistence functionality."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user