282 lines
9.2 KiB
Python
282 lines
9.2 KiB
Python
"""Queue management API endpoints."""
|
|
|
|
import logging
|
|
|
|
from flask import Blueprint, request
|
|
from utils import make_error_response, make_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 download_engine
|
|
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": "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 download_engine, queue_store, 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 download_engine, 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)
|
|
|
|
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)
|