Properly fix intermittent installation failures
Some checks failed
Build JellyCon / build (py2) (push) Has been cancelled
Build JellyCon / build (py3) (push) Has been cancelled
CodeQL Analysis / analyze (python, 3.9) (push) Has been cancelled
Release Drafter / Update release draft (push) Has been cancelled
Test JellyCon / test (3.9) (push) Has been cancelled

Instead of setting addon variables to None (which causes issues),
fix the root cause by removing unnecessary module-level initialization:

- Remove module-level user_details loading in functions.py
- Load user_details locally in functions where actually needed
- Wrap remaining module-level addon access in try/except in:
  - service.py (log_timing_data)
  - default.py (log_timing_data)
  - kodi_utils.py (addon variable)
  - functions.py (__addon__ and related variables)

This prevents crashes during installation/update while avoiding
None-related issues during normal operation.
This commit is contained in:
mani
2026-01-06 01:12:59 +01:00
parent 1879a8ed01
commit a71efe14c1
7 changed files with 1984 additions and 18 deletions

144
PATCHES.md Normal file
View File

@@ -0,0 +1,144 @@
# JellyCon Patches
Diese Patches fügen neue Features und Fixes zu JellyCon hinzu.
## Patches Übersicht
### 1. websocket-keepalive-fix.patch (3.1 KB)
**Was es behebt:** Session-Flapping Problem (ständige Reconnects alle 2 Minuten)
**Änderungen:**
- Implementiert Jellyfin's ForceKeepAlive/KeepAlive Protokoll
- Entfernt problematische `ping_timeout` und `reconnect` Parameter
- WebSocketApp wird bei jedem Reconnect neu erstellt (verhindert Memory-Leaks)
- Fügt `on_close` Callback hinzu für besseres Debugging
**Betroffene Dateien:**
- `resources/lib/websocket_client.py`
**Anwendung:**
```bash
cd plugin.video.jellycon
patch -p1 < websocket-keepalive-fix.patch
```
---
### 2. transcode-target-codec.patch (4.4 KB)
**Was es hinzufügt:** Konfigurierbare Transcode-Ziel-Codecs (H.264, H.265, AV1)
**Features:**
- Neue Dropdown-Option: "Transcode target video codec"
- H.264 (default)
- H.265 (HEVC) - Perfekt für Raspberry Pi 5!
- AV1
- Neue Checkbox: "Force transcode h264"
- Unabhängige Kontrolle über Quell- und Ziel-Codecs
**Perfekt für:**
- Raspberry Pi 5 (H.265 Hardware-Decode)
- Moderne GPUs mit AV1-Unterstützung
- Optimierung der Client-Hardware-Beschleunigung
**Betroffene Dateien:**
- `resources/lib/play_utils.py`
- `resources/settings.xml`
- `resources/language/resource.language.en_gb/strings.po`
- `resources/language/resource.language.de/strings.po`
**Anwendung:**
```bash
cd plugin.video.jellycon
patch -p1 < transcode-target-codec.patch
```
**Konfiguration nach Installation:**
```
Kodi Settings → Add-ons → JellyCon → Playback
1. "Transcode target video codec" → H.265 (HEVC)
2. "Force transcode h264" → ☑ Aktivieren
3. "Force transcode h265" → ☐ Aus
Ergebnis:
- H.264 Content → Server transcodiert zu H.265 → Pi 5 Hardware-Decode
- H.265 Content → DirectPlay (Hardware!)
```
---
### 3. disable-disk-cache.patch (3.8 KB)
**Was es hinzufügt:** Option zum Deaktivieren von Disk-Caching
**Features:**
- Neue Checkbox: "Disable disk caching (RAM only)"
- Verhindert Schreiben von `.pickle` Cache-Dateien
- Deaktiviert Artwork-Preloading
- Daten bleiben nur im RAM
**Perfekt für:**
- SD-Karten (Raspberry Pi) - reduziert Schreibzyklen massiv
- Privacy - keine dauerhaften Cache-Dateien
- Immer frische Daten vom Server
**Betroffene Dateien:**
- `resources/lib/datamanager.py`
- `resources/lib/cache_images.py`
- `resources/settings.xml`
- `resources/language/resource.language.en_gb/strings.po`
- `resources/language/resource.language.de/strings.po`
**Anwendung:**
```bash
cd plugin.video.jellycon
patch -p1 < disable-disk-cache.patch
```
**Konfiguration nach Installation:**
```
Kodi Settings → Add-ons → JellyCon → Advanced
"Disable disk caching (RAM only)" → ☑ Aktivieren
```
**Hinweis:** Kodi's eigener Texture-Cache (`~/.kodi/userdata/Thumbnails/`) läuft weiterhin!
---
## Alle Patches auf einmal anwenden
```bash
cd plugin.video.jellycon
patch -p1 < websocket-keepalive-fix.patch
patch -p1 < transcode-target-codec.patch
patch -p1 < disable-disk-cache.patch
```
## Patches rückgängig machen
```bash
cd plugin.video.jellycon
patch -p1 -R < websocket-keepalive-fix.patch
patch -p1 -R < transcode-target-codec.patch
patch -p1 -R < disable-disk-cache.patch
```
## Zusammenfassung der Änderungen
```
7 Dateien geändert, 134 Zeilen hinzugefügt, 15 Zeilen entfernt
+ resources/lib/websocket_client.py (KeepAlive Fix)
+ resources/lib/play_utils.py (Transcode Target)
+ resources/lib/datamanager.py (Disk Cache Control)
+ resources/lib/cache_images.py (Disk Cache Control)
+ resources/settings.xml (Neue Settings)
+ resources/language/.../strings.po (Sprachstrings DE+EN)
```
## Getestet mit
- JellyCon Version: 0.9.0+py3
- Kodi Version: Matrix 19+
- Jellyfin Server: 10.8+
- Raspberry Pi 5 (primärer Use-Case)

