diff --git a/build-dist-windows.sh b/build-dist-windows.sh index a05d4de..b585b6a 100644 --- a/build-dist-windows.sh +++ b/build-dist-windows.sh @@ -43,7 +43,6 @@ CORE_DEPS=( "pyyaml>=6.0" "mutagen>=1.47.0" "pillow>=10.0.0" - "packaging>=23.0" ) # Windows-specific dependencies diff --git a/media_server/main.py b/media_server/main.py index 55f7190..d6fd69b 100644 --- a/media_server/main.py +++ b/media_server/main.py @@ -2,6 +2,7 @@ import argparse import logging +import socket import sys from contextlib import asynccontextmanager from pathlib import Path @@ -259,6 +260,19 @@ def main(): print("\nAuthentication is DISABLED (no tokens configured)") return + # Check if port is available before starting + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + sock.bind((args.host if args.host != "0.0.0.0" else "127.0.0.1", args.port)) + except OSError: + print( + f"ERROR: Port {args.port} is already in use. " + f"Another instance of Media Server may be running.\n" + f"Stop the other process or use --port to pick a different port.", + file=sys.stderr, + ) + sys.exit(1) + from .tray import PYSTRAY_AVAILABLE, TrayManager use_tray = PYSTRAY_AVAILABLE and not args.no_tray diff --git a/media_server/services/update_checker.py b/media_server/services/update_checker.py index a242cf6..37b451c 100644 --- a/media_server/services/update_checker.py +++ b/media_server/services/update_checker.py @@ -3,10 +3,9 @@ import asyncio import logging import re +from functools import total_ordering from typing import Any, Optional -from packaging.version import Version - from .release_provider import ReleaseProvider from .websocket_manager import ws_manager @@ -15,23 +14,67 @@ logger = logging.getLogger(__name__) _PRE_PATTERN = re.compile( r"^(\d+\.\d+\.\d+)[-.]?(alpha|beta|rc)[.-]?(\d+)$", re.IGNORECASE ) -_PRE_MAP = {"alpha": "a", "beta": "b", "rc": "rc"} +_PRE_ORDER = {"alpha": 0, "beta": 1, "rc": 2} -def _parse_version(raw: str) -> Version: - """Normalize a version tag to PEP 440 for correct comparison. +@total_ordering +class _Version: + """Lightweight PEP 440-ish version for comparison without packaging dep. + + Supports: X.Y.Z and X.Y.Z-{alpha,beta,rc}.N + Pre-releases sort before the corresponding stable release. + """ + + __slots__ = ("_release", "_pre") + + def __init__(self, release: tuple[int, ...], pre: Optional[tuple[int, int]]) -> None: + self._release = release + self._pre = pre + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _Version): + return NotImplemented + return self._release == other._release and self._pre == other._pre + + def __lt__(self, other: object) -> bool: + if not isinstance(other, _Version): + return NotImplemented + if self._release != other._release: + return self._release < other._release + # No pre-release (stable) is greater than any pre-release + if self._pre is None and other._pre is None: + return False + if self._pre is not None and other._pre is None: + return True + if self._pre is None and other._pre is not None: + return False + return self._pre < other._pre # type: ignore[operator] + + def __repr__(self) -> str: + v = ".".join(str(p) for p in self._release) + if self._pre is not None: + labels = {0: "alpha", 1: "beta", 2: "rc"} + v += f"-{labels[self._pre[0]]}.{self._pre[1]}" + return f"_Version('{v}')" + + +def _parse_version(raw: str) -> _Version: + """Parse a version tag for comparison. Examples: - v0.3.0-alpha.1 → 0.3.0a1 (pre-release, sorts below 0.3.0) - v0.3.0-rc.3 → 0.3.0rc3 - v1.0.0 → 1.0.0 + v0.3.0-alpha.1 → (0,3,0) pre=(0,1) (sorts below 0.3.0) + v0.3.0-rc.3 → (0,3,0) pre=(2,3) + v1.0.0 → (1,0,0) pre=None """ cleaned = raw.lstrip("v").strip() m = _PRE_PATTERN.match(cleaned) if m: - base, pre_label, pre_num = m.group(1), m.group(2).lower(), m.group(3) - cleaned = f"{base}{_PRE_MAP[pre_label]}{pre_num}" - return Version(cleaned) + base = tuple(int(x) for x in m.group(1).split(".")) + pre_label = m.group(2).lower() + pre_num = int(m.group(3)) + return _Version(base, (_PRE_ORDER[pre_label], pre_num)) + release = tuple(int(x) for x in cleaned.split(".")) + return _Version(release, None) class UpdateChecker: diff --git a/pyproject.toml b/pyproject.toml index 43dd227..cbfb136 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "pyyaml>=6.0", "mutagen>=1.47.0", "pillow>=10.0.0", - "packaging>=23.0", ] [project.optional-dependencies]