Reprogram part 1
This commit is contained in:
parent
5cdda0e334
commit
dbe0339b71
8 changed files with 1170 additions and 22 deletions
105
resources/lib/kodijsonrpc.py
Normal file
105
resources/lib/kodijsonrpc.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import xbmc
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class JSONRPCMethod:
|
||||||
|
|
||||||
|
class Exception(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.family = None
|
||||||
|
|
||||||
|
def __getattr__(self, method):
|
||||||
|
def handler(**kwargs):
|
||||||
|
command = {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': 1,
|
||||||
|
'method': '{0}.{1}'.format(self.family, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
if kwargs:
|
||||||
|
command['params'] = kwargs
|
||||||
|
|
||||||
|
# xbmc.log(json.dumps(command))
|
||||||
|
ret = json.loads(xbmc.executeJSONRPC(json.dumps(command)))
|
||||||
|
|
||||||
|
if ret:
|
||||||
|
if 'error' in ret:
|
||||||
|
raise self.Exception(ret['error'])
|
||||||
|
else:
|
||||||
|
return ret['result']
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def __call__(self, family):
|
||||||
|
self.family = family
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class KodiJSONRPC:
|
||||||
|
def __init__(self):
|
||||||
|
self.methodHandler = JSONRPCMethod()
|
||||||
|
|
||||||
|
def __getattr__(self, family):
|
||||||
|
return self.methodHandler(family)
|
||||||
|
|
||||||
|
|
||||||
|
rpc = KodiJSONRPC()
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltInMethod:
|
||||||
|
|
||||||
|
class Exception(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.module = None
|
||||||
|
|
||||||
|
def __getattr__(self, method):
|
||||||
|
def handler(*args, **kwargs):
|
||||||
|
args = [str(a).replace(',', '\,') for a in args]
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
args.append('{0}={v}'.format(k, str(v).replace(',', '\,')))
|
||||||
|
|
||||||
|
if args:
|
||||||
|
command = '{0}.{1}({2})'.format(self.module, method, ','.join(args))
|
||||||
|
else:
|
||||||
|
command = '{0}.{1}'.format(self.module, method)
|
||||||
|
|
||||||
|
xbmc.log(command, xbmc.LOGNOTICE)
|
||||||
|
|
||||||
|
xbmc.executebuiltin(command)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
args = [str(a).replace(',', '\,') for a in args]
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
args.append('{0}={v}'.format(k, str(v).replace(',', '\,')))
|
||||||
|
|
||||||
|
if args:
|
||||||
|
command = '{0}({1})'.format(self.module, ','.join(args))
|
||||||
|
else:
|
||||||
|
command = '{0}'.format(self.module)
|
||||||
|
|
||||||
|
xbmc.log(command, xbmc.LOGNOTICE)
|
||||||
|
|
||||||
|
xbmc.executebuiltin(command)
|
||||||
|
|
||||||
|
def initModule(self, module):
|
||||||
|
self.module = module
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class KodiBuiltin:
|
||||||
|
def __init__(self):
|
||||||
|
self.methodHandler = BuiltInMethod()
|
||||||
|
|
||||||
|
def __getattr__(self, module):
|
||||||
|
return self.methodHandler.initModule(module)
|
||||||
|
|
||||||
|
|
||||||
|
builtin = KodiBuiltin()
|
|
@ -3,8 +3,14 @@
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import gc
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
|
from . import plex, util, backgroundthread
|
||||||
|
from .plexnet import plexapp, threadutils
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from . import userclient
|
from . import userclient
|
||||||
from . import initialsetup
|
from . import initialsetup
|
||||||
|
@ -23,7 +29,7 @@ from . import loghandler
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
loghandler.config()
|
loghandler.config()
|
||||||
LOG = logging.getLogger("PLEX.service_entry")
|
LOG = logging.getLogger("PLEX.main")
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -264,22 +270,104 @@ class Service():
|
||||||
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
||||||
|
|
||||||
|
|
||||||
def start():
|
def waitForThreads():
|
||||||
# Safety net - Kody starts PKC twice upon first installation!
|
LOG.debug('Checking for any remaining threads')
|
||||||
if utils.window('plex_service_started') == 'true':
|
while len(threading.enumerate()) > 1:
|
||||||
EXIT = True
|
for t in threading.enumerate():
|
||||||
|
if t != threading.currentThread():
|
||||||
|
if t.isAlive():
|
||||||
|
LOG.debug('Waiting on thread: %s', t.name)
|
||||||
|
if isinstance(t, threading._Timer):
|
||||||
|
t.cancel()
|
||||||
|
t.join()
|
||||||
|
elif isinstance(t, threadutils.KillableThread):
|
||||||
|
t.kill(force_and_wait=True)
|
||||||
else:
|
else:
|
||||||
utils.window('plex_service_started', value='true')
|
t.join()
|
||||||
EXIT = False
|
LOG.debug('All threads done')
|
||||||
|
|
||||||
# Delay option
|
|
||||||
DELAY = int(utils.settings('startupDelay'))
|
|
||||||
|
|
||||||
LOG.info("Delaying Plex startup by: %s sec...", DELAY)
|
def signout():
|
||||||
if EXIT:
|
util.setSetting('auth.token', '')
|
||||||
LOG.error('PKC service.py already started - exiting this instance')
|
LOG.info('Signing out...')
|
||||||
elif DELAY and xbmc.Monitor().waitForAbort(DELAY):
|
plexapp.ACCOUNT.signOut()
|
||||||
# Start the service
|
|
||||||
LOG.info("Abort requested while waiting. PKC not started.")
|
|
||||||
|
def main():
|
||||||
|
LOG.info('Starting %s', util.ADDON.getAddonInfo('version'))
|
||||||
|
LOG.info('User-agent: %s', plex.defaultUserAgent())
|
||||||
|
|
||||||
|
try:
|
||||||
|
while not xbmc.abortRequested:
|
||||||
|
if plex.init():
|
||||||
|
while not xbmc.abortRequested:
|
||||||
|
if (
|
||||||
|
not plexapp.ACCOUNT.isOffline and not
|
||||||
|
plexapp.ACCOUNT.isAuthenticated and
|
||||||
|
(len(plexapp.ACCOUNT.homeUsers) > 1 or plexapp.ACCOUNT.isProtected)
|
||||||
|
|
||||||
|
):
|
||||||
|
result = userselect.start()
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
elif result == 'signout':
|
||||||
|
signout()
|
||||||
|
break
|
||||||
|
elif result == 'signin':
|
||||||
|
break
|
||||||
|
LOG.info('User selected')
|
||||||
|
|
||||||
|
try:
|
||||||
|
selectedServer = plexapp.SERVERMANAGER.selectedServer
|
||||||
|
|
||||||
|
if not selectedServer:
|
||||||
|
LOG.debug('Waiting for selected server...')
|
||||||
|
for timeout, skip_preferred, skip_owned in ((10, True, False), (10, True, True)):
|
||||||
|
plex.CallbackEvent(plexapp.APP, 'change:selectedServer', timeout=timeout).wait()
|
||||||
|
|
||||||
|
selectedServer = plexapp.SERVERMANAGER.checkSelectedServerSearch(skip_preferred=skip_preferred, skip_owned=skip_owned)
|
||||||
|
if selectedServer:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
Service().ServiceEntryPoint()
|
LOG.debug('Finished waiting for selected server...')
|
||||||
|
|
||||||
|
LOG.info('Starting with server: %s', selectedServer)
|
||||||
|
|
||||||
|
windowutils.HOME = home.HomeWindow.open()
|
||||||
|
util.CRON.cancelReceiver(windowutils.HOME)
|
||||||
|
|
||||||
|
if not windowutils.HOME.closeOption:
|
||||||
|
return
|
||||||
|
|
||||||
|
closeOption = windowutils.HOME.closeOption
|
||||||
|
|
||||||
|
windowutils.shutdownHome()
|
||||||
|
|
||||||
|
if closeOption == 'signout':
|
||||||
|
signout()
|
||||||
|
break
|
||||||
|
elif closeOption == 'switch':
|
||||||
|
plexapp.ACCOUNT.isAuthenticated = False
|
||||||
|
finally:
|
||||||
|
windowutils.shutdownHome()
|
||||||
|
gc.collect(2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
util.ERROR()
|
||||||
|
finally:
|
||||||
|
LOG.info('SHUTTING DOWN...')
|
||||||
|
# player.shutdown()
|
||||||
|
plexapp.APP.preShutdown()
|
||||||
|
if util.CRON:
|
||||||
|
util.CRON.stop()
|
||||||
|
backgroundthread.BGThreader.shutdown()
|
||||||
|
plexapp.APP.shutdown()
|
||||||
|
waitForThreads()
|
||||||
|
LOG.info('SHUTDOWN FINISHED')
|
||||||
|
|
||||||
|
from .windows import kodigui
|
||||||
|
kodigui.MONITOR = None
|
||||||
|
util.shutdown()
|
||||||
|
gc.collect(2)
|
390
resources/lib/plex.py
Normal file
390
resources/lib/plex.py
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
|
from .plexnet import plexapp, myplex
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
LOG = logging.getLogger('PLEX.plex')
|
||||||
|
|
||||||
|
|
||||||
|
class PlexTimer(plexapp.Timer):
|
||||||
|
def shouldAbort(self):
|
||||||
|
return xbmc.abortRequested
|
||||||
|
|
||||||
|
|
||||||
|
def abortFlag():
|
||||||
|
return util.MONITOR.abortRequested()
|
||||||
|
|
||||||
|
|
||||||
|
plexapp.setTimer(PlexTimer)
|
||||||
|
plexapp.setAbortFlagFunction(abortFlag)
|
||||||
|
|
||||||
|
maxVideoRes = plexapp.Res((3840, 2160)) # INTERFACE.globals["supports4k"] and plexapp.Res((3840, 2160)) or plexapp.Res((1920, 1080))
|
||||||
|
|
||||||
|
CLIENT_ID = util.getSetting('client.ID')
|
||||||
|
if not CLIENT_ID:
|
||||||
|
CLIENT_ID = str(uuid.uuid4())
|
||||||
|
util.setSetting('client.ID', CLIENT_ID)
|
||||||
|
|
||||||
|
|
||||||
|
def defaultUserAgent():
|
||||||
|
"""Return a string representing the default user agent."""
|
||||||
|
_implementation = platform.python_implementation()
|
||||||
|
|
||||||
|
if _implementation == 'CPython':
|
||||||
|
_implementation_version = platform.python_version()
|
||||||
|
elif _implementation == 'PyPy':
|
||||||
|
_implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
|
||||||
|
sys.pypy_version_info.minor,
|
||||||
|
sys.pypy_version_info.micro)
|
||||||
|
if sys.pypy_version_info.releaselevel != 'final':
|
||||||
|
_implementation_version = ''.join([_implementation_version, sys.pypy_version_info.releaselevel])
|
||||||
|
elif _implementation == 'Jython':
|
||||||
|
_implementation_version = platform.python_version() # Complete Guess
|
||||||
|
elif _implementation == 'IronPython':
|
||||||
|
_implementation_version = platform.python_version() # Complete Guess
|
||||||
|
else:
|
||||||
|
_implementation_version = 'Unknown'
|
||||||
|
|
||||||
|
try:
|
||||||
|
p_system = platform.system()
|
||||||
|
p_release = platform.release()
|
||||||
|
except IOError:
|
||||||
|
p_system = 'Unknown'
|
||||||
|
p_release = 'Unknown'
|
||||||
|
|
||||||
|
return " ".join(['%s/%s' % ('Plex-for-Kodi', util.ADDON.getAddonInfo('version')),
|
||||||
|
'%s/%s' % ('Kodi', xbmc.getInfoLabel('System.BuildVersion').replace(' ', '-')),
|
||||||
|
'%s/%s' % (_implementation, _implementation_version),
|
||||||
|
'%s/%s' % (p_system, p_release)])
|
||||||
|
|
||||||
|
class PlexInterface(plexapp.AppInterface):
|
||||||
|
_regs = {
|
||||||
|
None: {},
|
||||||
|
}
|
||||||
|
_globals = {
|
||||||
|
'platform': 'Kodi',
|
||||||
|
'appVersionStr': util.ADDON.getAddonInfo('version'),
|
||||||
|
'clientIdentifier': CLIENT_ID,
|
||||||
|
'platformVersion': xbmc.getInfoLabel('System.BuildVersion'),
|
||||||
|
'product': 'Plex for Kodi',
|
||||||
|
'provides': 'player',
|
||||||
|
'device': util.getPlatform() or plexapp.PLATFORM,
|
||||||
|
'model': 'Unknown',
|
||||||
|
'friendlyName': 'Kodi Add-on ({0})'.format(platform.node()),
|
||||||
|
'supports1080p60': True,
|
||||||
|
'vp9Support': True,
|
||||||
|
'transcodeVideoQualities': [
|
||||||
|
"10", "20", "30", "30", "40", "60", "60", "75", "100", "60", "75", "90", "100", "100"
|
||||||
|
],
|
||||||
|
'transcodeVideoResolutions': [
|
||||||
|
plexapp.Res((220, 180)),
|
||||||
|
plexapp.Res((220, 128)),
|
||||||
|
plexapp.Res((284, 160)),
|
||||||
|
plexapp.Res((420, 240)),
|
||||||
|
plexapp.Res((576, 320)),
|
||||||
|
plexapp.Res((720, 480)),
|
||||||
|
plexapp.Res((1024, 768)),
|
||||||
|
plexapp.Res((1280, 720)),
|
||||||
|
plexapp.Res((1280, 720)),
|
||||||
|
maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes, maxVideoRes
|
||||||
|
],
|
||||||
|
'transcodeVideoBitrates': [
|
||||||
|
"64", "96", "208", "320", "720", "1500", "2000", "3000", "4000", "8000", "10000", "12000", "20000", "200000"
|
||||||
|
],
|
||||||
|
'deviceInfo': plexapp.DeviceInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPreference(self, pref, default=None):
|
||||||
|
if pref == 'manual_connections':
|
||||||
|
return self.getManualConnections()
|
||||||
|
else:
|
||||||
|
return util.getSetting(pref, default)
|
||||||
|
|
||||||
|
def getManualConnections(self):
|
||||||
|
conns = []
|
||||||
|
for i in range(2):
|
||||||
|
ip = util.getSetting('manual_ip_{0}'.format(i))
|
||||||
|
if not ip:
|
||||||
|
continue
|
||||||
|
port = util.getSetting('manual_port_{0}'.format(i), 32400)
|
||||||
|
conns.append({'connection': ip, 'port': port})
|
||||||
|
return json.dumps(conns)
|
||||||
|
|
||||||
|
def setPreference(self, pref, value):
|
||||||
|
util.setSetting(pref, value)
|
||||||
|
|
||||||
|
def getRegistry(self, reg, default=None, sec=None):
|
||||||
|
if sec == 'myplex' and reg == 'MyPlexAccount':
|
||||||
|
ret = util.getSetting('{0}.{1}'.format(sec, reg), default)
|
||||||
|
if ret:
|
||||||
|
return ret
|
||||||
|
return json.dumps({'authToken': util.getSetting('auth.token')})
|
||||||
|
else:
|
||||||
|
return util.getSetting('{0}.{1}'.format(sec, reg), default)
|
||||||
|
|
||||||
|
def setRegistry(self, reg, value, sec=None):
|
||||||
|
util.setSetting('{0}.{1}'.format(sec, reg), value)
|
||||||
|
|
||||||
|
def clearRegistry(self, reg, sec=None):
|
||||||
|
util.setSetting('{0}.{1}'.format(sec, reg), '')
|
||||||
|
|
||||||
|
def addInitializer(self, sec):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clearInitializer(self, sec):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getGlobal(self, glbl, default=None):
|
||||||
|
if glbl == 'transcodeVideoResolutions':
|
||||||
|
maxres = self.getPreference('allow_4k', True) and plexapp.Res((3840, 2160)) or plexapp.Res((1920, 1080))
|
||||||
|
self._globals['transcodeVideoResolutions'][-5:] = [maxres] * 5
|
||||||
|
return self._globals.get(glbl, default)
|
||||||
|
|
||||||
|
def getCapabilities(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def LOG(self, msg):
|
||||||
|
LOG.debug('API: %s', msg)
|
||||||
|
|
||||||
|
def DEBUG_LOG(self, msg):
|
||||||
|
LOG.debug('API: %s', msg)
|
||||||
|
|
||||||
|
def WARN_LOG(self, msg):
|
||||||
|
LOG.warn('API: %s', msg)
|
||||||
|
|
||||||
|
def ERROR_LOG(self, msg):
|
||||||
|
LOG.error('API: %s', msg)
|
||||||
|
|
||||||
|
def ERROR(self, msg=None, err=None):
|
||||||
|
if err:
|
||||||
|
LOG.error('%s - %s', msg, err.message)
|
||||||
|
else:
|
||||||
|
util.ERROR()
|
||||||
|
|
||||||
|
def supportsAudioStream(self, codec, channels):
|
||||||
|
return True
|
||||||
|
# if codec = invalid then return true
|
||||||
|
|
||||||
|
# canDownmix = (m.globals["audioDownmix"][codec] <> invalid)
|
||||||
|
# supportsSurroundSound = m.SupportsSurroundSound()
|
||||||
|
|
||||||
|
# if not supportsSurroundSound and canDownmix then
|
||||||
|
# maxChannels = m.globals["audioDownmix"][codec]
|
||||||
|
# else
|
||||||
|
# maxChannels = firstOf(m.globals["audioDecoders"][codec], 0)
|
||||||
|
# end if
|
||||||
|
|
||||||
|
# if maxChannels > 2 and not canDownmix and not supportsSurroundSound then
|
||||||
|
# ' It's a surround sound codec and we can't do surround sound
|
||||||
|
# supported = false
|
||||||
|
# else if maxChannels = 0 or maxChannels < channels then
|
||||||
|
# ' The codec is either unsupported or can't handle the requested channels
|
||||||
|
# supported = false
|
||||||
|
# else
|
||||||
|
# supported = true
|
||||||
|
|
||||||
|
# return supported
|
||||||
|
|
||||||
|
def supportsSurroundSound(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getQualityIndex(self, qualityType):
|
||||||
|
if qualityType == self.QUALITY_LOCAL:
|
||||||
|
return self.getPreference("local_quality", 13)
|
||||||
|
elif qualityType == self.QUALITY_ONLINE:
|
||||||
|
return self.getPreference("online_quality", 8)
|
||||||
|
else:
|
||||||
|
return self.getPreference("remote_quality", 13)
|
||||||
|
|
||||||
|
def getMaxResolution(self, quality_type, allow4k=False):
|
||||||
|
qualityIndex = self.getQualityIndex(quality_type)
|
||||||
|
|
||||||
|
if qualityIndex >= 9:
|
||||||
|
if self.getPreference('allow_4k', True):
|
||||||
|
return allow4k and 2160 or 1088
|
||||||
|
else:
|
||||||
|
return 1088
|
||||||
|
elif qualityIndex >= 6:
|
||||||
|
return 720
|
||||||
|
elif qualityIndex >= 5:
|
||||||
|
return 480
|
||||||
|
else:
|
||||||
|
return 360
|
||||||
|
|
||||||
|
|
||||||
|
plexapp.setInterface(PlexInterface())
|
||||||
|
plexapp.setUserAgent(defaultUserAgent())
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackEvent(plexapp.CompatEvent):
|
||||||
|
def __init__(self, context, signal, timeout=15, *args, **kwargs):
|
||||||
|
threading._Event.__init__(self, *args, **kwargs)
|
||||||
|
self.start = time.time()
|
||||||
|
self.context = context
|
||||||
|
self.signal = signal
|
||||||
|
self.timeout = timeout
|
||||||
|
self.context.on(self.signal, self.set)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{0}:{1}>'.format(self.__class__.__name__, self.signal)
|
||||||
|
|
||||||
|
def set(self, **kwargs):
|
||||||
|
threading._Event.set(self)
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
if not threading._Event.wait(self, self.timeout):
|
||||||
|
LOG.debug('%s: TIMED-OUT', self)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def triggeredOrTimedOut(self, timeout=None):
|
||||||
|
try:
|
||||||
|
if time.time() - self.start() > self.timeout:
|
||||||
|
LOG.debug('%s: TIMED-OUT', self)
|
||||||
|
return True
|
||||||
|
|
||||||
|
if timeout:
|
||||||
|
threading._Event.wait(self, timeout)
|
||||||
|
finally:
|
||||||
|
return self.isSet()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.set()
|
||||||
|
self.context.off(self.signal, self.set)
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
LOG.info('Initializing')
|
||||||
|
|
||||||
|
with CallbackEvent(plexapp.APP, 'init'):
|
||||||
|
plexapp.init()
|
||||||
|
LOG.info('Waiting for account initialization...')
|
||||||
|
|
||||||
|
retry = True
|
||||||
|
|
||||||
|
while retry:
|
||||||
|
retry = False
|
||||||
|
if not plexapp.ACCOUNT.authToken:
|
||||||
|
token = authorize()
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
LOG.info('FAILED TO AUTHORIZE')
|
||||||
|
return False
|
||||||
|
|
||||||
|
with CallbackEvent(plexapp.APP, 'account:response'):
|
||||||
|
plexapp.ACCOUNT.validateToken(token)
|
||||||
|
LOG.info('Waiting for account initialization')
|
||||||
|
|
||||||
|
# if not PLEX:
|
||||||
|
# util.messageDialog('Connection Error', u'Unable to connect to any servers')
|
||||||
|
# util.DEBUG_LOG('SIGN IN: Failed to connect to any servers')
|
||||||
|
# return False
|
||||||
|
|
||||||
|
# util.DEBUG_LOG('SIGN IN: Connected to server: {0} - {1}'.format(PLEX.friendlyName, PLEX.baseuri))
|
||||||
|
success = requirePlexPass()
|
||||||
|
if success == 'RETRY':
|
||||||
|
retry = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def requirePlexPass():
|
||||||
|
return True
|
||||||
|
# if not plexapp.ACCOUNT.hasPlexPass():
|
||||||
|
# from windows import signin, background
|
||||||
|
# background.setSplash(False)
|
||||||
|
# w = signin.SignInPlexPass.open()
|
||||||
|
# retry = w.retry
|
||||||
|
# del w
|
||||||
|
# util.DEBUG_LOG('PlexPass required. Signing out...')
|
||||||
|
# plexapp.ACCOUNT.signOut()
|
||||||
|
# plexapp.SERVERMANAGER.clearState()
|
||||||
|
# if retry:
|
||||||
|
# return 'RETRY'
|
||||||
|
# else:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
# return True
|
||||||
|
|
||||||
|
|
||||||
|
def authorize():
|
||||||
|
from .windows import background
|
||||||
|
with background.BackgroundContext(function=_authorize) as win:
|
||||||
|
return win.result
|
||||||
|
|
||||||
|
|
||||||
|
def _authorize():
|
||||||
|
from .windows import signin, background
|
||||||
|
|
||||||
|
background.setSplash(False)
|
||||||
|
|
||||||
|
back = signin.Background.create()
|
||||||
|
|
||||||
|
pre = signin.PreSignInWindow.open()
|
||||||
|
try:
|
||||||
|
if not pre.doSignin:
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
del pre
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
pinLoginWindow = signin.PinLoginWindow.create()
|
||||||
|
try:
|
||||||
|
pl = myplex.PinLogin()
|
||||||
|
except requests.ConnectionError:
|
||||||
|
util.ERROR()
|
||||||
|
util.messageDialog(util.T(32427, 'Failed'), util.T(32449, 'Sign-in failed. Cound not connect to plex.tv'))
|
||||||
|
return
|
||||||
|
|
||||||
|
pinLoginWindow.setPin(pl.pin)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pl.startTokenPolling()
|
||||||
|
while not pl.finished():
|
||||||
|
if pinLoginWindow.abort:
|
||||||
|
util.DEBUG_LOG('SIGN IN: Pin login aborted')
|
||||||
|
pl.abort()
|
||||||
|
return None
|
||||||
|
xbmc.sleep(100)
|
||||||
|
else:
|
||||||
|
if not pl.expired():
|
||||||
|
if pl.authenticationToken:
|
||||||
|
pinLoginWindow.setLinking()
|
||||||
|
return pl.authenticationToken
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
pinLoginWindow.doClose()
|
||||||
|
del pinLoginWindow
|
||||||
|
|
||||||
|
if pl.expired():
|
||||||
|
util.DEBUG_LOG('SIGN IN: Pin expired')
|
||||||
|
expiredWindow = signin.ExpiredWindow.open()
|
||||||
|
try:
|
||||||
|
if not expiredWindow.refresh:
|
||||||
|
util.DEBUG_LOG('SIGN IN: Pin refresh aborted')
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
del expiredWindow
|
||||||
|
finally:
|
||||||
|
back.doClose()
|
||||||
|
del back
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -11,6 +12,7 @@ import asyncadapter
|
||||||
|
|
||||||
import util
|
import util
|
||||||
|
|
||||||
|
LOG = logging.getLogger('PLEX.myplexaccount')
|
||||||
ACCOUNT = None
|
ACCOUNT = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,7 +73,7 @@ class MyPlexAccount(object):
|
||||||
def loadState(self):
|
def loadState(self):
|
||||||
# Look for the new JSON serialization. If it's not there, look for the
|
# Look for the new JSON serialization. If it's not there, look for the
|
||||||
# old token and Plex Pass values.
|
# old token and Plex Pass values.
|
||||||
|
LOG.debug('Loading State')
|
||||||
plexapp.APP.addInitializer("myplex")
|
plexapp.APP.addInitializer("myplex")
|
||||||
|
|
||||||
jstring = plexapp.INTERFACE.getRegistry("MyPlexAccount", None, "myplex")
|
jstring = plexapp.INTERFACE.getRegistry("MyPlexAccount", None, "myplex")
|
||||||
|
|
9
resources/lib/user.py
Normal file
9
resources/lib/user.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
from logging import getLogger
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
|
||||||
|
from . import kodigui
|
||||||
|
from .. import backgroundthread, utils, plex_tv, variables as v
|
500
resources/lib/util.py
Normal file
500
resources/lib/util.py
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
import gc
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import binascii
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import contextlib
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
from .kodijsonrpc import rpc
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
from .plexnet import signalsmixin
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
_SHUTDOWN = False
|
||||||
|
|
||||||
|
ADDON = xbmcaddon.Addon()
|
||||||
|
|
||||||
|
PROFILE = xbmc.translatePath(ADDON.getAddonInfo('profile')).decode('utf-8')
|
||||||
|
|
||||||
|
SETTINGS_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class UtilityMonitor(xbmc.Monitor, signalsmixin.SignalsMixin):
|
||||||
|
def watchStatusChanged(self):
|
||||||
|
self.trigger('changed.watchstatus')
|
||||||
|
|
||||||
|
def onNotification(self, sender, method, data):
|
||||||
|
if (sender == 'plugin.video.plexkodiconnect' and
|
||||||
|
method.endswith('RESTORE')):
|
||||||
|
from .windows import kodigui
|
||||||
|
xbmc.executebuiltin('ActivateWindow({0})'.format(kodigui.BaseFunctions.lastWinID))
|
||||||
|
|
||||||
|
|
||||||
|
MONITOR = UtilityMonitor()
|
||||||
|
|
||||||
|
|
||||||
|
def T(ID, eng=''):
|
||||||
|
return ADDON.getLocalizedString(ID)
|
||||||
|
|
||||||
|
|
||||||
|
def LOG(msg, level=xbmc.LOGNOTICE):
|
||||||
|
xbmc.log('PLEX: {0}'.format(msg), level)
|
||||||
|
|
||||||
|
|
||||||
|
def DEBUG_LOG(msg):
|
||||||
|
if _SHUTDOWN:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not getSetting('debug', False) and not xbmc.getCondVisibility('System.GetBool(debug.showloginfo)'):
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def ERROR(txt='', hide_tb=False, notify=False):
|
||||||
|
if isinstance(txt, str):
|
||||||
|
txt = txt.decode("utf-8")
|
||||||
|
short = str(sys.exc_info()[1])
|
||||||
|
if hide_tb:
|
||||||
|
xbmc.log('PLEX: {0} - {1}'.format(txt, short), xbmc.LOGERROR)
|
||||||
|
return short
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
xbmc.log("_________________________________________________________________________________", xbmc.LOGERROR)
|
||||||
|
xbmc.log('PLEX: ' + txt, xbmc.LOGERROR)
|
||||||
|
for l in tb.splitlines():
|
||||||
|
xbmc.log(' ' + l, xbmc.LOGERROR)
|
||||||
|
xbmc.log("_________________________________________________________________________________", xbmc.LOGERROR)
|
||||||
|
xbmc.log("`", xbmc.LOGERROR)
|
||||||
|
if notify:
|
||||||
|
showNotification('ERROR: {0}'.format(short))
|
||||||
|
return short
|
||||||
|
|
||||||
|
|
||||||
|
def TEST(msg):
|
||||||
|
xbmc.log('---TEST: {0}'.format(msg), xbmc.LOGNOTICE)
|
||||||
|
|
||||||
|
|
||||||
|
def getSetting(key, default=None):
|
||||||
|
with SETTINGS_LOCK:
|
||||||
|
setting = ADDON.getSetting(key)
|
||||||
|
return _processSetting(setting, default)
|
||||||
|
|
||||||
|
|
||||||
|
def _processSetting(setting, default):
|
||||||
|
if not setting:
|
||||||
|
return default
|
||||||
|
if isinstance(default, bool):
|
||||||
|
return setting.lower() == 'true'
|
||||||
|
elif isinstance(default, float):
|
||||||
|
return float(setting)
|
||||||
|
elif isinstance(default, int):
|
||||||
|
return int(float(setting or 0))
|
||||||
|
elif isinstance(default, list):
|
||||||
|
if setting:
|
||||||
|
return json.loads(binascii.unhexlify(setting))
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
|
return setting
|
||||||
|
|
||||||
|
|
||||||
|
def setSetting(key, value):
|
||||||
|
with SETTINGS_LOCK:
|
||||||
|
value = _processSettingForWrite(value)
|
||||||
|
ADDON.setSetting(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def _processSettingForWrite(value):
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = binascii.hexlify(json.dumps(value))
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
value = value and 'true' or 'false'
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def setGlobalProperty(key, val):
|
||||||
|
xbmcgui.Window(10000).setProperty(
|
||||||
|
'plugin.video.plexkodiconnect.{0}'.format(key), val)
|
||||||
|
|
||||||
|
|
||||||
|
def setGlobalBoolProperty(key, boolean):
|
||||||
|
xbmcgui.Window(10000).setProperty(
|
||||||
|
'plugin.video.plexkodiconnect.{0}'.format(key), boolean and '1' or '')
|
||||||
|
|
||||||
|
|
||||||
|
def getGlobalProperty(key):
|
||||||
|
return xbmc.getInfoLabel(
|
||||||
|
'Window(10000).Property(plugin.video.plexkodiconnect.{0})'.format(key))
|
||||||
|
|
||||||
|
|
||||||
|
def showNotification(message, time_ms=3000, icon_path=None, header=ADDON.getAddonInfo('name')):
|
||||||
|
try:
|
||||||
|
icon_path = icon_path or xbmc.translatePath(ADDON.getAddonInfo('icon')).decode('utf-8')
|
||||||
|
xbmc.executebuiltin('Notification({0},{1},{2},{3})'.format(header, message, time_ms, icon_path))
|
||||||
|
except RuntimeError: # Happens when disabling the addon
|
||||||
|
LOG(message)
|
||||||
|
|
||||||
|
|
||||||
|
def videoIsPlaying():
|
||||||
|
return xbmc.getCondVisibility('Player.HasVideo')
|
||||||
|
|
||||||
|
|
||||||
|
def messageDialog(heading='Message', msg=''):
|
||||||
|
from .windows import optionsdialog
|
||||||
|
optionsdialog.show(heading, msg, 'OK')
|
||||||
|
|
||||||
|
|
||||||
|
def showTextDialog(heading, text):
|
||||||
|
t = TextBox()
|
||||||
|
t.setControls(heading, text)
|
||||||
|
|
||||||
|
|
||||||
|
def sortTitle(title):
|
||||||
|
return title.startswith('The ') and title[4:] or title
|
||||||
|
|
||||||
|
|
||||||
|
def durationToText(seconds):
|
||||||
|
"""
|
||||||
|
Converts seconds to a short user friendly string
|
||||||
|
Example: 143 -> 2m 23s
|
||||||
|
"""
|
||||||
|
days = int(seconds / 86400000)
|
||||||
|
if days:
|
||||||
|
return '{0} day{1}'.format(days, days > 1 and 's' or '')
|
||||||
|
left = seconds % 86400000
|
||||||
|
hours = int(left / 3600000)
|
||||||
|
if hours:
|
||||||
|
hours = '{0} hr{1} '.format(hours, hours > 1 and 's' or '')
|
||||||
|
else:
|
||||||
|
hours = ''
|
||||||
|
left = left % 3600000
|
||||||
|
mins = int(left / 60000)
|
||||||
|
if mins:
|
||||||
|
return hours + '{0} min{1}'.format(mins, mins > 1 and 's' or '')
|
||||||
|
elif hours:
|
||||||
|
return hours.rstrip()
|
||||||
|
secs = int(left % 60000)
|
||||||
|
if secs:
|
||||||
|
secs /= 1000
|
||||||
|
return '{0} sec{1}'.format(secs, secs > 1 and 's' or '')
|
||||||
|
return '0 seconds'
|
||||||
|
|
||||||
|
|
||||||
|
def durationToShortText(seconds):
|
||||||
|
"""
|
||||||
|
Converts seconds to a short user friendly string
|
||||||
|
Example: 143 -> 2m 23s
|
||||||
|
"""
|
||||||
|
days = int(seconds / 86400000)
|
||||||
|
if days:
|
||||||
|
return '{0} d'.format(days)
|
||||||
|
left = seconds % 86400000
|
||||||
|
hours = int(left / 3600000)
|
||||||
|
if hours:
|
||||||
|
hours = '{0} h '.format(hours)
|
||||||
|
else:
|
||||||
|
hours = ''
|
||||||
|
left = left % 3600000
|
||||||
|
mins = int(left / 60000)
|
||||||
|
if mins:
|
||||||
|
return hours + '{0} m'.format(mins)
|
||||||
|
elif hours:
|
||||||
|
return hours.rstrip()
|
||||||
|
secs = int(left % 60000)
|
||||||
|
if secs:
|
||||||
|
secs /= 1000
|
||||||
|
return '{0} s'.format(secs)
|
||||||
|
return '0 s'
|
||||||
|
|
||||||
|
|
||||||
|
def cleanLeadingZeros(text):
|
||||||
|
if not text:
|
||||||
|
return ''
|
||||||
|
return re.sub('(?<= )0(\d)', r'\1', text)
|
||||||
|
|
||||||
|
|
||||||
|
def removeDups(dlist):
|
||||||
|
return [ii for n, ii in enumerate(dlist) if ii not in dlist[:n]]
|
||||||
|
|
||||||
|
|
||||||
|
SIZE_NAMES = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||||
|
|
||||||
|
|
||||||
|
def simpleSize(size):
|
||||||
|
"""
|
||||||
|
Converts bytes to a short user friendly string
|
||||||
|
Example: 12345 -> 12.06 KB
|
||||||
|
"""
|
||||||
|
s = 0
|
||||||
|
if size > 0:
|
||||||
|
i = int(math.floor(math.log(size, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = round(size / p, 2)
|
||||||
|
if (s > 0):
|
||||||
|
return '%s %s' % (s, SIZE_NAMES[i])
|
||||||
|
else:
|
||||||
|
return '0B'
|
||||||
|
|
||||||
|
|
||||||
|
def timeDisplay(ms):
|
||||||
|
h = ms / 3600000
|
||||||
|
m = (ms % 3600000) / 60000
|
||||||
|
s = (ms % 60000) / 1000
|
||||||
|
return '{0:0>2}:{1:0>2}:{2:0>2}'.format(h, m, s)
|
||||||
|
|
||||||
|
|
||||||
|
def simplifiedTimeDisplay(ms):
|
||||||
|
left, right = timeDisplay(ms).rsplit(':', 1)
|
||||||
|
left = left.lstrip('0:') or '0'
|
||||||
|
return left + ':' + right
|
||||||
|
|
||||||
|
|
||||||
|
def shortenText(text, size):
|
||||||
|
if len(text) < size:
|
||||||
|
return text
|
||||||
|
|
||||||
|
return u'{0}\u2026'.format(text[:size - 1])
|
||||||
|
|
||||||
|
|
||||||
|
class TextBox:
|
||||||
|
# constants
|
||||||
|
WINDOW = 10147
|
||||||
|
CONTROL_LABEL = 1
|
||||||
|
CONTROL_TEXTBOX = 5
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# activate the text viewer window
|
||||||
|
xbmc.executebuiltin("ActivateWindow(%d)" % (self.WINDOW, ))
|
||||||
|
# get window
|
||||||
|
self.win = xbmcgui.Window(self.WINDOW)
|
||||||
|
# give window time to initialize
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
|
||||||
|
def setControls(self, heading, text):
|
||||||
|
# set heading
|
||||||
|
self.win.getControl(self.CONTROL_LABEL).setLabel(heading)
|
||||||
|
# set text
|
||||||
|
self.win.getControl(self.CONTROL_TEXTBOX).setText(text)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingControl:
|
||||||
|
def __init__(self, setting, log_display, disable_value=''):
|
||||||
|
self.setting = setting
|
||||||
|
self.logDisplay = log_display
|
||||||
|
self.disableValue = disable_value
|
||||||
|
self._originalMode = None
|
||||||
|
self.store()
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
rpc.Settings.SetSettingValue(setting=self.setting, value=self.disableValue)
|
||||||
|
DEBUG_LOG('{0}: DISABLED'.format(self.logDisplay))
|
||||||
|
|
||||||
|
def set(self, value):
|
||||||
|
rpc.Settings.SetSettingValue(setting=self.setting, value=value)
|
||||||
|
DEBUG_LOG('{0}: SET={1}'.format(self.logDisplay, value))
|
||||||
|
|
||||||
|
def store(self):
|
||||||
|
try:
|
||||||
|
self._originalMode = rpc.Settings.GetSettingValue(setting=self.setting).get('value')
|
||||||
|
DEBUG_LOG('{0}: Mode stored ({1})'.format(self.logDisplay, self._originalMode))
|
||||||
|
except:
|
||||||
|
ERROR()
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
if self._originalMode is None:
|
||||||
|
return
|
||||||
|
rpc.Settings.SetSettingValue(setting=self.setting, value=self._originalMode)
|
||||||
|
DEBUG_LOG('{0}: RESTORED'.format(self.logDisplay))
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def suspend(self):
|
||||||
|
self.disable()
|
||||||
|
yield
|
||||||
|
self.restore()
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def save(self):
|
||||||
|
yield
|
||||||
|
self.restore()
|
||||||
|
|
||||||
|
|
||||||
|
def timeInDayLocalSeconds():
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
sod = datetime.datetime(year=now.year, month=now.month, day=now.day)
|
||||||
|
sod = int(time.mktime(sod.timetuple()))
|
||||||
|
return int(time.time() - sod)
|
||||||
|
|
||||||
|
|
||||||
|
CRON = None
|
||||||
|
|
||||||
|
|
||||||
|
class CronReceiver():
|
||||||
|
def tick(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def halfHour(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def day(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Cron(threading.Thread):
|
||||||
|
def __init__(self, interval):
|
||||||
|
threading.Thread.__init__(self, name='CRON')
|
||||||
|
self.stopped = threading.Event()
|
||||||
|
self.force = threading.Event()
|
||||||
|
self.interval = interval
|
||||||
|
self._lastHalfHour = self._getHalfHour()
|
||||||
|
self._receivers = []
|
||||||
|
|
||||||
|
global CRON
|
||||||
|
|
||||||
|
CRON = self
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.start()
|
||||||
|
DEBUG_LOG('Cron started')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.stop()
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def _wait(self):
|
||||||
|
ct = 0
|
||||||
|
while ct < self.interval:
|
||||||
|
xbmc.sleep(100)
|
||||||
|
ct += 0.1
|
||||||
|
if self.force.isSet():
|
||||||
|
self.force.clear()
|
||||||
|
return True
|
||||||
|
if xbmc.abortRequested or self.stopped.isSet():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def forceTick(self):
|
||||||
|
self.force.set()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stopped.set()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self._wait():
|
||||||
|
self._tick()
|
||||||
|
DEBUG_LOG('Cron stopped')
|
||||||
|
|
||||||
|
def _getHalfHour(self):
|
||||||
|
tid = timeInDayLocalSeconds() / 60
|
||||||
|
return tid - (tid % 30)
|
||||||
|
|
||||||
|
def _tick(self):
|
||||||
|
receivers = list(self._receivers)
|
||||||
|
receivers = self._halfHour(receivers)
|
||||||
|
for r in receivers:
|
||||||
|
try:
|
||||||
|
r.tick()
|
||||||
|
except:
|
||||||
|
ERROR()
|
||||||
|
|
||||||
|
def _halfHour(self, receivers):
|
||||||
|
hh = self._getHalfHour()
|
||||||
|
if hh == self._lastHalfHour:
|
||||||
|
return receivers
|
||||||
|
try:
|
||||||
|
receivers = self._day(receivers, hh)
|
||||||
|
ret = []
|
||||||
|
for r in receivers:
|
||||||
|
try:
|
||||||
|
if not r.halfHour():
|
||||||
|
ret.append(r)
|
||||||
|
except:
|
||||||
|
ret.append(r)
|
||||||
|
ERROR()
|
||||||
|
return ret
|
||||||
|
finally:
|
||||||
|
self._lastHalfHour = hh
|
||||||
|
|
||||||
|
def _day(self, receivers, hh):
|
||||||
|
if hh >= self._lastHalfHour:
|
||||||
|
return receivers
|
||||||
|
ret = []
|
||||||
|
for r in receivers:
|
||||||
|
try:
|
||||||
|
if not r.day():
|
||||||
|
ret.append(r)
|
||||||
|
except:
|
||||||
|
ret.append(r)
|
||||||
|
ERROR()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def registerReceiver(self, receiver):
|
||||||
|
if receiver not in self._receivers:
|
||||||
|
DEBUG_LOG('Cron: Receiver added: {0}'.format(receiver))
|
||||||
|
self._receivers.append(receiver)
|
||||||
|
|
||||||
|
def cancelReceiver(self, receiver):
|
||||||
|
if receiver in self._receivers:
|
||||||
|
DEBUG_LOG('Cron: Receiver canceled: {0}'.format(receiver))
|
||||||
|
self._receivers.pop(self._receivers.index(receiver))
|
||||||
|
|
||||||
|
|
||||||
|
def getPlatform():
|
||||||
|
for key in [
|
||||||
|
'System.Platform.Android',
|
||||||
|
'System.Platform.Linux.RaspberryPi',
|
||||||
|
'System.Platform.Linux',
|
||||||
|
'System.Platform.Windows',
|
||||||
|
'System.Platform.OSX',
|
||||||
|
'System.Platform.IOS',
|
||||||
|
'System.Platform.Darwin',
|
||||||
|
'System.Platform.ATV2'
|
||||||
|
]:
|
||||||
|
if xbmc.getCondVisibility(key):
|
||||||
|
return key.rsplit('.', 1)[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def getProgressImage(obj):
|
||||||
|
if not obj.get('viewOffset'):
|
||||||
|
return ''
|
||||||
|
pct = int((obj.viewOffset.asInt() / obj.duration.asFloat()) * 100)
|
||||||
|
pct = pct - pct % 2 # Round to even number - we have even numbered progress only
|
||||||
|
return 'plugin.video.plexkodiconnect/progress/{0}.png'.format(pct)
|
||||||
|
|
||||||
|
|
||||||
|
def trackIsPlaying(track):
|
||||||
|
return xbmc.getCondVisibility('String.StartsWith(MusicPlayer.Comment,{0})'.format('PLEX-{0}:'.format(track.ratingKey)))
|
||||||
|
|
||||||
|
|
||||||
|
def addURLParams(url, params):
|
||||||
|
if '?' in url:
|
||||||
|
url += '&'
|
||||||
|
else:
|
||||||
|
url += '?'
|
||||||
|
url += urllib.urlencode(params)
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def garbageCollect():
|
||||||
|
gc.collect(2)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
global MONITOR, ADDON, T, _SHUTDOWN
|
||||||
|
_SHUTDOWN = True
|
||||||
|
del MONITOR
|
||||||
|
del T
|
||||||
|
del ADDON
|
|
@ -42,3 +42,27 @@ def setSplash(on=True):
|
||||||
|
|
||||||
def setShutdown(on=True):
|
def setShutdown(on=True):
|
||||||
utils.setGlobalProperty('background.shutdown', on and '1' or '')
|
utils.setGlobalProperty('background.shutdown', on and '1' or '')
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundContext(object):
|
||||||
|
"""
|
||||||
|
Context Manager to open a Plex background window - in the background. This
|
||||||
|
will e.g. ensure that you can capture key-presses
|
||||||
|
Use like this:
|
||||||
|
with BackgroundContext(function) as win:
|
||||||
|
<now function will be executed immediately. Get its results:>
|
||||||
|
result = win.result
|
||||||
|
"""
|
||||||
|
def __init__(self, function=None):
|
||||||
|
self.window = None
|
||||||
|
self.result = None
|
||||||
|
self.function = function
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.window = BackgroundWindow.create(function=self.function)
|
||||||
|
self.window.modal()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.result = self.window.result
|
||||||
|
del self.window
|
||||||
|
|
36
service.py
36
service.py
|
@ -1,8 +1,38 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
###############################################################################
|
###############################################################################
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from resources.lib import service_entry
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def start():
|
||||||
service_entry.start()
|
# Safety net - Kodi starts PKC twice upon first installation!
|
||||||
|
if xbmc.getInfoLabel(
|
||||||
|
'Window(10000).Property(plugin.video.plexkodiconnect.running)').decode('utf-8') == '1':
|
||||||
|
xbmc.log('PLEX: PlexKodiConnect is already running',
|
||||||
|
level=xbmc.LOGWARNING)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
xbmcgui.Window(10000).setProperty(
|
||||||
|
'plugin.video.plexkodiconnect.running', '1')
|
||||||
|
try:
|
||||||
|
# We might have to wait a bit before starting PKC
|
||||||
|
delay = int(xbmcaddon.Addon(
|
||||||
|
id='plugin.video.plexkodiconnect').getSetting('startupDelay'))
|
||||||
|
xbmc.log('PLEX: Delaying PKC startup by: %s seconds'.format(delay),
|
||||||
|
level=xbmc.LOGNOTICE)
|
||||||
|
if delay and xbmc.Monitor().waitForAbort(delay):
|
||||||
|
xbmc.log('PLEX: Kodi shutdown while waiting for PKC startup',
|
||||||
|
level=xbmc.LOGWARNING)
|
||||||
|
return
|
||||||
|
from resources.lib import main
|
||||||
|
main.main()
|
||||||
|
finally:
|
||||||
|
xbmcgui.Window(10000).setProperty(
|
||||||
|
'plugin.video.plexkodiconnect.running', '')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
start()
|
||||||
|
|
Loading…
Reference in a new issue