Merge pull request #618 from croneter/pickle
Fix rare EOFError and PKC starting wrong video as a consequence
This commit is contained in:
commit
15c0322f27
10 changed files with 180 additions and 157 deletions
|
@ -4,8 +4,8 @@
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.9.1" />
|
<import addon="script.module.requests" version="2.9.1" />
|
||||||
<import addon="script.module.defusedxml" version="0.5.0"/>
|
<import addon="script.module.defusedxml" version="0.5.0"/>
|
||||||
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.7" />
|
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.8" />
|
||||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.8" />
|
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.9" />
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||||
<provides>video audio image</provides>
|
<provides>video audio image</provides>
|
||||||
|
|
|
@ -40,9 +40,10 @@ def main():
|
||||||
'kodi_id': kodi_id,
|
'kodi_id': kodi_id,
|
||||||
'kodi_type': kodi_type
|
'kodi_type': kodi_type
|
||||||
}
|
}
|
||||||
while window.getProperty('plex_command'):
|
while window.getProperty('plexkodiconnect.command'):
|
||||||
sleep(20)
|
sleep(20)
|
||||||
window.setProperty('plex_command', 'CONTEXT_menu?%s' % urlencode(args))
|
window.setProperty('plexkodiconnect.command',
|
||||||
|
'CONTEXT_menu?%s' % urlencode(args))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
77
default.py
77
default.py
|
@ -5,18 +5,18 @@ from __future__ import absolute_import, division, unicode_literals
|
||||||
import logging
|
import logging
|
||||||
from sys import argv
|
from sys import argv
|
||||||
from urlparse import parse_qsl
|
from urlparse import parse_qsl
|
||||||
from xbmc import sleep, executebuiltin
|
|
||||||
from xbmcgui import ListItem, getCurrentWindowId
|
|
||||||
from xbmcplugin import setResolvedUrl
|
|
||||||
|
|
||||||
from resources.lib import entrypoint, utils, pickler, pkc_listitem, \
|
import xbmc
|
||||||
variables as v, loghandler
|
import xbmcgui
|
||||||
|
import xbmcplugin
|
||||||
|
|
||||||
|
from resources.lib import entrypoint, utils, transfer, variables as v, loghandler
|
||||||
from resources.lib.tools import unicode_paths
|
from resources.lib.tools import unicode_paths
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
loghandler.config()
|
loghandler.config()
|
||||||
log = logging.getLogger('PLEX.default')
|
LOG = logging.getLogger('PLEX.default')
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class Main():
|
||||||
# MAIN ENTRY POINT
|
# MAIN ENTRY POINT
|
||||||
# @utils.profiling()
|
# @utils.profiling()
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
log.debug('Full sys.argv received: %s', argv)
|
LOG.debug('Full sys.argv received: %s', argv)
|
||||||
# Parse parameters
|
# Parse parameters
|
||||||
path = unicode_paths.decode(argv[0])
|
path = unicode_paths.decode(argv[0])
|
||||||
arguments = unicode_paths.decode(argv[2])
|
arguments = unicode_paths.decode(argv[2])
|
||||||
|
@ -73,23 +73,23 @@ class Main():
|
||||||
# Hack so we can store this path in the Kodi DB
|
# Hack so we can store this path in the Kodi DB
|
||||||
handle = ('plugin://%s?mode=extras&plex_id=%s'
|
handle = ('plugin://%s?mode=extras&plex_id=%s'
|
||||||
% (v.ADDON_ID, params.get('plex_id')))
|
% (v.ADDON_ID, params.get('plex_id')))
|
||||||
if getCurrentWindowId() == 10025:
|
if xbmcgui.getCurrentWindowId() == 10025:
|
||||||
# Video Window
|
# Video Window
|
||||||
executebuiltin('Container.Update(\"%s\")' % handle)
|
xbmc.executebuiltin('Container.Update(\"%s\")' % handle)
|
||||||
else:
|
else:
|
||||||
executebuiltin('ActivateWindow(videos, \"%s\")' % handle)
|
xbmc.executebuiltin('ActivateWindow(videos, \"%s\")' % handle)
|
||||||
|
|
||||||
elif mode == 'extras':
|
elif mode == 'extras':
|
||||||
entrypoint.extras(plex_id=params.get('plex_id'))
|
entrypoint.extras(plex_id=params.get('plex_id'))
|
||||||
|
|
||||||
elif mode == 'settings':
|
elif mode == 'settings':
|
||||||
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
|
xbmc.executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
|
||||||
|
|
||||||
elif mode == 'enterPMS':
|
elif mode == 'enterPMS':
|
||||||
entrypoint.create_new_pms()
|
entrypoint.create_new_pms()
|
||||||
|
|
||||||
elif mode == 'reset':
|
elif mode == 'reset':
|
||||||
utils.plex_command('RESET-PKC')
|
transfer.plex_command('RESET-PKC')
|
||||||
|
|
||||||
elif mode == 'togglePlexTV':
|
elif mode == 'togglePlexTV':
|
||||||
entrypoint.toggle_plex_tv_sign_in()
|
entrypoint.toggle_plex_tv_sign_in()
|
||||||
|
@ -102,15 +102,15 @@ class Main():
|
||||||
|
|
||||||
elif mode in ('manualsync', 'repair'):
|
elif mode in ('manualsync', 'repair'):
|
||||||
if mode == 'repair':
|
if mode == 'repair':
|
||||||
log.info('Requesting repair lib sync')
|
LOG.info('Requesting repair lib sync')
|
||||||
utils.plex_command('repair-scan')
|
transfer.plex_command('repair-scan')
|
||||||
elif mode == 'manualsync':
|
elif mode == 'manualsync':
|
||||||
log.info('Requesting full library scan')
|
LOG.info('Requesting full library scan')
|
||||||
utils.plex_command('full-scan')
|
transfer.plex_command('full-scan')
|
||||||
|
|
||||||
elif mode == 'texturecache':
|
elif mode == 'texturecache':
|
||||||
log.info('Requesting texture caching of all textures')
|
LOG.info('Requesting texture caching of all textures')
|
||||||
utils.plex_command('textures-scan')
|
transfer.plex_command('textures-scan')
|
||||||
|
|
||||||
elif mode == 'chooseServer':
|
elif mode == 'chooseServer':
|
||||||
entrypoint.choose_pms_server()
|
entrypoint.choose_pms_server()
|
||||||
|
@ -119,8 +119,8 @@ class Main():
|
||||||
self.deviceid()
|
self.deviceid()
|
||||||
|
|
||||||
elif mode == 'fanart':
|
elif mode == 'fanart':
|
||||||
log.info('User requested fanarttv refresh')
|
LOG.info('User requested fanarttv refresh')
|
||||||
utils.plex_command('fanart-scan')
|
transfer.plex_command('fanart-scan')
|
||||||
|
|
||||||
elif '/extrafanart' in path:
|
elif '/extrafanart' in path:
|
||||||
plexpath = arguments[1:]
|
plexpath = arguments[1:]
|
||||||
|
@ -150,51 +150,52 @@ class Main():
|
||||||
"""
|
"""
|
||||||
request = '%s&handle=%s' % (argv[2], HANDLE)
|
request = '%s&handle=%s' % (argv[2], HANDLE)
|
||||||
# Put the request into the 'queue'
|
# Put the request into the 'queue'
|
||||||
utils.plex_command('PLAY-%s' % request)
|
transfer.plex_command('PLAY-%s' % request)
|
||||||
if HANDLE == -1:
|
if HANDLE == -1:
|
||||||
# Handle -1 received, not waiting for main thread
|
# Handle -1 received, not waiting for main thread
|
||||||
return
|
return
|
||||||
# Wait for the result
|
# Wait for the result from the main PKC thread
|
||||||
while not pickler.pickl_window('plex_result'):
|
result = transfer.wait_for_transfer()
|
||||||
sleep(50)
|
|
||||||
result = pickler.unpickle_me()
|
|
||||||
if result is None:
|
if result is None:
|
||||||
log.error('Error encountered, aborting')
|
LOG.error('Error encountered, aborting')
|
||||||
utils.dialog('notification',
|
utils.dialog('notification',
|
||||||
heading='{plex}',
|
heading='{plex}',
|
||||||
message=utils.lang(30128),
|
message=utils.lang(30128),
|
||||||
icon='{error}',
|
icon='{error}',
|
||||||
time=3000)
|
time=3000)
|
||||||
setResolvedUrl(HANDLE, False, ListItem())
|
xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem())
|
||||||
elif result.listitem:
|
elif result is True:
|
||||||
listitem = pkc_listitem.convert_pkc_to_listitem(result.listitem)
|
pass
|
||||||
setResolvedUrl(HANDLE, True, listitem)
|
else:
|
||||||
|
# Received a xbmcgui.ListItem()
|
||||||
|
xbmcplugin.setResolvedUrl(HANDLE, True, result)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def deviceid():
|
def deviceid():
|
||||||
deviceId_old = pickler.pickl_window('plex_client_Id')
|
window = xbmcgui.Window(10000)
|
||||||
|
deviceId_old = window.getProperty('plex_client_Id')
|
||||||
from resources.lib import clientinfo
|
from resources.lib import clientinfo
|
||||||
try:
|
try:
|
||||||
deviceId = clientinfo.getDeviceId(reset=True)
|
deviceId = clientinfo.getDeviceId(reset=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Failed to generate a new device Id: %s' % e)
|
LOG.error('Failed to generate a new device Id: %s' % e)
|
||||||
utils.messageDialog(utils.lang(29999), utils.lang(33032))
|
utils.messageDialog(utils.lang(29999), utils.lang(33032))
|
||||||
else:
|
else:
|
||||||
log.info('Successfully removed old device ID: %s New deviceId:'
|
LOG.info('Successfully removed old device ID: %s New deviceId:'
|
||||||
'%s' % (deviceId_old, deviceId))
|
'%s' % (deviceId_old, deviceId))
|
||||||
# 'Kodi will now restart to apply the changes'
|
# 'Kodi will now restart to apply the changes'
|
||||||
utils.messageDialog(utils.lang(29999), utils.lang(33033))
|
utils.messageDialog(utils.lang(29999), utils.lang(33033))
|
||||||
executebuiltin('RestartApp')
|
xbmc.executebuiltin('RestartApp')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log.info('%s started' % v.ADDON_ID)
|
LOG.info('%s started' % v.ADDON_ID)
|
||||||
try:
|
try:
|
||||||
v.database_paths()
|
v.database_paths()
|
||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
# Database does not exists
|
# Database does not exists
|
||||||
log.error('The current Kodi version is incompatible')
|
LOG.error('The current Kodi version is incompatible')
|
||||||
log.error('Error: %s', err)
|
LOG.error('Error: %s', err)
|
||||||
else:
|
else:
|
||||||
Main()
|
Main()
|
||||||
log.info('%s stopped' % v.ADDON_ID)
|
LOG.info('%s stopped' % v.ADDON_ID)
|
||||||
|
|
|
@ -14,6 +14,7 @@ from xbmcgui import ListItem
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from . import path_ops
|
from . import path_ops
|
||||||
|
from . import transfer
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from . import plex_functions as PF
|
from . import plex_functions as PF
|
||||||
|
@ -33,7 +34,7 @@ def choose_pms_server():
|
||||||
Lets user choose from list of PMS
|
Lets user choose from list of PMS
|
||||||
"""
|
"""
|
||||||
LOG.info("Choosing PMS server requested, starting")
|
LOG.info("Choosing PMS server requested, starting")
|
||||||
utils.plex_command('choose_pms_server')
|
transfer.plex_command('choose_pms_server')
|
||||||
|
|
||||||
|
|
||||||
def toggle_plex_tv_sign_in():
|
def toggle_plex_tv_sign_in():
|
||||||
|
@ -42,7 +43,7 @@ def toggle_plex_tv_sign_in():
|
||||||
Or signs in to plex.tv if the user was not logged in before.
|
Or signs in to plex.tv if the user was not logged in before.
|
||||||
"""
|
"""
|
||||||
LOG.info('Toggle of Plex.tv sign-in requested')
|
LOG.info('Toggle of Plex.tv sign-in requested')
|
||||||
utils.plex_command('toggle_plex_tv_sign_in')
|
transfer.plex_command('toggle_plex_tv_sign_in')
|
||||||
|
|
||||||
|
|
||||||
def directory_item(label, path, folder=True):
|
def directory_item(label, path, folder=True):
|
||||||
|
@ -128,7 +129,7 @@ def switch_plex_user():
|
||||||
# position = 0
|
# position = 0
|
||||||
# utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
|
# utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
|
||||||
LOG.info("Plex home user switch requested")
|
LOG.info("Plex home user switch requested")
|
||||||
utils.plex_command('switch_plex_user')
|
transfer.plex_command('switch_plex_user')
|
||||||
|
|
||||||
|
|
||||||
def create_listitem(item, append_show_title=False, append_sxxexx=False):
|
def create_listitem(item, append_show_title=False, append_sxxexx=False):
|
||||||
|
@ -905,4 +906,4 @@ def create_new_pms():
|
||||||
Opens dialogs for the user the plug in the PMS details
|
Opens dialogs for the user the plug in the PMS details
|
||||||
"""
|
"""
|
||||||
LOG.info('Request to manually enter new PMS address')
|
LOG.info('Request to manually enter new PMS address')
|
||||||
utils.plex_command('enter_new_pms_address')
|
transfer.plex_command('enter_new_pms_address')
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
|
||||||
from cPickle import dumps, loads
|
|
||||||
from xbmcgui import Window
|
|
||||||
from xbmc import log, LOGDEBUG
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
WINDOW = Window(10000)
|
|
||||||
PREFIX = 'PLEX.pickler: '
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
def try_encode(input_str, encoding='utf-8'):
|
|
||||||
"""
|
|
||||||
Will try to encode input_str (in unicode) to encoding. This possibly
|
|
||||||
fails with e.g. Android TV's Python, which does not accept arguments for
|
|
||||||
string.encode()
|
|
||||||
|
|
||||||
COPY to avoid importing utils on calling default.py
|
|
||||||
"""
|
|
||||||
if isinstance(input_str, str):
|
|
||||||
# already encoded
|
|
||||||
return input_str
|
|
||||||
try:
|
|
||||||
input_str = input_str.encode(encoding, "ignore")
|
|
||||||
except TypeError:
|
|
||||||
input_str = input_str.encode()
|
|
||||||
return input_str
|
|
||||||
|
|
||||||
|
|
||||||
def pickl_window(property, value=None, clear=False):
|
|
||||||
"""
|
|
||||||
Get or set window property - thread safe! For use with Pickle
|
|
||||||
Property and value must be string
|
|
||||||
"""
|
|
||||||
if clear:
|
|
||||||
WINDOW.clearProperty(property)
|
|
||||||
elif value is not None:
|
|
||||||
WINDOW.setProperty(property, value)
|
|
||||||
else:
|
|
||||||
return try_encode(WINDOW.getProperty(property))
|
|
||||||
|
|
||||||
|
|
||||||
def pickle_me(obj, window_var='plex_result'):
|
|
||||||
"""
|
|
||||||
Pickles the obj to the window variable. Use to transfer Python
|
|
||||||
objects between different PKC python instances (e.g. if default.py is
|
|
||||||
called and you'd want to use the service.py instance)
|
|
||||||
|
|
||||||
obj can be pretty much any Python object. However, classes and
|
|
||||||
functions won't work. See the Pickle documentation
|
|
||||||
"""
|
|
||||||
log('%sStart pickling' % PREFIX, level=LOGDEBUG)
|
|
||||||
pickl_window(window_var, value=dumps(obj))
|
|
||||||
log('%sSuccessfully pickled' % PREFIX, level=LOGDEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
def unpickle_me(window_var='plex_result'):
|
|
||||||
"""
|
|
||||||
Unpickles a Python object from the window variable window_var.
|
|
||||||
Will then clear the window variable!
|
|
||||||
"""
|
|
||||||
result = pickl_window(window_var)
|
|
||||||
pickl_window(window_var, clear=True)
|
|
||||||
log('%sStart unpickling' % PREFIX, level=LOGDEBUG)
|
|
||||||
obj = loads(result)
|
|
||||||
log('%sSuccessfully unpickled' % PREFIX, level=LOGDEBUG)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class Playback_Successful(object):
|
|
||||||
"""
|
|
||||||
Used to communicate with another PKC Python instance
|
|
||||||
"""
|
|
||||||
listitem = None
|
|
|
@ -16,9 +16,8 @@ from .kodi_db import KodiVideoDB
|
||||||
from . import playlist_func as PL
|
from . import playlist_func as PL
|
||||||
from . import playqueue as PQ
|
from . import playqueue as PQ
|
||||||
from . import json_rpc as js
|
from . import json_rpc as js
|
||||||
from . import pickler
|
from . import transfer
|
||||||
from .playutils import PlayUtils
|
from .playutils import PlayUtils
|
||||||
from .pkc_listitem import PKCListItem
|
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import app
|
from . import app
|
||||||
|
|
||||||
|
@ -261,11 +260,11 @@ def _ensure_resolve(abort=False):
|
||||||
"""
|
"""
|
||||||
if RESOLVE:
|
if RESOLVE:
|
||||||
if not abort:
|
if not abort:
|
||||||
result = pickler.Playback_Successful()
|
# Releases the other Python thread without a ListItem
|
||||||
pickler.pickle_me(result)
|
transfer.send(True)
|
||||||
else:
|
else:
|
||||||
# Shows PKC error message
|
# Shows PKC error message
|
||||||
pickler.pickle_me(None)
|
transfer.send(None)
|
||||||
if abort:
|
if abort:
|
||||||
# Reset some playback variables
|
# Reset some playback variables
|
||||||
app.PLAYSTATE.context_menu_play = False
|
app.PLAYSTATE.context_menu_play = False
|
||||||
|
@ -383,8 +382,7 @@ def _conclude_playback(playqueue, pos):
|
||||||
return PKC listitem attached to result
|
return PKC listitem attached to result
|
||||||
"""
|
"""
|
||||||
LOG.info('Concluding playback for playqueue position %s', pos)
|
LOG.info('Concluding playback for playqueue position %s', pos)
|
||||||
result = pickler.Playback_Successful()
|
listitem = transfer.PKCListItem()
|
||||||
listitem = PKCListItem()
|
|
||||||
item = playqueue.items[pos]
|
item = playqueue.items[pos]
|
||||||
if item.xml is not None:
|
if item.xml is not None:
|
||||||
# Got a Plex element
|
# Got a Plex element
|
||||||
|
@ -399,7 +397,7 @@ def _conclude_playback(playqueue, pos):
|
||||||
if not playurl:
|
if not playurl:
|
||||||
LOG.info('Did not get a playurl, aborting playback silently')
|
LOG.info('Did not get a playurl, aborting playback silently')
|
||||||
app.PLAYSTATE.resume_playback = False
|
app.PLAYSTATE.resume_playback = False
|
||||||
pickler.pickle_me(result)
|
transfer.send(True)
|
||||||
return
|
return
|
||||||
listitem.setPath(utils.try_encode(playurl))
|
listitem.setPath(utils.try_encode(playurl))
|
||||||
if item.playmethod == 'DirectStream':
|
if item.playmethod == 'DirectStream':
|
||||||
|
@ -427,8 +425,7 @@ def _conclude_playback(playqueue, pos):
|
||||||
listitem.setProperty('StartOffset', str(item.offset))
|
listitem.setProperty('StartOffset', str(item.offset))
|
||||||
listitem.setProperty('resumetime', str(item.offset))
|
listitem.setProperty('resumetime', str(item.offset))
|
||||||
# Reset the resumable flag
|
# Reset the resumable flag
|
||||||
result.listitem = listitem
|
transfer.send(listitem)
|
||||||
pickler.pickle_me(result)
|
|
||||||
LOG.info('Done concluding playback')
|
LOG.info('Done concluding playback')
|
||||||
|
|
||||||
|
|
||||||
|
@ -447,7 +444,6 @@ def process_indirect(key, offset, resolve=True):
|
||||||
key, offset, resolve)
|
key, offset, resolve)
|
||||||
global RESOLVE
|
global RESOLVE
|
||||||
RESOLVE = resolve
|
RESOLVE = resolve
|
||||||
result = pickler.Playback_Successful()
|
|
||||||
if key.startswith('http') or key.startswith('{server}'):
|
if key.startswith('http') or key.startswith('{server}'):
|
||||||
xml = DU().downloadUrl(key)
|
xml = DU().downloadUrl(key)
|
||||||
elif key.startswith('/system/services'):
|
elif key.startswith('/system/services'):
|
||||||
|
@ -464,7 +460,7 @@ def process_indirect(key, offset, resolve=True):
|
||||||
offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset))
|
offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset))
|
||||||
# Todo: implement offset
|
# Todo: implement offset
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
listitem = PKCListItem()
|
listitem = transfer.PKCListItem()
|
||||||
api.create_listitem(listitem)
|
api.create_listitem(listitem)
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = PQ.get_playqueue_from_type(
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
||||||
|
@ -488,8 +484,7 @@ def process_indirect(key, offset, resolve=True):
|
||||||
listitem.setPath(utils.try_encode(playurl))
|
listitem.setPath(utils.try_encode(playurl))
|
||||||
playqueue.items.append(item)
|
playqueue.items.append(item)
|
||||||
if resolve is True:
|
if resolve is True:
|
||||||
result.listitem = listitem
|
transfer.send(listitem)
|
||||||
pickler.pickle_me(result)
|
|
||||||
else:
|
else:
|
||||||
thread = Thread(target=app.APP.player.play,
|
thread = Thread(target=app.APP.player.play,
|
||||||
args={'item': utils.try_encode(playurl),
|
args={'item': utils.try_encode(playurl),
|
||||||
|
|
|
@ -7,7 +7,7 @@ from urlparse import parse_qsl
|
||||||
|
|
||||||
from . import playback
|
from . import playback
|
||||||
from . import context_entry
|
from . import context_entry
|
||||||
from . import pickler
|
from . import transfer
|
||||||
from . import backgroundthread
|
from . import backgroundthread
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -33,7 +33,7 @@ class PlaybackTask(backgroundthread.Task):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# E.g. other add-ons scanning for Extras folder
|
# E.g. other add-ons scanning for Extras folder
|
||||||
LOG.debug('Detected 3rd party add-on call - ignoring')
|
LOG.debug('Detected 3rd party add-on call - ignoring')
|
||||||
pickler.pickle_me(pickler.Playback_Successful())
|
transfer.send(True)
|
||||||
return
|
return
|
||||||
params = dict(parse_qsl(params))
|
params = dict(parse_qsl(params))
|
||||||
mode = params.get('mode')
|
mode = params.get('mode')
|
||||||
|
|
|
@ -29,7 +29,7 @@ LOG = logging.getLogger("PLEX.service")
|
||||||
WINDOW_PROPERTIES = (
|
WINDOW_PROPERTIES = (
|
||||||
"plex_dbScan", "pms_token", "plex_token", "pms_server",
|
"plex_dbScan", "pms_token", "plex_token", "pms_server",
|
||||||
"plex_authenticated", "plex_restricteduser", "plex_allows_mediaDeletion",
|
"plex_authenticated", "plex_restricteduser", "plex_allows_mediaDeletion",
|
||||||
"plex_command", "plex_result")
|
"plexkodiconnect.command", "plex_result")
|
||||||
|
|
||||||
# "Start from beginning", "Play from beginning"
|
# "Start from beginning", "Play from beginning"
|
||||||
STRINGS = (utils.try_encode(utils.lang(12021)),
|
STRINGS = (utils.try_encode(utils.lang(12021)),
|
||||||
|
@ -393,11 +393,11 @@ class Service():
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check for PKC commands from other Python instances
|
# Check for PKC commands from other Python instances
|
||||||
plex_command = utils.window('plex_command')
|
plex_command = utils.window('plexkodiconnect.command')
|
||||||
if plex_command:
|
if plex_command:
|
||||||
# Commands/user interaction received from other PKC Python
|
# Commands/user interaction received from other PKC Python
|
||||||
# instances (default.py and context.py instead of service.py)
|
# instances (default.py and context.py instead of service.py)
|
||||||
utils.window('plex_command', clear=True)
|
utils.window('plexkodiconnect.command', clear=True)
|
||||||
task = None
|
task = None
|
||||||
if plex_command.startswith('PLAY-'):
|
if plex_command.startswith('PLAY-'):
|
||||||
# Add-on path playback!
|
# Add-on path playback!
|
||||||
|
|
|
@ -1,9 +1,120 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Used to shovel data from separate Kodi Python instances to the main thread
|
||||||
|
and vice versa.
|
||||||
|
"""
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from xbmcgui import ListItem
|
from logging import getLogger
|
||||||
|
import json
|
||||||
|
|
||||||
from . import utils
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
|
||||||
|
LOG = getLogger('PLEX.transfer')
|
||||||
|
MONITOR = xbmc.Monitor()
|
||||||
|
WINDOW = xbmcgui.Window(10000)
|
||||||
|
WINDOW_RESULT = 'plexkodiconnect.result'.encode('utf-8')
|
||||||
|
WINDOW_COMMAND = 'plexkodiconnect.command'.encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def cast(func, value):
|
||||||
|
"""
|
||||||
|
Cast the specified value to the specified type (returned by func). Currently this
|
||||||
|
only support int, float, bool. Should be extended if needed.
|
||||||
|
Parameters:
|
||||||
|
func (func): Calback function to used cast to type (int, bool, float).
|
||||||
|
value (any): value to be cast and returned.
|
||||||
|
"""
|
||||||
|
if value is not None:
|
||||||
|
if func == bool:
|
||||||
|
return bool(int(value))
|
||||||
|
elif func == unicode:
|
||||||
|
if isinstance(value, (int, long, float)):
|
||||||
|
return unicode(value)
|
||||||
|
else:
|
||||||
|
return value.decode('utf-8')
|
||||||
|
elif func == str:
|
||||||
|
if isinstance(value, (int, long, float)):
|
||||||
|
return str(value)
|
||||||
|
else:
|
||||||
|
return value.encode('utf-8')
|
||||||
|
elif func in (int, float):
|
||||||
|
try:
|
||||||
|
return func(value)
|
||||||
|
except ValueError:
|
||||||
|
return float('nan')
|
||||||
|
return func(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def kodi_window(property, value=None, clear=False):
|
||||||
|
"""
|
||||||
|
Get or set window property - thread safe! value must be string
|
||||||
|
"""
|
||||||
|
if clear:
|
||||||
|
WINDOW.clearProperty(property)
|
||||||
|
elif value is not None:
|
||||||
|
WINDOW.setProperty(property, value)
|
||||||
|
else:
|
||||||
|
return WINDOW.getProperty(property)
|
||||||
|
|
||||||
|
|
||||||
|
def plex_command(value):
|
||||||
|
"""
|
||||||
|
Used to funnel states between different Python instances. NOT really thread
|
||||||
|
safe - let's hope the Kodi user can't click fast enough
|
||||||
|
"""
|
||||||
|
while kodi_window(WINDOW_COMMAND):
|
||||||
|
if MONITOR.waitForAbort(20):
|
||||||
|
return
|
||||||
|
kodi_window(WINDOW_COMMAND, value=value)
|
||||||
|
|
||||||
|
|
||||||
|
def serialize(obj):
|
||||||
|
if isinstance(obj, PKCListItem):
|
||||||
|
return {'type': 'PKCListItem', 'data': obj.data}
|
||||||
|
else:
|
||||||
|
return {'type': 'other', 'data': obj}
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def de_serialize(answ):
|
||||||
|
if answ['type'] == 'PKCListItem':
|
||||||
|
result = PKCListItem()
|
||||||
|
result.data = answ['data']
|
||||||
|
return convert_pkc_to_listitem(result)
|
||||||
|
elif answ['type'] == 'other':
|
||||||
|
return answ['data']
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Not implemented: %s' % answ)
|
||||||
|
|
||||||
|
|
||||||
|
def send(pkc_listitem):
|
||||||
|
"""
|
||||||
|
Pickles the obj to the window variable. Use to transfer Python
|
||||||
|
objects between different PKC python instances (e.g. if default.py is
|
||||||
|
called and you'd want to use the service.py instance)
|
||||||
|
|
||||||
|
obj can be pretty much any Python object. However, classes and
|
||||||
|
functions won't work. See the Pickle documentation
|
||||||
|
"""
|
||||||
|
LOG.debug('Sending: %s', pkc_listitem)
|
||||||
|
kodi_window(WINDOW_RESULT,
|
||||||
|
value=json.dumps(serialize(pkc_listitem)))
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_transfer():
|
||||||
|
result = ''
|
||||||
|
while not result:
|
||||||
|
result = kodi_window(WINDOW_RESULT)
|
||||||
|
if result:
|
||||||
|
kodi_window(WINDOW_RESULT, clear=True)
|
||||||
|
LOG.debug('Received')
|
||||||
|
result = json.loads(result)
|
||||||
|
return de_serialize(result)
|
||||||
|
elif MONITOR.waitForAbort(0.05):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def convert_pkc_to_listitem(pkc_listitem):
|
def convert_pkc_to_listitem(pkc_listitem):
|
||||||
|
@ -11,7 +122,7 @@ def convert_pkc_to_listitem(pkc_listitem):
|
||||||
Insert a PKCListItem() and you will receive a valid XBMC listitem
|
Insert a PKCListItem() and you will receive a valid XBMC listitem
|
||||||
"""
|
"""
|
||||||
data = pkc_listitem.data
|
data = pkc_listitem.data
|
||||||
listitem = ListItem(label=data.get('label'),
|
listitem = xbmcgui.ListItem(label=data.get('label'),
|
||||||
label2=data.get('label2'),
|
label2=data.get('label2'),
|
||||||
path=data.get('path'))
|
path=data.get('path'))
|
||||||
if data['info']:
|
if data['info']:
|
||||||
|
@ -23,7 +134,7 @@ def convert_pkc_to_listitem(pkc_listitem):
|
||||||
if data['art']:
|
if data['art']:
|
||||||
listitem.setArt(data['art'])
|
listitem.setArt(data['art'])
|
||||||
for key, value in data['property'].iteritems():
|
for key, value in data['property'].iteritems():
|
||||||
listitem.setProperty(key, utils.cast(str, value))
|
listitem.setProperty(key, cast(str, value))
|
||||||
if data['subtitles']:
|
if data['subtitles']:
|
||||||
listitem.setSubtitles(data['subtitles'])
|
listitem.setSubtitles(data['subtitles'])
|
||||||
return listitem
|
return listitem
|
|
@ -104,16 +104,6 @@ def window(prop, value=None, clear=False, windowid=10000):
|
||||||
return try_decode(win.getProperty(prop))
|
return try_decode(win.getProperty(prop))
|
||||||
|
|
||||||
|
|
||||||
def plex_command(value):
|
|
||||||
"""
|
|
||||||
Used to funnel states between different Python instances. NOT really thread
|
|
||||||
safe - let's hope the Kodi user can't click fast enough
|
|
||||||
"""
|
|
||||||
while window('plex_command'):
|
|
||||||
xbmc.sleep(20)
|
|
||||||
window('plex_command', value=value)
|
|
||||||
|
|
||||||
|
|
||||||
def settings(setting, value=None):
|
def settings(setting, value=None):
|
||||||
"""
|
"""
|
||||||
Get or add addon setting. Returns unicode
|
Get or add addon setting. Returns unicode
|
||||||
|
|
Loading…
Reference in a new issue