feat(UI): added docker image and config
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
Task Queue Manager for sequential execution of scheduled downloads.
|
||||
Ensures only one task runs at a time.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional, Dict
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaskQueue:
|
||||
"""
|
||||
Manages sequential execution of scheduled download tasks.
|
||||
Ensures only one task runs at a time.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.queue: asyncio.Queue = asyncio.Queue()
|
||||
self.current_task: Optional[str] = None # Current task_id being executed
|
||||
self.is_processing: bool = False
|
||||
self.worker_task: Optional[asyncio.Task] = None
|
||||
self._execute_callback = None
|
||||
|
||||
def set_execute_callback(self, callback):
|
||||
"""
|
||||
Set the callback function to execute tasks.
|
||||
|
||||
Args:
|
||||
callback: Async function that takes task_id and executes it
|
||||
"""
|
||||
self._execute_callback = callback
|
||||
|
||||
async def add_task(self, task_id: str, priority: int = 0):
|
||||
"""
|
||||
Add a task to the queue.
|
||||
|
||||
Args:
|
||||
task_id: UUID of the scheduled task
|
||||
priority: 0 = scheduled (normal), 1 = manual "Run Now" (higher priority)
|
||||
"""
|
||||
task_info = {
|
||||
'task_id': task_id,
|
||||
'priority': priority,
|
||||
'queued_at': datetime.now()
|
||||
}
|
||||
|
||||
# For priority tasks, we need to reorder the queue
|
||||
if priority > 0:
|
||||
# Get all items from queue
|
||||
items = []
|
||||
while not self.queue.empty():
|
||||
try:
|
||||
items.append(await asyncio.wait_for(self.queue.get(), timeout=0.1))
|
||||
except asyncio.TimeoutError:
|
||||
break
|
||||
|
||||
# Add new priority task
|
||||
await self.queue.put(task_info)
|
||||
|
||||
# Re-add other items
|
||||
for item in items:
|
||||
await self.queue.put(item)
|
||||
|
||||
logger.info(f"Priority task {task_id} added to front of queue (queue_size={self.queue.qsize()})")
|
||||
else:
|
||||
await self.queue.put(task_info)
|
||||
logger.info(f"Task {task_id} added to queue (priority={priority}, queue_size={self.queue.qsize()})")
|
||||
|
||||
# Start worker if not already running
|
||||
if not self.is_processing:
|
||||
await self.start_worker()
|
||||
|
||||
async def start_worker(self):
|
||||
"""Start the queue worker if not already running"""
|
||||
if self.worker_task is None or self.worker_task.done():
|
||||
self.worker_task = asyncio.create_task(self._process_queue())
|
||||
logger.info("Queue worker started")
|
||||
|
||||
async def _process_queue(self):
|
||||
"""Process tasks from queue sequentially"""
|
||||
self.is_processing = True
|
||||
logger.info("Queue worker processing started")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Wait for next task (with timeout to allow graceful shutdown)
|
||||
try:
|
||||
task_info = await asyncio.wait_for(
|
||||
self.queue.get(),
|
||||
timeout=60.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
# Check if queue is empty
|
||||
if self.queue.empty():
|
||||
logger.info("Queue empty, worker stopping")
|
||||
break
|
||||
continue
|
||||
|
||||
task_id = task_info['task_id']
|
||||
self.current_task = task_id
|
||||
|
||||
logger.info(f"Executing task {task_id} from queue (queue_size={self.queue.qsize()})")
|
||||
|
||||
# Execute the task (this will block until download completes)
|
||||
try:
|
||||
if self._execute_callback:
|
||||
await self._execute_callback(task_id)
|
||||
logger.info(f"Task {task_id} completed successfully")
|
||||
else:
|
||||
logger.error(f"No execute callback set, cannot run task {task_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Task {task_id} failed: {e}", exc_info=True)
|
||||
finally:
|
||||
self.current_task = None
|
||||
self.queue.task_done()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Queue worker error: {e}", exc_info=True)
|
||||
|
||||
self.is_processing = False
|
||||
logger.info("Queue worker stopped")
|
||||
|
||||
def get_queue_status(self) -> Dict:
|
||||
"""Get current queue status"""
|
||||
return {
|
||||
'current_task': self.current_task,
|
||||
'queue_size': self.queue.qsize(),
|
||||
'is_processing': self.is_processing
|
||||
}
|
||||
|
||||
async def stop(self):
|
||||
"""Stop the queue worker gracefully"""
|
||||
logger.info("Stopping queue worker...")
|
||||
if self.worker_task and not self.worker_task.done():
|
||||
# Wait for current task to complete
|
||||
try:
|
||||
await asyncio.wait_for(self.worker_task, timeout=300) # 5 minute timeout
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Queue worker did not stop within timeout, cancelling")
|
||||
self.worker_task.cancel()
|
||||
logger.info("Queue worker stopped")
|
||||
|
||||
|
||||
# Global queue instance
|
||||
task_queue = TaskQueue()
|
||||
Reference in New Issue
Block a user