diff --git a/playtube/config.py b/playtube/config.py index bffbf69..be53143 100644 --- a/playtube/config.py +++ b/playtube/config.py @@ -48,6 +48,14 @@ def _app_data_dir() -> Path: return d +def app_data_dir() -> Path: + """Oeffentlicher Zugriff auf den App-Datenordner, z.B. fuer Debug-Logs - die + gepackte .exe laeuft ohne Konsolenfenster (console=False), print()-Debugging ist + dort also unsichtbar; ein Log-File ist die einzige Moeglichkeit, dort etwas + nachtraeglich einzusehen.""" + return _app_data_dir() + + def _config_path() -> Path: # Im Entwicklungsmodus liegt config.json direkt im Projektordner (leicht editierbar), # im gepackten Build im APPDATA-Ordner. diff --git a/playtube/debug_log.py b/playtube/debug_log.py new file mode 100644 index 0000000..f299d1c --- /dev/null +++ b/playtube/debug_log.py @@ -0,0 +1,44 @@ +"""Sehr einfaches, immer aktives Datei-Log fuer schwer reproduzierbare Bugs. + +Die gepackte .exe laeuft ohne Konsolenfenster (console=False in packaging/playtube.spec), +also sind print()-Debugausgaben dort unsichtbar - selbst mit PLAYTUBE_DEBUG=1 sieht der +Nutzer nichts. Dieses Modul schreibt stattdessen in eine kleine, automatisch gekappte +Log-Datei im App-Datenordner, die sich jederzeit nachtraeglich auslesen laesst. +""" +from __future__ import annotations + +import time +import traceback +from pathlib import Path + +from .config import app_data_dir + +_MAX_LINES = 500 + + +def _log_path() -> Path: + d = app_data_dir() / "logs" + d.mkdir(parents=True, exist_ok=True) + return d / "discord_rpc.log" + + +def log_line(msg: str) -> None: + """Haengt eine Zeile mit Zeitstempel an - haelt die Datei klein, indem bei + Ueberlaenge nur die letzten _MAX_LINES Zeilen behalten werden.""" + try: + path = _log_path() + timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + line = f"[{timestamp}] {msg}\n" + with open(path, "a", encoding="utf-8") as f: + f.write(line) + + lines = path.read_text(encoding="utf-8", errors="replace").splitlines() + if len(lines) > _MAX_LINES: + path.write_text("\n".join(lines[-_MAX_LINES:]) + "\n", encoding="utf-8") + except Exception: + # Logging darf niemals die App zum Absturz bringen. + pass + + +def log_exception(context: str, exc: BaseException) -> None: + log_line(f"{context}: {exc!r}\n{''.join(traceback.format_exception(exc))}") diff --git a/playtube/discord_rpc.py b/playtube/discord_rpc.py index 880f745..8937c62 100644 --- a/playtube/discord_rpc.py +++ b/playtube/discord_rpc.py @@ -16,6 +16,7 @@ from typing import Any from PySide6.QtCore import QThread from .config import APP_NAME +from .debug_log import log_line try: from pypresence.types import ActivityType @@ -199,6 +200,7 @@ class DiscordRPCWorker(QThread): self._presence = Presence(self._client_id) self._presence.connect() self._connected = True + log_line("[connect] verbunden") if os.environ.get("PLAYTUBE_DEBUG"): print("[discord-rpc] verbunden", flush=True) except Exception as exc: @@ -224,6 +226,7 @@ class DiscordRPCWorker(QThread): now = time.time() if key != self._track_key: + old_key = self._track_key self._track_key = key # current_time nur als grobe Anfangs-Schaetzung verwenden (z.B. Programm # startet waehrend ein Titel schon laeuft) - plausibilisiert, damit ein @@ -231,6 +234,7 @@ class DiscordRPCWorker(QThread): # rein ueber die Systemzeit weiterlaeuft statt ueber currentTime. offset = current_time if (duration <= 0 or 0 <= current_time <= duration) else 0 self._track_start_ts = int(now - offset) + log_line(f"[track-change] alt={old_key!r} neu={key!r} offset={offset:.1f}s") start_ts = self._track_start_ts if self._track_start_ts is not None else int(now) end_ts = start_ts + int(duration) if duration and duration > 0 else None @@ -254,11 +258,22 @@ class DiscordRPCWorker(QThread): start_ts, end_ts = self._track_timestamps(item) payload = build_presence_payload(item, self._session_start, start_ts, end_ts) self._presence.update(**payload) + log_line( + "[send] title=%r thumbnail=%r large_image=%r start=%s end=%s" + % ( + item.get("title"), + item.get("thumbnail"), + payload.get("large_image"), + start_ts, + end_ts, + ) + ) if os.environ.get("PLAYTUBE_DEBUG"): print(f"[discord-rpc] gesendet: {payload!r}", flush=True) except Exception as exc: # Discord evtl. geschlossen worden -> beim naechsten Mal neu verbinden. self._connected = False + log_line(f"[send-error] {exc!r}") if os.environ.get("PLAYTUBE_DEBUG"): print(f"[discord-rpc] Senden fehlgeschlagen: {exc!r}", flush=True)