diff --git a/Pipfile.lock b/Pipfile.lock index 3431b8c9..0571232f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -117,11 +117,11 @@ }, "aiosignal": { "hashes": [ - "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", - "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" + "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", + "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54" ], - "markers": "python_version >= '3.7'", - "version": "==1.3.1" + "markers": "python_version >= '3.9'", + "version": "==1.3.2" }, "anyio": { "hashes": [ @@ -142,11 +142,11 @@ }, "attrs": { "hashes": [ - "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", - "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", + "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" ], - "markers": "python_version >= '3.7'", - "version": "==24.2.0" + "markers": "python_version >= '3.8'", + "version": "==24.3.0" }, "bidict": { "hashes": [ @@ -166,11 +166,11 @@ }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "cffi": { "hashes": [ @@ -281,36 +281,36 @@ }, "debugpy": { "hashes": [ - "sha256:1339e14c7d980407248f09824d1b25ff5c5616651689f1e0f0e51bdead3ea13e", - "sha256:17c5e0297678442511cf00a745c9709e928ea4ca263d764e90d233208889a19e", - "sha256:1efbb3ff61487e2c16b3e033bc8595aea578222c08aaf3c4bf0f93fadbd662ee", - "sha256:365e556a4772d7d0d151d7eb0e77ec4db03bcd95f26b67b15742b88cacff88e9", - "sha256:3d9755e77a2d680ce3d2c5394a444cf42be4a592caaf246dbfbdd100ffcf7ae5", - "sha256:3e59842d6c4569c65ceb3751075ff8d7e6a6ada209ceca6308c9bde932bcef11", - "sha256:472a3994999fe6c0756945ffa359e9e7e2d690fb55d251639d07208dbc37caea", - "sha256:54a7e6d3014c408eb37b0b06021366ee985f1539e12fe49ca2ee0d392d9ceca5", - "sha256:5e565fc54b680292b418bb809f1386f17081d1346dca9a871bf69a8ac4071afe", - "sha256:62d22dacdb0e296966d7d74a7141aaab4bec123fa43d1a35ddcb39bf9fd29d70", - "sha256:66eeae42f3137eb428ea3a86d4a55f28da9bd5a4a3d369ba95ecc3a92c1bba53", - "sha256:6953b335b804a41f16a192fa2e7851bdcfd92173cbb2f9f777bb934f49baab65", - "sha256:7c4d65d03bee875bcb211c76c1d8f10f600c305dbd734beaed4077e902606fee", - "sha256:7e646e62d4602bb8956db88b1e72fe63172148c1e25c041e03b103a25f36673c", - "sha256:7e8b079323a56f719977fde9d8115590cb5e7a1cba2fcee0986ef8817116e7c1", - "sha256:8138efff315cd09b8dcd14226a21afda4ca582284bf4215126d87342bba1cc66", - "sha256:8e99c0b1cc7bf86d83fb95d5ccdc4ad0586d4432d489d1f54e4055bcc795f693", - "sha256:957363d9a7a6612a37458d9a15e72d03a635047f946e5fceee74b50d52a9c8e2", - "sha256:957ecffff80d47cafa9b6545de9e016ae8c9547c98a538ee96ab5947115fb3dd", - "sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f", - "sha256:b74a49753e21e33e7cf030883a92fa607bddc4ede1aa4145172debc637780040", - "sha256:c36856343cbaa448171cba62a721531e10e7ffb0abff838004701454149bc037", - "sha256:cc37a6c9987ad743d9c3a14fa1b1a14b7e4e6041f9dd0c8abf8895fe7a97b899", - "sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e", - "sha256:e46b420dc1bea64e5bbedd678148be512442bc589b0111bd799367cde051e71a", - "sha256:ff54ef77ad9f5c425398efb150239f6fe8e20c53ae2f68367eba7ece1e96226d" + "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920", + "sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5", + "sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0", + "sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851", + "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b", + "sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd", + "sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280", + "sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9", + "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1", + "sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0", + "sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7", + "sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f", + "sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458", + "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57", + "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e", + "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308", + "sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296", + "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3", + "sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1", + "sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1", + "sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e", + "sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db", + "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28", + "sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737", + "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768", + "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.8.9" + "version": "==1.8.11" }, "frozenlist": { "hashes": [ diff --git a/app/library/Emitter.py b/app/library/Emitter.py index e39e2d7b..f81a011b 100644 --- a/app/library/Emitter.py +++ b/app/library/Emitter.py @@ -45,14 +45,20 @@ class Emitter: tasks = [] for emitter in self.emitters: - _ret = emitter(event, data, **kwargs) - if _ret: - if isinstance(_ret, list): - tasks.extend(_ret) - else: - tasks.append(_ret) + try: + _ret = emitter(event, data, **kwargs) + if _ret: + if isinstance(_ret, list): + tasks.extend(_ret) + else: + tasks.append(_ret) + except Exception as e: + LOG.error(f"Emitter '{emitter}' failed with error '{e}'.") + + if len(tasks) < 1: + return try: await asyncio.wait_for(asyncio.gather(*tasks), timeout=60) except asyncio.TimeoutError: - LOG.error(f"Timed out sending event {event}.") + LOG.error(f"Timed out sending event '{event}'.") diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index 62c4f4ff..3244a2cf 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -109,7 +109,7 @@ class HttpAPI(common): contentType = self.extToMime.get(os.path.splitext(file)[1], MIME.from_file(file)) self.staticHolder[urlPath] = {'content': content, 'content_type': contentType} - LOG.debug(f'Preloading: [{urlPath}].') + LOG.debug(f"Preloading '{urlPath}'.") app.router.add_get(urlPath, self.staticFile) if urlPath.endswith('/index.html') and urlPath != '/index.html': @@ -148,7 +148,7 @@ class HttpAPI(common): self.routes.get(self.config.url_prefix[:-1])(lambda _: web.HTTPFound(self.config.url_prefix)) # add static files. - self.routes.static(f'{self.config.url_prefix}download/', self.config.download_path) + self.routes.static(f"{self.config.url_prefix}download/", self.config.download_path) self.preloadStatic(app) try: @@ -304,7 +304,7 @@ class HttpAPI(common): updated = True setattr(item.info, k, v) - LOG.info(f'Updated [{k}] to [{v}] for [{item.info.id}]') + LOG.info(f"Updated '{k}' to '{v}' for '{item.info.id}'") status = 200 if updated else 304 if updated: @@ -478,7 +478,7 @@ class HttpAPI(common): 'X-Accel-Buffering': 'no', 'Access-Control-Allow-Origin': '*', 'Pragma': 'public', - 'Cache-Control': f'public, max-age={time.time() + 31536000}', + 'Cache-Control': f"public, max-age={time.time() + 31536000}", 'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(os.path.getmtime(file_path)).timetuple()), 'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()), }) @@ -504,7 +504,7 @@ class HttpAPI(common): 'X-Accel-Buffering': 'no', 'Access-Control-Allow-Origin': '*', 'Pragma': 'public', - 'Cache-Control': f'public, max-age={time.time() + 31536000}', + 'Cache-Control': f"public, max-age={time.time() + 31536000}", 'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(os.path.getmtime(file_path)).timetuple()), 'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()), }) diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py index 6e0a9957..c9e31131 100644 --- a/app/library/HttpSocket.py +++ b/app/library/HttpSocket.py @@ -13,7 +13,7 @@ from .config import Config from .DownloadQueue import DownloadQueue from .common import common from .encoder import Encoder -from .Utils import isDownloaded, load_file +from .Utils import isDownloaded from .Emitter import Emitter LOG = logging.getLogger('socket') @@ -65,7 +65,7 @@ class HttpSocket(common): return try: - LOG.info(f'Cli command from {sid=}: {data}') + LOG.info(f"Cli command from client '{sid}'. '{data}'") args = ['yt-dlp'] + shlex.split(data) _env = os.environ.copy() @@ -93,7 +93,7 @@ class HttpSocket(common): try: os.close(slave_fd) except Exception as e: - LOG.error(f'Error closing PTY: {str(e)}') + LOG.error(f"Error closing PTY. '{str(e)}'.") async def read_pty(): loop = asyncio.get_running_loop() @@ -126,7 +126,7 @@ class HttpSocket(common): try: os.close(master_fd) except Exception as e: - LOG.error(f'Error closing PTY: {str(e)}') + LOG.error(f"Error closing PTY. '{str(e)}'.") # Start reading output from PTY read_task = asyncio.create_task(read_pty()) @@ -139,7 +139,7 @@ class HttpSocket(common): await self.emitter.emit('cli_close', {'exitcode': returncode}, to=sid) except Exception as e: - LOG.error(f'CLI Error for {sid=}: {str(e)}') + LOG.error(f"CLI execute exception was thrown for client '{sid}'. {str(e)}") await self.emitter.emit('cli_out1put', { 'type': 'stderr', 'line': str(e), @@ -152,7 +152,7 @@ class HttpSocket(common): quality: str = data.get('quality') if not url: - self.emitter.warning('No URL provided.') + self.emitter.warning('No URL provided.', to=sid) return format: str = data.get('format') @@ -178,7 +178,7 @@ class HttpSocket(common): @ws_event async def item_cancel(self, sid: str, id: str): if not id: - await self.emitter.warning('Invalid request.') + await self.emitter.warning('Invalid request.', to=sid) return status: dict[str, str] = {} @@ -190,7 +190,7 @@ class HttpSocket(common): @ws_event async def item_delete(self, sid: str, id: str): if not id: - await self.emitter.warning('Invalid request.') + await self.emitter.warning('Invalid request.', to=sid) return status: dict[str, str] = {} @@ -230,30 +230,22 @@ class HttpSocket(common): with open(manual_archive, 'a') as f: f.write(f"{idDict['archive_id']} - at: {datetime.now().isoformat()}\n") - LOG.info(f'Archiving item: {data["url"]=}') + LOG.info(f"Archiving url '{data['url']}' with id '{idDict['archive_id']}'.") @ws_event async def connect(self, sid: str, _=None): data: dict = { **self.queue.get(), "config": self.config.frontend(), - "tasks": [], + "tasks": self.config.tasks, "presets": [], + "directories": [], } - if os.path.exists(os.path.join(self.config.config_path, 'tasks.json')): - try: - (tasks, status, error) = load_file(os.path.join(self.config.config_path, 'tasks.json'), list) - if status is False: - LOG.error(f"Could not load tasks file. Error message '{error}'.") - else: - data['tasks'] = tasks - - except Exception as e: - pass - # get directory listing dlDir: str = self.config.download_path - data['directories'] = [name for name in os.listdir(dlDir) if os.path.isdir(os.path.join(dlDir, name))] + data['directories'] = [ + name for name in os.listdir(dlDir) if os.path.isdir(os.path.join(dlDir, name)) + ] await self.emitter.emit('initial_data', data, to=sid) diff --git a/app/library/config.py b/app/library/config.py index e710eda4..62215ab0 100644 --- a/app/library/config.py +++ b/app/library/config.py @@ -64,9 +64,13 @@ class Config: auth_password: str = None ytdlp_version: str = YTDLP_VERSION + tasks: list = [] _manual_vars: tuple = ('temp_path', 'config_path', 'download_path',) - _immutable: tuple = ('version', '__instance', 'ytdl_options', 'new_version_available', 'ytdlp_version', 'started') + _immutable: tuple = ( + 'version', '__instance', 'ytdl_options', 'tasks', + 'new_version_available', 'ytdlp_version', 'started', + ) _int_vars: tuple = ('port', 'max_workers', 'socket_timeout', 'extract_info_timeout', 'debugpy_port',) _boolean_vars: tuple = ('keep_archive', 'ytdl_debug', 'debug', 'temp_keep', 'allow_manifestless',) @@ -122,7 +126,7 @@ class Config: for key in re.findall(r'\{.*?\}', v): localKey: str = key[1:-1] if localKey not in self.__dict__: - logging.error(f'Config variable "{k}" had non existing config reference "{key}"') + logging.error(f"Config variable '{k}' had non-existing config reference '{key}'.") sys.exit(1) v = v.replace(key, getattr(self, localKey)) @@ -131,7 +135,7 @@ class Config: if k in self._boolean_vars: if str(v).lower() not in (True, False, 'true', 'false', 'on', 'off', '1', '0'): - raise ValueError(f'Config variable "{k}" is set to a non-boolean value "{v}".') + raise ValueError(f"Config variable '{k}' is set to a non-boolean value '{v}'.") setattr(self, k, str(v).lower() in (True, 'true', 'on', '1')) @@ -143,7 +147,7 @@ class Config: numeric_level = getattr(logging, self.log_level.upper(), None) if not isinstance(numeric_level, int): - raise ValueError(f"Invalid log level: {self.log_level}") + raise ValueError(f"Invalid log level '{self.log_level}' specified.") coloredlogs.install( level=numeric_level, @@ -157,36 +161,48 @@ class Config: try: import debugpy debugpy.listen(("0.0.0.0", self.debugpy_port)) - LOG.info(f"starting debugpy server on [0.0.0.0:{self.debugpy_port}]") + LOG.info(f"starting debugpy server on '0.0.0.0:{self.debugpy_port}'.") except ImportError: - LOG.error("debugpy not found, please install it with 'pip install debugpy'") + LOG.error("debugpy package not found, please install it with 'pip install debugpy'.") except Exception as e: - LOG.error(f"Error starting debugpy server at [0.0.0.0:{self.debugpy_port}]: {e}") + LOG.error(f"Error starting debugpy server at '0.0.0.0:{self.debugpy_port}'. {e}") optsFile: str = os.path.join(self.config_path, 'ytdlp.json') if os.path.exists(optsFile) and os.path.getsize(optsFile) > 0: - LOG.info(f'Loading yt-dlp custom options from "{optsFile}"') + LOG.info(f"Loading yt-dlp custom options from '{optsFile}'.") (opts, status, error) = load_file(optsFile, dict) if not status: - LOG.error(f'Error loading "{optsFile}": {error}') + LOG.error(f"Could not load yt-dlp custom options from '{optsFile}'. {error}") sys.exit(1) self.ytdl_options.update(opts) else: - LOG.info(f'No custom yt-dlp options found in "{self.config_path}"') + LOG.info(f"No yt-dlp custom options found at '{optsFile}'.") + + tasksFile = os.path.join(self.config_path, 'tasks.json') + if os.path.exists(tasksFile) and os.path.getsize(tasksFile) > 0: + LOG.info(f"Loading tasks from '{tasksFile}'.") + try: + (tasks, status, error) = load_file(tasksFile, list) + if not status: + LOG.error(f"Could not load tasks file from '{tasksFile}'. '{error}'.") + sys.exit(1) + self.tasks.extend(tasks) + except Exception as e: + pass self.ytdl_options['socket_timeout'] = self.socket_timeout if self.keep_archive: - LOG.info(f'keep archive: {self.keep_archive}') - self.ytdl_options['download_archive'] = os.path.join( - self.config_path, 'archive.log') + LOG.info(f"keep archive option is enabled.") + self.ytdl_options['download_archive'] = os.path.join(self.config_path, 'archive.log') - LOG.info(f'Keep temp: {self.temp_keep}') + if self.temp_keep: + LOG.info(f'Keep temp files option is enabled.') if self.auth_password and self.auth_username: - LOG.warn(f"Basic authentication enabled with username: '{self.auth_username}'.") + LOG.warning(f"Basic authentication enabled with username '{self.auth_username}'.") self.started = time.time() diff --git a/app/main.py b/app/main.py index d75bceac..fc110dad 100644 --- a/app/main.py +++ b/app/main.py @@ -3,7 +3,6 @@ import asyncio import os import random -import socketio import logging import caribou import sqlite3 @@ -11,7 +10,6 @@ import magic from datetime import datetime from aiohttp import web from pathlib import Path -from library.Utils import load_file from library.Emitter import Emitter from library.config import Config from library.encoder import Encoder @@ -21,17 +19,13 @@ from library.HttpSocket import HttpSocket from library.HttpAPI import HttpAPI from aiocron import crontab -LOG = logging.getLogger('app') +LOG = logging.getLogger("app") MIME = magic.Magic(mime=True) class main: config: Config = None app: web.Application = None - sio: socketio.AsyncServer = None - routes: web.RouteTableDef = None - loop: asyncio.AbstractEventLoop = None - appLoader: str = None http: HttpAPI = None socket: HttpSocket = None @@ -42,11 +36,11 @@ class main: self.encoder = Encoder() self.checkDirectories() - caribou.upgrade(self.config.db_file, os.path.join(self.rootPath, 'migrations')) + caribou.upgrade(self.config.db_file, os.path.join(self.rootPath, "migrations")) connection = sqlite3.connect(database=self.config.db_file, isolation_level=None) connection.row_factory = sqlite3.Row - connection.execute('PRAGMA journal_mode=wal') + connection.execute("PRAGMA journal_mode=wal") emitter = Emitter() @@ -56,96 +50,91 @@ class main: self.http = HttpAPI(queue=queue, emitter=emitter, encoder=self.encoder) self.socket = HttpSocket(queue=queue, emitter=emitter, encoder=self.encoder) - WebhookFile = os.path.join(self.config.config_path, 'webhooks.json') + WebhookFile = os.path.join(self.config.config_path, "webhooks.json") if os.path.exists(WebhookFile): emitter.add_emitter(Webhooks(WebhookFile).emit) def checkDirectories(self) -> None: try: - LOG.debug(f'Checking download folder at [{self.config.download_path}]') + LOG.debug(f"Checking download folder at '{self.config.download_path}'.") if not os.path.exists(self.config.download_path): - LOG.info(f'Creating download folder at [{self.config.download_path}]') + LOG.info(f"Creating download folder at '{self.config.download_path}'.") os.makedirs(self.config.download_path, exist_ok=True) except OSError as e: - LOG.error(f'Could not create download folder at [{self.config.download_path}]') + LOG.error( + f"Could not create download folder at '{self.config.download_path}'." + ) raise e try: - LOG.debug(f'Checking temp folder at [{self.config.temp_path}]') + LOG.debug(f"Checking temp folder at '{self.config.temp_path}'.") if not os.path.exists(self.config.temp_path): - LOG.info(f'Creating temp folder at [{self.config.temp_path}]') + LOG.info(f"Creating temp folder at '{self.config.temp_path}'.") os.makedirs(self.config.temp_path, exist_ok=True) except OSError as e: - LOG.error(f'Could not create temp folder at [{self.config.temp_path}]') + LOG.error(f"Could not create temp folder at '{self.config.temp_path}'.") raise e try: - LOG.debug(f'Checking config folder at [{self.config.config_path}]') + LOG.debug(f"Checking config folder at '{self.config.config_path}'.") if not os.path.exists(self.config.config_path): - LOG.info(f'Creating config folder at [{self.config.config_path}]') + LOG.info(f"Creating config folder at '{self.config.config_path}'.") os.makedirs(self.config.config_path, exist_ok=True) except OSError as e: - LOG.error(f'Could not create config folder at [{self.config.config_path}]') + LOG.error(f"Could not create config folder at '{self.config.config_path}'.") raise e try: - LOG.debug(f'Checking database file at [{self.config.db_file}]') + LOG.debug(f"Checking database file at '{self.config.db_file}'.") if not os.path.exists(self.config.db_file): - LOG.info(f'Creating database file at [{self.config.db_file}]') - with open(self.config.db_file, 'w') as _: + LOG.info(f"Creating database file at '{self.config.db_file}'.") + with open(self.config.db_file, "w") as _: pass except OSError as e: - LOG.error(f'Could not create database file at [{self.config.db_file}]') + LOG.error(f"Could not create database file at '{self.config.db_file}'.") raise e - def load_tasks(self): - tasks_file: str = os.path.join(self.config.config_path, 'tasks.json') - if not os.path.exists(tasks_file): - LOG.info(f'No tasks file found at {tasks_file}. Skipping Tasks.') - return - + async def cron_runner(self, task: dict): try: - (tasks, status, error) = load_file(tasks_file, list) - if status is False: - raise Exception(error) + taskName = task.get("name", task.get("url")) + timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + LOG.info(f"Started 'Task: {taskName}' at '{timeNow}'.") + await self.socket.add( + url=task.get("url"), + quality=task.get("quality", "best"), + format=task.get("format", "any"), + folder=task.get("folder"), + ytdlp_cookies=task.get("ytdlp_cookies"), + ytdlp_config=task.get("ytdlp_config"), + output_template=task.get("output_template"), + ) + timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + LOG.info(f"Completed 'Task: {taskName}' at '{timeNow}'.") except Exception as e: - LOG.error(f"Could not load tasks file '{tasks_file}'. Error message '{str(e)}'. Skipping Tasks.") - return + timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + LOG.error( + f"Failed 'Task: {taskName}' at '{timeNow}'. Error message '{str(e)}'." + ) - for task in tasks: - if not task.get('url'): - LOG.warning(f'Invalid task {task}.') + def load_tasks(self): + for task in self.config.tasks: + if not task.get("url"): + LOG.warning(f"Invalid task '{task}'. No URL found.") continue - cron_timer: str = task.get('timer', f'{random.randint(1,59)} */1 * * *') - - async def cron_runner(task: dict): - taskName = task.get("name", task.get("url")) - timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - LOG.info(f'Started [Task: {taskName}] at [{timeNow}].') - - await self.add( - url=task.get('url'), - quality=task.get('quality', 'best'), - format=task.get('format', 'any'), - folder=task.get('folder'), - ytdlp_cookies=task.get('ytdlp_cookies'), - ytdlp_config=task.get('ytdlp_config'), - output_template=task.get('output_template') - ) - - timeNow = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - LOG.info(f'Completed [Task: {taskName}] at [{timeNow}].') + cron_timer: str = task.get("timer", f"{random.randint(1,59)} */1 * * *") crontab( spec=cron_timer, - func=cron_runner, + func=self.cron_runner, args=(task,), start=True, - loop=self.loop + loop=asyncio.get_event_loop(), ) - LOG.info(f'Added [Task: {task.get("name", task.get("url"))}] executed every [{cron_timer}].') + LOG.info( + f"Added 'Task: {task.get('name', task.get('url'))}' to be executed every '{cron_timer}'." + ) def start(self): self.socket.attach(self.app) @@ -153,7 +142,7 @@ class main: self.load_tasks() - start: str = f'YTPTube v{self.config.version} - listening on http://{self.config.host}:{self.config.port}' + start: str = f"YTPTube v{self.config.version} - started on http://{self.config.host}:{self.config.port}" web.run_app( self.app, host=self.config.host, @@ -161,10 +150,10 @@ class main: reuse_port=True, loop=asyncio.get_event_loop(), access_log=None, - print=lambda _: LOG.info(start) + print=lambda _: LOG.info(start), ) -if __name__ == '__main__': +if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) main().start()