Files
ViewIT/addon/core/router.py
itdrui.de 7b60b00c8b dev: umfangreiches Refactoring, Trakt-Integration und Code-Review-Fixes (0.1.69-dev)
Core & Architektur:
- Neues Verzeichnis addon/core/ mit router.py, trakt.py, metadata.py,
  gui.py, playstate.py, plugin_manager.py, updater.py
- Tests-Verzeichnis hinzugefügt (24 Tests, pytest + Coverage)

Trakt-Integration:
- OAuth Device Flow, Scrobbling, Watchlist, History, Calendar
- Upcoming Episodes, Weiterschauen (Continue Watching)
- Watched-Status in Episodenlisten
- _trakt_find_in_plugins() mit 5-Min-Cache

Serienstream-Suche:
- API-Ergebnisse werden immer mit Katalog-Cache ergänzt (serverseitiges 10-Treffer-Limit)
- Katalog-Cache wird beim Addon-Start im Daemon-Thread vorgewärmt
- Notification nach Cache-Load via xbmc.executebuiltin() (thread-sicher)

Bugfixes (Code-Review):
- Race Condition auf _TRAKT_WATCHED_CACHE: _TRAKT_WATCHED_CACHE_LOCK hinzugefügt
- GUI-Dialog aus Daemon-Thread: xbmcgui -> xbmc.executebuiltin()
- ValueError in Trakt-Watchlist-Routen abgesichert
- Token expires_at==0 Check korrigiert
- get_setting_bool() Kontrollfluss in gui.py bereinigt
- topstreamfilm_plugin: try-finally um xbmcvfs.File.close()

Cleanup:
- default.py.bak und refactor_router.py entfernt
- .gitignore: /tests/ Eintrag entfernt
- Type-Hints vereinheitlicht (Dict/List/Tuple -> dict/list/tuple)
2026-03-01 18:39:05 +01:00

59 lines
2.2 KiB
Python

from __future__ import annotations
import sys
from typing import Any, Callable, Dict, Optional
from urllib.parse import parse_qs
class Router:
"""A simple router for Kodi add-ons."""
def __init__(self) -> None:
self._routes: Dict[str, Callable[[Dict[str, str]], Any]] = {}
self._fallback: Optional[Callable[[Dict[str, str]], Any]] = None
def route(self, action: str) -> Callable[[Callable[[Dict[str, str]], Any]], Callable[[Dict[str, str]], Any]]:
"""Decorator to register a function for a specific action."""
def decorator(handler: Callable[[Dict[str, str]], Any]) -> Callable[[Dict[str, str]], Any]:
self._routes[action] = handler
return handler
return decorator
def fallback(self) -> Callable[[Callable[[Dict[str, str]], Any]], Callable[[Dict[str, str]], Any]]:
"""Decorator to register the fallback (default) handler."""
def decorator(handler: Callable[[Dict[str, str]], Any]) -> Callable[[Dict[str, str]], Any]:
self._fallback = handler
return handler
return decorator
def dispatch(self, action: Optional[str] = None, params: Optional[Dict[str, str]] = None) -> Any:
"""Dispatch the request to the registered handler."""
if params is None:
params = {}
handler = self._routes.get(action) if action else self._fallback
if not handler:
handler = self._fallback
if handler:
return handler(params)
raise KeyError(f"No route or fallback defined for action: {action}")
def parse_params(argv: Optional[list[str]] = None) -> dict[str, str]:
"""Parst Kodi-Plugin-Parameter aus `sys.argv[2]` oder der übergebenen Liste."""
if argv is None:
argv = sys.argv
if len(argv) <= 2 or not argv[2]:
return {}
raw_params = parse_qs(argv[2].lstrip("?"), keep_blank_values=True)
return {key: values[0] for key, values in raw_params.items()}
def parse_positive_int(value: str, *, default: int = 1) -> int:
try:
parsed = int(value)
return parsed if parsed > 0 else default
except (ValueError, TypeError):
return default