Fix bot polling, downloads, and file delivery
This commit is contained in:
commit
8a21cbe18a
16 changed files with 1712 additions and 0 deletions
127
app/queue_manager.py
Normal file
127
app/queue_manager.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Менеджер очереди задач для последовательной обработки."""
|
||||
import asyncio
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Callable, Awaitable
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
"""Задача на скачивание и конвертацию."""
|
||||
task_id: int
|
||||
user_id: int
|
||||
username: Optional[str]
|
||||
url: str
|
||||
chat_id: int
|
||||
message_id: int
|
||||
created_at: datetime
|
||||
callback: Optional[Callable[[str], Awaitable[None]]] = None # callback для отправки статуса
|
||||
status_message_ids: list[int] = field(default_factory=list)
|
||||
|
||||
|
||||
class QueueManager:
|
||||
"""Менеджер очереди для последовательной обработки задач."""
|
||||
|
||||
def __init__(self, worker: Callable[[Task], Awaitable[str]]):
|
||||
"""
|
||||
Args:
|
||||
worker: Асинхронная функция для обработки задачи, возвращает путь к файлу
|
||||
"""
|
||||
self.queue: asyncio.Queue = asyncio.Queue()
|
||||
self.worker = worker
|
||||
self.task_counter = 0
|
||||
self._worker_task: Optional[asyncio.Task] = None
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
async def start(self):
|
||||
"""Запуск воркера для обработки очереди."""
|
||||
if self._worker_task is None or self._worker_task.done():
|
||||
self._worker_task = asyncio.create_task(self._process_queue())
|
||||
logger.info("Queue manager started")
|
||||
|
||||
async def stop(self):
|
||||
"""Остановка воркера."""
|
||||
if self._worker_task and not self._worker_task.done():
|
||||
self._worker_task.cancel()
|
||||
try:
|
||||
await self._worker_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
logger.info("Queue manager stopped")
|
||||
|
||||
async def add_task(
|
||||
self,
|
||||
user_id: int,
|
||||
username: Optional[str],
|
||||
url: str,
|
||||
chat_id: int,
|
||||
message_id: int,
|
||||
callback: Optional[Callable[[str], Awaitable[None]]] = None,
|
||||
status_message_ids: Optional[list[int]] = None
|
||||
) -> int:
|
||||
"""Добавить задачу в очередь."""
|
||||
async with self._lock:
|
||||
self.task_counter += 1
|
||||
task_id = self.task_counter
|
||||
|
||||
task = Task(
|
||||
task_id=task_id,
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
url=url,
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
created_at=datetime.now(),
|
||||
callback=callback,
|
||||
status_message_ids=status_message_ids if status_message_ids is not None else []
|
||||
)
|
||||
|
||||
await self.queue.put(task)
|
||||
position = self.queue.qsize()
|
||||
|
||||
logger.info(f"Task {task_id} added to queue. Position: {position}, User: {user_id} (@{username}), URL: {url}")
|
||||
|
||||
if callback:
|
||||
await callback(f"Принято в очередь, позиция: {position}")
|
||||
|
||||
return task_id
|
||||
|
||||
async def _process_queue(self):
|
||||
"""Обработка очереди задач (FIFO, последовательно)."""
|
||||
logger.info("Queue processor started")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Получаем задачу из очереди (блокирующая операция)
|
||||
task = await self.queue.get()
|
||||
|
||||
logger.info(f"Processing task {task.task_id} for user {task.user_id}")
|
||||
|
||||
if task.callback:
|
||||
await task.callback("Начинаю обработку")
|
||||
|
||||
try:
|
||||
# Обрабатываем задачу
|
||||
result = await self.worker(task)
|
||||
|
||||
logger.info(f"Task {task.task_id} completed successfully")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Ошибка при обработке: {str(e)}"
|
||||
logger.error(f"Task {task.task_id} failed: {e}", exc_info=True)
|
||||
|
||||
if task.callback:
|
||||
await task.callback(error_msg)
|
||||
|
||||
finally:
|
||||
self.queue.task_done()
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info("Queue processor cancelled")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Error in queue processor: {e}", exc_info=True)
|
||||
await asyncio.sleep(1) # Небольшая задержка перед следующей попыткой
|
||||
Loading…
Add table
Add a link
Reference in a new issue