jellyfin-kodi/jellyfin_kodi/entrypoint/service.py

432 lines
15 KiB
Python
Raw Permalink 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
# Workaround for threads using datetime: _striptime is locked
import _strptime # noqa:F401
from kodi_six import xbmc, xbmcgui
from six.moves import reload_module as reload
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
from ..helper import translate, window, settings, event, dialog, set_addon_mode, LazyLogger
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
settings = {'last_progress': datetime.today(), 'last_progress_report': datetime.today()}
2018-09-06 08:36:32 +00:00
def __init__(self):
window('jellyfin_should_stop', clear=True)
self.settings['addon_version'] = client.get_version()
2021-10-02 17:26:37 +00:00
self.settings['profile'] = translate_path('special://profile')
2018-09-06 08:36:32 +00:00
self.settings['mode'] = settings('useDirectPaths')
self.settings['log_level'] = settings('logLevel') or "1"
self.settings['auth_check'] = True
2018-09-06 08:36:32 +00:00
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'])
2018-11-02 21:26:29 +00:00
settings('platformDetected', client.get_platform())
2018-09-06 08:36:32 +00:00
if self.settings['enable_context']:
window('jellyfin_context.bool', True)
2018-09-06 08:36:32 +00:00
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())
LOG.info("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion'))
LOG.info("Platform: %s", settings('platformDetected'))
LOG.info("Python Version: %s", sys.version)
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()
window('jellyfin.connected.bool', True)
2018-09-06 08:36:32 +00:00
settings('groupedSets.bool', objects.utils.get_grouped_set())
xbmc.Monitor.__init__(self)
2018-09-06 08:36:32 +00:00
def service(self):
''' Keeps the service monitor going.
Exit on Kodi shutdown or profile switch.
2019-07-08 00:26:54 +00:00
if profile switch happens more than once,
2018-09-06 08:36:32 +00:00
Threads depending on abortRequest will not trigger.
'''
self.monitor = monitor.Monitor()
player = self.monitor.player
2018-09-06 08:36:32 +00:00
self.connect = connect.Connect()
self.start_default()
self.settings['mode'] = settings('useDirectPaths')
while self.running:
if window('jellyfin_online.bool'):
2018-09-06 08:36:32 +00:00
if self.settings['profile'] != window('jellyfin_kodiProfile'):
2018-09-06 08:36:32 +00:00
LOG.info("[ profile switch ] %s", self.settings['profile'])
break
if player.isPlaying() and player.is_playing_file(player.get_playing_file()):
2018-09-06 08:36:32 +00:00
difference = datetime.today() - self.settings['last_progress']
2018-09-06 22:28:51 +00:00
if difference.seconds > 10:
self.settings['last_progress'] = datetime.today()
2018-10-01 02:13:22 +00:00
update = (datetime.today() - self.settings['last_progress_report']).seconds > 250
2018-09-09 02:05:41 +00:00
event('ReportProgressRequested', {'Report': update})
2018-09-09 02:05:41 +00:00
if update:
self.settings['last_progress_report'] = datetime.today()
2018-09-06 08:36:32 +00:00
if window('jellyfin.restart.bool'):
window('jellyfin.restart', clear=True)
dialog("notification", heading="{jellyfin}", message=translate(33193), icon="{jellyfin}", time=1000, sound=False)
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()
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):
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):
''' 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
if sender == 'plugin.video.jellyfin':
2018-09-06 08:36:32 +00:00
method = method.split('.')[1]
if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect',
'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary',
2019-02-02 14:41:24 +00:00
'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:
if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake'):
return
data = json.loads(data)
LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data))
2018-09-06 08:36:32 +00:00
if method == 'ServerOnline':
2018-10-05 04:38:55 +00:00
if data.get('ServerId') is None:
2018-09-06 08:36:32 +00:00
window('jellyfin_online.bool', True)
self.settings['auth_check'] = True
2018-09-06 08:36:32 +00:00
self.warn = True
2018-10-07 22:55:34 +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)),
2019-10-03 02:14:54 +00:00
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()
elif method in ('ServerUnreachable', 'ServerShuttingDown'):
if self.warn or data.get('ServerId'):
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
if data.get('ServerId') is None:
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()
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
if data.get('ServerId') is None and self.settings['auth_check']:
self.settings['auth_check'] = False
self.stop_default()
if self.waitForAbort(5):
return
2019-07-08 00:26:54 +00:00
self.start_default()
2018-09-06 08:36:32 +00:00
elif method == 'ServerRestarting':
if data.get('ServerId'):
return
2019-07-08 00:26:54 +00:00
2018-09-06 08:36:32 +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()
elif method == 'ServerConnect':
self.connect.register(data['Id'])
xbmc.executebuiltin("Container.Refresh")
elif method == 'AddServer':
self.connect.setup_manual_server()
xbmc.executebuiltin("Container.Refresh")
elif method == 'RemoveServer':
self.connect.remove_server(data['Id'])
xbmc.executebuiltin("Container.Refresh")
elif method == 'UpdatePassword':
self.connect.setup_login_manual()
2018-09-06 08:36:32 +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)
2018-09-06 08:36:32 +00:00
self.library_thread.userdata(data['UserDataList'])
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)
2018-09-06 08:36:32 +00:00
self.library_thread.updated(data['ItemsUpdated'] + data['ItemsAdded'])
self.library_thread.removed(data['ItemsRemoved'])
elif method == 'System.OnQuit':
window('jellyfin_should_stop.bool', True)
2018-09-06 08:36:32 +00:00
self.running = False
elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'):
self.library_thread.select_libraries(method)
2018-09-06 08:36:32 +00:00
elif method == 'SyncLibrary':
2018-09-08 09:01:14 +00:00
if not data.get('Id'):
return
self.library_thread.add_library(data['Id'], data.get('Update', False))
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
elif method == 'RepairLibrary':
2018-09-08 09:01:14 +00:00
if not data.get('Id'):
return
2018-09-06 08:36:32 +00:00
libraries = data['Id'].split(',')
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
self.library_thread.add_library(data['Id'])
2018-09-06 08:36:32 +00:00
xbmc.executebuiltin("Container.Refresh")
elif method == 'RemoveLibrary':
libraries = data['Id'].split(',')
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")
elif method == 'System.OnSleep':
2019-07-08 00:26:54 +00:00
2018-09-06 08:36:32 +00:00
LOG.info("-->[ sleep ]")
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
elif method == 'System.OnWake':
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
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
elif method == 'GUI.OnScreensaverDeactivated':
LOG.info("--<[ screensaver ]")
xbmc.sleep(5000)
if self.library_thread is not None:
self.library_thread.fast_sync()
2020-08-02 14:46:32 +00:00
elif method == 'UserConfigurationUpdated' and data.get('ServerId') is None:
Views().get_views()
2018-09-06 08:36:32 +00:00
def onSettingsChanged(self):
''' React to setting changes that impact window values.
'''
if window('jellyfin_should_stop.bool'):
2018-09-06 08:36:32 +00:00
return
if settings('logLevel') != self.settings['log_level']:
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
if settings('enableContext.bool') != self.settings['enable_context']:
window('jellyfin_context', settings('enableContext'))
2018-09-06 08:36:32 +00:00
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
if settings('enableContextTranscode.bool') != self.settings['enable_context_transcode']:
window('jellyfin_context_transcode', settings('enableContextTranscode'))
2018-09-06 08:36:32 +00:00
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
if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started:
self.settings['mode'] = settings('useDirectPaths')
LOG.info("New playback mode setting: %s", self.settings['mode'])
2018-09-06 08:36:32 +00:00
if not self.settings.get('mode_warn'):
self.settings['mode_warn'] = True
dialog("yesno", "{jellyfin}", translate(33118))
2018-09-06 08:36:32 +00:00
if settings('kodiCompanion.bool') != self.settings['kodi_companion']:
self.settings['kodi_companion'] = settings('kodiCompanion.bool')
if not self.settings['kodi_companion']:
dialog("ok", "{jellyfin}", translate(33138))
2018-09-06 08:36:32 +00:00
def reload_objects(self):
''' 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 ]")
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
"jellyfin_state", "jellyfin_serverStatus", "jellyfin_currUser",
2018-09-06 08:36:32 +00:00
2020-07-31 22:51:26 +00:00
"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())