Reprogram part 1

This commit is contained in:
croneter 2018-09-30 17:35:23 +02:00
parent 5cdda0e334
commit dbe0339b71
8 changed files with 1170 additions and 22 deletions

View 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()

View file

@ -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():
else: if t != threading.currentThread():
utils.window('plex_service_started', value='true') if t.isAlive():
EXIT = False 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:
t.join()
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.")
else: def main():
Service().ServiceEntryPoint() 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:
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
View 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

View file

@ -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
View 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
View 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

View file

@ -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

View file

@ -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()