jellyfin-kodi/jellyfin_kodi/entrypoint/service.py

535 lines
17 KiB
Python
Raw Normal View History

2018-09-06 08:36:32 +00:00
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
2018-09-06 08:36:32 +00:00
#################################################################################################
import json
import sys
from datetime import datetime
2024-06-11 03:27:14 +00:00
from importlib import reload
2018-09-06 08:36:32 +00:00
# Workaround for threads using datetime: _striptime is locked
import _strptime # noqa:F401
2024-06-11 05:10:57 +00:00
import xbmc
import xbmcgui
2018-09-06 08:36:32 +00:00
2021-10-10 18:38:25 +00:00
from .. import objects
from .. import connect
from .. import client
from .. import library
from .. import monitor
from ..views import Views
2024-06-10 09:19:47 +00:00
from ..helper import (
translate,
window,
settings,
event,
dialog,
set_addon_mode,
LazyLogger,
)
2021-10-10 18:38:25 +00:00
from ..helper.utils import JsonDebugPrinter, translate_path
from ..helper.xmls import verify_kodi_defaults
2021-10-10 18:38:25 +00:00
from ..jellyfin import Jellyfin
2018-09-06 08:36:32 +00:00
#################################################################################################
2020-04-19 01:05:59 +00:00
LOG = LazyLogger(__name__)
2018-09-06 08:36:32 +00:00
#################################################################################################
class Service(xbmc.Monitor):
running = True
library_thread = None
monitor = None
play_event = None
warn = True
2024-06-10 09:19:47 +00:00
settings = {
"last_progress": datetime.today(),
"last_progress_report": datetime.today(),
}
2018-09-06 08:36:32 +00:00
def __init__(self):
2024-06-10 09:19:47 +00:00
window("jellyfin_should_stop", clear=True)
self.settings["addon_version"] = client.get_version()
self.settings["profile"] = translate_path("special://profile")
self.settings["mode"] = settings("useDirectPaths")
self.settings["log_level"] = settings("logLevel") or "1"
self.settings["auth_check"] = True
self.settings["enable_context"] = settings("enableContext.bool")
self.settings["enable_context_transcode"] = settings(
"enableContextTranscode.bool"
)
self.settings["kodi_companion"] = settings("kodiCompanion.bool")
window("jellyfin_kodiProfile", value=self.settings["profile"])
settings("platformDetected", client.get_platform())
if self.settings["enable_context"]:
window("jellyfin_context.bool", True)
if self.settings["enable_context_transcode"]:
window("jellyfin_context_transcode.bool", True)
2018-09-06 08:36:32 +00:00
LOG.info("--->>>[ %s ]", client.get_addon_name())
LOG.info("Version: %s", client.get_version())
2024-06-10 09:19:47 +00:00
LOG.info("KODI Version: %s", xbmc.getInfoLabel("System.BuildVersion"))
LOG.info("Platform: %s", settings("platformDetected"))
LOG.info("Python Version: %s", sys.version)
2024-06-10 09:19:47 +00:00
LOG.info("Using dynamic paths: %s", settings("useDirectPaths") == "0")
LOG.info("Log Level: %s", self.settings["log_level"])
2018-09-06 08:36:32 +00:00
verify_kodi_defaults()
2024-06-10 09:19:47 +00:00
window("jellyfin.connected.bool", True)
settings("groupedSets.bool", objects.utils.get_grouped_set())
xbmc.Monitor.__init__(self)
2018-09-06 08:36:32 +00:00
def service(self):
2024-06-10 09:19:47 +00:00
"""Keeps the service monitor going.
Exit on Kodi shutdown or profile switch.
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if profile switch happens more than once,
Threads depending on abortRequest will not trigger.
"""
2018-09-06 08:36:32 +00:00
self.monitor = monitor.Monitor()
player = self.monitor.player
2018-09-06 08:36:32 +00:00
self.connect = connect.Connect()
self.start_default()
2024-06-10 09:19:47 +00:00
self.settings["mode"] = settings("useDirectPaths")
2018-09-06 08:36:32 +00:00
while self.running:
2024-06-10 09:19:47 +00:00
if window("jellyfin_online.bool"):
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if self.settings["profile"] != window("jellyfin_kodiProfile"):
LOG.info("[ profile switch ] %s", self.settings["profile"])
2018-09-06 08:36:32 +00:00
break
2024-06-10 09:19:47 +00:00
if player.isPlaying() and player.is_playing_file(
player.get_playing_file()
):
difference = datetime.today() - self.settings["last_progress"]
2018-09-06 08:36:32 +00:00
2018-09-06 22:28:51 +00:00
if difference.seconds > 10:
2024-06-10 09:19:47 +00:00
self.settings["last_progress"] = datetime.today()
2018-10-01 02:13:22 +00:00
2024-06-10 09:19:47 +00:00
update = (
datetime.today() - self.settings["last_progress_report"]
).seconds > 250
event("ReportProgressRequested", {"Report": update})
2018-09-09 02:05:41 +00:00
if update:
2024-06-10 09:19:47 +00:00
self.settings["last_progress_report"] = datetime.today()
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if window("jellyfin.restart.bool"):
2024-06-10 09:19:47 +00:00
window("jellyfin.restart", clear=True)
dialog(
"notification",
heading="{jellyfin}",
message=translate(33193),
icon="{jellyfin}",
time=1000,
sound=False,
)
2024-06-10 09:19:47 +00:00
raise Exception("RestartService")
2018-09-06 08:36:32 +00:00
if self.waitForAbort(1):
break
self.shutdown()
2019-01-09 21:46:22 +00:00
raise Exception("ExitService")
2018-09-06 08:36:32 +00:00
def start_default(self):
try:
self.connect.register()
2024-06-10 09:19:47 +00:00
if not settings("SyncInstallRunDone.bool"):
set_addon_mode()
2018-09-06 08:36:32 +00:00
except Exception as error:
2019-07-09 20:05:28 +00:00
LOG.exception(error)
2018-09-06 08:36:32 +00:00
def stop_default(self):
2024-06-10 09:19:47 +00:00
window("jellyfin_online", clear=True)
Jellyfin().close()
2018-09-06 08:36:32 +00:00
if self.library_thread is not None:
self.library_thread.stop_client()
self.library_thread = None
def onNotification(self, sender, method, data):
2024-06-10 09:19:47 +00:00
"""All notifications are sent via NotifyAll built-in or Kodi.
Central hub.
"""
if sender.lower() not in ("plugin.video.jellyfin", "xbmc"):
2018-09-06 08:36:32 +00:00
return
2024-06-10 09:19:47 +00:00
if sender == "plugin.video.jellyfin":
method = method.split(".")[1]
if method not in (
"ServerUnreachable",
"ServerShuttingDown",
"UserDataChanged",
"ServerConnect",
"LibraryChanged",
"ServerOnline",
"SyncLibrary",
"RepairLibrary",
"RemoveLibrary",
"SyncLibrarySelection",
"RepairLibrarySelection",
"AddServer",
"Unauthorized",
"UserConfigurationUpdated",
"ServerRestarting",
"RemoveServer",
"UpdatePassword",
"AddLibrarySelection",
"RemoveLibrarySelection",
):
2018-09-06 08:36:32 +00:00
return
data = json.loads(data)[0]
else:
2024-06-10 09:19:47 +00:00
if method not in ("System.OnQuit", "System.OnSleep", "System.OnWake"):
2018-09-06 08:36:32 +00:00
return
data = json.loads(data)
LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data))
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if method == "ServerOnline":
if data.get("ServerId") is None:
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
window("jellyfin_online.bool", True)
self.settings["auth_check"] = True
2018-09-06 08:36:32 +00:00
self.warn = True
2024-06-10 09:19:47 +00:00
if settings("connectMsg.bool"):
users = [
user
for user in (settings("additionalUsers") or "").split(",")
if user
]
users.insert(0, settings("username"))
dialog(
"notification",
heading="{jellyfin}",
message="%s %s" % (translate(33000), ", ".join(users)),
icon="{jellyfin}",
time=1500,
sound=False,
)
2018-10-07 22:55:34 +00:00
2018-09-06 08:36:32 +00:00
if self.library_thread is None:
self.library_thread = library.Library(self)
self.library_thread.start()
2024-06-10 09:19:47 +00:00
elif method in ("ServerUnreachable", "ServerShuttingDown"):
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if self.warn or data.get("ServerId"):
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
self.warn = data.get("ServerId") is not None
dialog(
"notification",
heading="{jellyfin}",
message=(
translate(33146)
if data.get("ServerId") is None
else translate(33149)
),
icon=xbmcgui.NOTIFICATION_ERROR,
)
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if data.get("ServerId") is None:
2018-09-06 08:36:32 +00:00
self.stop_default()
2019-01-14 08:05:37 +00:00
if self.waitForAbort(120):
2018-09-06 08:36:32 +00:00
return
2019-07-08 00:26:54 +00:00
2018-09-06 08:36:32 +00:00
self.start_default()
2024-06-10 09:19:47 +00:00
elif method == "Unauthorized":
dialog(
"notification",
heading="{jellyfin}",
message=(
translate(33147) if data["ServerId"] is None else translate(33148)
),
icon=xbmcgui.NOTIFICATION_ERROR,
)
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if data.get("ServerId") is None and self.settings["auth_check"]:
2024-06-10 09:19:47 +00:00
self.settings["auth_check"] = False
self.stop_default()
if self.waitForAbort(5):
return
2019-07-08 00:26:54 +00:00
self.start_default()
2024-06-10 09:19:47 +00:00
elif method == "ServerRestarting":
if data.get("ServerId"):
2018-09-06 08:36:32 +00:00
return
2019-07-08 00:26:54 +00:00
2024-06-10 09:19:47 +00:00
if settings("restartMsg.bool"):
dialog(
"notification",
heading="{jellyfin}",
message=translate(33006),
icon="{jellyfin}",
)
2018-09-06 08:36:32 +00:00
self.stop_default()
2018-10-01 02:13:22 +00:00
if self.waitForAbort(15):
2018-09-06 08:36:32 +00:00
return
2019-07-08 00:26:54 +00:00
2018-09-06 08:36:32 +00:00
self.start_default()
2024-06-10 09:19:47 +00:00
elif method == "ServerConnect":
self.connect.register(data["Id"])
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
2024-06-10 09:19:47 +00:00
elif method == "AddServer":
2018-09-06 08:36:32 +00:00
self.connect.setup_manual_server()
xbmc.executebuiltin("Container.Refresh")
2024-06-10 09:19:47 +00:00
elif method == "RemoveServer":
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
self.connect.remove_server(data["Id"])
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
2024-06-10 09:19:47 +00:00
elif method == "UpdatePassword":
self.connect.setup_login_manual()
2024-06-10 09:19:47 +00:00
elif method == "UserDataChanged" and self.library_thread:
if data.get("ServerId") or not window("jellyfin_startup.bool"):
2018-09-06 08:36:32 +00:00
return
2018-09-13 00:43:51 +00:00
LOG.info("[ UserDataChanged ] %s", data)
2024-06-10 09:19:47 +00:00
self.library_thread.userdata(data["UserDataList"])
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
elif method == "LibraryChanged" and self.library_thread:
if data.get("ServerId") or not window("jellyfin_startup.bool"):
2018-09-06 08:36:32 +00:00
return
2018-09-13 00:43:51 +00:00
LOG.info("[ LibraryChanged ] %s", data)
2024-06-10 09:19:47 +00:00
self.library_thread.updated(data["ItemsUpdated"] + data["ItemsAdded"])
self.library_thread.removed(data["ItemsRemoved"])
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
elif method == "System.OnQuit":
window("jellyfin_should_stop.bool", True)
2018-09-06 08:36:32 +00:00
self.running = False
2024-06-10 09:19:47 +00:00
elif method in (
"SyncLibrarySelection",
"RepairLibrarySelection",
"AddLibrarySelection",
"RemoveLibrarySelection",
):
self.library_thread.select_libraries(method)
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
elif method == "SyncLibrary":
if not data.get("Id"):
2018-09-08 09:01:14 +00:00
return
2024-06-10 09:19:47 +00:00
self.library_thread.add_library(data["Id"], data.get("Update", False))
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
2024-06-10 09:19:47 +00:00
elif method == "RepairLibrary":
if not data.get("Id"):
2018-09-08 09:01:14 +00:00
return
2024-06-10 09:19:47 +00:00
libraries = data["Id"].split(",")
2018-09-06 08:36:32 +00:00
for lib in libraries:
2019-01-25 15:30:30 +00:00
if not self.library_thread.remove_library(lib):
return
2019-07-08 00:26:54 +00:00
2024-06-10 09:19:47 +00:00
self.library_thread.add_library(data["Id"])
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
2024-06-10 09:19:47 +00:00
elif method == "RemoveLibrary":
libraries = data["Id"].split(",")
2018-09-06 08:36:32 +00:00
for lib in libraries:
2019-07-08 00:26:54 +00:00
2019-01-25 15:30:30 +00:00
if not self.library_thread.remove_library(lib):
return
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
2024-06-10 09:19:47 +00:00
elif method == "System.OnSleep":
2019-07-08 00:26:54 +00:00
2018-09-06 08:36:32 +00:00
LOG.info("-->[ sleep ]")
2024-06-10 09:19:47 +00:00
window("jellyfin_should_stop.bool", True)
2018-09-06 08:36:32 +00:00
if self.library_thread is not None:
self.library_thread.stop_client()
self.library_thread = None
Jellyfin.close_all()
2018-10-01 02:13:22 +00:00
self.monitor.server = []
self.monitor.sleep = True
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
elif method == "System.OnWake":
2018-09-06 08:36:32 +00:00
if not self.monitor.sleep:
LOG.warning("System.OnSleep was never called, skip System.OnWake")
return
2018-09-06 08:36:32 +00:00
LOG.info("--<[ sleep ]")
2019-10-03 02:14:54 +00:00
xbmc.sleep(10000) # Allow network to wake up
2018-10-01 02:13:22 +00:00
self.monitor.sleep = False
2024-06-10 09:19:47 +00:00
window("jellyfin_should_stop", clear=True)
2018-09-06 08:36:32 +00:00
try:
self.connect.register()
except Exception as error:
2019-07-09 20:05:28 +00:00
LOG.exception(error)
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
elif method == "GUI.OnScreensaverDeactivated":
2018-09-06 08:36:32 +00:00
LOG.info("--<[ screensaver ]")
xbmc.sleep(5000)
if self.library_thread is not None:
self.library_thread.fast_sync()
2024-06-10 09:19:47 +00:00
elif method == "UserConfigurationUpdated" and data.get("ServerId") is None:
2020-08-02 14:46:32 +00:00
Views().get_views()
2018-09-06 08:36:32 +00:00
def onSettingsChanged(self):
2024-06-10 09:19:47 +00:00
"""React to setting changes that impact window values."""
if window("jellyfin_should_stop.bool"):
2018-09-06 08:36:32 +00:00
return
2024-06-10 09:19:47 +00:00
if settings("logLevel") != self.settings["log_level"]:
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
log_level = settings("logLevel")
self.settings["logLevel"] = log_level
LOG.info("New log level: %s", log_level)
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if settings("enableContext.bool") != self.settings["enable_context"]:
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
window("jellyfin_context", settings("enableContext"))
self.settings["enable_context"] = settings("enableContext.bool")
LOG.info("New context setting: %s", self.settings["enable_context"])
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if (
settings("enableContextTranscode.bool")
!= self.settings["enable_context_transcode"]
):
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
window("jellyfin_context_transcode", settings("enableContextTranscode"))
self.settings["enable_context_transcode"] = settings(
"enableContextTranscode.bool"
)
LOG.info(
"New context transcode setting: %s",
self.settings["enable_context_transcode"],
)
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if (
settings("useDirectPaths") != self.settings["mode"]
and self.library_thread.started
):
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
self.settings["mode"] = settings("useDirectPaths")
LOG.info("New playback mode setting: %s", self.settings["mode"])
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if not self.settings.get("mode_warn"):
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
self.settings["mode_warn"] = True
dialog("yesno", "{jellyfin}", translate(33118))
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if settings("kodiCompanion.bool") != self.settings["kodi_companion"]:
self.settings["kodi_companion"] = settings("kodiCompanion.bool")
2018-09-06 08:36:32 +00:00
2024-06-10 09:19:47 +00:00
if not self.settings["kodi_companion"]:
dialog("ok", "{jellyfin}", translate(33138))
2018-09-06 08:36:32 +00:00
def reload_objects(self):
2024-06-10 09:19:47 +00:00
"""Reload objects which depends on the patch module.
This allows to see the changes in code without restarting the python interpreter.
"""
reload_modules = [
"objects.movies",
"objects.musicvideos",
"objects.tvshows",
"objects.music",
"objects.obj",
"objects.actions",
"objects.kodi.kodi",
"objects.kodi.movies",
"objects.kodi.musicvideos",
"objects.kodi.tvshows",
"objects.kodi.music",
"objects.kodi.artwork",
"objects.kodi.queries",
"objects.kodi.queries_music",
"objects.kodi.queries_texture",
]
for mod in reload_modules:
del sys.modules[mod]
reload(objects.kodi)
reload(objects)
reload(library)
reload(monitor)
objects.obj.Objects().mapping()
LOG.info("---[ objects reloaded ]")
2018-09-06 08:36:32 +00:00
def shutdown(self):
LOG.info("---<[ EXITING ]")
2024-06-10 09:19:47 +00:00
window("jellyfin_should_stop.bool", True)
2018-09-06 08:36:32 +00:00
2019-10-03 02:14:54 +00:00
properties = [ # TODO: review
2024-06-10 09:19:47 +00:00
"jellyfin_state",
"jellyfin_serverStatus",
"jellyfin_currUser",
"jellyfin_play",
"jellyfin_online",
"jellyfin.connected",
"jellyfin_startup",
"jellyfin.external",
"jellyfin.external_check",
"jellyfin_deviceId",
"jellyfin_db_check",
"jellyfin_pathverified",
"jellyfin_sync",
2018-09-06 08:36:32 +00:00
]
for prop in properties:
window(prop, clear=True)
Jellyfin.close_all()
2018-09-06 08:36:32 +00:00
if self.library_thread is not None:
self.library_thread.stop_client()
if self.monitor is not None:
2019-01-13 10:21:35 +00:00
2018-09-06 08:36:32 +00:00
self.monitor.listener.stop()
LOG.info("---<<<[ %s ]", client.get_addon_name())