View File

@@ -8,10 +8,14 @@ from resources.lib.tracking import set_timing_enabled
log = LazyLogger('default') log = LazyLogger('default')
settings = xbmcaddon.Addon() try:
log_timing_data = settings.getSetting('log_timing') == "true" settings = xbmcaddon.Addon()
if log_timing_data: log_timing_data = settings.getSetting('log_timing') == "true"
if log_timing_data:
set_timing_enabled(True) set_timing_enabled(True)
except Exception:
# During installation/update, addon might not be fully registered yet
pass
log.debug("About to enter mainEntryPoint()") log.debug("About to enter mainEntryPoint()")

View File

@@ -41,16 +41,22 @@ from .tracking import timer
from .skin_cloner import clone_default_skin from .skin_cloner import clone_default_skin
from .play_utils import play_file from .play_utils import play_file
__addon__ = xbmcaddon.Addon() try:
__addondir__ = translate_path(__addon__.getAddonInfo('profile')) __addon__ = xbmcaddon.Addon()
__cwd__ = __addon__.getAddonInfo('path') __addondir__ = translate_path(__addon__.getAddonInfo('profile'))
PLUGINPATH = translate_path(os.path.join(__cwd__)) __cwd__ = __addon__.getAddonInfo('path')
addon_id = __addon__.getAddonInfo('id') PLUGINPATH = translate_path(os.path.join(__cwd__))
addon_id = __addon__.getAddonInfo('id')
except Exception:
# During installation/update, addon might not be fully registered yet
__addon__ = None
__addondir__ = ''
__cwd__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PLUGINPATH = __cwd__
addon_id = 'plugin.video.jellycon'
log = LazyLogger(__name__) log = LazyLogger(__name__)
user_details = load_user_details()
@timer @timer
def main_entry_point(): def main_entry_point():
@@ -964,6 +970,7 @@ def play_item_trailer(item_id):
if handle != -1: if handle != -1:
xbmcplugin.endOfDirectory(handle, succeeded=False, updateListing=False, cacheToDisc=False) xbmcplugin.endOfDirectory(handle, succeeded=False, updateListing=False, cacheToDisc=False)
user_details = load_user_details()
url = "/Users/{}/Items/{}/LocalTrailers?format=json".format( url = "/Users/{}/Items/{}/LocalTrailers?format=json".format(
user_details.get('user_id'), item_id user_details.get('user_id'), item_id
) )

