281 lines
9.2 KiB
Python

"""Queue management API endpoints."""
import logging
from flask import Blueprint, request
from utils import make_response, make_error_response
logger = logging.getLogger(__name__)
queue_bp = Blueprint('queue', __name__, url_prefix='/api')
@queue_bp.route('/queue', methods=['GET'])
def get_queue():
"""Get all queue items."""
from app import queue_store
try:
items = queue_store.get_all()
items_data = [item.to_dict() for item in items]
stats = queue_store.get_stats()
return make_response({
"queue": items_data,
"total": stats["total"],
"pendingCount": stats["pending"],
"downloadingCount": stats["downloading"],
})
except Exception as e:
return make_error_response(f"Failed to get queue: {str(e)}", 500)
@queue_bp.route('/queue', methods=['POST'])
def add_to_queue():
"""Add a video to the download queue."""
from app import queue_store, download_engine, yt_cli
try:
data = request.get_json()
if not data:
return make_error_response("Request body is required", 400)
url = data.get('url', '').strip()
title = data.get('title', 'Unknown Title')
video_id = data.get('videoId', '')
thumbnail = data.get('thumbnail', '')
category = data.get('category', '')
network_folder = data.get('network_folder')
quality = data.get('quality')
if not url:
return make_error_response("Video URL is required", 400)
logger.info(f"Queue add request: title={title}, videoId={video_id}, url={url}, category={category}")
# Generate queue ID
import uuid
queue_id = str(uuid.uuid4())
# Create queue item
from models import QueueItem
queue_item = QueueItem(
id=queue_id,
video_id=video_id,
title=title,
url=url,
thumbnail=thumbnail,
category=category,
network_folder=network_folder,
quality=quality,
)
# Enqueue item (will be processed in order by queue processor)
download_engine.enqueue_download(queue_item)
logger.info(f"Queue added successfully: queueId={queue_id}, title={title}")
return make_response(queue_item.to_dict()), 202
except Exception as e:
logger.error(f"Failed to add to queue: title={title}, videoId={video_id}, error={str(e)}")
return make_error_response(f"Failed to add to queue: {str(e)}", 500)
@queue_bp.route('/queue', methods=['DELETE'])
def clear_queue():
"""Clear the entire queue."""
from app import queue_store, socketio
try:
count = queue_store.clear_all()
socketio.emit("queue:cleared")
return make_response({
"message": f"Queue cleared",
"count": count
})
except Exception as e:
return make_error_response(f"Failed to clear queue: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>', methods=['GET'])
def get_queue_item(queue_id):
"""Get a specific queue item."""
from app import queue_store
try:
item = queue_store.get_item(queue_id)
if not item:
return make_error_response(f"Queue item {queue_id} not found", 404)
return make_response(item.to_dict())
except Exception as e:
return make_error_response(f"Failed to get queue item: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>', methods=['DELETE'])
def remove_from_queue(queue_id):
"""Remove an item from the queue."""
from app import queue_store, socketio
try:
removed = queue_store.remove_item(queue_id)
if not removed:
return make_error_response(f"Queue item {queue_id} not found", 404)
socketio.emit("queue:removed", {"queueId": queue_id})
return make_response({"message": f"Item {queue_id} removed from queue"})
except Exception as e:
return make_error_response(f"Failed to remove from queue: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>', methods=['PUT'])
def update_queue_item(queue_id):
"""Update a queue item's fields."""
from app import queue_store
try:
data = request.get_json()
if not data:
return make_error_response("Request body is required", 400)
item = queue_store.update_item(queue_id, data)
if not item:
return make_error_response(f"Queue item {queue_id} not found", 404)
return make_response(item.to_dict())
except Exception as e:
return make_error_response(f"Failed to update queue item: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>/retry', methods=['POST'])
def retry_download(queue_id):
"""Retry a failed download."""
from app import queue_store, download_engine, yt_cli
try:
item = queue_store.get_item(queue_id)
if not item:
return make_error_response(f"Queue item {queue_id} not found", 404)
# Reset status
queue_store.update_status(queue_id, "pending")
queue_store.update_progress(queue_id, 0)
# Re-download based on type
config = yt_cli.config
if item.item_type == "playlist":
download_engine.download_playlist(
queue_id=queue_id,
url=item.url,
config=config,
category=item.category,
network_folder=item.network_folder,
quality=item.quality,
)
else:
download_engine.download_video(
queue_id=queue_id,
url=item.url,
config=config,
category=item.category,
network_folder=item.network_folder,
quality=item.quality,
)
return make_response({
"queueId": queue_id,
"status": "downloading",
"message": "Download retry started"
})
except Exception as e:
return make_error_response(f"Retry failed: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>/cancel', methods=['POST'])
def cancel_download(queue_id):
"""Cancel a download."""
from app import queue_store, download_engine
try:
item = queue_store.get_item(queue_id)
if not item:
return make_error_response(f"Queue item {queue_id} not found", 404)
if item.status in ("completed", "failed"):
return make_error_response(f"Cannot cancel {item.status} download", 400)
# Try to cancel active download
download_engine.cancel_download(queue_id)
return make_response({
"queueId": queue_id,
"status": "cancelled",
"message": "Download cancelled"
})
except Exception as e:
return make_error_response(f"Failed to cancel download: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>/status', methods=['GET'])
def get_queue_status(queue_id):
"""Get the status of a queue item."""
from app import queue_store
try:
item = queue_store.get_item(queue_id)
if not item:
return make_error_response(f"Queue item {queue_id} not found", 404)
return make_response({
"queueId": queue_id,
"status": item.status,
"progress": item.progress,
"speed": item.speed,
"eta": item.eta,
})
except Exception as e:
return make_error_response(f"Failed to get queue status: {str(e)}", 500)
@queue_bp.route('/queue/<queue_id>/move', methods=['POST'])
def move_queue_item(queue_id):
"""Reorder a queue item."""
from app import queue_store
try:
data = request.get_json()
direction = data.get('direction', '').strip() if data else ''
if direction not in ('up', 'down'):
return make_error_response("Direction must be 'up' or 'down'", 400)
moved = queue_store.reorder_item(queue_id, direction)
if not moved:
return make_error_response(f"Could not move item {queue_id} {direction}", 400)
return make_response({"message": f"Item moved {direction}"})
except Exception as e:
return make_error_response(f"Failed to move queue item: {str(e)}", 500)
@queue_bp.route('/queue/clear/completed', methods=['POST'])
def clear_completed():
"""Clear completed items from the queue."""
from app import queue_store
try:
count = queue_store.clear_completed()
return make_response({
"cleared": count,
"count": count
})
except Exception as e:
return make_error_response(f"Failed to clear completed items: {str(e)}", 500)
@queue_bp.route('/queue/clear/failed', methods=['POST'])
def clear_failed():
"""Clear failed items from the queue."""
from app import queue_store
try:
count = queue_store.clear_failed()
return make_response({
"cleared": count,
"count": count
})
except Exception as e:
return make_error_response(f"Failed to clear failed items: {str(e)}", 500)
@queue_bp.route('/queue/stats', methods=['GET'])
def get_queue_stats():
"""Get queue statistics."""
from app import queue_store
try:
stats = queue_store.get_stats()
return make_response(stats)
except Exception as e:
return make_error_response(f"Failed to get queue stats: {str(e)}", 500)