jellyfin-kodi/resources/lib/entrypoint/service.py

522 lines
18 KiB
Python
Raw Normal View History

2018-09-06 08:36:32 +00:00
# -*- coding: utf-8 -*-
#################################################################################################
import _strptime # Workaround for threads using datetime: _striptime is locked
import json
import logging
import sys
from datetime import datetime
import xbmc
import xbmcgui
import objects
2018-09-06 08:36:32 +00:00
import connect
import client
import library
import setup
import monitor
from libraries import requests
from views import Views, verify_kodi_defaults
from helper import _, window, settings, event, dialog, find, compare_version
2018-09-06 08:36:32 +00:00
from downloader import get_objects
from emby import Emby
2018-10-03 23:25:51 +00:00
from database import Database, emby_db, reset
2018-09-06 08:36:32 +00:00
#################################################################################################
LOG = logging.getLogger("EMBY."+__name__)
#################################################################################################
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('emby_should_stop', clear=True)
self.settings['addon_version'] = client.get_version()
2018-09-06 08:36:32 +00:00
self.settings['profile'] = xbmc.translatePath('special://profile')
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('emby_logLevel', value=str(self.settings['log_level']))
window('emby_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']:
2018-11-02 21:26:29 +00:00
window('emby_context.bool', True)
2018-09-06 08:36:32 +00:00
if self.settings['enable_context_transcode']:
2018-11-02 21:26:29 +00:00
window('emby_context_transcode.bool', True)
2018-09-06 08:36:32 +00:00
LOG.warn("--->>>[ %s ]", client.get_addon_name())
LOG.warn("Version: %s", client.get_version())
LOG.warn("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion'))
2018-11-02 21:26:29 +00:00
LOG.warn("Platform: %s", settings('platformDetected'))
2018-09-06 08:36:32 +00:00
LOG.warn("Python Version: %s", sys.version)
LOG.warn("Using dynamic paths: %s", settings('useDirectPaths') == "0")
LOG.warn("Log Level: %s", self.settings['log_level'])
2018-10-03 23:25:51 +00:00
self.check_version()
2018-09-06 08:36:32 +00:00
verify_kodi_defaults()
try:
Views().get_nodes()
except Exception as error:
LOG.error(error)
window('emby.connected.bool', True)
2018-09-06 08:36:32 +00:00
self.check_update()
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.
if profile switch happens more than once,
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('emby_online.bool'):
if self.settings['profile'] != window('emby_kodiProfile'):
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('emby.restart.bool'):
window('emby.restart', clear=True)
dialog("notification", heading="{emby}", message=_(33193), icon="{emby}", 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()
setup.Setup()
except Exception as error:
LOG.error(error)
def stop_default(self):
window('emby_online', clear=True)
Emby().close()
if self.library_thread is not None:
self.library_thread.stop_client()
self.library_thread = None
2018-10-03 23:25:51 +00:00
def check_version(self):
''' Check the database version to ensure we do not need to do a reset.
'''
with Database('emby') as embydb:
version = emby_db.EmbyDatabase(embydb.cursor).get_version()
LOG.info("---[ db/%s ]", version)
if version and compare_version(version, "3.1.0") < 0:
resp = dialog("yesno", heading=_('addon_name'), line1=_(33022))
if not resp:
LOG.warn("Database version is out of date! USER IGNORED!")
dialog("ok", heading=_('addon_name'), line1=_(33023))
raise Exception("User backed out of a required database reset")
else:
reset()
raise Exception("Completed database reset")
2018-09-06 08:36:32 +00:00
def check_update(self, forced=False):
2018-09-06 08:36:32 +00:00
''' Check for objects build version and compare.
This pulls a dict that contains all the information for the build needed.
'''
LOG.info("--[ check updates/%s ]", objects.version)
2018-12-14 10:31:26 +00:00
kodi = "DEV" if settings('devMode.bool') else xbmc.getInfoLabel('System.BuildVersion')
2018-12-14 00:24:44 +00:00
2018-09-06 08:36:32 +00:00
try:
2018-09-11 03:23:26 +00:00
versions = requests.get('http://kodi.emby.media/Public%20testing/Dependencies/databases.json').json()
2018-09-16 01:11:12 +00:00
build = find(versions, kodi)
2018-09-06 08:36:32 +00:00
if not build:
raise Exception("build %s incompatible?!" % kodi)
2018-09-06 08:36:32 +00:00
label, zipfile = build.split('-', 1)
if label == 'DEV' and forced:
LOG.info("--[ force/objects/%s ]", label)
elif label == objects.version:
LOG.info("--[ objects/%s ]", objects.version)
2018-09-06 08:36:32 +00:00
return False
2018-09-06 08:36:32 +00:00
get_objects(zipfile, label + '.zip')
self.reload_objects()
2018-09-14 04:55:39 +00:00
dialog("notification", heading="{emby}", message=_(33156), icon="{emby}")
LOG.info("--[ new objects/%s ]", objects.version)
2018-09-08 23:11:19 +00:00
try:
if compare_version(self.settings['addon_version'], objects.embyversion) < 0:
dialog("ok", heading="{emby}", line1="%s %s" % (_(33160), objects.embyversion))
except Exception:
pass
2018-09-06 08:36:32 +00:00
except Exception as error:
2018-09-08 23:11:19 +00:00
LOG.exception(error)
2019-01-14 04:18:34 +00:00
return True
2018-09-06 08:36:32 +00:00
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.emby', 'xbmc'):
return
if sender == 'plugin.video.emby':
method = method.split('.')[1]
if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect',
'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary',
'EmbyConnect', 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer',
2018-09-07 22:52:14 +00:00
'Unauthorized', 'UpdateServer', 'UserConfigurationUpdated', 'ServerRestarting',
'RemoveServer', 'AddLibrarySelection', 'CheckUpdate', '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)
2018-09-23 22:37:30 +00:00
LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data, indent=4))
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('emby_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'):
2018-10-08 21:10:38 +00:00
users = [user for user in (settings('additionalUsers') or "").decode('utf-8').split(',') if user]
users.insert(0, settings('username').decode('utf-8'))
2018-10-07 22:55:34 +00:00
dialog("notification", heading="{emby}", message="%s %s" % (_(33000), ", ".join(users)),
icon="{emby}", time=1500, sound=False)
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="{emby}", message=_(33146) if data.get('ServerId') is None else _(33149), icon=xbmcgui.NOTIFICATION_ERROR)
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
self.start_default()
elif method == 'Unauthorized':
dialog("notification", heading="{emby}", message=_(33147) if data['ServerId'] is None else _(33148), icon=xbmcgui.NOTIFICATION_ERROR)
if data.get('ServerId') is None and self.settings['auth_check']:
self.settings['auth_check'] = False
self.stop_default()
if self.waitForAbort(5):
return
self.start_default()
2018-09-06 08:36:32 +00:00
elif method == 'ServerRestarting':
if data.get('ServerId'):
return
if settings('restartMsg.bool'):
dialog("notification", heading="{emby}", message=_(33006), icon="{emby}")
self.stop_default()
2018-10-01 02:13:22 +00:00
if self.waitForAbort(15):
2018-09-06 08:36:32 +00:00
return
self.start_default()
elif method == 'ServerConnect':
self.connect.register(data['Id'])
xbmc.executebuiltin("Container.Refresh")
elif method == 'EmbyConnect':
self.connect.setup_login_connect()
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 == 'UpdateServer':
dialog("ok", heading="{emby}", line1=_(33151))
self.connect.setup_manual_server()
elif method == 'UserDataChanged' and self.library_thread:
if data.get('ServerId') or not window('emby_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('emby_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('emby_should_stop.bool', True)
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
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-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':
2018-10-01 02:13:22 +00:00
2018-09-06 08:36:32 +00:00
LOG.info("-->[ sleep ]")
2018-10-01 02:13:22 +00:00
window('emby_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
Emby.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.warn("System.OnSleep was never called, skip System.OnWake")
return
2018-09-06 08:36:32 +00:00
LOG.info("--<[ sleep ]")
xbmc.sleep(10000)# Allow network to wake up
2018-10-01 02:13:22 +00:00
self.monitor.sleep = False
window('emby_should_stop', clear=True)
2018-09-06 08:36:32 +00:00
try:
self.connect.register()
except Exception as error:
LOG.error(error)
elif method == 'GUI.OnScreensaverDeactivated':
LOG.info("--<[ screensaver ]")
xbmc.sleep(5000)
if self.library_thread is not None:
self.library_thread.fast_sync()
elif method == 'UserConfigurationUpdated':
if data.get('ServerId') is None:
Views().get_views()
elif method == 'CheckUpdate':
2019-01-14 04:18:34 +00:00
if not self.check_update(True):
dialog("notification", heading="{emby}", message=_(21341), icon="{emby}", sound=False)
2019-01-14 04:18:34 +00:00
else:
dialog("notification", heading="{emby}", message=_(33181), icon="{emby}", sound=False)
window('emby.restart.bool', True)
2018-09-06 08:36:32 +00:00
def onSettingsChanged(self):
''' React to setting changes that impact window values.
'''
if window('emby_should_stop.bool'):
return
if settings('logLevel') != self.settings['log_level']:
log_level = settings('logLevel')
window('emby_logLevel', str(log_level))
self.settings['logLevel'] = log_level
LOG.warn("New log level: %s", log_level)
if settings('enableContext.bool') != self.settings['enable_context']:
window('emby_context', settings('enableContext'))
self.settings['enable_context'] = settings('enableContext.bool')
LOG.warn("New context setting: %s", self.settings['enable_context'])
if settings('enableContextTranscode.bool') != self.settings['enable_context_transcode']:
window('emby_context_transcode', settings('enableContextTranscode'))
self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool')
LOG.warn("New context transcode setting: %s", self.settings['enable_context_transcode'])
if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started:
self.settings['mode'] = settings('useDirectPaths')
LOG.warn("New playback mode setting: %s", self.settings['mode'])
if not self.settings.get('mode_warn'):
self.settings['mode_warn'] = True
dialog("yesno", heading="{emby}", line1=_(33118))
if settings('kodiCompanion.bool') != self.settings['kodi_companion']:
self.settings['kodi_companion'] = settings('kodiCompanion.bool')
if not self.settings['kodi_companion']:
dialog("ok", heading="{emby}", line1=_(33138))
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)
LOG.warn("---[ objects reloaded ]")
2018-09-06 08:36:32 +00:00
def shutdown(self):
LOG.warn("---<[ EXITING ]")
window('emby_should_stop.bool', True)
2018-09-06 08:36:32 +00:00
properties = [ # TODO: review
2019-01-25 11:32:29 +00:00
"emby_state", "emby_serverStatus", "emby_currUser",
2018-09-06 08:36:32 +00:00
"emby_play", "emby_online", "emby.connected", "emby.resume", "emby_startup",
2019-01-25 11:32:29 +00:00
"emby.external", "emby.external_check", "emby_deviceId", "emby_db_check", "emby_pathverified",
"emby_sync"
2018-09-06 08:36:32 +00:00
]
for prop in properties:
window(prop, clear=True)
Emby.close_all()
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()
2019-01-13 10:21:35 +00:00
self.monitor.webservice.stop()
2018-09-06 08:36:32 +00:00
LOG.warn("---<<<[ %s ]", client.get_addon_name())