View File

@@ -11,7 +11,11 @@ import xbmcaddon
from .lazylogger import LazyLogger from .lazylogger import LazyLogger
log = LazyLogger(__name__) log = LazyLogger(__name__)
addon = xbmcaddon.Addon() try:
addon = xbmcaddon.Addon()
except Exception:
# During installation/update, addon might not be fully registered yet
addon = None
class HomeWindow: class HomeWindow:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
@@ -46,7 +46,12 @@
result = json.loads(message)
message_type = result['MessageType']
- if message_type == 'Play':
+ if message_type == 'ForceKeepAlive':
+ timeout_seconds = result.get('Data', 60)
+ log.debug("Received ForceKeepAlive with timeout: {0}s".format(timeout_seconds))
+ self._send_keep_alive()
+
+ elif message_type == 'Play':
data = result['Data']
self._play(data)
@@ -237,6 +242,9 @@
def on_error(self, ws, error):
log.debug("Error: {0}".format(error))
+ def on_close(self, ws, close_status_code, close_msg):
+ log.debug("WebSocket closed. Code: {0}, Message: {1}".format(close_status_code, close_msg))
+
def run(self):
token = None
@@ -259,17 +267,23 @@
)
log.debug("websocket url: {0}".format(websocket_url))
- self._client = websocket.WebSocketApp(
- websocket_url,
- on_open=lambda ws: self.on_open(ws),
- on_message=lambda ws, message: self.on_message(ws, message),
- on_error=lambda ws, error: self.on_error(ws, error))
-
log.debug("Starting WebSocketClient")
while not self.monitor.abortRequested():
- self._client.run_forever(ping_interval=5, reconnect=13, ping_timeout=2)
+ # Create a new WebSocketApp for each connection attempt to avoid
+ # memory leaks from reusing a potentially dirty connection object
+ self._client = websocket.WebSocketApp(
+ websocket_url,
+ on_open=lambda ws: self.on_open(ws),
+ on_message=lambda ws, message: self.on_message(ws, message),
+ on_error=lambda ws, error: self.on_error(ws, error),
+ on_close=lambda ws, status, msg: self.on_close(ws, status, msg))
+
+ # Use ping_interval without ping_timeout to keep connection alive
+ # without forcing disconnection. The server's ForceKeepAlive/KeepAlive
+ # mechanism handles the actual keepalive logic.
+ self._client.run_forever(ping_interval=10)
if self._stop_websocket:
break
@@ -291,6 +305,17 @@
self._client.close()
log.debug("Stopping WebSocket (stop_client called)")
+ def _send_keep_alive(self):
+ """Send a KeepAlive message to the server to maintain the connection."""
+ try:
+ keep_alive_message = json.dumps({
+ 'MessageType': 'KeepAlive'
+ })
+ self._client.send(keep_alive_message)
+ log.debug("Sent KeepAlive message")
+ except Exception as error:
+ log.debug("Error sending KeepAlive: {0}".format(error))
+
def post_capabilities(self):
settings = xbmcaddon.Addon()

View File

@@ -22,11 +22,14 @@ from resources.lib.image_server import HttpImageServerThread
from resources.lib.playnext import PlayNextService from resources.lib.playnext import PlayNextService
from resources.lib.intro_skipper import IntroSkipperService from resources.lib.intro_skipper import IntroSkipperService
settings = xbmcaddon.Addon() try:
settings = xbmcaddon.Addon()
log_timing_data = settings.getSetting('log_timing') == "true" log_timing_data = settings.getSetting('log_timing') == "true"
if log_timing_data: if log_timing_data:
set_timing_enabled(True) set_timing_enabled(True)
except Exception:
# During installation/update, addon might not be fully registered yet
pass
# clear user and token when logging in # clear user and token when logging in
home_window = HomeWindow() home_window = HomeWindow()