Files
manga-mover-and-metadata-co…/main.py
T
2026-05-26 21:03:37 +02:00

130 lines
4.5 KiB
Python

"""
main.py
=======
Container entry point. Watches the mounted Suwayomi download directory
and, after a quiet period, triggers SuwayomiMover (which also runs the
Kavita person sync for every processed series).
Mount points (Docker)
---------------------
/mnt/suwayomi -> Suwayomi downloads (read/write, sources deleted)
/mnt/kavita -> Kavita library (read/write, CBZs written here)
Environment variables
---------------------
Required:
KAVITA_URL base URL of the Kavita server, e.g. http://kavita:5000
KAVITA_API_KEY Kavita API key (Settings → User → API key)
Optional:
SUWAYOMI_PATH default /mnt/suwayomi
KAVITA_PATH default /mnt/kavita
LANGUAGE default en
SETTLE_SECONDS default 600 (10-minute quiet window)
REQUEST_TIMEOUT default 30
DELETE_SOURCE default true (delete source folders after pack)
MATCH_PATH default /config/matches.json
WEB_PORT default 8080 (Flask web UI for matches.json)
WEB_HOST default 0.0.0.0
"""
from __future__ import annotations
import os
import signal
import sys
from pathlib import Path
# Make src/ importable when running as `python main.py`.
sys.path.insert(0, str(Path(__file__).resolve().parent / "src"))
from src.SuwayomiMover import SuwayomiMover # noqa: E402
from src.SuwayomiFolderWatcher import SuwayomiFolderWatcher # noqa: E402
from src.MatchesCache import MatchesCache # noqa: E402
from src.MatchesWebApp import MatchesWebApp # noqa: E402
def _env_str(name: str, default: "str | None" = None,
required: bool = False) -> "str | None":
value = os.environ.get(name, default)
if required and not value:
print(f"[main] missing required env var: {name}", flush=True)
sys.exit(2)
return value
def _env_int(name: str, default: int) -> int:
raw = os.environ.get(name)
if raw is None or raw == "":
return default
try:
return int(raw)
except ValueError:
print(f"[main] {name}={raw!r} is not a valid integer; "
f"falling back to {default}", flush=True)
return default
def _env_bool(name: str, default: bool) -> bool:
raw = os.environ.get(name)
if raw is None:
return default
return raw.strip().lower() in ("1", "true", "yes", "y", "on")
def main() -> int:
suwayomi_path = _env_str("SUWAYOMI_PATH", r"M:\config\downloads\mangas")
kavita_path = _env_str("KAVITA_PATH", "/mnt/kavita")
kavita_url = _env_str("KAVITA_URL", "http://kavita:5000")
kavita_api_key = _env_str("KAVITA_API_KEY", "")
language = _env_str("LANGUAGE", "en") or "en"
settle_seconds = _env_int("SETTLE_SECONDS", 600)
request_timeout = _env_int("REQUEST_TIMEOUT", 30)
delete_source = _env_bool("DELETE_SOURCE", True)
match_path = _env_str("MATCH_PATH", "matches.json")
web_host = _env_str("WEB_HOST", "0.0.0.0") or "0.0.0.0"
web_port = _env_int("WEB_PORT", 8080)
print(f"[main] suwayomi = {suwayomi_path}", flush=True)
print(f"[main] kavita = {kavita_path}", flush=True)
print(f"[main] kavita url= {kavita_url}", flush=True)
print(f"[main] settle = {settle_seconds}s", flush=True)
print(f"[main] language = {language}", flush=True)
print(f"[main] delete src= {delete_source}", flush=True)
print(f"[main] match path= {match_path}", flush=True)
print(f"[main] web = {web_host}:{web_port}", flush=True)
matches_cache = MatchesCache(match_path)
mover = SuwayomiMover(
suwayomi_path, kavita_path,
kavita_base_url=kavita_url,
kavita_api_key=kavita_api_key,
language=language,
request_timeout=request_timeout,
delete_source=delete_source,
matches_cache=matches_cache,
)
# watcher = SuwayomiFolderWatcher(suwayomi_path, mover, settle_seconds=settle_seconds)
web_app = MatchesWebApp(matches_cache, mover=mover, host=web_host, port=web_port)
web_app.start()
# def shutdown(signum, _frame):
# print(f"[main] received signal {signum}", flush=True)
# watcher.stop()
#
# signal.signal(signal.SIGTERM, shutdown)
# signal.signal(signal.SIGINT, shutdown)
#
# watcher.start()
# watcher.wait() # blocks until stop() is called via a signal
web_app.wait() # keep process alive while the watcher is disabled
return 0
if __name__ == "__main__":
sys.exit(main())