ytptube/app/main.py

188 lines
6.1 KiB
Python

#!/usr/bin/env python3
import sys
from pathlib import Path
APP_ROOT = str((Path(__file__).parent / "..").resolve())
if APP_ROOT not in sys.path:
sys.path.insert(0, APP_ROOT)
import asyncio
import logging
import sqlite3
from pathlib import Path
import caribou
import magic
from aiohttp import web
from app.library.BackgroundWorker import BackgroundWorker
from app.library.conditions import Conditions
from app.library.config import Config
from app.library.dl_fields import DLFields
from app.library.DownloadQueue import DownloadQueue
from app.library.Events import EventBus, Events
from app.library.HttpAPI import HttpAPI
from app.library.HttpSocket import HttpSocket
from app.library.Notifications import Notification
from app.library.Presets import Presets
from app.library.Scheduler import Scheduler
from app.library.Services import Services
from app.library.Tasks import Tasks
LOG = logging.getLogger("app")
MIME = magic.Magic(mime=True)
ROOT_PATH: Path = Path(__file__).parent.absolute()
class Main:
def __init__(self, is_native: bool = False):
self._config = Config.get_instance(is_native=is_native)
self._app = web.Application()
self._app.on_shutdown.append(self.on_shutdown)
self._background_worker = BackgroundWorker()
Services.get_instance().add("app", self._app)
Services.get_instance().add("background_worker", self._background_worker)
self._check_folders()
caribou.upgrade(self._config.db_file, ROOT_PATH / "migrations")
connection = sqlite3.connect(database=self._config.db_file, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA journal_mode=wal")
async def _close_connection(_):
LOG.debug("Closing database connection.")
connection.close()
LOG.debug("Database connection closed.")
try:
db_file = Path(self._config.db_file)
if "600" != oct(db_file.stat().st_mode)[-3:]:
db_file.chmod(0o600)
except Exception:
pass
self._queue = DownloadQueue(connection=connection)
self._http = HttpAPI(root_path=ROOT_PATH, queue=self._queue)
self._socket = HttpSocket(root_path=ROOT_PATH, queue=self._queue)
self._app.on_cleanup.append(_close_connection)
def _check_folders(self):
"""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)
for folder in folders:
folder = Path(folder)
try:
LOG.debug(f"Checking folder at '{folder}'.")
if not folder.exists():
LOG.info(f"Creating folder at '{folder}'.")
folder.mkdir(parents=True, exist_ok=True)
except OSError:
LOG.error(f"Could not create folder at '{folder}'.")
raise
try:
db_file = Path(self._config.db_file)
LOG.debug(f"Checking database file at '{db_file}'.")
if not db_file.exists():
LOG.info(f"Creating database file at '{db_file}'.")
db_file.touch(exist_ok=True)
except OSError as e:
LOG.error(f"Could not create database file at '{self._config.db_file}'. {e!s}")
raise
async def on_shutdown(self, _: web.Application):
await EventBus.get_instance().emit(
Events.SHUTDOWN,
data={"app": self._app},
title="Application Shutdown",
message="The application is shutting down.",
)
def start(self, host: str | None = None, port: int | None = None, cb=None):
"""
Start the application.
"""
host = host or self._config.host
port = port or self._config.port
EventBus.get_instance().sync_emit(
Events.STARTUP,
data={"app": self._app},
title="Application Startup",
message="The application is starting up.",
)
Scheduler.get_instance().attach(self._app)
self._socket.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)
Notification.get_instance().attach(self._app)
Conditions.get_instance().attach(self._app)
DLFields.get_instance().attach(self._app)
self._background_worker.attach(self._app)
EventBus.get_instance().sync_emit(
Events.LOADED,
data={"app": self._app},
title="Application Loaded",
message="The application has loaded all components.",
)
def started(_):
LOG.info("=" * 40)
LOG.info(f"YTPTube {self._config.app_version} - started on http://{host}:{port}{self._config.base_path}")
LOG.info("=" * 40)
loop = asyncio.get_event_loop()
EventBus.get_instance().sync_emit(
Events.STARTED,
data={"app": self._app},
title="Application Started",
message="The application has started successfully.",
loop=loop,
wait=False,
)
if loop and self._config.debug:
loop.set_debug(True)
loop.slow_callback_duration = 0.05
if cb:
cb()
HTTP_LOGGER = None
if self._config.access_log:
from app.library.HttpAPI import LOG as HTTP_LOGGER
HTTP_LOGGER.addFilter(
lambda record: f"GET {str(self._app.router['ping'].url_for()).rstrip('/')}" not in record.getMessage()
)
web.run_app(
self._app,
host=host,
port=port,
loop=asyncio.get_event_loop(),
access_log=HTTP_LOGGER,
print=started,
handle_signals=cb is None,
)
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
from multiprocessing import freeze_support
freeze_support()
Main().start()