Refactor: make classes more modular
This commit is contained in:
parent
71cdcc220f
commit
5a5f16c9f8
13 changed files with 81 additions and 56 deletions
|
|
@ -6,6 +6,7 @@ from queue import Empty, Queue
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .Services import Services
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
LOG: logging.Logger = logging.getLogger("BackgroundWorker")
|
LOG: logging.Logger = logging.getLogger("BackgroundWorker")
|
||||||
|
|
@ -35,6 +36,7 @@ class BackgroundWorker(metaclass=Singleton):
|
||||||
return BackgroundWorker()
|
return BackgroundWorker()
|
||||||
|
|
||||||
def attach(self, app: web.Application):
|
def attach(self, app: web.Application):
|
||||||
|
Services.get_instance().add("background_worker", self)
|
||||||
app.on_shutdown.append(self.on_shutdown)
|
app.on_shutdown.append(self.on_shutdown)
|
||||||
|
|
||||||
LOG.debug("Starting background worker...")
|
LOG.debug("Starting background worker...")
|
||||||
|
|
|
||||||
|
|
@ -154,18 +154,25 @@ class DataStore:
|
||||||
|
|
||||||
async def get_items_paginated(
|
async def get_items_paginated(
|
||||||
self, page: int = 1, per_page: int = 50, order: str = "DESC", status_filter: str | None = None
|
self, page: int = 1, per_page: int = 50, order: str = "DESC", status_filter: str | None = None
|
||||||
):
|
) -> tuple[list[tuple[str, Download]], int, int, int]:
|
||||||
if page < 1:
|
if page < 1:
|
||||||
msg = "page must be >= 1"
|
msg = "page must be >= 1"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
if per_page < 1:
|
if per_page < 1:
|
||||||
msg = "per_page must be >= 1"
|
msg = "per_page must be >= 1"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
order = order.upper()
|
order = order.upper()
|
||||||
if order not in ("ASC", "DESC"):
|
if order not in ("ASC", "DESC"):
|
||||||
msg = f"order must be 'ASC' or 'DESC', got '{order}'"
|
msg = f"order must be 'ASC' or 'DESC', got '{order}'"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
return await self._connection.paginate(str(self._type), page, per_page, order, status_filter)
|
|
||||||
|
items, total_items, current_page, total_pages = await self._connection.paginate(
|
||||||
|
str(self._type), page, per_page, order, status_filter
|
||||||
|
)
|
||||||
|
|
||||||
|
return [(item_id, Download(info=item)) for item_id, item in items], total_items, current_page, total_pages
|
||||||
|
|
||||||
async def bulk_delete(self, ids: Iterable[str]) -> int:
|
async def bulk_delete(self, ids: Iterable[str]) -> int:
|
||||||
deleted = await self._connection.bulk_delete(str(self._type), ids)
|
deleted = await self._connection.bulk_delete(str(self._type), ids)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ from .Events import EventBus, Events
|
||||||
from .ItemDTO import Item, ItemDTO
|
from .ItemDTO import Item, ItemDTO
|
||||||
from .Presets import Presets
|
from .Presets import Presets
|
||||||
from .Scheduler import Scheduler
|
from .Scheduler import Scheduler
|
||||||
|
from .Services import Services
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
from .sqlite_store import SqliteStore
|
from .sqlite_store import SqliteStore
|
||||||
from .Utils import (
|
from .Utils import (
|
||||||
|
|
@ -54,9 +55,9 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
"Configuration instance."
|
"Configuration instance."
|
||||||
self._notify: EventBus = EventBus.get_instance()
|
self._notify: EventBus = EventBus.get_instance()
|
||||||
"Event bus instance."
|
"Event bus instance."
|
||||||
self.done = DataStore(type=StoreType.HISTORY, connection=SqliteStore.get_instance(config.db_file))
|
self.done = DataStore(type=StoreType.HISTORY, connection=SqliteStore.get_instance())
|
||||||
"DataStore for the completed downloads."
|
"DataStore for the completed downloads."
|
||||||
self.queue = DataStore(type=StoreType.QUEUE, connection=SqliteStore.get_instance(config.db_file))
|
self.queue = DataStore(type=StoreType.QUEUE, connection=SqliteStore.get_instance())
|
||||||
"DataStore for the download queue."
|
"DataStore for the download queue."
|
||||||
self.workers = asyncio.Semaphore(self.config.max_workers)
|
self.workers = asyncio.Semaphore(self.config.max_workers)
|
||||||
"Semaphore to limit the number of concurrent downloads."
|
"Semaphore to limit the number of concurrent downloads."
|
||||||
|
|
@ -74,7 +75,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
self.paused.set()
|
self.paused.set()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> "DownloadQueue":
|
def get_instance(config: Config | None = None) -> "DownloadQueue":
|
||||||
"""
|
"""
|
||||||
Get the instance of the DownloadQueue.
|
Get the instance of the DownloadQueue.
|
||||||
|
|
||||||
|
|
@ -82,7 +83,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
DownloadQueue: The instance of the DownloadQueue
|
DownloadQueue: The instance of the DownloadQueue
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return DownloadQueue()
|
return DownloadQueue(config=config)
|
||||||
|
|
||||||
def _get_limit(self, extractor: str) -> asyncio.Semaphore:
|
def _get_limit(self, extractor: str) -> asyncio.Semaphore:
|
||||||
"""
|
"""
|
||||||
|
|
@ -121,6 +122,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
_ (web.Application): The application to attach the download queue to.
|
_ (web.Application): The application to attach the download queue to.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
Services.get_instance().add("queue", self)
|
||||||
|
|
||||||
async def event_handler(_, __):
|
async def event_handler(_, __):
|
||||||
await self.initialize()
|
await self.initialize()
|
||||||
|
|
@ -1021,7 +1023,6 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
|
|
||||||
if mode in ("all", "done"):
|
if mode in ("all", "done"):
|
||||||
for k, v in await self.done.saved_items():
|
for k, v in await self.done.saved_items():
|
||||||
v.get_file_sidecar()
|
|
||||||
items["done"][k] = v
|
items["done"][k] = v
|
||||||
|
|
||||||
if mode in ("all", "queue"):
|
if mode in ("all", "queue"):
|
||||||
|
|
@ -1034,7 +1035,6 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
if k in items["done"]:
|
if k in items["done"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
v.info.get_file_sidecar()
|
|
||||||
items["done"][k] = v.info
|
items["done"][k] = v.info
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class Events:
|
||||||
CONNECTED: str = "connected"
|
CONNECTED: str = "connected"
|
||||||
|
|
||||||
CONFIGURATION: str = "configuration"
|
CONFIGURATION: str = "configuration"
|
||||||
|
ACTIVE_QUEUE: str = "active_queue"
|
||||||
|
|
||||||
LOG_INFO: str = "log_info"
|
LOG_INFO: str = "log_info"
|
||||||
LOG_WARNING: str = "log_warning"
|
LOG_WARNING: str = "log_warning"
|
||||||
|
|
@ -94,6 +95,7 @@ class Events:
|
||||||
return [
|
return [
|
||||||
Events.CONFIGURATION,
|
Events.CONFIGURATION,
|
||||||
Events.CONNECTED,
|
Events.CONNECTED,
|
||||||
|
Events.ACTIVE_QUEUE,
|
||||||
Events.LOG_INFO,
|
Events.LOG_INFO,
|
||||||
Events.LOG_WARNING,
|
Events.LOG_WARNING,
|
||||||
Events.LOG_ERROR,
|
Events.LOG_ERROR,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ from app.library.Services import Services
|
||||||
|
|
||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .DownloadQueue import DownloadQueue
|
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .Events import EventBus
|
from .Events import EventBus
|
||||||
from .router import RouteType, get_routes
|
from .router import RouteType, get_routes
|
||||||
|
|
@ -24,8 +23,7 @@ LOG: logging.Logger = logging.getLogger("http_api")
|
||||||
|
|
||||||
|
|
||||||
class HttpAPI:
|
class HttpAPI:
|
||||||
def __init__(self, root_path: Path, queue: DownloadQueue):
|
def __init__(self, root_path: Path):
|
||||||
self.queue: DownloadQueue = queue or DownloadQueue.get_instance()
|
|
||||||
self.encoder: Encoder = Encoder()
|
self.encoder: Encoder = Encoder()
|
||||||
self.config: Config = Config.get_instance()
|
self.config: Config = Config.get_instance()
|
||||||
self._notify: EventBus = EventBus.get_instance()
|
self._notify: EventBus = EventBus.get_instance()
|
||||||
|
|
@ -33,12 +31,11 @@ class HttpAPI:
|
||||||
self.cache = Cache()
|
self.cache = Cache()
|
||||||
self.app: web.Application | None = None
|
self.app: web.Application | None = None
|
||||||
|
|
||||||
services = Services.get_instance()
|
services: Services = Services.get_instance()
|
||||||
services.add_all(
|
services.add_all(
|
||||||
{
|
{
|
||||||
k: v
|
k: v
|
||||||
for k, v in {
|
for k, v in {
|
||||||
"queue": self.queue,
|
|
||||||
"encoder": self.encoder,
|
"encoder": self.encoder,
|
||||||
"config": self.config,
|
"config": self.config,
|
||||||
"notify": self._notify,
|
"notify": self._notify,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ from app.library.Services import Services
|
||||||
from app.library.Utils import load_modules
|
from app.library.Utils import load_modules
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .DownloadQueue import DownloadQueue
|
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .Events import Event, EventBus, Events
|
from .Events import Event, EventBus, Events
|
||||||
from .ItemDTO import Item
|
from .ItemDTO import Item
|
||||||
|
|
@ -26,19 +25,16 @@ class HttpSocket:
|
||||||
|
|
||||||
config: Config
|
config: Config
|
||||||
sio: socketio.AsyncServer
|
sio: socketio.AsyncServer
|
||||||
queue: DownloadQueue
|
|
||||||
di_context: dict[str, Any] = {}
|
di_context: dict[str, Any] = {}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
root_path: Path,
|
root_path: Path,
|
||||||
queue: DownloadQueue | None = None,
|
|
||||||
encoder: Encoder | None = None,
|
encoder: Encoder | None = None,
|
||||||
config: Config | None = None,
|
config: Config | None = None,
|
||||||
sio: socketio.AsyncServer | None = None,
|
sio: socketio.AsyncServer | None = None,
|
||||||
):
|
):
|
||||||
self.config = config or Config.get_instance()
|
self.config = config or Config.get_instance()
|
||||||
self.queue = queue or DownloadQueue.get_instance()
|
|
||||||
self._notify = EventBus.get_instance()
|
self._notify = EventBus.get_instance()
|
||||||
|
|
||||||
self.sio = sio or socketio.AsyncServer(
|
self.sio = sio or socketio.AsyncServer(
|
||||||
|
|
@ -63,7 +59,6 @@ class HttpSocket:
|
||||||
k: v
|
k: v
|
||||||
for k, v in {
|
for k, v in {
|
||||||
"config": self.config,
|
"config": self.config,
|
||||||
"queue": self.queue,
|
|
||||||
"sio": self.sio,
|
"sio": self.sio,
|
||||||
"encoder": encoder,
|
"encoder": encoder,
|
||||||
"notify": self._notify,
|
"notify": self._notify,
|
||||||
|
|
@ -104,7 +99,7 @@ class HttpSocket:
|
||||||
|
|
||||||
async def event_handler(data: Event, _):
|
async def event_handler(data: Event, _):
|
||||||
if data and data.data:
|
if data and data.data:
|
||||||
await self.queue.add(item=Item.format(data.data))
|
await Services.get_instance().get("queue").add(item=Item.format(data.data))
|
||||||
|
|
||||||
self._notify.subscribe(Events.ADD_URL, event_handler, f"{__class__.__name__}.add")
|
self._notify.subscribe(Events.ADD_URL, event_handler, f"{__class__.__name__}.add")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -397,11 +397,8 @@ class ItemDTO:
|
||||||
dict: The serialized item.
|
dict: The serialized item.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "finished" == self.status:
|
if "finished" == self.status and not self._recomputed:
|
||||||
if not self._recomputed:
|
self.archive_status()
|
||||||
self.archive_status()
|
|
||||||
|
|
||||||
self.get_file_sidecar()
|
|
||||||
|
|
||||||
item, _ = clean_item(self.__dict__.copy(), ItemDTO.removed_fields())
|
item, _ = clean_item(self.__dict__.copy(), ItemDTO.removed_fields())
|
||||||
return item
|
return item
|
||||||
|
|
@ -661,4 +658,3 @@ class ItemDTO:
|
||||||
self.get_archive_id()
|
self.get_archive_id()
|
||||||
self.get_archive_file()
|
self.get_archive_file()
|
||||||
self.archive_status()
|
self.archive_status()
|
||||||
self.get_file_sidecar()
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from aiohttp import web
|
||||||
|
|
||||||
from .ItemDTO import ItemDTO
|
from .ItemDTO import ItemDTO
|
||||||
from .operations import Operation, matches_condition
|
from .operations import Operation, matches_condition
|
||||||
|
from .Services import Services
|
||||||
from .Singleton import ThreadSafe
|
from .Singleton import ThreadSafe
|
||||||
from .Utils import init_class
|
from .Utils import init_class
|
||||||
|
|
||||||
|
|
@ -43,25 +44,24 @@ class _Op:
|
||||||
class SqliteStore(metaclass=ThreadSafe):
|
class SqliteStore(metaclass=ThreadSafe):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance(db_path: str | None = None) -> "SqliteStore":
|
def get_instance(db_path: str | None = None) -> "SqliteStore":
|
||||||
return SqliteStore(db_path)
|
return SqliteStore(db_path=db_path)
|
||||||
|
|
||||||
def attach(self, app: web.Application):
|
def attach(self, app: web.Application):
|
||||||
"""Get/create singleton bound to db_path."""
|
Services.get_instance().add("sqlite_store", self)
|
||||||
app.on_shutdown.append(self.on_shutdown)
|
app.on_shutdown.append(self.on_shutdown)
|
||||||
|
|
||||||
async def on_shutdown(self, _: web.Application):
|
async def on_shutdown(self, _: web.Application):
|
||||||
"""Close singleton on app shutdown."""
|
|
||||||
LOG.debug("Shutting down SqliteStore...")
|
LOG.debug("Shutting down SqliteStore...")
|
||||||
await self.close()
|
await self.close()
|
||||||
LOG.debug("SqliteStore shut down complete.")
|
LOG.debug("SqliteStore shut down complete.")
|
||||||
|
|
||||||
def __init__(self, db_path: str | None = None, *, max_pending: int = 200, flush_interval: float = 0.05):
|
def __init__(self, db_path: str, *, max_pending: int = 200, flush_interval: float = 0.05):
|
||||||
self._db_path = db_path
|
self._db_path: str = db_path
|
||||||
self._conn: aiosqlite.Connection | None = None
|
self._conn: aiosqlite.Connection | None = None
|
||||||
self._queue: asyncio.Queue[_Op] | None = None
|
self._queue: asyncio.Queue[_Op] | None = None
|
||||||
self._task: asyncio.Task | None = None
|
self._task: asyncio.Task | None = None
|
||||||
self._flush_interval = flush_interval
|
self._flush_interval: float = flush_interval
|
||||||
self._max_pending = max_pending
|
self._max_pending: int = max_pending
|
||||||
self._lock = asyncio.Lock()
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
async def __aenter__(self) -> "SqliteStore":
|
async def __aenter__(self) -> "SqliteStore":
|
||||||
|
|
@ -303,7 +303,6 @@ class SqliteStore(metaclass=ThreadSafe):
|
||||||
|
|
||||||
return items, total_items, page, total_pages
|
return items, total_items, page, total_pages
|
||||||
|
|
||||||
# direct CRUD helpers (used by DataStore)
|
|
||||||
async def upsert(self, type_value: str, item: ItemDTO) -> None:
|
async def upsert(self, type_value: str, item: ItemDTO) -> None:
|
||||||
await self._ensure_conn()
|
await self._ensure_conn()
|
||||||
await self._upsert_now(type_value, item)
|
await self._upsert_now(type_value, item)
|
||||||
|
|
@ -441,7 +440,7 @@ class SqliteStore(metaclass=ThreadSafe):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self._db_path:
|
if not self._db_path:
|
||||||
msg = "SqliteStore requires db_path or injected connection."
|
msg = "No database path specified for SqliteStore."
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
from app.library import migrate
|
from app.library import migrate
|
||||||
|
|
|
||||||
19
app/main.py
19
app/main.py
|
|
@ -17,7 +17,6 @@ from aiohttp import web
|
||||||
from app.library.BackgroundWorker import BackgroundWorker
|
from app.library.BackgroundWorker import BackgroundWorker
|
||||||
from app.library.conditions import Conditions
|
from app.library.conditions import Conditions
|
||||||
from app.library.config import Config
|
from app.library.config import Config
|
||||||
from app.library.DataStore import SqliteStore
|
|
||||||
from app.library.dl_fields import DLFields
|
from app.library.dl_fields import DLFields
|
||||||
from app.library.DownloadQueue import DownloadQueue
|
from app.library.DownloadQueue import DownloadQueue
|
||||||
from app.library.Events import EventBus, Events
|
from app.library.Events import EventBus, Events
|
||||||
|
|
@ -27,6 +26,7 @@ from app.library.Notifications import Notification
|
||||||
from app.library.Presets import Presets
|
from app.library.Presets import Presets
|
||||||
from app.library.Scheduler import Scheduler
|
from app.library.Scheduler import Scheduler
|
||||||
from app.library.Services import Services
|
from app.library.Services import Services
|
||||||
|
from app.library.sqlite_store import SqliteStore
|
||||||
from app.library.TaskDefinitions import TaskDefinitions
|
from app.library.TaskDefinitions import TaskDefinitions
|
||||||
from app.library.Tasks import Tasks
|
from app.library.Tasks import Tasks
|
||||||
|
|
||||||
|
|
@ -42,10 +42,8 @@ class Main:
|
||||||
self._config.set_app_path(str(ROOT_PATH))
|
self._config.set_app_path(str(ROOT_PATH))
|
||||||
self._app = web.Application()
|
self._app = web.Application()
|
||||||
self._app.on_shutdown.append(self.on_shutdown)
|
self._app.on_shutdown.append(self.on_shutdown)
|
||||||
self._background_worker = BackgroundWorker()
|
|
||||||
|
|
||||||
Services.get_instance().add("app", self._app)
|
Services.get_instance().add("app", self._app)
|
||||||
Services.get_instance().add("background_worker", self._background_worker)
|
|
||||||
|
|
||||||
self._check_folders()
|
self._check_folders()
|
||||||
|
|
||||||
|
|
@ -56,13 +54,12 @@ class Main:
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self._queue = DownloadQueue(config=self._config)
|
self._http = HttpAPI(root_path=ROOT_PATH)
|
||||||
self._http = HttpAPI(root_path=ROOT_PATH, queue=self._queue)
|
self._socket = HttpSocket(root_path=ROOT_PATH)
|
||||||
self._socket = HttpSocket(root_path=ROOT_PATH, queue=self._queue)
|
|
||||||
|
|
||||||
def _check_folders(self):
|
def _check_folders(self):
|
||||||
"""Check if the required folders exist and create them if they do not."""
|
"""Check if the required folders exist and create them if they do not."""
|
||||||
folders = (self._config.download_path, self._config.temp_path, self._config.config_path)
|
folders: tuple[str, str, str] = (self._config.download_path, self._config.temp_path, self._config.config_path)
|
||||||
|
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
folder = Path(folder)
|
folder = Path(folder)
|
||||||
|
|
@ -109,20 +106,20 @@ class Main:
|
||||||
if self._config.debug:
|
if self._config.debug:
|
||||||
EventBus.get_instance().debug_enable()
|
EventBus.get_instance().debug_enable()
|
||||||
|
|
||||||
|
SqliteStore.get_instance(db_path=self._config.db_file).attach(self._app)
|
||||||
|
BackgroundWorker.get_instance().attach(self._app)
|
||||||
Scheduler.get_instance().attach(self._app)
|
Scheduler.get_instance().attach(self._app)
|
||||||
|
|
||||||
self._socket.attach(self._app)
|
self._socket.attach(self._app)
|
||||||
self._http.attach(self._app)
|
self._http.attach(self._app)
|
||||||
self._queue.attach(self._app)
|
|
||||||
|
|
||||||
Tasks.get_instance().attach(self._app)
|
|
||||||
Presets.get_instance().attach(self._app)
|
Presets.get_instance().attach(self._app)
|
||||||
|
Tasks.get_instance().attach(self._app)
|
||||||
Notification.get_instance().attach(self._app)
|
Notification.get_instance().attach(self._app)
|
||||||
Conditions.get_instance().attach(self._app)
|
Conditions.get_instance().attach(self._app)
|
||||||
DLFields.get_instance().attach(self._app)
|
DLFields.get_instance().attach(self._app)
|
||||||
TaskDefinitions.get_instance().attach(self._app)
|
TaskDefinitions.get_instance().attach(self._app)
|
||||||
SqliteStore.get_instance().attach(self._app)
|
DownloadQueue.get_instance().attach(self._app)
|
||||||
self._background_worker.attach(self._app)
|
|
||||||
|
|
||||||
EventBus.get_instance().emit(
|
EventBus.get_instance().emit(
|
||||||
Events.LOADED,
|
Events.LOADED,
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,16 @@ async def items_list(request: Request, queue: DownloadQueue, encoder: Encoder, c
|
||||||
page=page, per_page=per_page, order=order, status_filter=status_filter
|
page=page, per_page=per_page, order=order, status_filter=status_filter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if store_type == StoreType.HISTORY:
|
||||||
|
for _, download in items:
|
||||||
|
if not download.info:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
download.info.sidecar = download.get_file_sidecar()
|
||||||
|
except Exception:
|
||||||
|
download.info.sidecar = {}
|
||||||
|
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
data={
|
data={
|
||||||
"type": store_type.value,
|
"type": store_type.value,
|
||||||
|
|
@ -100,7 +110,7 @@ async def items_list(request: Request, queue: DownloadQueue, encoder: Encoder, c
|
||||||
"has_next": current_page < total_pages,
|
"has_next": current_page < total_pages,
|
||||||
"has_prev": current_page > 1,
|
"has_prev": current_page > 1,
|
||||||
},
|
},
|
||||||
"items": [item for _, item in items],
|
"items": [download.info for _, download in items],
|
||||||
},
|
},
|
||||||
status=web.HTTPOk.status_code,
|
status=web.HTTPOk.status_code,
|
||||||
dumps=encoder.encode,
|
dumps=encoder.encode,
|
||||||
|
|
@ -227,6 +237,7 @@ async def item_view(request: Request, queue: DownloadQueue, encoder: Encoder) ->
|
||||||
info: dict = {
|
info: dict = {
|
||||||
**item.info.serialize(),
|
**item.info.serialize(),
|
||||||
"ffprobe": {},
|
"ffprobe": {},
|
||||||
|
"sidecar": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if "finished" == item.info.status and (filename := item.info.get_file()):
|
if "finished" == item.info.status and (filename := item.info.get_file()):
|
||||||
|
|
@ -237,6 +248,11 @@ async def item_view(request: Request, queue: DownloadQueue, encoder: Encoder) ->
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
info["sidecar"] = item.info.get_file_sidecar()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return web.json_response(data=info, status=web.HTTPOk.status_code, dumps=encoder.encode)
|
return web.json_response(data=info, status=web.HTTPOk.status_code, dumps=encoder.encode)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,14 @@ async def connect(config: Config, queue: DownloadQueue, notify: EventBus, sid: s
|
||||||
to=sid,
|
to=sid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
notify.emit(
|
||||||
|
Events.ACTIVE_QUEUE,
|
||||||
|
data={"queue": (await queue.get("queue"))["queue"]},
|
||||||
|
title="Sending initial active queue data",
|
||||||
|
message=f"Sending active queue data to client '{sid}'.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@route(RouteType.SOCKET, "disconnect", "socket_disconnect")
|
@route(RouteType.SOCKET, "disconnect", "socket_disconnect")
|
||||||
async def disconnect(sio: socketio.AsyncServer, sid: str, data: str = None):
|
async def disconnect(sio: socketio.AsyncServer, sid: str, data: str = None):
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from app.library.DataStore import DataStore, StoreType
|
from app.library.DataStore import DataStore, StoreType
|
||||||
|
from app.library.Download import Download
|
||||||
from app.library.ItemDTO import ItemDTO
|
from app.library.ItemDTO import ItemDTO
|
||||||
from app.library.sqlite_store import SqliteStore
|
from app.library.sqlite_store import SqliteStore
|
||||||
|
|
||||||
|
|
@ -75,8 +76,9 @@ class TestDataStorePagination:
|
||||||
assert page == 1
|
assert page == 1
|
||||||
assert total_pages == 10
|
assert total_pages == 10
|
||||||
for _, item in items:
|
for _, item in items:
|
||||||
assert isinstance(item, ItemDTO)
|
assert isinstance(item, Download)
|
||||||
assert item._id.startswith("test-id-")
|
assert isinstance(item.info, ItemDTO)
|
||||||
|
assert item.info._id.startswith("test-id-")
|
||||||
finally:
|
finally:
|
||||||
await db.close()
|
await db.close()
|
||||||
|
|
||||||
|
|
@ -253,7 +255,7 @@ class TestDataStorePagination:
|
||||||
assert len(items) == 50 # First page of finished items
|
assert len(items) == 50 # First page of finished items
|
||||||
assert total == 100 # Only 100 finished items in fixture
|
assert total == 100 # Only 100 finished items in fixture
|
||||||
for _item_id, item in items:
|
for _item_id, item in items:
|
||||||
assert item.status == "finished"
|
assert item.info.status == "finished"
|
||||||
|
|
||||||
# Filter for pending items only
|
# Filter for pending items only
|
||||||
items, total, _page, _total_pages = await datastore.get_items_paginated(
|
items, total, _page, _total_pages = await datastore.get_items_paginated(
|
||||||
|
|
@ -262,7 +264,7 @@ class TestDataStorePagination:
|
||||||
|
|
||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
assert total == 1
|
assert total == 1
|
||||||
assert items[0][1].status == "pending"
|
assert items[0][1].info.status == "pending"
|
||||||
finally:
|
finally:
|
||||||
await db.close()
|
await db.close()
|
||||||
|
|
||||||
|
|
@ -317,10 +319,10 @@ class TestDataStorePagination:
|
||||||
assert total == 2 # Only 2 non-finished items
|
assert total == 2 # Only 2 non-finished items
|
||||||
assert len(items) == 2
|
assert len(items) == 2
|
||||||
for _item_id, item in items:
|
for _item_id, item in items:
|
||||||
assert item.status != "finished"
|
assert item.info.status != "finished"
|
||||||
|
|
||||||
# Verify we have pending and error
|
# Verify we have pending and error
|
||||||
statuses = {item.status for _, item in items}
|
statuses = {item.info.status for _, item in items}
|
||||||
assert statuses == {"pending", "error"}
|
assert statuses == {"pending", "error"}
|
||||||
finally:
|
finally:
|
||||||
await db.close()
|
await db.close()
|
||||||
|
|
|
||||||
|
|
@ -118,10 +118,6 @@ export const useSocketStore = defineStore('socket', () => {
|
||||||
config.add('folders', json.data.folders)
|
config.add('folders', json.data.folders)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.data?.queue) {
|
|
||||||
stateStore.addAll('queue', json.data.queue || {})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json.data?.history_count === 'number') {
|
if (typeof json.data?.history_count === 'number') {
|
||||||
stateStore.setHistoryCount(json.data.history_count)
|
stateStore.setHistoryCount(json.data.history_count)
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +125,14 @@ export const useSocketStore = defineStore('socket', () => {
|
||||||
error.value = null;
|
error.value = null;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
on('active_queue', stream => {
|
||||||
|
const json = JSON.parse(stream);
|
||||||
|
if (!json?.data?.queue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stateStore.addAll('queue', json.data.queue || {})
|
||||||
|
})
|
||||||
|
|
||||||
on('item_added', stream => {
|
on('item_added', stream => {
|
||||||
const json = JSON.parse(stream);
|
const json = JSON.parse(stream);
|
||||||
stateStore.add('queue', json.data._id, json.data);
|
stateStore.add('queue', json.data._id, json.data);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue