diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index a9535b78..e8f06278 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -47,6 +47,7 @@ from .Utils import ( get_file_sidecar, get_files, get_mime_type, + init_class, read_logfile, validate_url, validate_uuid, @@ -914,7 +915,7 @@ class HttpAPI(Common): status=web.HTTPBadRequest.status_code, ) - items.append(Condition(**item)) + items.append(init_class(Condition, item)) try: items = cls.save(items=items).load().get_all() except Exception as e: diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py index c0d06eb0..275a5b91 100644 --- a/app/library/HttpSocket.py +++ b/app/library/HttpSocket.py @@ -6,6 +6,7 @@ import os import shlex import time from datetime import UTC, datetime +from pathlib import Path import anyio import socketio @@ -289,13 +290,17 @@ class HttpSocket(Common): manual_archive = self.config.manual_archive if manual_archive: + manual_archive = Path(manual_archive) + + if not manual_archive.exists(): + manual_archive.touch(exist_ok=True) + previouslyArchived = False - if os.path.exists(manual_archive): - async with await anyio.open_file(manual_archive) as f: - async for line in f: - if idDict["archive_id"] in line: - previouslyArchived = True - break + async with await anyio.open_file(manual_archive) as f: + async for line in f: + if idDict["archive_id"] in line: + previouslyArchived = True + break if not previouslyArchived: async with await anyio.open_file(manual_archive, "a") as f: @@ -313,9 +318,7 @@ class HttpSocket(Common): "paused": self.queue.is_paused(), } - # get download folder listing. - downloadPath: str = self.config.download_path - data["folders"] = [name for name in os.listdir(downloadPath) if os.path.isdir(os.path.join(downloadPath, name))] + data["folders"] = [folder.name for folder in Path(self.config.download_path).iterdir() if folder.is_dir()] await self._notify.emit(Events.INITIAL_DATA, data=data, to=sid) @@ -355,10 +358,11 @@ class HttpSocket(Common): await self.subscribe_emit(event=event, data=data) if "log_lines" == event and self.log_task is None: - LOG.debug("Starting log tailing task.") + log_file = Path(self.config.config_path) / "logs" / "app.log" + LOG.debug(f"Starting tailing '{log_file!s}'.") self.log_task = asyncio.create_task( tail_log( - file=os.path.join(self.config.config_path, "logs", "app.log"), + file=log_file, emitter=emit_logs, ), name="tail_log", diff --git a/app/library/Notifications.py b/app/library/Notifications.py index 3c7d93af..ad616f15 100644 --- a/app/library/Notifications.py +++ b/app/library/Notifications.py @@ -1,10 +1,10 @@ import asyncio import json import logging -import os from collections.abc import Awaitable from dataclasses import dataclass, field from datetime import datetime +from pathlib import Path from typing import Any import httpx @@ -134,15 +134,15 @@ class Notification(metaclass=Singleton): config: Config = config or Config.get_instance() self._debug = config.debug - self._file: str = file or os.path.join(config.config_path, "notifications.json") + self._file: Path = Path(file) if file else (Path(config.config_path) / "notifications.json") self._client: httpx.AsyncClient = client or httpx.AsyncClient() self._encoder: Encoder = encoder or Encoder() self._version = config.version - if os.path.exists(self._file): + if self._file.exists(): try: - if "600" != oct(os.stat(self._file).st_mode)[-3:]: - os.chmod(self._file, 0o600) + if "600" != self._file.stat().st_mode: + self._file.chmod(0o600) except Exception: pass @@ -184,34 +184,30 @@ class Notification(metaclass=Singleton): Notification: The Notification instance. """ - LOG.info(f"Saving notification targets to '{self._file}'.") try: - with open(self._file, "w") as f: - json.dump([t.serialize() for t in targets], fp=f, indent=4) + self._file.write_text(json.dumps([t.serialize() for t in targets], indent=4)) + LOG.info(f"Updated '{self._file}'.") except Exception as e: LOG.exception(e) - LOG.error(f"Error saving notification targets to '{self._file}'. '{e!s}'") + LOG.error(f"Error saving '{self._file}'. '{e!s}'") return self def load(self) -> "Notification": """Load or reload notification targets from the file.""" if len(self._targets) > 0: - LOG.info("Clearing existing notification targets.") self.clear() - if not os.path.exists(self._file) or os.path.getsize(self._file) < 10: + if not self._file.exists() or self._file.stat().st_size < 1: return self targets = [] - LOG.info(f"Loading notification targets from '{self._file}'.") - try: - with open(self._file) as f: - targets = json.load(f) + LOG.info(f"Loading '{self._file}'.") + targets = json.loads(self._file.read_text()) except Exception as e: - LOG.error(f"Error loading notification targets from '{self._file}'. '{e!s}'") + LOG.error(f"Error loading '{self._file}'. '{e!s}'") for target in targets: try: @@ -227,7 +223,7 @@ class Notification(metaclass=Singleton): self._targets.append(target) LOG.info( - f"Will send {target.request.type} request on '{', '.join(target.on) if len(target.on) > 0 else 'all events'}' to '{target.name}'." + f"Send '{target.request.type}' request on '{', '.join(target.on) if len(target.on) > 0 else 'all events'}' to '{target.name}'." ) except Exception as e: LOG.error(f"Error loading notification target '{target}'. '{e!s}'") diff --git a/app/library/Utils.py b/app/library/Utils.py index b4ca56dc..01385fad 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -951,7 +951,7 @@ async def read_logfile(file: str, offset: int = 0, limit: int = 50) -> dict: return {"logs": [], "next_offset": None, "end_is_reached": True} -async def tail_log(file: str, emitter: callable, sleep_time: float = 0.5): +async def tail_log(file: pathlib.Path, emitter: callable, sleep_time: float = 0.5): """ Continuously read a log file and emit new lines. @@ -966,7 +966,7 @@ async def tail_log(file: str, emitter: callable, sleep_time: float = 0.5): from anyio import open_file - if not pathlib.Path(file).exists(): + if not file.exists(): return try: diff --git a/app/library/conditions.py b/app/library/conditions.py index 7b3a2909..f9ee2ee4 100644 --- a/app/library/conditions.py +++ b/app/library/conditions.py @@ -11,7 +11,7 @@ from .config import Config from .encoder import Encoder from .Events import EventBus, Events from .Singleton import Singleton -from .Utils import arg_converter +from .Utils import arg_converter, init_class LOG = logging.getLogger("conditions") @@ -117,12 +117,12 @@ class Conditions(metaclass=Singleton): if not self._file.exists() or self._file.stat().st_size < 1: return self - LOG.info(f"Loading '{self._file}'.") try: - with open(self._file) as f: - items = json.load(f) + LOG.info(f"Loading '{self._file}'.") + items = json.loads(self._file.read_text()) except Exception as e: - LOG.error(f"Failed to parse '{self._file}'. '{e}'.") + LOG.exception(e) + LOG.error(f"Error loading '{self._file}'. '{e}'.") return self if not items or len(items) < 1: @@ -137,15 +137,15 @@ class Conditions(metaclass=Singleton): item["id"] = str(uuid.uuid4()) need_save = True - item = Condition(**item) + item: Condition = init_class(Condition, item) self._items.append(item) except Exception as e: - LOG.error(f"Failed to parse failure condition at list position '{i}'. '{e!s}'.") + LOG.error(f"Failed to parse condition at list position '{i}'. '{e!s}'.") continue if need_save: - LOG.info("Saving failure conditions due to format, or id change.") + LOG.info("Saving conditions due to format, or id change.") self.save(self._items) return self @@ -229,7 +229,7 @@ class Conditions(metaclass=Singleton): for i, item in enumerate(items): try: if not isinstance(item, Condition): - item = Condition(**item) + item: Condition = init_class(Condition, item) items[i] = item except Exception as e: LOG.error(f"Failed to save '{i}' due to parsing error. '{e!s}'.") @@ -242,12 +242,10 @@ class Conditions(metaclass=Singleton): continue try: - with open(self._file, "w") as f: - json.dump(obj=[item.serialize() for item in items], fp=f, indent=4) - + self._file.write_text(json.dumps(obj=[item.serialize() for item in items], indent=4)) LOG.info(f"Updated '{self._file}'.") except Exception as e: - LOG.error(f"Failed to save '{self._file}'. '{e!s}'.") + LOG.error(f"Error saving '{self._file}'. '{e!s}'.") return self diff --git a/app/library/ffprobe.py b/app/library/ffprobe.py index b38da7a7..3475aea2 100644 --- a/app/library/ffprobe.py +++ b/app/library/ffprobe.py @@ -7,6 +7,7 @@ import functools import json import operator import os +from pathlib import Path import anyio @@ -271,7 +272,7 @@ async def ffprobe(file: str) -> FFProbeResult: msg = "ffprobe not found." raise OSError(msg) from e - if not os.path.isfile(file): + if not Path(file).exists(): msg = f"No such media file '{file}'." raise OSError(msg) @@ -290,7 +291,7 @@ async def ffprobe(file: str) -> FFProbeResult: if 0 == exitCode: parsed: dict = json.loads(data.decode("utf-8")) else: - msg = f"FFProbe return with non-0 exit code. '{err.decode('utf-8')}'" + msg = f"ffprobe returned with non-0 exit code. '{err.decode('utf-8')}'" raise FFProbeError(msg) result = FFProbeResult()