minor update to store fields
This commit is contained in:
parent
61cbd4aec8
commit
1239643f97
2 changed files with 84 additions and 48 deletions
|
|
@ -5,9 +5,9 @@ import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
|
from enum import Enum
|
||||||
from sqlite3 import Connection
|
from sqlite3 import Connection
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
from .Download import Download
|
from .Download import Download
|
||||||
from .ItemDTO import ItemDTO
|
from .ItemDTO import ItemDTO
|
||||||
from .Utils import init_class
|
from .Utils import init_class
|
||||||
|
|
@ -15,60 +15,100 @@ from .Utils import init_class
|
||||||
LOG = logging.getLogger("datastore")
|
LOG = logging.getLogger("datastore")
|
||||||
|
|
||||||
|
|
||||||
|
class StoreType(str, Enum):
|
||||||
|
DONE = "done"
|
||||||
|
QUEUE = "queue"
|
||||||
|
PENDING = "pending"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> list[str]:
|
||||||
|
return [member.value for member in cls]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_value(cls, value: str) -> "StoreType":
|
||||||
|
"""
|
||||||
|
Returns the StoreType enum member corresponding to the given value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): The value to match against the enum members.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StoreType: The enum member that matches the value.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the value does not match any member.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for member in cls:
|
||||||
|
if member.value == value:
|
||||||
|
return member
|
||||||
|
|
||||||
|
msg = f"Invalid StoreType value: {value}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class DataStore:
|
class DataStore:
|
||||||
"""
|
"""
|
||||||
Persistent queue.
|
Persistent queue.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: str = None
|
_type: StoreType = None
|
||||||
dict: OrderedDict[str, Download] = None
|
"""Type of the store, e.g., DONE, QUEUE, PENDING."""
|
||||||
config: Config = None
|
|
||||||
|
|
||||||
connection: Connection
|
_dict: OrderedDict[str, Download] = None
|
||||||
|
"""Ordered dictionary to store Download objects."""
|
||||||
|
|
||||||
def __init__(self, type: str, connection: Connection):
|
_connection: Connection
|
||||||
self.dict = OrderedDict()
|
"""SQLite connection to the database."""
|
||||||
self.type = type
|
|
||||||
self.config = Config.get_instance()
|
def __init__(self, type: StoreType, connection: Connection):
|
||||||
self.connection = connection
|
self._dict = OrderedDict()
|
||||||
|
self._type = type
|
||||||
|
self._connection = connection
|
||||||
|
|
||||||
def load(self) -> None:
|
def load(self) -> None:
|
||||||
for id, item in self.saved_items():
|
for id, item in self.saved_items():
|
||||||
self.dict.update({id: Download(info=item)})
|
self._dict.update({id: Download(info=item)})
|
||||||
|
|
||||||
def exists(self, key: str | None = None, url: str | None = None) -> bool:
|
def exists(self, key: str | None = None, url: str | None = None) -> bool:
|
||||||
if not key and not url:
|
if not key and not url:
|
||||||
msg = "key or url must be provided."
|
msg = "key or url must be provided."
|
||||||
raise KeyError(msg)
|
raise KeyError(msg)
|
||||||
|
|
||||||
if key and key in self.dict:
|
if key and key in self._dict:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return any((key and self.dict[i].info._id == key) or (url and self.dict[i].info.url == url) for i in self.dict)
|
return any(
|
||||||
|
(key and self._dict[i].info._id == key) or (url and self._dict[i].info.url == url) for i in self._dict
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, key: str | None = None, url: str | None = None) -> Download:
|
def get(self, key: str | None = None, url: str | None = None) -> Download:
|
||||||
if not key and not url:
|
if not key and not url:
|
||||||
msg = "key or url must be provided."
|
msg = "key or url must be provided."
|
||||||
raise KeyError(msg)
|
raise KeyError(msg)
|
||||||
|
|
||||||
for i in self.dict:
|
for i in self._dict:
|
||||||
if (key and self.dict[i].info._id == key) or (url and self.dict[i].info.url == url):
|
if (key and self._dict[i].info._id == key) or (url and self._dict[i].info.url == url):
|
||||||
return self.dict[i]
|
return self._dict[i]
|
||||||
|
|
||||||
msg: str = f"{key=} or {url=} not found."
|
msg: str = f"{key=} or {url=} not found."
|
||||||
raise KeyError(msg)
|
raise KeyError(msg)
|
||||||
|
|
||||||
def get_by_id(self, id: str) -> Download | None:
|
def get_by_id(self, id: str) -> Download | None:
|
||||||
return self.dict.get(id, None)
|
return self._dict.get(id, None)
|
||||||
|
|
||||||
def items(self) -> list[tuple[str, Download]]:
|
def items(self) -> list[tuple[str, Download]]:
|
||||||
return self.dict.items()
|
return self._dict.items()
|
||||||
|
|
||||||
def saved_items(self) -> list[tuple[str, ItemDTO]]:
|
def saved_items(self) -> list[tuple[str, ItemDTO]]:
|
||||||
items: list[tuple[str, ItemDTO]] = []
|
items: list[tuple[str, ItemDTO]] = []
|
||||||
|
|
||||||
cursor = self.connection.execute(
|
cursor = self._connection.execute(
|
||||||
'SELECT "id", "data", "created_at" FROM "history" WHERE "type" = ? ORDER BY "created_at" ASC', (self.type,)
|
'SELECT "id", "data", "created_at" FROM "history" WHERE "type" = ? ORDER BY "created_at" ASC',
|
||||||
|
(str(self._type),),
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
|
|
@ -88,39 +128,39 @@ class DataStore:
|
||||||
|
|
||||||
asyncio.create_task(EventBus.get_instance().emit(Events.ITEM_ERROR, value.info), name="emit_item_error")
|
asyncio.create_task(EventBus.get_instance().emit(Events.ITEM_ERROR, value.info), name="emit_item_error")
|
||||||
|
|
||||||
self.dict.update({value.info._id: value})
|
self._dict.update({value.info._id: value})
|
||||||
self._update_store_item(self.type, value.info)
|
self._update_store_item(self._type, value.info)
|
||||||
|
|
||||||
return self.dict[value.info._id]
|
return self._dict[value.info._id]
|
||||||
|
|
||||||
def delete(self, key: str) -> None:
|
def delete(self, key: str) -> None:
|
||||||
self.dict.pop(key, None)
|
self._dict.pop(key, None)
|
||||||
self._delete_store_item(key)
|
self._delete_store_item(key)
|
||||||
|
|
||||||
def next(self) -> tuple[str, Download]:
|
def next(self) -> tuple[str, Download]:
|
||||||
return next(iter(self.dict.items()))
|
return next(iter(self._dict.items()))
|
||||||
|
|
||||||
def empty(self):
|
def empty(self):
|
||||||
return not bool(self.dict)
|
return not bool(self._dict)
|
||||||
|
|
||||||
def has_downloads(self):
|
def has_downloads(self):
|
||||||
if 0 == len(self.dict):
|
if 0 == len(self._dict):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return any(self.dict[key].started() is False for key in self.dict)
|
return any(self._dict[key].started() is False for key in self._dict)
|
||||||
|
|
||||||
def get_next_download(self) -> Download:
|
def get_next_download(self) -> Download:
|
||||||
for key in self.dict:
|
for key in self._dict:
|
||||||
if self.dict[key].started() is False and self.dict[key].is_cancelled() is False:
|
if self._dict[key].started() is False and self._dict[key].is_cancelled() is False:
|
||||||
return self.dict[key]
|
return self._dict[key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def test(self) -> bool:
|
async def test(self) -> bool:
|
||||||
self.connection.execute('SELECT "id" FROM "history" LIMIT 1').fetchone()
|
self._connection.execute('SELECT "id" FROM "history" LIMIT 1').fetchone()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _update_store_item(self, type: str, item: ItemDTO) -> None:
|
def _update_store_item(self, type: StoreType, item: ItemDTO) -> None:
|
||||||
sqlStatement = """
|
sqlStatement = """
|
||||||
INSERT INTO "history" ("id", "type", "url", "data")
|
INSERT INTO "history" ("id", "type", "url", "data")
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
|
|
@ -141,14 +181,14 @@ class DataStore:
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.connection.execute(
|
self._connection.execute(
|
||||||
sqlStatement.strip(),
|
sqlStatement.strip(),
|
||||||
(
|
(
|
||||||
stored._id,
|
stored._id,
|
||||||
type,
|
str(type),
|
||||||
stored.url,
|
stored.url,
|
||||||
stored.json(),
|
stored.json(),
|
||||||
type,
|
str(type),
|
||||||
stored.url,
|
stored.url,
|
||||||
stored.json(),
|
stored.json(),
|
||||||
datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
|
datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
|
@ -156,4 +196,4 @@ class DataStore:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _delete_store_item(self, key: str) -> None:
|
def _delete_store_item(self, key: str) -> None:
|
||||||
self.connection.execute('DELETE FROM "history" WHERE "type" = ? AND "id" = ?', (self.type, key))
|
self._connection.execute('DELETE FROM "history" WHERE "type" = ? AND "id" = ?', (str(self._type), key))
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,10 @@ from typing import TYPE_CHECKING
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from app.library.ag_utils import ag
|
from .ag_utils import ag
|
||||||
|
|
||||||
from .conditions import Conditions
|
from .conditions import Conditions
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .DataStore import DataStore
|
from .DataStore import DataStore, StoreType
|
||||||
from .Download import Download
|
from .Download import Download
|
||||||
from .Events import EventBus, Events
|
from .Events import EventBus, Events
|
||||||
from .Events import info as event_info
|
from .Events import info as event_info
|
||||||
|
|
@ -50,12 +49,6 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
DownloadQueue class is a singleton class that manages the download queue and the download history.
|
DownloadQueue class is a singleton class that manages the download queue and the download history.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TYPE_DONE: str = "done"
|
|
||||||
"""Queue type for completed downloads."""
|
|
||||||
|
|
||||||
TYPE_QUEUE: str = "queue"
|
|
||||||
"""Queue type for pending downloads."""
|
|
||||||
|
|
||||||
paused: asyncio.Event
|
paused: asyncio.Event
|
||||||
"""Event to pause the download queue."""
|
"""Event to pause the download queue."""
|
||||||
|
|
||||||
|
|
@ -74,6 +67,9 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
done: DataStore
|
done: DataStore
|
||||||
"""DataStore for the completed downloads."""
|
"""DataStore for the completed downloads."""
|
||||||
|
|
||||||
|
pending: DataStore
|
||||||
|
"""DataStore for the pending downloads."""
|
||||||
|
|
||||||
workers: asyncio.Semaphore
|
workers: asyncio.Semaphore
|
||||||
"""Semaphore to limit the number of concurrent downloads."""
|
"""Semaphore to limit the number of concurrent downloads."""
|
||||||
|
|
||||||
|
|
@ -85,8 +81,8 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
|
|
||||||
self.config = config or Config.get_instance()
|
self.config = config or Config.get_instance()
|
||||||
self._notify = EventBus.get_instance()
|
self._notify = EventBus.get_instance()
|
||||||
self.done = DataStore(type=DownloadQueue.TYPE_DONE, connection=connection)
|
self.done = DataStore(type=StoreType.DONE, connection=connection)
|
||||||
self.queue = DataStore(type=DownloadQueue.TYPE_QUEUE, connection=connection)
|
self.queue = DataStore(type=StoreType.QUEUE, connection=connection)
|
||||||
self.done.load()
|
self.done.load()
|
||||||
self.queue.load()
|
self.queue.load()
|
||||||
self.paused = asyncio.Event()
|
self.paused = asyncio.Event()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue