Switch to absolute imports

This commit is contained in:
Croneter 2018-06-21 19:24:37 +02:00
parent 657ba47714
commit c440dc7779
65 changed files with 1733 additions and 1694 deletions

View File

@ -3,8 +3,8 @@
<requires> <requires>
<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="plugin.video.plexkodiconnect.movies" version="2.0.4" /> <import addon="plugin.video.plexkodiconnect.movies" version="2.0.5" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.4" /> <import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.5" />
</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>

View File

@ -1,47 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from __future__ import unicode_literals # nice to fix os.walk unicode from __future__ import absolute_import, division, unicode_literals
import logging import logging
from os import path as os_path from sys import argv
from sys import path as sys_path, argv
from urlparse import parse_qsl from urlparse import parse_qsl
from xbmc import sleep, executebuiltin
from xbmc import translatePath, sleep, executebuiltin
from xbmcaddon import Addon
from xbmcgui import ListItem from xbmcgui import ListItem
from xbmcplugin import setResolvedUrl from xbmcplugin import setResolvedUrl
_addon = Addon(id='plugin.video.plexkodiconnect') from resources.lib import entrypoint, utils, pickler, pkc_listitem, \
try: variables as v, loghandler
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
except TypeError:
_addon_path = _addon.getAddonInfo('path').decode()
try:
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode('utf-8')
except TypeError:
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode()
sys_path.append(_base_resource)
############################################################################### ###############################################################################
import entrypoint
from utils import window, reset, passwords_xml, language as lang, dialog, \
plex_command
from pickler import unpickle_me, pickl_window
from PKC_listitem import convert_PKC_to_listitem
import variables as v
###############################################################################
import loghandler
loghandler.config() loghandler.config()
log = logging.getLogger('PLEX.default') log = logging.getLogger('PLEX.default')
@ -104,7 +76,7 @@ class Main():
entrypoint.create_new_pms() entrypoint.create_new_pms()
elif mode == 'reset': elif mode == 'reset':
reset() utils.reset()
elif mode == 'togglePlexTV': elif mode == 'togglePlexTV':
entrypoint.toggle_plex_tv_sign_in() entrypoint.toggle_plex_tv_sign_in()
@ -113,41 +85,41 @@ class Main():
entrypoint.reset_authorization() entrypoint.reset_authorization()
elif mode == 'passwords': elif mode == 'passwords':
passwords_xml() utils.passwords_xml()
elif mode == 'switchuser': elif mode == 'switchuser':
entrypoint.switch_plex_user() entrypoint.switch_plex_user()
elif mode in ('manualsync', 'repair'): elif mode in ('manualsync', 'repair'):
if window('plex_online') != 'true': if pickler.pickl_window('plex_online') != 'true':
# Server is not online, do not run the sync # Server is not online, do not run the sync
dialog('ok', lang(29999), lang(39205)) utils.dialog('ok', utils.lang(29999), utils.lang(39205))
log.error('Not connected to a PMS.') log.error('Not connected to a PMS.')
else: else:
if mode == 'repair': if mode == 'repair':
log.info('Requesting repair lib sync') log.info('Requesting repair lib sync')
plex_command('RUN_LIB_SCAN', 'repair') utils.plex_command('RUN_LIB_SCAN', 'repair')
elif mode == 'manualsync': elif mode == 'manualsync':
log.info('Requesting full library scan') log.info('Requesting full library scan')
plex_command('RUN_LIB_SCAN', 'full') utils.plex_command('RUN_LIB_SCAN', 'full')
elif mode == 'texturecache': elif mode == 'texturecache':
log.info('Requesting texture caching of all textures') log.info('Requesting texture caching of all textures')
plex_command('RUN_LIB_SCAN', 'textures') utils.plex_command('RUN_LIB_SCAN', 'textures')
elif mode == 'chooseServer': elif mode == 'chooseServer':
entrypoint.choose_pms_server() entrypoint.choose_pms_server()
elif mode == 'refreshplaylist': elif mode == 'refreshplaylist':
log.info('Requesting playlist/nodes refresh') log.info('Requesting playlist/nodes refresh')
plex_command('RUN_LIB_SCAN', 'views') utils.plex_command('RUN_LIB_SCAN', 'views')
elif mode == 'deviceid': elif mode == 'deviceid':
self.deviceid() self.deviceid()
elif mode == 'fanart': elif mode == 'fanart':
log.info('User requested fanarttv refresh') log.info('User requested fanarttv refresh')
plex_command('RUN_LIB_SCAN', 'fanart') utils.plex_command('RUN_LIB_SCAN', 'fanart')
elif '/extrafanart' in argv[0]: elif '/extrafanart' in argv[0]:
plexpath = argv[2][1:] plexpath = argv[2][1:]
@ -171,40 +143,40 @@ 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'
plex_command('PLAY', request) utils.plex_command('PLAY', 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
while not pickl_window('plex_result'): while not pickler.pickl_window('plex_result'):
sleep(50) sleep(50)
result = unpickle_me() result = pickler.unpickle_me()
if result is None: if result is None:
log.error('Error encountered, aborting') log.error('Error encountered, aborting')
dialog('notification', utils.dialog('notification',
heading='{plex}', heading='{plex}',
message=lang(30128), message=utils.lang(30128),
icon='{error}', icon='{error}',
time=3000) time=3000)
setResolvedUrl(HANDLE, False, ListItem()) setResolvedUrl(HANDLE, False, ListItem())
elif result.listitem: elif result.listitem:
listitem = convert_PKC_to_listitem(result.listitem) listitem = pkc_listitem.convert_pkc_to_listitem(result.listitem)
setResolvedUrl(HANDLE, True, listitem) setResolvedUrl(HANDLE, True, listitem)
@staticmethod @staticmethod
def deviceid(): def deviceid():
deviceId_old = window('plex_client_Id') deviceId_old = pickler.pickl_window('plex_client_Id')
from clientinfo import getDeviceId from clientinfo import getDeviceId
try: try:
deviceId = getDeviceId(reset=True) deviceId = 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)
dialog('ok', lang(29999), lang(33032)) utils.dialog('ok', 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'
dialog('ok', lang(29999), lang(33033)) utils.dialog('ok', utils.lang(29999), utils.lang(33033))
executebuiltin('RestartApp') executebuiltin('RestartApp')

View File

@ -3,11 +3,11 @@
from xbmcgui import ListItem from xbmcgui import ListItem
def convert_PKC_to_listitem(PKC_listitem): def convert_pkc_to_listitem(pkc_listitem):
""" """
Insert a PKC_listitem 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 = ListItem(label=data.get('label'),
label2=data.get('label2'), label2=data.get('label2'),
path=data.get('path')) path=data.get('path'))
@ -26,7 +26,7 @@ def convert_PKC_to_listitem(PKC_listitem):
return listitem return listitem
class PKC_ListItem(object): class PKCListItem(object):
""" """
Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data
when pickling! when pickling!

View File

@ -12,18 +12,17 @@ import requests
import xbmc import xbmc
from xbmcvfs import exists from xbmcvfs import exists
from utils import settings, language as lang, kodi_sql, try_encode, try_decode,\ from . import utils
thread_methods, dialog, exists_dir from . import state
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.artwork')
# Disable annoying requests warnings # Disable annoying requests warnings
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
ARTWORK_QUEUE = Queue() ARTWORK_QUEUE = Queue()
IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC'] IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC']
if not settings('imageSyncDuringPlayback') == 'true': if not utils.settings('imageSyncDuringPlayback') == 'true':
IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC') IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC')
############################################################################### ###############################################################################
@ -37,7 +36,7 @@ def double_urldecode(text):
return unquote(unquote(text)) return unquote(unquote(text))
@thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS) @utils.thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS)
class Image_Cache_Thread(Thread): class Image_Cache_Thread(Thread):
sleep_between = 50 sleep_between = 50
# Potentially issues with limited number of threads # Potentially issues with limited number of threads
@ -73,20 +72,20 @@ class Image_Cache_Thread(Thread):
'Window.IsVisible(DialogAddonSettings.xml)'): 'Window.IsVisible(DialogAddonSettings.xml)'):
# Avoid saving '0' all the time # Avoid saving '0' all the time
set_zero = True set_zero = True
settings('caching_artwork_count', value='0') utils.settings('caching_artwork_count', value='0')
xbmc.sleep(1000) xbmc.sleep(1000)
continue continue
set_zero = False set_zero = False
if isinstance(url, ArtworkSyncMessage): if isinstance(url, ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS: if state.IMAGE_SYNC_NOTIFICATIONS:
dialog('notification', utils.dialog('notification',
heading=lang(29999), heading=utils.lang(29999),
message=url.message, message=url.message,
icon='{plex}', icon='{plex}',
sound=False) sound=False)
queue.task_done() queue.task_done()
continue continue
url = double_urlencode(try_encode(url)) url = double_urlencode(utils.try_encode(url))
sleeptime = 0 sleeptime = 0
while True: while True:
try: try:
@ -133,14 +132,15 @@ class Image_Cache_Thread(Thread):
if (counter > 20 and not xbmc.getCondVisibility( if (counter > 20 and not xbmc.getCondVisibility(
'Window.IsVisible(DialogAddonSettings.xml)')): 'Window.IsVisible(DialogAddonSettings.xml)')):
counter = 0 counter = 0
settings('caching_artwork_count', value=str(queue.qsize())) utils.settings('caching_artwork_count',
value=str(queue.qsize()))
# Sleep for a bit to reduce CPU strain # Sleep for a bit to reduce CPU strain
xbmc.sleep(sleep_between) xbmc.sleep(sleep_between)
LOG.info("---===### Stopped Image_Cache_Thread ###===---") LOG.info("---===### Stopped Image_Cache_Thread ###===---")
class Artwork(): class Artwork():
enableTextureCache = settings('enableTextureCache') == "true" enableTextureCache = utils.settings('enableTextureCache') == "true"
if enableTextureCache: if enableTextureCache:
queue = ARTWORK_QUEUE queue = ARTWORK_QUEUE
@ -156,7 +156,7 @@ class Artwork():
artworks = list() artworks = list()
# Get all posters and fanart/background for video and music # Get all posters and fanart/background for video and music
for kind in ('video', 'music'): for kind in ('video', 'music'):
connection = kodi_sql(kind) connection = utils.kodi_sql(kind)
cursor = connection.cursor() cursor = connection.cursor()
for typus in ('poster', 'fanart'): for typus in ('poster', 'fanart'):
cursor.execute('SELECT url FROM art WHERE type == ?', cursor.execute('SELECT url FROM art WHERE type == ?',
@ -164,7 +164,7 @@ class Artwork():
artworks.extend(cursor.fetchall()) artworks.extend(cursor.fetchall())
connection.close() connection.close()
artworks_to_cache = list() artworks_to_cache = list()
connection = kodi_sql('texture') connection = utils.kodi_sql('texture')
cursor = connection.cursor() cursor = connection.cursor()
for url in artworks: for url in artworks:
query = 'SELECT url FROM texture WHERE url == ? LIMIT 1' query = 'SELECT url FROM texture WHERE url == ? LIMIT 1'
@ -175,40 +175,41 @@ class Artwork():
if not artworks_to_cache: if not artworks_to_cache:
LOG.info('Caching of major images to Kodi texture cache done') LOG.info('Caching of major images to Kodi texture cache done')
# Set to "None" # Set to "None"
settings('caching_artwork_count', value=lang(30069)) utils.settings('caching_artwork_count', value=utils.lang(30069))
return return
length = len(artworks_to_cache) length = len(artworks_to_cache)
LOG.info('Caching has not been completed - caching %s major images', LOG.info('Caching has not been completed - caching %s major images',
length) length)
settings('caching_artwork_count', value=str(length)) utils.settings('caching_artwork_count', value=str(length))
# Caching %s Plex images # Caching %s Plex images
self.queue.put(ArtworkSyncMessage(lang(30006) % length)) self.queue.put(ArtworkSyncMessage(utils.lang(30006) % length))
for i, url in enumerate(artworks_to_cache): for i, url in enumerate(artworks_to_cache):
self.queue.put(url[0]) self.queue.put(url[0])
# Plex image caching done # Plex image caching done
self.queue.put(ArtworkSyncMessage(lang(30007))) self.queue.put(ArtworkSyncMessage(utils.lang(30007)))
def fullTextureCacheSync(self): def fullTextureCacheSync(self):
""" """
This method will sync all Kodi artwork to textures13.db This method will sync all Kodi artwork to textures13.db
and cache them locally. This takes diskspace! and cache them locally. This takes diskspace!
""" """
if not dialog('yesno', "Image Texture Cache", lang(39250)): if not utils.dialog('yesno', "Image Texture Cache", utils.lang(39250)):
return return
LOG.info("Doing Image Cache Sync") LOG.info("Doing Image Cache Sync")
# ask to rest all existing or not # ask to rest all existing or not
if dialog('yesno', "Image Texture Cache", lang(39251)): if utils.dialog('yesno', "Image Texture Cache", utils.lang(39251)):
LOG.info("Resetting all cache data first") LOG.info("Resetting all cache data first")
# Remove all existing textures first # Remove all existing textures first
path = try_decode(xbmc.translatePath("special://thumbnails/")) path = utils.try_decode(
if exists_dir(path): xbmc.translatePath("special://thumbnails/"))
if utils.exists_dir(path):
rmtree(path, ignore_errors=True) rmtree(path, ignore_errors=True)
self.restore_cache_directories() self.restore_cache_directories()
# remove all existing data from texture DB # remove all existing data from texture DB
connection = kodi_sql('texture') connection = utils.kodi_sql('texture')
cursor = connection.cursor() cursor = connection.cursor()
query = 'SELECT tbl_name FROM sqlite_master WHERE type=?' query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ('table', )) cursor.execute(query, ('table', ))
@ -221,7 +222,7 @@ class Artwork():
connection.close() connection.close()
# Cache all entries in video DB # Cache all entries in video DB
connection = kodi_sql('video') connection = utils.kodi_sql('video')
cursor = connection.cursor() cursor = connection.cursor()
# dont include actors # dont include actors
query = "SELECT url FROM art WHERE media_type != ?" query = "SELECT url FROM art WHERE media_type != ?"
@ -234,7 +235,7 @@ class Artwork():
for url in result: for url in result:
self.cache_texture(url[0]) self.cache_texture(url[0])
# Cache all entries in music DB # Cache all entries in music DB
connection = kodi_sql('music') connection = utils.kodi_sql('music')
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT url FROM art") cursor.execute("SELECT url FROM art")
result = cursor.fetchall() result = cursor.fetchall()
@ -309,7 +310,7 @@ class Artwork():
""" """
Deleted the cached artwork with path url (if it exists) Deleted the cached artwork with path url (if it exists)
""" """
connection = kodi_sql('texture') connection = utils.kodi_sql('texture')
cursor = connection.cursor() cursor = connection.cursor()
try: try:
cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1", cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
@ -323,7 +324,7 @@ class Artwork():
path = xbmc.translatePath("special://thumbnails/%s" % cachedurl) path = xbmc.translatePath("special://thumbnails/%s" % cachedurl)
LOG.debug("Deleting cached thumbnail: %s", path) LOG.debug("Deleting cached thumbnail: %s", path)
if exists(path): if exists(path):
rmtree(try_decode(path), ignore_errors=True) rmtree(utils.try_decode(path), ignore_errors=True)
cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit() connection.commit()
finally: finally:
@ -336,8 +337,8 @@ class Artwork():
"a", "b", "c", "d", "e", "f", "a", "b", "c", "d", "e", "f",
"Video", "plex") "Video", "plex")
for path in paths: for path in paths:
makedirs(try_decode(xbmc.translatePath("special://thumbnails/%s" makedirs(utils.try_decode(
% path))) xbmc.translatePath("special://thumbnails/%s" % path)))
class ArtworkSyncMessage(object): class ArtworkSyncMessage(object):

View File

@ -3,12 +3,12 @@
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from utils import window, settings from . import utils
import variables as v from . import variables as v
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger('PLEX.clientinfo')
############################################################################### ###############################################################################
@ -43,8 +43,8 @@ def getXArgsDeviceInfo(options=None, include_token=True):
'X-Plex-Client-Identifier': getDeviceId(), 'X-Plex-Client-Identifier': getDeviceId(),
'X-Plex-Provides': 'client,controller,player,pubsub-player', 'X-Plex-Provides': 'client,controller,player,pubsub-player',
} }
if include_token and window('pms_token'): if include_token and utils.window('pms_token'):
xargs['X-Plex-Token'] = window('pms_token') xargs['X-Plex-Token'] = utils.window('pms_token')
if options is not None: if options is not None:
xargs.update(options) xargs.update(options)
return xargs return xargs
@ -60,26 +60,26 @@ def getDeviceId(reset=False):
""" """
if reset is True: if reset is True:
v.PKC_MACHINE_IDENTIFIER = None v.PKC_MACHINE_IDENTIFIER = None
window('plex_client_Id', clear=True) utils.window('plex_client_Id', clear=True)
settings('plex_client_Id', value="") utils.settings('plex_client_Id', value="")
client_id = v.PKC_MACHINE_IDENTIFIER client_id = v.PKC_MACHINE_IDENTIFIER
if client_id: if client_id:
return client_id return client_id
client_id = settings('plex_client_Id') client_id = utils.settings('plex_client_Id')
# Because Kodi appears to cache file settings!! # Because Kodi appears to cache file settings!!
if client_id != "" and reset is False: if client_id != "" and reset is False:
v.PKC_MACHINE_IDENTIFIER = client_id v.PKC_MACHINE_IDENTIFIER = client_id
window('plex_client_Id', value=client_id) utils.window('plex_client_Id', value=client_id)
log.info("Unique device Id plex_client_Id loaded: %s", client_id) LOG.info("Unique device Id plex_client_Id loaded: %s", client_id)
return client_id return client_id
log.info("Generating a new deviceid.") LOG.info("Generating a new deviceid.")
from uuid import uuid4 from uuid import uuid4
client_id = str(uuid4()) client_id = str(uuid4())
settings('plex_client_Id', value=client_id) utils.settings('plex_client_Id', value=client_id)
v.PKC_MACHINE_IDENTIFIER = client_id v.PKC_MACHINE_IDENTIFIER = client_id
window('plex_client_Id', value=client_id) utils.window('plex_client_Id', value=client_id)
log.info("Unique device Id plex_client_Id generated: %s", client_id) LOG.info("Unique device Id plex_client_Id generated: %s", client_id)
return client_id return client_id

View File

@ -2,18 +2,17 @@
############################################################################### ###############################################################################
import logging import logging
from threading import Thread from threading import Thread
from xbmc import sleep from xbmc import sleep
from utils import window, thread_methods from . import utils
import state from . import state
############################################################################### ###############################################################################
LOG = logging.getLogger("PLEX." + __name__) LOG = logging.getLogger('PLEX.command_pipeline')
############################################################################### ###############################################################################
@thread_methods @utils.thread_methods
class Monitor_Window(Thread): class Monitor_Window(Thread):
""" """
Monitors window('plex_command') for new entries that we need to take care Monitors window('plex_command') for new entries that we need to take care
@ -26,9 +25,9 @@ class Monitor_Window(Thread):
queue = state.COMMAND_PIPELINE_QUEUE queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting Kodi_Play_Client ##===----") LOG.info("----===## Starting Kodi_Play_Client ##===----")
while not stopped(): while not stopped():
if window('plex_command'): if utils.window('plex_command'):
value = window('plex_command') value = utils.window('plex_command')
window('plex_command', clear=True) utils.window('plex_command', clear=True)
if value.startswith('PLAY-'): if value.startswith('PLAY-'):
queue.put(value.replace('PLAY-', '')) queue.put(value.replace('PLAY-', ''))
elif value == 'SUSPEND_LIBRARY_THREAD-True': elif value == 'SUSPEND_LIBRARY_THREAD-True':

View File

@ -2,18 +2,17 @@
Processes Plex companion inputs from the plexbmchelper to Kodi commands Processes Plex companion inputs from the plexbmchelper to Kodi commands
""" """
from logging import getLogger from logging import getLogger
from xbmc import Player from xbmc import Player
from variables import ALEXA_TO_COMPANION from . import playqueue as PQ
import playqueue as PQ from . import plex_functions as PF
from PlexFunctions import GetPlexKeyNumber from . import json_rpc as js
import json_rpc as js from . import variables as v
import state from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.companion')
############################################################################### ###############################################################################
@ -25,7 +24,7 @@ def skip_to(params):
Does not seem to be implemented yet by Plex! Does not seem to be implemented yet by Plex!
""" """
playqueue_item_id = params.get('playQueueItemID') playqueue_item_id = params.get('playQueueItemID')
_, plex_id = GetPlexKeyNumber(params.get('key')) _, plex_id = PF.GetPlexKeyNumber(params.get('key'))
LOG.debug('Skipping to playQueueItemID %s, plex_id %s', LOG.debug('Skipping to playQueueItemID %s, plex_id %s',
playqueue_item_id, plex_id) playqueue_item_id, plex_id)
found = True found = True
@ -51,8 +50,8 @@ def convert_alexa_to_companion(dictionary):
The params passed by Alexa must first be converted to Companion talk The params passed by Alexa must first be converted to Companion talk
""" """
for key in dictionary: for key in dictionary:
if key in ALEXA_TO_COMPANION: if key in v.ALEXA_TO_COMPANION:
dictionary[ALEXA_TO_COMPANION[key]] = dictionary[key] dictionary[v.ALEXA_TO_COMPANION[key]] = dictionary[key]
del dictionary[key] del dictionary[key]

View File

@ -2,15 +2,14 @@
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from os.path import join from os.path import join
import xbmcgui import xbmcgui
from xbmcaddon import Addon from xbmcaddon import Addon
from utils import window from . import utils
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.context')
ADDON = Addon('plugin.video.plexkodiconnect') ADDON = Addon('plugin.video.plexkodiconnect')
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
@ -44,8 +43,8 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
return self.selected_option return self.selected_option
def onInit(self): def onInit(self):
if window('PlexUserImage'): if utils.window('PlexUserImage'):
self.getControl(USER_IMAGE).setImage(window('PlexUserImage')) self.getControl(USER_IMAGE).setImage(utils.window('PlexUserImage'))
height = 479 + (len(self._options) * 55) height = 479 + (len(self._options) * 55)
LOG.debug("options: %s", self._options) LOG.debug("options: %s", self._options)
self.list_ = self.getControl(LIST) self.list_ = self.getControl(LIST)

View File

@ -1,35 +1,33 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from xbmcaddon import Addon from xbmcaddon import Addon
import xbmc import xbmc
import xbmcplugin
import xbmcgui import xbmcgui
import context
import plexdb_functions as plexdb from . import context
from utils import window, settings, dialog, language as lang from . import plexdb_functions as plexdb
import PlexFunctions as PF from . import utils
from PlexAPI import API from . import plex_functions as PF
import playqueue as PQ from .plex_api import API
import variables as v from . import playqueue as PQ
import state from . import variables as v
from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.context_entry')
OPTIONS = { OPTIONS = {
'Refresh': lang(30410), 'Refresh': utils.lang(30410),
'Delete': lang(30409), 'Delete': utils.lang(30409),
'Addon': lang(30408), 'Addon': utils.lang(30408),
# 'AddFav': lang(30405), # 'AddFav': utils.lang(30405),
# 'RemoveFav': lang(30406), # 'RemoveFav': utils.lang(30406),
# 'RateSong': lang(30407), # 'RateSong': utils.lang(30407),
'Transcode': lang(30412), 'Transcode': utils.lang(30412),
'PMS_Play': lang(30415), # Use PMS to start playback 'PMS_Play': utils.lang(30415), # Use PMS to start playback
'Extras': lang(30235) 'Extras': utils.lang(30235)
} }
############################################################################### ###############################################################################
@ -98,8 +96,8 @@ class ContextMenu(object):
options.append(OPTIONS['Transcode']) options.append(OPTIONS['Transcode'])
# Delete item, only if the Plex Home main user is logged in # Delete item, only if the Plex Home main user is logged in
if (window('plex_restricteduser') != 'true' and if (utils.window('plex_restricteduser') != 'true' and
window('plex_allows_mediaDeletion') == 'true'): utils.window('plex_allows_mediaDeletion') == 'true'):
options.append(OPTIONS['Delete']) options.append(OPTIONS['Delete'])
# Addon settings # Addon settings
options.append(OPTIONS['Addon']) options.append(OPTIONS['Addon'])
@ -138,14 +136,14 @@ class ContextMenu(object):
Delete item on PMS Delete item on PMS
""" """
delete = True delete = True
if settings('skipContextMenu') != "true": if utils.settings('skipContextMenu') != "true":
if not dialog("yesno", heading="{plex}", line1=lang(33041)): if not utils.dialog("yesno", heading="{plex}", line1=utils.lang(33041)):
LOG.info("User skipped deletion for: %s", self.plex_id) LOG.info("User skipped deletion for: %s", self.plex_id)
delete = False delete = False
if delete: if delete:
LOG.info("Deleting Plex item with id %s", self.plex_id) LOG.info("Deleting Plex item with id %s", self.plex_id)
if PF.delete_item_from_pms(self.plex_id) is False: if PF.delete_item_from_pms(self.plex_id) is False:
dialog("ok", heading="{plex}", line1=lang(30414)) utils.dialog("ok", heading="{plex}", line1=utils.lang(30414))
def _PMS_play(self): def _PMS_play(self):
""" """

View File

@ -5,10 +5,9 @@ from logging import getLogger
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import requests import requests
from utils import window, language as lang, dialog from . import utils
import clientinfo as client from . import clientinfo
from . import state
import state
############################################################################### ###############################################################################
@ -16,7 +15,7 @@ import state
import requests.packages.urllib3 import requests.packages.urllib3
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.downloadutils')
############################################################################### ###############################################################################
@ -75,22 +74,22 @@ class DownloadUtils():
# Start session # Start session
self.s = requests.Session() self.s = requests.Session()
self.deviceId = client.getDeviceId() self.deviceId = clientinfo.getDeviceId()
# Attach authenticated header to the session # Attach authenticated header to the session
self.s.headers = client.getXArgsDeviceInfo() self.s.headers = clientinfo.getXArgsDeviceInfo()
self.s.encoding = 'utf-8' self.s.encoding = 'utf-8'
# Set SSL settings # Set SSL settings
self.setSSL() self.setSSL()
# Set other stuff # Set other stuff
self.setServer(window('pms_server')) self.setServer(utils.window('pms_server'))
# Counters to declare PMS dead or unauthorized # Counters to declare PMS dead or unauthorized
# Use window variables because start of movies will be called with a # Use window variables because start of movies will be called with a
# new plugin instance - it's impossible to share data otherwise # new plugin instance - it's impossible to share data otherwise
if reset is True: if reset is True:
window('countUnauthorized', value='0') utils.window('countUnauthorized', value='0')
window('countError', value='0') utils.window('countError', value='0')
# Retry connections to the server # Retry connections to the server
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
@ -110,7 +109,7 @@ class DownloadUtils():
LOG.info('Request session stopped') LOG.info('Request session stopped')
def getHeader(self, options=None): def getHeader(self, options=None):
header = client.getXArgsDeviceInfo() header = clientinfo.getXArgsDeviceInfo()
if options is not None: if options is not None:
header.update(options) header.update(options)
return header return header
@ -227,9 +226,9 @@ class DownloadUtils():
else: else:
# We COULD contact the PMS, hence it ain't dead # We COULD contact the PMS, hence it ain't dead
if authenticate is True: if authenticate is True:
window('countError', value='0') utils.window('countError', value='0')
if r.status_code != 401: if r.status_code != 401:
window('countUnauthorized', value='0') utils.window('countUnauthorized', value='0')
if r.status_code == 204: if r.status_code == 204:
# No body in the response # No body in the response
@ -247,9 +246,10 @@ class DownloadUtils():
LOG.info(r.text) LOG.info(r.text)
if '401 Unauthorized' in r.text: if '401 Unauthorized' in r.text:
# Truly unauthorized # Truly unauthorized
window('countUnauthorized', utils.window(
value=str(int(window('countUnauthorized')) + 1)) 'countUnauthorized',
if (int(window('countUnauthorized')) >= value=str(int(utils.window('countUnauthorized')) + 1))
if (int(utils.window('countUnauthorized')) >=
self.unauthorizedAttempts): self.unauthorizedAttempts):
LOG.warn('We seem to be truly unauthorized for PMS' LOG.warn('We seem to be truly unauthorized for PMS'
' %s ', url) ' %s ', url)
@ -258,11 +258,11 @@ class DownloadUtils():
LOG.debug('Setting PMS server status to ' LOG.debug('Setting PMS server status to '
'unauthorized') 'unauthorized')
state.PMS_STATUS = '401' state.PMS_STATUS = '401'
window('plex_serverStatus', value="401") utils.window('plex_serverStatus', value="401")
dialog('notification', utils.dialog('notification',
lang(29999), utils.lang(29999),
lang(30017), utils.lang(30017),
icon='{error}') icon='{error}')
else: else:
# there might be other 401 where e.g. PMS under strain # there might be other 401 where e.g. PMS under strain
LOG.info('PMS might only be under strain') LOG.info('PMS might only be under strain')
@ -312,12 +312,12 @@ class DownloadUtils():
if authenticate is True: if authenticate is True:
# Make the addon aware of status # Make the addon aware of status
try: try:
window('countError', utils.window('countError',
value=str(int(window('countError')) + 1)) value=str(int(utils.window('countError')) + 1))
if int(window('countError')) >= self.connectionAttempts: if int(utils.window('countError')) >= self.connectionAttempts:
LOG.warn('Failed to connect to %s too many times. ' LOG.warn('Failed to connect to %s too many times. '
'Declare PMS dead', url) 'Declare PMS dead', url)
window('plex_online', value="false") utils.window('plex_online', value="false")
except ValueError: except ValueError:
# 'countError' not yet set # 'countError' not yet set
pass pass

View File

@ -10,23 +10,19 @@ from os import walk, makedirs
from os.path import basename, join from os.path import basename, join
from sys import argv from sys import argv
from urllib import urlencode from urllib import urlencode
import xbmcplugin import xbmcplugin
from xbmc import sleep, executebuiltin, translatePath from xbmc import sleep, executebuiltin, translatePath
from xbmcgui import ListItem from xbmcgui import ListItem
from utils import window, settings, language as lang, dialog, try_encode, \ from . import utils
catch_exceptions, exists_dir, plex_command, try_decode from .downloadutils import DownloadUtils as DU
import downloadutils from .plex_api import API
from . import plex_functions as PF
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \ from . import json_rpc as js
GetMachineIdentifier from . import variables as v
from PlexAPI import API
import json_rpc as js
import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.entrypoint')
try: try:
HANDLE = int(argv[1]) HANDLE = int(argv[1])
@ -47,8 +43,8 @@ def choose_pms_server():
server = setup.pick_pms(showDialog=True) server = setup.pick_pms(showDialog=True)
if server is None: if server is None:
LOG.error('We did not connect to a new PMS, aborting') LOG.error('We did not connect to a new PMS, aborting')
plex_command('SUSPEND_USER_CLIENT', 'False') utils.plex_command('SUSPEND_USER_CLIENT', 'False')
plex_command('SUSPEND_LIBRARY_THREAD', 'False') utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
return return
LOG.info("User chose server %s", server['name']) LOG.info("User chose server %s", server['name'])
@ -65,12 +61,12 @@ def choose_pms_server():
_log_in() _log_in()
LOG.info("Choosing new PMS complete") LOG.info("Choosing new PMS complete")
# '<PMS> connected' # '<PMS> connected'
dialog('notification', utils.dialog('notification',
lang(29999), utils.lang(29999),
'%s %s' % (server['name'], lang(39220)), '%s %s' % (server['name'], utils.lang(39220)),
icon='{plex}', icon='{plex}',
time=3000, time=3000,
sound=False) sound=False)
def toggle_plex_tv_sign_in(): def toggle_plex_tv_sign_in():
@ -78,38 +74,38 @@ def toggle_plex_tv_sign_in():
Signs out of Plex.tv if there was a token saved and thus deletes the token. Signs out of Plex.tv if there was a token saved and thus deletes the token.
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.
""" """
if settings('plexToken'): if utils.settings('plexToken'):
LOG.info('Reseting plex.tv credentials in settings') LOG.info('Reseting plex.tv credentials in settings')
settings('plexLogin', value="") utils.settings('plexLogin', value="")
settings('plexToken', value="") utils.settings('plexToken', value="")
settings('plexid', value="") utils.settings('plexid', value="")
settings('plexHomeSize', value="1") utils.settings('plexHomeSize', value="1")
settings('plexAvatar', value="") utils.settings('plexAvatar', value="")
settings('plex_status', value=lang(39226)) utils.settings('plex_status', value=utils.lang(39226))
window('plex_token', clear=True) utils.window('plex_token', clear=True)
plex_command('PLEX_TOKEN', '') utils.plex_command('PLEX_TOKEN', '')
plex_command('PLEX_USERNAME', '') utils.plex_command('PLEX_USERNAME', '')
else: else:
LOG.info('Login to plex.tv') LOG.info('Login to plex.tv')
import initialsetup import initialsetup
initialsetup.InitialSetup().plex_tv_sign_in() initialsetup.InitialSetup().plex_tv_sign_in()
dialog('notification', utils.dialog('notification',
lang(29999), utils.lang(29999),
lang(39221), utils.lang(39221),
icon='{plex}', icon='{plex}',
time=3000, time=3000,
sound=False) sound=False)
def reset_authorization(): def reset_authorization():
""" """
User tried login and failed too many times. Reset # of logins User tried login and failed too many times. Reset # of logins
""" """
resp = dialog('yesno', heading="{plex}", line1=lang(39206)) resp = utils.dialog('yesno', heading="{plex}", line1=utils.lang(39206))
if resp == 1: if resp == 1:
LOG.info("Reset login attempts.") LOG.info("Reset login attempts.")
plex_command('PMS_STATUS', 'Auth') utils.plex_command('PMS_STATUS', 'Auth')
else: else:
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)') executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
@ -138,17 +134,17 @@ def show_main_menu(content_type=None):
LOG.debug('Do main listing with content_type: %s', content_type) LOG.debug('Do main listing with content_type: %s', content_type)
xbmcplugin.setContent(HANDLE, 'files') xbmcplugin.setContent(HANDLE, 'files')
# Get emby nodes from the window props # Get emby nodes from the window props
plexprops = window('Plex.nodes.total') plexprops = utils.window('Plex.nodes.total')
if plexprops: if plexprops:
totalnodes = int(plexprops) totalnodes = int(plexprops)
for i in range(totalnodes): for i in range(totalnodes):
path = window('Plex.nodes.%s.index' % i) path = utils.window('Plex.nodes.%s.index' % i)
if not path: if not path:
path = window('Plex.nodes.%s.content' % i) path = utils.window('Plex.nodes.%s.content' % i)
if not path: if not path:
continue continue
label = window('Plex.nodes.%s.title' % i) label = utils.window('Plex.nodes.%s.title' % i)
node_type = window('Plex.nodes.%s.type' % i) node_type = utils.window('Plex.nodes.%s.type' % i)
# because we do not use seperate entrypoints for each content type, # because we do not use seperate entrypoints for each content type,
# we need to figure out which items to show in each listing. for # we need to figure out which items to show in each listing. for
# now we just only show picture nodes in the picture library video # now we just only show picture nodes in the picture library video
@ -161,21 +157,19 @@ def show_main_menu(content_type=None):
# Plex Watch later # Plex Watch later
if content_type not in ('image', 'audio'): if content_type not in ('image', 'audio'):
directory_item(lang(39211), directory_item(utils.lang(39211),
"plugin://%s?mode=watchlater" % v.ADDON_ID) "plugin://%s?mode=watchlater" % v.ADDON_ID)
# Plex Channels # Plex Channels
directory_item(lang(30173), directory_item(utils.lang(30173), "plugin://%s?mode=channels" % v.ADDON_ID)
"plugin://%s?mode=channels" % v.ADDON_ID)
# Plex user switch # Plex user switch
directory_item('%s%s' % (lang(39200), settings('username')), directory_item('%s%s' % (utils.lang(39200), utils.settings('username')),
"plugin://%s?mode=switchuser" % v.ADDON_ID) "plugin://%s?mode=switchuser" % v.ADDON_ID)
# some extra entries for settings and stuff # some extra entries for settings and stuff
directory_item(lang(39201), directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID)
"plugin://%s?mode=settings" % v.ADDON_ID) directory_item(utils.lang(39203),
directory_item(lang(39203),
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID) "plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
directory_item(lang(39204), directory_item(utils.lang(39204),
"plugin://%s?mode=manualsync" % v.ADDON_ID) "plugin://%s?mode=manualsync" % v.ADDON_ID)
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
@ -188,7 +182,7 @@ def switch_plex_user():
# Guess these user avatars are a future feature. Skipping for now # Guess these user avatars are a future feature. Skipping for now
# Delete any userimages. Since there's always only 1 user: position = 0 # Delete any userimages. Since there's always only 1 user: position = 0
# position = 0 # position = 0
# 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")
if not _log_out(): if not _log_out():
return return
@ -258,7 +252,8 @@ def create_listitem(item, append_show_title=False, append_sxxexx=False):
listitem.setArt({'icon': 'DefaultTVShows.png'}) listitem.setArt({'icon': 'DefaultTVShows.png'})
listitem.setProperty('fanart_image', item['art'].get('tvshow.fanart', '')) listitem.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
try: try:
listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)]) listitem.addContextMenuItems([(utils.lang(30032),
'XBMC.Action(Info)',)])
except TypeError: except TypeError:
# Kodi fuck-up # Kodi fuck-up
pass pass
@ -287,7 +282,7 @@ def next_up_episodes(tagname, limit):
'properties': ['title', 'studio', 'mpaa', 'file', 'art'] 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
} }
for item in js.get_tv_shows(params): for item in js.get_tv_shows(params):
if settings('ignoreSpecialsNextEpisodes') == "true": if utils.settings('ignoreSpecialsNextEpisodes') == "true":
params = { params = {
'tvshowid': item['tvshowid'], 'tvshowid': item['tvshowid'],
'sort': {'method': "episode"}, 'sort': {'method': "episode"},
@ -383,8 +378,8 @@ def recent_episodes(mediatype, tagname, limit):
# if the addon is called with recentepisodes parameter, # if the addon is called with recentepisodes parameter,
# we return the recentepisodes list of the given tagname # we return the recentepisodes list of the given tagname
xbmcplugin.setContent(HANDLE, 'episodes') xbmcplugin.setContent(HANDLE, 'episodes')
append_show_title = settings('RecentTvAppendShow') == 'true' append_show_title = utils.settings('RecentTvAppendShow') == 'true'
append_sxxexx = settings('RecentTvAppendSeason') == 'true' append_sxxexx = utils.settings('RecentTvAppendSeason') == 'true'
# First we get a list of all the TV shows - filtered by tag # First we get a list of all the TV shows - filtered by tag
show_ids = set() show_ids = set()
params = { params = {
@ -401,7 +396,7 @@ def recent_episodes(mediatype, tagname, limit):
"dateadded", "lastplayed"], "dateadded", "lastplayed"],
"limits": {"end": limit} "limits": {"end": limit}
} }
if settings('TVShowWatched') == 'false': if utils.settings('TVShowWatched') == 'false':
params['filter'] = { params['filter'] = {
'operator': "lessthan", 'operator': "lessthan",
'field': "playcount", 'field': "playcount",
@ -444,7 +439,7 @@ def get_video_files(plex_id, params):
LOG.info('No Plex ID found, abort getting Extras') LOG.info('No Plex ID found, abort getting Extras')
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
item = GetPlexMetadata(plex_id) item = PF.GetPlexMetadata(plex_id)
try: try:
path = item[0][0][0].attrib['file'] path = item[0][0][0].attrib['file']
except (TypeError, IndexError, AttributeError, KeyError): except (TypeError, IndexError, AttributeError, KeyError):
@ -459,17 +454,17 @@ def get_video_files(plex_id, params):
path = path.replace('\\', '\\\\') path = path.replace('\\', '\\\\')
# Directory only, get rid of filename # Directory only, get rid of filename
path = path.replace(basename(path), '') path = path.replace(basename(path), '')
if exists_dir(path): if utils.exists_dir(path):
for root, dirs, files in walk(path): for root, dirs, files in walk(path):
for directory in dirs: for directory in dirs:
item_path = try_encode(join(root, directory)) item_path = utils.try_encode(join(root, directory))
listitem = ListItem(item_path, path=item_path) listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=item_path, url=item_path,
listitem=listitem, listitem=listitem,
isFolder=True) isFolder=True)
for file in files: for file in files:
item_path = try_encode(join(root, file)) item_path = utils.try_encode(join(root, file))
listitem = ListItem(item_path, path=item_path) listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=file, url=file,
@ -480,7 +475,7 @@ def get_video_files(plex_id, params):
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
@catch_exceptions(warnuser=False) @utils.catch_exceptions(warnuser=False)
def extra_fanart(plex_id, plex_path): def extra_fanart(plex_id, plex_path):
""" """
Get extrafanart for listitem Get extrafanart for listitem
@ -497,12 +492,12 @@ def extra_fanart(plex_id, plex_path):
# We need to store the images locally for this to work # We need to store the images locally for this to work
# because of the caching system in xbmc # because of the caching system in xbmc
fanart_dir = try_decode(translatePath( fanart_dir = utils.try_decode(translatePath(
"special://thumbnails/plex/%s/" % plex_id)) "special://thumbnails/plex/%s/" % plex_id))
if not exists_dir(fanart_dir): if not utils.exists_dir(fanart_dir):
# Download the images to the cache directory # Download the images to the cache directory
makedirs(fanart_dir) makedirs(fanart_dir)
xml = GetPlexMetadata(plex_id) xml = PF.GetPlexMetadata(plex_id)
if xml is None: if xml is None:
LOG.error('Could not download metadata for %s', plex_id) LOG.error('Could not download metadata for %s', plex_id)
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
@ -511,19 +506,20 @@ def extra_fanart(plex_id, plex_path):
backdrops = api.artwork()['Backdrop'] backdrops = api.artwork()['Backdrop']
for count, backdrop in enumerate(backdrops): for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork # Same ordering as in artwork
art_file = try_encode(join(fanart_dir, "fanart%.3d.jpg" % count)) art_file = utils.try_encode(join(fanart_dir,
"fanart%.3d.jpg" % count))
listitem = ListItem("%.3d" % count, path=art_file) listitem = ListItem("%.3d" % count, path=art_file)
xbmcplugin.addDirectoryItem( xbmcplugin.addDirectoryItem(
handle=HANDLE, handle=HANDLE,
url=art_file, url=art_file,
listitem=listitem) listitem=listitem)
copyfile(backdrop, try_decode(art_file)) copyfile(backdrop, utils.try_decode(art_file))
else: else:
LOG.info("Found cached backdrop.") LOG.info("Found cached backdrop.")
# Use existing cached images # Use existing cached images
for root, _, files in walk(fanart_dir): for root, _, files in walk(fanart_dir):
for file in files: for file in files:
art_file = try_encode(join(root, file)) art_file = utils.try_encode(join(root, file))
listitem = ListItem(file, path=art_file) listitem = ListItem(file, path=art_file)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=art_file, url=art_file,
@ -541,13 +537,13 @@ def on_deck_episodes(viewid, tagname, limit):
limit: Max. number of items to retrieve, e.g. 50 limit: Max. number of items to retrieve, e.g. 50
""" """
xbmcplugin.setContent(HANDLE, 'episodes') xbmcplugin.setContent(HANDLE, 'episodes')
append_show_title = settings('OnDeckTvAppendShow') == 'true' append_show_title = utils.settings('OnDeckTvAppendShow') == 'true'
append_sxxexx = settings('OnDeckTvAppendSeason') == 'true' append_sxxexx = utils.settings('OnDeckTvAppendSeason') == 'true'
if settings('OnDeckTVextended') == 'false': if utils.settings('OnDeckTVextended') == 'false':
# Chances are that this view is used on Kodi startup # Chances are that this view is used on Kodi startup
# Wait till we've connected to a PMS. At most 30s # Wait till we've connected to a PMS. At most 30s
counter = 0 counter = 0
while window('plex_authenticated') != 'true': while utils.window('plex_authenticated') != 'true':
counter += 1 counter += 1
if counter == 300: if counter == 300:
LOG.error('Aborting On Deck view, we were not authenticated ' LOG.error('Aborting On Deck view, we were not authenticated '
@ -555,13 +551,12 @@ def on_deck_episodes(viewid, tagname, limit):
xbmcplugin.endOfDirectory(HANDLE, False) xbmcplugin.endOfDirectory(HANDLE, False)
return return
sleep(100) sleep(100)
xml = downloadutils.DownloadUtils().downloadUrl( xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid)
'{server}/library/sections/%s/onDeck' % viewid)
if xml in (None, 401): if xml in (None, 401):
LOG.error('Could not download PMS xml for view %s', viewid) LOG.error('Could not download PMS xml for view %s', viewid)
xbmcplugin.endOfDirectory(HANDLE, False) xbmcplugin.endOfDirectory(HANDLE, False)
return return
direct_paths = settings('useDirectPaths') == '1' direct_paths = utils.settings('useDirectPaths') == '1'
counter = 0 counter = 0
for item in xml: for item in xml:
api = API(item) api = API(item)
@ -580,7 +575,7 @@ def on_deck_episodes(viewid, tagname, limit):
break break
xbmcplugin.endOfDirectory( xbmcplugin.endOfDirectory(
handle=HANDLE, handle=HANDLE,
cacheToDisc=settings('enableTextureCache') == 'true') cacheToDisc=utils.settings('enableTextureCache') == 'true')
return return
# if the addon is called with nextup parameter, # if the addon is called with nextup parameter,
@ -610,7 +605,7 @@ def on_deck_episodes(viewid, tagname, limit):
"dateadded", "lastplayed" "dateadded", "lastplayed"
], ],
} }
if settings('ignoreSpecialsNextEpisodes') == "true": if utils.settings('ignoreSpecialsNextEpisodes') == "true":
params['filter'] = { params['filter'] = {
'and': [ 'and': [
{'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "lessthan", 'field': "playcount", 'value': "1"},
@ -663,37 +658,34 @@ def watchlater():
""" """
Listing for plex.tv Watch Later section (if signed in to plex.tv) Listing for plex.tv Watch Later section (if signed in to plex.tv)
""" """
if window('plex_token') == '': if utils.window('plex_token') == '':
LOG.error('No watch later - not signed in to plex.tv') LOG.error('No watch later - not signed in to plex.tv')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
if window('plex_restricteduser') == 'true': if utils.window('plex_restricteduser') == 'true':
LOG.error('No watch later - restricted user') LOG.error('No watch later - restricted user')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
xml = downloadutils.DownloadUtils().downloadUrl( xml = DU().downloadUrl('https://plex.tv/pms/playlists/queue/all',
'https://plex.tv/pms/playlists/queue/all', authenticate=False,
authenticate=False, headerOptions={'X-Plex-Token': utils.window('plex_token')})
headerOptions={'X-Plex-Token': window('plex_token')})
if xml in (None, 401): if xml in (None, 401):
LOG.error('Could not download watch later list from plex.tv') LOG.error('Could not download watch later list from plex.tv')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
LOG.info('Displaying watch later plex.tv items') LOG.info('Displaying watch later plex.tv items')
xbmcplugin.setContent(HANDLE, 'movies') xbmcplugin.setContent(HANDLE, 'movies')
direct_paths = settings('useDirectPaths') == '1' direct_paths = utils.settings('useDirectPaths') == '1'
for item in xml: for item in xml:
__build_item(item, direct_paths) __build_item(item, direct_paths)
xbmcplugin.endOfDirectory( xbmcplugin.endOfDirectory(
handle=HANDLE, handle=HANDLE,
cacheToDisc=settings('enableTextureCache') == 'true') cacheToDisc=utils.settings('enableTextureCache') == 'true')
def channels(): def channels():
""" """
Listing for Plex Channels Listing for Plex Channels
""" """
xml = downloadutils.DownloadUtils().downloadUrl('{server}/channels/all') xml = DU().downloadUrl('{server}/channels/all')
try: try:
xml[0].attrib xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError): except (ValueError, AttributeError, IndexError, TypeError):
@ -708,7 +700,7 @@ def channels():
__build_folder(item) __build_folder(item)
xbmcplugin.endOfDirectory( xbmcplugin.endOfDirectory(
handle=HANDLE, handle=HANDLE,
cacheToDisc=settings('enableTextureCache') == 'true') cacheToDisc=utils.settings('enableTextureCache') == 'true')
def browse_plex(key=None, plex_section_id=None): def browse_plex(key=None, plex_section_id=None):
@ -717,9 +709,9 @@ def browse_plex(key=None, plex_section_id=None):
be used directly for PMS url {server}<key>) or the plex_section_id be used directly for PMS url {server}<key>) or the plex_section_id
""" """
if key: if key:
xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key) xml = DU().downloadUrl('{server}%s' % key)
else: else:
xml = GetPlexSectionResults(plex_section_id) xml = PF.GetPlexSectionResults(plex_section_id)
try: try:
xml[0].attrib xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError): except (ValueError, AttributeError, IndexError, TypeError):
@ -735,7 +727,7 @@ def browse_plex(key=None, plex_section_id=None):
artists = False artists = False
albums = False albums = False
musicvideos = False musicvideos = False
direct_paths = settings('useDirectPaths') == '1' direct_paths = utils.settings('useDirectPaths') == '1'
for item in xml: for item in xml:
if item.tag == 'Directory': if item.tag == 'Directory':
__build_folder(item, plex_section_id=plex_section_id) __build_folder(item, plex_section_id=plex_section_id)
@ -802,7 +794,7 @@ def browse_plex(key=None, plex_section_id=None):
xbmcplugin.endOfDirectory( xbmcplugin.endOfDirectory(
handle=HANDLE, handle=HANDLE,
cacheToDisc=settings('enableTextureCache') == 'true') cacheToDisc=utils.settings('enableTextureCache') == 'true')
def __build_folder(xml_element, plex_section_id=None): def __build_folder(xml_element, plex_section_id=None):
@ -854,7 +846,7 @@ def extras(plex_id):
Lists all extras for plex_id Lists all extras for plex_id
""" """
xbmcplugin.setContent(HANDLE, 'movies') xbmcplugin.setContent(HANDLE, 'movies')
xml = GetPlexMetadata(plex_id) xml = PF.GetPlexMetadata(plex_id)
try: try:
xml[0].attrib xml[0].attrib
except (TypeError, IndexError, KeyError): except (TypeError, IndexError, KeyError):
@ -874,41 +866,47 @@ 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
""" """
# "Enter your Plex Media Server's IP or URL. Examples are:" # "Enter your Plex Media Server's IP or URL. Examples are:"
dialog('ok', lang(29999), lang(39215), '192.168.1.2', 'plex.myServer.org') utils.dialog('ok',
address = dialog('input', "Enter PMS IP or URL") utils.lang(29999),
utils.lang(39215),
'192.168.1.2',
'plex.myServer.org')
address = utils.dialog('input', "Enter PMS IP or URL")
if address == '': if address == '':
return return
port = dialog('input', "Enter PMS port", '32400', type='{numeric}') port = utils.dialog('input', "Enter PMS port", '32400', type='{numeric}')
if port == '': if port == '':
return return
url = '%s:%s' % (address, port) url = '%s:%s' % (address, port)
# "Does your Plex Media Server support SSL connections? # "Does your Plex Media Server support SSL connections?
# (https instead of http)" # (https instead of http)"
https = dialog('yesno', lang(29999), lang(39217)) https = utils.dialog('yesno', utils.lang(29999), utils.lang(39217))
if https: if https:
url = 'https://%s' % url url = 'https://%s' % url
else: else:
url = 'http://%s' % url url = 'http://%s' % url
https = 'true' if https else 'false' https = 'true' if https else 'false'
machine_identifier = GetMachineIdentifier(url) machine_identifier = PF.GetMachineIdentifier(url)
if machine_identifier is None: if machine_identifier is None:
# "Error contacting url # "Error contacting url
# Abort (Yes) or save address anyway (No)" # Abort (Yes) or save address anyway (No)"
if dialog('yesno', if utils.dialog('yesno',
lang(29999), utils.lang(29999),
'%s %s. %s' % (lang(39218), url, lang(39219))): '%s %s. %s' % (utils.lang(39218),
url,
utils.lang(39219))):
return return
else: else:
settings('plex_machineIdentifier', '') utils.settings('plex_machineIdentifier', '')
else: else:
settings('plex_machineIdentifier', machine_identifier) utils.settings('plex_machineIdentifier', machine_identifier)
LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s', LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s',
https, address, port, machine_identifier) https, address, port, machine_identifier)
settings('https', value=https) utils.settings('https', value=https)
settings('ipaddress', value=address) utils.settings('ipaddress', value=address)
settings('port', value=port) utils.settings('port', value=port)
# Chances are this is a local PMS, so disable SSL certificate check # Chances are this is a local PMS, so disable SSL certificate check
settings('sslverify', value='false') utils.settings('sslverify', value='false')
# Sign out to trigger new login # Sign out to trigger new login
if _log_out(): if _log_out():
@ -923,9 +921,9 @@ def _log_in():
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
out! out!
""" """
plex_command('RUN_LIB_SCAN', 'full') utils.plex_command('RUN_LIB_SCAN', 'full')
# Restart user client # Restart user client
plex_command('SUSPEND_USER_CLIENT', 'False') utils.plex_command('SUSPEND_USER_CLIENT', 'False')
def _log_out(): def _log_out():
@ -935,22 +933,22 @@ def _log_out():
Returns True if successfully signed out, False otherwise Returns True if successfully signed out, False otherwise
""" """
# Resetting, please wait # Resetting, please wait
dialog('notification', utils.dialog('notification',
lang(29999), utils.lang(29999),
lang(39207), utils.lang(39207),
icon='{plex}', icon='{plex}',
time=3000, time=3000,
sound=False) sound=False)
# Pause library sync thread # Pause library sync thread
plex_command('SUSPEND_LIBRARY_THREAD', 'True') utils.plex_command('SUSPEND_LIBRARY_THREAD', 'True')
# Wait max for 10 seconds for all lib scans to shutdown # Wait max for 10 seconds for all lib scans to shutdown
counter = 0 counter = 0
while window('plex_dbScan') == 'true': while utils.window('plex_dbScan') == 'true':
if counter > 200: if counter > 200:
# Failed to reset PMS and plex.tv connects. Try to restart Kodi. # Failed to reset PMS and plex.tv connects. Try to restart Kodi.
dialog('ok', lang(29999), lang(39208)) utils.dialog('ok', utils.lang(29999), utils.lang(39208))
# Resuming threads, just in case # Resuming threads, just in case
plex_command('SUSPEND_LIBRARY_THREAD', 'False') utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
LOG.error("Could not stop library sync, aborting") LOG.error("Could not stop library sync, aborting")
return False return False
counter += 1 counter += 1
@ -959,17 +957,17 @@ def _log_out():
counter = 0 counter = 0
# Log out currently signed in user: # Log out currently signed in user:
window('plex_serverStatus', value='401') utils.window('plex_serverStatus', value='401')
plex_command('PMS_STATUS', '401') utils.plex_command('PMS_STATUS', '401')
# Above method needs to have run its course! Hence wait # Above method needs to have run its course! Hence wait
while window('plex_serverStatus') == "401": while utils.window('plex_serverStatus') == "401":
if counter > 100: if counter > 100:
# 'Failed to reset PKC. Try to restart Kodi.' # 'Failed to reset PKC. Try to restart Kodi.'
dialog('ok', lang(29999), lang(39208)) utils.dialog('ok', utils.lang(29999), utils.lang(39208))
LOG.error("Could not sign out user, aborting") LOG.error("Could not sign out user, aborting")
return False return False
counter += 1 counter += 1
sleep(50) sleep(50)
# Suspend the user client during procedure # Suspend the user client during procedure
plex_command('SUSPEND_USER_CLIENT', 'True') utils.plex_command('SUSPEND_USER_CLIENT', 'True')
return True return True

View File

@ -6,23 +6,22 @@ import xml.etree.ElementTree as etree
from xbmc import executebuiltin, translatePath from xbmc import executebuiltin, translatePath
from utils import settings, window, language as lang, try_decode, dialog, \ from . import utils
XmlKodiSetting, reboot_kodi from . import migration
from migration import check_migration from .downloadutils import DownloadUtils as DU
from downloadutils import DownloadUtils as DU from . import videonodes
from userclient import UserClient from . import userclient
from clientinfo import getDeviceId from . import clientinfo
import PlexFunctions as PF from . import plex_functions as PF
import plex_tv from . import plex_tv
import json_rpc as js from . import json_rpc as js
import playqueue as PQ from . import playqueue as PQ
from videonodes import VideoNodes from . import state
import state from . import variables as v
import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.initialsetup')
############################################################################### ###############################################################################
@ -48,27 +47,28 @@ def reload_pkc():
reload(state) reload(state)
# Reset window props # Reset window props
for prop in WINDOW_PROPERTIES: for prop in WINDOW_PROPERTIES:
window(prop, clear=True) utils.window(prop, clear=True)
# Clear video nodes properties # Clear video nodes properties
VideoNodes().clearProperties() videonodes.VideoNodes().clearProperties()
# Initializing # Initializing
state.VERIFY_SSL_CERT = settings('sslverify') == 'true' state.VERIFY_SSL_CERT = utils.settings('sslverify') == 'true'
state.SSL_CERT_PATH = settings('sslcert') \ state.SSL_CERT_PATH = utils.settings('sslcert') \
if settings('sslcert') != 'None' else None if utils.settings('sslcert') != 'None' else None
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60 state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber')) state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
state.SYNC_DIALOG = settings('dbSyncIndicator') == 'true' state.SYNC_DIALOG = utils.settings('dbSyncIndicator') == 'true'
state.ENABLE_MUSIC = settings('enableMusic') == 'true' state.ENABLE_MUSIC = utils.settings('enableMusic') == 'true'
state.BACKGROUND_SYNC_DISABLED = settings( state.BACKGROUND_SYNC_DISABLED = utils.settings(
'enableBackgroundSync') == 'false' 'enableBackgroundSync') == 'false'
state.BACKGROUNDSYNC_SAFTYMARGIN = int( state.BACKGROUNDSYNC_SAFTYMARGIN = int(
settings('backgroundsync_saftyMargin')) utils.settings('backgroundsync_saftyMargin'))
state.REPLACE_SMB_PATH = settings('replaceSMB') == 'true' state.REPLACE_SMB_PATH = utils.settings('replaceSMB') == 'true'
state.REMAP_PATH = settings('remapSMB') == 'true' state.REMAP_PATH = utils.settings('remapSMB') == 'true'
state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset')) state.KODI_PLEX_TIME_OFFSET = float(utils.settings('kodiplextimeoffset'))
state.FETCH_PMS_ITEM_NUMBER = settings('fetch_pms_item_number') state.FETCH_PMS_ITEM_NUMBER = utils.settings('fetch_pms_item_number')
state.FORCE_RELOAD_SKIN = settings('forceReloadSkinOnPlaybackStop') == 'true' state.FORCE_RELOAD_SKIN = \
utils.settings('forceReloadSkinOnPlaybackStop') == 'true'
# Init some Queues() # Init some Queues()
state.COMMAND_PIPELINE_QUEUE = Queue() state.COMMAND_PIPELINE_QUEUE = Queue()
state.COMPANION_QUEUE = Queue(maxsize=100) state.COMPANION_QUEUE = Queue(maxsize=100)
@ -76,9 +76,9 @@ def reload_pkc():
set_replace_paths() set_replace_paths()
set_webserver() set_webserver()
# To detect Kodi profile switches # To detect Kodi profile switches
window('plex_kodiProfile', utils.window('plex_kodiProfile',
value=try_decode(translatePath("special://profile"))) value=utils.try_decode(translatePath("special://profile")))
getDeviceId() clientinfo.getDeviceId()
# Initialize the PKC playqueues # Initialize the PKC playqueues
PQ.init_playqueues() PQ.init_playqueues()
LOG.info('Done (re-)loading PKC settings') LOG.info('Done (re-)loading PKC settings')
@ -92,7 +92,7 @@ def set_replace_paths():
for typus in v.REMAP_TYPE_FROM_PLEXTYPE.values(): for typus in v.REMAP_TYPE_FROM_PLEXTYPE.values():
for arg in ('Org', 'New'): for arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg) key = 'remapSMB%s%s' % (typus, arg)
value = settings(key) value = utils.settings(key)
if '://' in value: if '://' in value:
protocol = value.split('://', 1)[0] protocol = value.split('://', 1)[0]
value = value.replace(protocol, protocol.lower()) value = value.replace(protocol, protocol.lower())
@ -129,8 +129,8 @@ def _write_pms_settings(url, token):
for entry in xml: for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion': if entry.attrib.get('id', '') == 'allowMediaDeletion':
value = 'true' if entry.get('value', '1') == '1' else 'false' value = 'true' if entry.get('value', '1') == '1' else 'false'
settings('plex_allows_mediaDeletion', value=value) utils.settings('plex_allows_mediaDeletion', value=value)
window('plex_allows_mediaDeletion', value=value) utils.window('plex_allows_mediaDeletion', value=value)
class InitialSetup(object): class InitialSetup(object):
@ -140,8 +140,8 @@ class InitialSetup(object):
""" """
def __init__(self): def __init__(self):
LOG.debug('Entering initialsetup class') LOG.debug('Entering initialsetup class')
self.server = UserClient().get_server() self.server = userclient.UserClient().get_server()
self.serverid = settings('plex_machineIdentifier') self.serverid = utils.settings('plex_machineIdentifier')
# Get Plex credentials from settings file, if they exist # Get Plex credentials from settings file, if they exist
plexdict = PF.GetPlexLoginFromSettings() plexdict = PF.GetPlexLoginFromSettings()
self.myplexlogin = plexdict['myplexlogin'] == 'true' self.myplexlogin = plexdict['myplexlogin'] == 'true'
@ -149,7 +149,7 @@ class InitialSetup(object):
self.plex_token = plexdict['plexToken'] self.plex_token = plexdict['plexToken']
self.plexid = plexdict['plexid'] self.plexid = plexdict['plexid']
# Token for the PMS, not plex.tv # Token for the PMS, not plex.tv
self.pms_token = settings('accessToken') self.pms_token = utils.settings('accessToken')
if self.plex_token: if self.plex_token:
LOG.debug('Found a plex.tv token in the settings') LOG.debug('Found a plex.tv token in the settings')
@ -179,20 +179,20 @@ class InitialSetup(object):
# HTTP Error: unauthorized. Token is no longer valid # HTTP Error: unauthorized. Token is no longer valid
LOG.info('plex.tv connection returned HTTP %s', str(chk)) LOG.info('plex.tv connection returned HTTP %s', str(chk))
# Delete token in the settings # Delete token in the settings
settings('plexToken', value='') utils.settings('plexToken', value='')
settings('plexLogin', value='') utils.settings('plexLogin', value='')
# Could not login, please try again # Could not login, please try again
dialog('ok', lang(29999), lang(39009)) utils.dialog('ok', utils.lang(29999), utils.lang(39009))
answer = self.plex_tv_sign_in() answer = self.plex_tv_sign_in()
elif chk is False or chk >= 400: elif chk is False or chk >= 400:
# Problems connecting to plex.tv. Network or internet issue? # Problems connecting to plex.tv. Network or internet issue?
LOG.info('Problems connecting to plex.tv; connection returned ' LOG.info('Problems connecting to plex.tv; connection returned '
'HTTP %s', str(chk)) 'HTTP %s', str(chk))
dialog('ok', lang(29999), lang(39010)) utils.dialog('ok', utils.lang(29999), utils.lang(39010))
answer = False answer = False
else: else:
LOG.info('plex.tv connection with token successful') LOG.info('plex.tv connection with token successful')
settings('plex_status', value=lang(39227)) utils.settings('plex_status', value=utils.lang(39227))
# Refresh the info from Plex.tv # Refresh the info from Plex.tv
xml = DU().downloadUrl('https://plex.tv/users/account', xml = DU().downloadUrl('https://plex.tv/users/account',
authenticate=False, authenticate=False,
@ -202,11 +202,12 @@ class InitialSetup(object):
except (AttributeError, KeyError): except (AttributeError, KeyError):
LOG.error('Failed to update Plex info from plex.tv') LOG.error('Failed to update Plex info from plex.tv')
else: else:
settings('plexLogin', value=self.plex_login) utils.settings('plexLogin', value=self.plex_login)
home = 'true' if xml.attrib.get('home') == '1' else 'false' home = 'true' if xml.attrib.get('home') == '1' else 'false'
settings('plexhome', value=home) utils.settings('plexhome', value=home)
settings('plexAvatar', value=xml.attrib.get('thumb')) utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1')) utils.settings('plexHomeSize',
value=xml.attrib.get('homeSize', '1'))
LOG.info('Updated Plex info from plex.tv') LOG.info('Updated Plex info from plex.tv')
return answer return answer
@ -233,7 +234,7 @@ class InitialSetup(object):
LOG.warn('Could not retrieve machineIdentifier') LOG.warn('Could not retrieve machineIdentifier')
answer = False answer = False
else: else:
settings('plex_machineIdentifier', value=self.serverid) utils.settings('plex_machineIdentifier', value=self.serverid)
elif answer is True: elif answer is True:
temp_server_id = PF.GetMachineIdentifier(self.server) temp_server_id = PF.GetMachineIdentifier(self.server)
if temp_server_id != self.serverid: if temp_server_id != self.serverid:
@ -325,7 +326,7 @@ class InitialSetup(object):
if item.get('machineIdentifier') == self.serverid: if item.get('machineIdentifier') == self.serverid:
server = item server = item
if server is None: if server is None:
name = settings('plex_servername') name = utils.settings('plex_servername')
LOG.warn('The PMS you have used before with a unique ' LOG.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is ' 'machineIdentifier of %s and name %s is '
'offline', self.serverid, name) 'offline', self.serverid, name)
@ -356,18 +357,18 @@ class InitialSetup(object):
""" """
https_updated = False https_updated = False
# Searching for PMS # Searching for PMS
dialog('notification', utils.dialog('notification',
heading='{plex}', heading='{plex}',
message=lang(30001), message=utils.lang(30001),
icon='{plex}', icon='{plex}',
time=5000) time=5000)
while True: while True:
if https_updated is False: if https_updated is False:
serverlist = PF.discover_pms(self.plex_token) serverlist = PF.discover_pms(self.plex_token)
# Exit if no servers found # Exit if no servers found
if not serverlist: if not serverlist:
LOG.warn('No plex media servers found!') LOG.warn('No plex media servers found!')
dialog('ok', lang(29999), lang(39011)) utils.dialog('ok', utils.lang(29999), utils.lang(39011))
return return
# Get a nicer list # Get a nicer list
dialoglist = [] dialoglist = []
@ -375,10 +376,10 @@ class InitialSetup(object):
if server['local']: if server['local']:
# server is in the same network as client. # server is in the same network as client.
# Add"local" # Add"local"
msg = lang(39022) msg = utils.lang(39022)
else: else:
# Add 'remote' # Add 'remote'
msg = lang(39054) msg = utils.lang(39054)
if server.get('ownername'): if server.get('ownername'):
# Display username if its not our PMS # Display username if its not our PMS
dialoglist.append('%s (%s, %s)' dialoglist.append('%s (%s, %s)'
@ -389,7 +390,7 @@ class InitialSetup(object):
dialoglist.append('%s (%s)' dialoglist.append('%s (%s)'
% (server['name'], msg)) % (server['name'], msg))
# Let user pick server from a list # Let user pick server from a list
resp = dialog('select', lang(39012), dialoglist) resp = utils.dialog('select', utils.lang(39012), dialoglist)
if resp == -1: if resp == -1:
# User cancelled # User cancelled
return return
@ -406,17 +407,19 @@ class InitialSetup(object):
LOG.warn('Not yet authorized for Plex server %s', LOG.warn('Not yet authorized for Plex server %s',
server['name']) server['name'])
# Please sign in to plex.tv # Please sign in to plex.tv
dialog('ok', utils.dialog('ok',
lang(29999), utils.lang(29999),
lang(39013) + server['name'], utils.lang(39013) + server['name'],
lang(39014)) utils.lang(39014))
if self.plex_tv_sign_in() is False: if self.plex_tv_sign_in() is False:
# Exit while loop if user cancels # Exit while loop if user cancels
return return
# Problems connecting # Problems connecting
elif chk >= 400 or chk is False: elif chk >= 400 or chk is False:
# Problems connecting to server. Pick another server? # Problems connecting to server. Pick another server?
answ = dialog('yesno', lang(29999), lang(39015)) answ = utils.dialog('yesno',
utils.lang(29999),
utils.lang(39015))
# Exit while loop if user chooses No # Exit while loop if user chooses No
if not answ: if not answ:
return return
@ -429,30 +432,31 @@ class InitialSetup(object):
""" """
Saves server to file settings Saves server to file settings
""" """
settings('plex_machineIdentifier', server['machineIdentifier']) utils.settings('plex_machineIdentifier', server['machineIdentifier'])
settings('plex_servername', server['name']) utils.settings('plex_servername', server['name'])
settings('plex_serverowned', 'true' if server['owned'] else 'false') utils.settings('plex_serverowned',
'true' if server['owned'] else 'false')
# Careful to distinguish local from remote PMS # Careful to distinguish local from remote PMS
if server['local']: if server['local']:
scheme = server['scheme'] scheme = server['scheme']
settings('ipaddress', server['ip']) utils.settings('ipaddress', server['ip'])
settings('port', server['port']) utils.settings('port', server['port'])
LOG.debug("Setting SSL verify to false, because server is " LOG.debug("Setting SSL verify to false, because server is "
"local") "local")
settings('sslverify', 'false') utils.settings('sslverify', 'false')
else: else:
baseURL = server['baseURL'].split(':') baseURL = server['baseURL'].split(':')
scheme = baseURL[0] scheme = baseURL[0]
settings('ipaddress', baseURL[1].replace('//', '')) utils.settings('ipaddress', baseURL[1].replace('//', ''))
settings('port', baseURL[2]) utils.settings('port', baseURL[2])
LOG.debug("Setting SSL verify to true, because server is not " LOG.debug("Setting SSL verify to true, because server is not "
"local") "local")
settings('sslverify', 'true') utils.settings('sslverify', 'true')
if scheme == 'https': if scheme == 'https':
settings('https', 'true') utils.settings('https', 'true')
else: else:
settings('https', 'false') utils.settings('https', 'false')
# And finally do some logging # And finally do some logging
LOG.debug("Writing to Kodi user settings file") LOG.debug("Writing to Kodi user settings file")
LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ", LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ",
@ -468,9 +472,9 @@ class InitialSetup(object):
""" """
LOG.info("Initial setup called.") LOG.info("Initial setup called.")
try: try:
with XmlKodiSetting('advancedsettings.xml', with utils.XmlKodiSetting('advancedsettings.xml',
force_create=True, force_create=True,
top_element='advancedsettings') as xml: top_element='advancedsettings') as xml:
# Get current Kodi video cache setting # Get current Kodi video cache setting
cache = xml.get_setting(['cache', 'memorysize']) cache = xml.get_setting(['cache', 'memorysize'])
# Disable foreground "Loading media information from files" # Disable foreground "Loading media information from files"
@ -493,13 +497,13 @@ class InitialSetup(object):
# Kodi default cache if no setting is set # Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520' cache = str(cache.text) if cache is not None else '20971520'
LOG.info('Current Kodi video memory cache in bytes: %s', cache) LOG.info('Current Kodi video memory cache in bytes: %s', cache)
settings('kodi_video_cache', value=cache) utils.settings('kodi_video_cache', value=cache)
# Hack to make PKC Kodi master lock compatible # Hack to make PKC Kodi master lock compatible
try: try:
with XmlKodiSetting('sources.xml', with utils.XmlKodiSetting('sources.xml',
force_create=True, force_create=True,
top_element='sources') as xml: top_element='sources') as xml:
root = xml.set_setting(['video']) root = xml.set_setting(['video'])
count = 2 count = 2
for source in root.findall('.//path'): for source in root.findall('.//path'):
@ -526,21 +530,21 @@ class InitialSetup(object):
pass pass
# Do we need to migrate stuff? # Do we need to migrate stuff?
check_migration() migration.check_migration()
# Reload the server IP cause we might've deleted it during migration # Reload the server IP cause we might've deleted it during migration
self.server = UserClient().get_server() self.server = userclient.UserClient().get_server()
# Display a warning if Kodi puts ALL movies into the queue, basically # Display a warning if Kodi puts ALL movies into the queue, basically
# breaking playback reporting for PKC # breaking playback reporting for PKC
if js.settings_getsettingvalue('videoplayer.autoplaynextitem'): if js.settings_getsettingvalue('videoplayer.autoplaynextitem'):
LOG.warn('Kodi setting videoplayer.autoplaynextitem is enabled!') LOG.warn('Kodi setting videoplayer.autoplaynextitem is enabled!')
if settings('warned_setting_videoplayer.autoplaynextitem') == 'false': if utils.settings('warned_setting_videoplayer.autoplaynextitem') == 'false':
# Only warn once # Only warn once
settings('warned_setting_videoplayer.autoplaynextitem', utils.settings('warned_setting_videoplayer.autoplaynextitem',
value='true') value='true')
# Warning: Kodi setting "Play next video automatically" is # Warning: Kodi setting "Play next video automatically" is
# enabled. This could break PKC. Deactivate? # enabled. This could break PKC. Deactivate?
if dialog('yesno', lang(29999), lang(30003)): if utils.dialog('yesno', utils.lang(29999), utils.lang(30003)):
js.settings_setsettingvalue('videoplayer.autoplaynextitem', js.settings_setsettingvalue('videoplayer.autoplaynextitem',
False) False)
# Set any video library updates to happen in the background in order to # Set any video library updates to happen in the background in order to
@ -556,7 +560,7 @@ class InitialSetup(object):
self.server, self.serverid) self.server, self.serverid)
_write_pms_settings(self.server, self.pms_token) _write_pms_settings(self.server, self.pms_token)
if reboot is True: if reboot is True:
reboot_kodi() utils.reboot_kodi()
return return
# If not already retrieved myplex info, optionally let user sign in # If not already retrieved myplex info, optionally let user sign in
@ -570,78 +574,91 @@ class InitialSetup(object):
self.write_pms_to_settings(server) self.write_pms_to_settings(server)
# User already answered the installation questions # User already answered the installation questions
if settings('InstallQuestionsAnswered') == 'true': if utils.settings('InstallQuestionsAnswered') == 'true':
if reboot is True: if reboot is True:
reboot_kodi() utils.reboot_kodi()
return return
# Additional settings where the user needs to choose # Additional settings where the user needs to choose
# Direct paths (\\NAS\mymovie.mkv) or addon (http)? # Direct paths (\\NAS\mymovie.mkv) or addon (http)?
goto_settings = False goto_settings = False
if dialog('yesno', if utils.dialog('yesno',
lang(29999), utils.lang(29999),
lang(39027), utils.lang(39027),
lang(39028), utils.lang(39028),
nolabel="Addon (Default)", nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)"): yeslabel="Native (Direct Paths)"):
LOG.debug("User opted to use direct paths.") LOG.debug("User opted to use direct paths.")
settings('useDirectPaths', value="1") utils.settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True state.DIRECT_PATHS = True
# Are you on a system where you would like to replace paths # Are you on a system where you would like to replace paths
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows) # \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
if dialog('yesno', heading=lang(29999), line1=lang(39033)): if utils.dialog('yesno',
heading=utils.lang(29999),
line1=utils.lang(39033)):
LOG.debug("User chose to replace paths with smb") LOG.debug("User chose to replace paths with smb")
else: else:
settings('replaceSMB', value="false") utils.settings('replaceSMB', value="false")
# complete replace all original Plex library paths with custom SMB # complete replace all original Plex library paths with custom SMB
if dialog('yesno', heading=lang(29999), line1=lang(39043)): if utils.dialog('yesno',
heading=utils.lang(29999),
line1=utils.lang(39043)):
LOG.debug("User chose custom smb paths") LOG.debug("User chose custom smb paths")
settings('remapSMB', value="true") utils.settings('remapSMB', value="true")
# Please enter your custom smb paths in the settings under # Please enter your custom smb paths in the settings under
# "Sync Options" and then restart Kodi # "Sync Options" and then restart Kodi
dialog('ok', heading=lang(29999), line1=lang(39044)) utils.dialog('ok',
heading=utils.lang(29999),
line1=utils.lang(39044))
goto_settings = True goto_settings = True
# Go to network credentials? # Go to network credentials?
if dialog('yesno', if utils.dialog('yesno',
heading=lang(29999), heading=utils.lang(29999),
line1=lang(39029), line1=utils.lang(39029),
line2=lang(39030)): line2=utils.lang(39030)):
LOG.debug("Presenting network credentials dialog.") LOG.debug("Presenting network credentials dialog.")
from utils import passwords_xml from utils import passwords_xml
passwords_xml() passwords_xml()
# Disable Plex music? # Disable Plex music?
if dialog('yesno', heading=lang(29999), line1=lang(39016)): if utils.dialog('yesno',
heading=utils.lang(29999),
line1=utils.lang(39016)):
LOG.debug("User opted to disable Plex music library.") LOG.debug("User opted to disable Plex music library.")
settings('enableMusic', value="false") utils.settings('enableMusic', value="false")
# Download additional art from FanArtTV # Download additional art from FanArtTV
if dialog('yesno', heading=lang(29999), line1=lang(39061)): if utils.dialog('yesno',
heading=utils.lang(29999),
line1=utils.lang(39061)):
LOG.debug("User opted to use FanArtTV") LOG.debug("User opted to use FanArtTV")
settings('FanartTV', value="true") utils.settings('FanartTV', value="true")
# Do you want to replace your custom user ratings with an indicator of # Do you want to replace your custom user ratings with an indicator of
# how many versions of a media item you posses? # how many versions of a media item you posses?
if dialog('yesno', heading=lang(29999), line1=lang(39718)): if utils.dialog('yesno',
heading=utils.lang(29999),
line1=utils.lang(39718)):
LOG.debug("User opted to replace user ratings with version number") LOG.debug("User opted to replace user ratings with version number")
settings('indicate_media_versions', value="true") utils.settings('indicate_media_versions', value="true")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and # If you use several Plex libraries of one kind, e.g. "Kids Movies" and
# "Parents Movies", be sure to check https://goo.gl/JFtQV9 # "Parents Movies", be sure to check https://goo.gl/JFtQV9
# dialog.ok(heading=lang(29999), line1=lang(39076)) # dialog.ok(heading=utils.lang(29999), line1=utils.lang(39076))
# Need to tell about our image source for collections: themoviedb.org # Need to tell about our image source for collections: themoviedb.org
# dialog.ok(heading=lang(29999), line1=lang(39717)) # dialog.ok(heading=utils.lang(29999), line1=utils.lang(39717))
# Make sure that we only ask these questions upon first installation # Make sure that we only ask these questions upon first installation
settings('InstallQuestionsAnswered', value='true') utils.settings('InstallQuestionsAnswered', value='true')
if goto_settings is False: if goto_settings is False:
# Open Settings page now? You will need to restart! # Open Settings page now? You will need to restart!
goto_settings = dialog('yesno', goto_settings = utils.dialog('yesno',
heading=lang(29999), heading=utils.lang(29999),
line1=lang(39017)) line1=utils.lang(39017))
if goto_settings: if goto_settings:
state.PMS_STATUS = 'Stop' state.PMS_STATUS = 'Stop'
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)') executebuiltin(
'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
elif reboot is True: elif reboot is True:
reboot_kodi() utils.reboot_kodi()

View File

@ -4,18 +4,17 @@ from logging import getLogger
from ntpath import dirname from ntpath import dirname
from datetime import datetime from datetime import datetime
from artwork import Artwork from . import artwork
from utils import window, kodi_sql, catch_exceptions from . import utils
import plexdb_functions as plexdb from . import plexdb_functions as plexdb
import kodidb_functions as kodidb from . import kodidb_functions as kodidb
from .plex_api import API
from PlexAPI import API from . import plex_functions as PF
from PlexFunctions import GetPlexMetadata from . import variables as v
import variables as v from . import state
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.itemtypes')
# Note: always use same order of URL arguments, NOT urlencode: # Note: always use same order of URL arguments, NOT urlencode:
# plex_id=<plex_id>&plex_type=<plex_type>&mode=play # plex_id=<plex_id>&plex_type=<plex_type>&mode=play
@ -32,8 +31,8 @@ class Items(object):
kodiType: optional argument; e.g. 'video' or 'music' kodiType: optional argument; e.g. 'video' or 'music'
""" """
def __init__(self): def __init__(self):
self.artwork = Artwork() self.artwork = artwork.Artwork()
self.server = window('pms_server') self.server = utils.window('pms_server')
self.plexconn = None self.plexconn = None
self.plexcursor = None self.plexcursor = None
self.kodiconn = None self.kodiconn = None
@ -45,9 +44,9 @@ class Items(object):
""" """
Open DB connections and cursors Open DB connections and cursors
""" """
self.plexconn = kodi_sql('plex') self.plexconn = utils.kodi_sql('plex')
self.plexcursor = self.plexconn.cursor() self.plexcursor = self.plexconn.cursor()
self.kodiconn = kodi_sql('video') self.kodiconn = utils.kodi_sql('video')
self.kodicursor = self.kodiconn.cursor() self.kodicursor = self.kodiconn.cursor()
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor) self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
self.kodi_db = kodidb.KodiDBMethods(self.kodicursor) self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
@ -63,7 +62,7 @@ class Items(object):
self.kodiconn.close() self.kodiconn.close()
return self return self
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def getfanart(self, plex_id, refresh=False): def getfanart(self, plex_id, refresh=False):
""" """
Tries to get additional fanart for movies (+sets) and TV shows. Tries to get additional fanart for movies (+sets) and TV shows.
@ -95,7 +94,7 @@ class Items(object):
LOG.debug('Already got all fanart for Plex id %s', plex_id) LOG.debug('Already got all fanart for Plex id %s', plex_id)
return True return True
xml = GetPlexMetadata(plex_id) xml = PF.GetPlexMetadata(plex_id)
if xml is None: if xml is None:
# Did not receive a valid XML - skip that item for now # Did not receive a valid XML - skip that item for now
LOG.error("Could not get metadata for %s. Skipping that item " LOG.error("Could not get metadata for %s. Skipping that item "
@ -183,7 +182,7 @@ class Movies(Items):
""" """
Used for plex library-type movies Used for plex library-type movies
""" """
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_update(self, item, viewtag=None, viewid=None): def add_update(self, item, viewtag=None, viewid=None):
""" """
Process single movie Process single movie
@ -513,7 +512,7 @@ class TVShows(Items):
""" """
For Plex library-type TV shows For Plex library-type TV shows
""" """
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_update(self, item, viewtag=None, viewid=None): def add_update(self, item, viewtag=None, viewid=None):
""" """
Process a single show Process a single show
@ -722,7 +721,7 @@ class TVShows(Items):
tags.extend(collections) tags.extend(collections)
self.kodi_db.modify_tags(showid, v.KODI_TYPE_SHOW, tags) self.kodi_db.modify_tags(showid, v.KODI_TYPE_SHOW, tags)
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_updateSeason(self, item, viewtag=None, viewid=None): def add_updateSeason(self, item, viewtag=None, viewid=None):
""" """
Process a single season of a certain tv show Process a single season of a certain tv show
@ -768,7 +767,7 @@ class TVShows(Items):
view_id=viewid, view_id=viewid,
checksum=checksum) checksum=checksum)
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_updateEpisode(self, item, viewtag=None, viewid=None): def add_updateEpisode(self, item, viewtag=None, viewid=None):
""" """
Process single episode Process single episode
@ -998,7 +997,7 @@ class TVShows(Items):
runtime, runtime,
playcount, playcount,
dateplayed, dateplayed,
None) # Do send None, we check here None) # Do send None, we check here
if not state.DIRECT_PATHS: if not state.DIRECT_PATHS:
# need to set a SECOND file entry for a path without plex show id # need to set a SECOND file entry for a path without plex show id
filename = api.file_name(force_first_media=True) filename = api.file_name(force_first_media=True)
@ -1014,9 +1013,9 @@ class TVShows(Items):
runtime, runtime,
playcount, playcount,
dateplayed, dateplayed,
None) # Do send None - 2nd entry None) # Do send None - 2nd entry
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def remove(self, plex_id): def remove(self, plex_id):
""" """
Remove the entire TV shows object (show, season or episode) including Remove the entire TV shows object (show, season or episode) including
@ -1139,16 +1138,16 @@ class Music(Items):
OVERWRITE this method, because we need to open another DB. OVERWRITE this method, because we need to open another DB.
Open DB connections and cursors Open DB connections and cursors
""" """
self.plexconn = kodi_sql('plex') self.plexconn = utils.kodi_sql('plex')
self.plexcursor = self.plexconn.cursor() self.plexcursor = self.plexconn.cursor()
# Here it is, not 'video' but 'music' # Here it is, not 'video' but 'music'
self.kodiconn = kodi_sql('music') self.kodiconn = utils.kodi_sql('music')
self.kodicursor = self.kodiconn.cursor() self.kodicursor = self.kodiconn.cursor()
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor) self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
self.kodi_db = kodidb.KodiDBMethods(self.kodicursor) self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
return self return self
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_updateArtist(self, item, viewtag=None, viewid=None): def add_updateArtist(self, item, viewtag=None, viewid=None):
""" """
Adds a single artist Adds a single artist
@ -1236,7 +1235,7 @@ class Music(Items):
v.KODI_TYPE_ARTIST, v.KODI_TYPE_ARTIST,
kodicursor) kodicursor)
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None, def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None,
scan_children=True): scan_children=True):
""" """
@ -1362,7 +1361,7 @@ class Music(Items):
artist_id = plex_db.getItem_byId(parent_id)[0] artist_id = plex_db.getItem_byId(parent_id)[0]
except TypeError: except TypeError:
LOG.info('Artist %s does not yet exist in Plex DB', parent_id) LOG.info('Artist %s does not yet exist in Plex DB', parent_id)
artist = GetPlexMetadata(parent_id) artist = PF.GetPlexMetadata(parent_id)
try: try:
artist[0].attrib artist[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
@ -1393,13 +1392,16 @@ class Music(Items):
self.genres, self.genres,
v.KODI_TYPE_ALBUM) v.KODI_TYPE_ALBUM)
# Update artwork # Update artwork
artwork.modify_artwork(artworks, album_id, v.KODI_TYPE_ALBUM, kodicursor) artwork.modify_artwork(artworks,
album_id,
v.KODI_TYPE_ALBUM,
kodicursor)
# Add all children - all tracks # Add all children - all tracks
if scan_children: if scan_children:
for child in children: for child in children:
self.add_updateSong(child, viewtag, viewid) self.add_updateSong(child, viewtag, viewid)
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def add_updateSong(self, item, viewtag=None, viewid=None): def add_updateSong(self, item, viewtag=None, viewid=None):
""" """
Process single song Process single song
@ -1459,7 +1461,7 @@ class Music(Items):
if disc == 1: if disc == 1:
track = tracknumber track = tracknumber
else: else:
track = disc*2**16 + tracknumber track = disc * 2 ** 16 + tracknumber
year = api.year() year = api.year()
_, duration = api.resume_runtime() _, duration = api.resume_runtime()
rating = userdata['UserRating'] rating = userdata['UserRating']
@ -1573,7 +1575,7 @@ class Music(Items):
# No album found. Let's create it # No album found. Let's create it
LOG.info("Album database entry missing.") LOG.info("Album database entry missing.")
plex_album_id = api.parent_plex_id() plex_album_id = api.parent_plex_id()
album = GetPlexMetadata(plex_album_id) album = PF.GetPlexMetadata(plex_album_id)
if album is None or album == 401: if album is None or album == 401:
LOG.error('Could not download album, abort') LOG.error('Could not download album, abort')
return return
@ -1664,7 +1666,8 @@ class Music(Items):
idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
''' '''
kodicursor.execute(query, (songid, albumid, track, title, duration)) kodicursor.execute(query,
(songid, albumid, track, title, duration))
# Link song to artists # Link song to artists
artist_loop = [{ artist_loop = [{
'Name': api.grandparent_title(), 'Name': api.grandparent_title(),
@ -1680,7 +1683,7 @@ class Music(Items):
artistid = artist_edb[0] artistid = artist_edb[0]
except TypeError: except TypeError:
# Artist is missing from plex database, add it. # Artist is missing from plex database, add it.
artist_xml = GetPlexMetadata(artist_eid) artist_xml = PF.GetPlexMetadata(artist_eid)
if artist_xml is None or artist_xml == 401: if artist_xml is None or artist_xml == 401:
LOG.error('Error getting artist, abort') LOG.error('Error getting artist, abort')
return return
@ -1718,9 +1721,12 @@ class Music(Items):
artwork.modify_artwork(artworks, songid, v.KODI_TYPE_SONG, kodicursor) artwork.modify_artwork(artworks, songid, v.KODI_TYPE_SONG, kodicursor)
if item.get('parentKey') is None: if item.get('parentKey') is None:
# Update album artwork # Update album artwork
artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor) artwork.modify_artwork(artworks,
albumid,
v.KODI_TYPE_ALBUM,
kodicursor)
@catch_exceptions(warnuser=True) @utils.catch_exceptions(warnuser=True)
def remove(self, plex_id): def remove(self, plex_id):
""" """
Completely remove the item with plex_id from the Kodi and Plex DBs. Completely remove the item with plex_id from the Kodi and Plex DBs.
@ -1768,7 +1774,8 @@ class Music(Items):
##### IF ARTIST ##### ##### IF ARTIST #####
elif kodi_type == v.KODI_TYPE_ARTIST: elif kodi_type == v.KODI_TYPE_ARTIST:
# Delete songs, album, artist # Delete songs, album, artist
albums = self.plex_db.getItem_byParentId(kodi_id, v.KODI_TYPE_ALBUM) albums = self.plex_db.getItem_byParentId(kodi_id,
v.KODI_TYPE_ALBUM)
for album in albums: for album in albums:
songs = self.plex_db.getItem_byParentId(album[1], songs = self.plex_db.getItem_byParentId(album[1],
v.KODI_TYPE_SONG) v.KODI_TYPE_SONG)

View File

@ -3,9 +3,10 @@ Collection of functions using the Kodi JSON RPC interface.
See http://kodi.wiki/view/JSON-RPC_API See http://kodi.wiki/view/JSON-RPC_API
""" """
from json import loads, dumps from json import loads, dumps
from utils import millis_to_kodi_time
from xbmc import executeJSONRPC from xbmc import executeJSONRPC
from . import utils
class JsonRPC(object): class JsonRPC(object):
""" """
@ -152,7 +153,7 @@ def seek_to(offset):
for playerid in get_player_ids(): for playerid in get_player_ids():
JsonRPC("Player.Seek").execute( JsonRPC("Player.Seek").execute(
{"playerid": playerid, {"playerid": playerid,
"value": millis_to_kodi_time(offset)}) "value": utils.millis_to_kodi_time(offset)})
def smallforward(): def smallforward():

View File

@ -7,14 +7,14 @@ from logging import getLogger
from ntpath import dirname from ntpath import dirname
from sqlite3 import IntegrityError from sqlite3 import IntegrityError
import artwork from . import artwork
from utils import kodi_sql, try_decode, unix_timestamp, unix_date_to_kodi from . import utils
import variables as v from . import variables as v
import state from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.kodidb_functions')
############################################################################### ###############################################################################
@ -35,7 +35,7 @@ class GetKodiDB(object):
self.db_type = db_type self.db_type = db_type
def __enter__(self): def __enter__(self):
self.kodiconn = kodi_sql(self.db_type) self.kodiconn = utils.kodi_sql(self.db_type)
kodi_db = KodiDBMethods(self.kodiconn.cursor()) kodi_db = KodiDBMethods(self.kodiconn.cursor())
return kodi_db return kodi_db
@ -118,7 +118,7 @@ class KodiDBMethods(object):
if pathid is None: if pathid is None:
self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path") self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path")
pathid = self.cursor.fetchone()[0] + 1 pathid = self.cursor.fetchone()[0] + 1
datetime = unix_date_to_kodi(unix_timestamp()) datetime = utils.unix_date_to_kodi(utils.unix_timestamp())
query = ''' query = '''
INSERT INTO path(idPath, strPath, dateAdded) INSERT INTO path(idPath, strPath, dateAdded)
VALUES (?, ?, ?) VALUES (?, ?, ?)
@ -209,13 +209,14 @@ class KodiDBMethods(object):
INSERT INTO files(idFile, idPath, strFilename, dateAdded) INSERT INTO files(idFile, idPath, strFilename, dateAdded)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''' '''
self.cursor.execute(query, (file_id, path_id, filename, date_added)) self.cursor.execute(query,
(file_id, path_id, filename, date_added))
return file_id return file_id
def obsolete_file_ids(self): def obsolete_file_ids(self):
""" """
Returns a list of (idFile,) tuples (ints) of all Kodi file ids that do Returns a list of (idFile,) tuples (ints) of all Kodi file ids that do
not have a dateAdded set (dateAdded is NULL) and the filename start with not have a dateAdded set (dateAdded NULL) and the filename start with
'plugin://plugin.video.plexkodiconnect' 'plugin://plugin.video.plexkodiconnect'
These entries should be deleted as they're created falsely by Kodi. These entries should be deleted as they're created falsely by Kodi.
""" """
@ -1236,7 +1237,7 @@ def kodiid_from_filename(path, kodi_type=None, db_type=None):
Returns None, <kodi_type> if not possible Returns None, <kodi_type> if not possible
""" """
kodi_id = None kodi_id = None
path = try_decode(path) path = utils.try_decode(path)
try: try:
filename = path.rsplit('/', 1)[1] filename = path.rsplit('/', 1)[1]
path = path.rsplit('/', 1)[0] + '/' path = path.rsplit('/', 1)[0] + '/'

View File

@ -5,29 +5,25 @@ from logging import getLogger
from json import loads from json import loads
from threading import Thread from threading import Thread
import copy import copy
import xbmc import xbmc
from xbmcgui import Window from xbmcgui import Window
import plexdb_functions as plexdb from . import plexdb_functions as plexdb
import kodidb_functions as kodidb from . import kodidb_functions as kodidb
from utils import window, settings, plex_command, thread_methods, try_encode, \ from . import utils
kodi_time_to_millis, unix_date_to_kodi, unix_timestamp from . import plex_functions as PF
from PlexFunctions import scrobble from .downloadutils import DownloadUtils as DU
from downloadutils import DownloadUtils as DU from . import playback
from kodidb_functions import kodiid_from_filename from . import initialsetup
from plexbmchelper.subscribers import LOCKER from . import playqueue as PQ
from playback import playback_triage from . import json_rpc as js
from initialsetup import set_replace_paths from . import playlist_func as PL
import playqueue as PQ from . import state
import json_rpc as js from . import variables as v
import playlist_func as PL
import state
import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.kodimonitor')
# settings: window-variable # settings: window-variable
WINDOW_SETTINGS = { WINDOW_SETTINGS = {
@ -65,6 +61,7 @@ class KodiMonitor(xbmc.Monitor):
def __init__(self): def __init__(self):
self.xbmcplayer = xbmc.Player() self.xbmcplayer = xbmc.Player()
self._already_slept = False self._already_slept = False
self.hack_replay = None
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
for playerid in state.PLAYER_STATES: for playerid in state.PLAYER_STATES:
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE) state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
@ -91,14 +88,14 @@ class KodiMonitor(xbmc.Monitor):
changed = False changed = False
# Reset the window variables from the settings variables # Reset the window variables from the settings variables
for settings_value, window_value in WINDOW_SETTINGS.iteritems(): for settings_value, window_value in WINDOW_SETTINGS.iteritems():
if window(window_value) != settings(settings_value): if utils.window(window_value) != utils.settings(settings_value):
changed = True changed = True
LOG.debug('PKC window settings changed: %s is now %s', LOG.debug('PKC window settings changed: %s is now %s',
settings_value, settings(settings_value)) settings_value, utils.settings(settings_value))
window(window_value, value=settings(settings_value)) utils.window(window_value, value=utils.settings(settings_value))
# Reset the state variables in state.py # Reset the state variables in state.py
for settings_value, state_name in STATE_SETTINGS.iteritems(): for settings_value, state_name in STATE_SETTINGS.iteritems():
new = settings(settings_value) new = utils.settings(settings_value)
if new == 'true': if new == 'true':
new = True new = True
elif new == 'false': elif new == 'false':
@ -110,19 +107,17 @@ class KodiMonitor(xbmc.Monitor):
setattr(state, state_name, new) setattr(state, state_name, new)
if state_name == 'FETCH_PMS_ITEM_NUMBER': if state_name == 'FETCH_PMS_ITEM_NUMBER':
LOG.info('Requesting playlist/nodes refresh') LOG.info('Requesting playlist/nodes refresh')
plex_command('RUN_LIB_SCAN', 'views') utils.plex_command('RUN_LIB_SCAN', 'views')
# Special cases, overwrite all internal settings # Special cases, overwrite all internal settings
set_replace_paths() initialsetup.set_replace_paths()
state.BACKGROUND_SYNC_DISABLED = settings( state.BACKGROUND_SYNC_DISABLED = utils.settings(
'enableBackgroundSync') == 'false' 'enableBackgroundSync') == 'false'
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60 state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
state.BACKGROUNDSYNC_SAFTYMARGIN = int( state.BACKGROUNDSYNC_SAFTYMARGIN = int(
settings('backgroundsync_saftyMargin')) utils.settings('backgroundsync_saftyMargin'))
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber')) state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
state.SSL_CERT_PATH = settings('sslcert') \ state.SSL_CERT_PATH = utils.settings('sslcert') \
if settings('sslcert') != 'None' else None if utils.settings('sslcert') != 'None' else None
# Never set through the user
# state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
if changed is True: if changed is True:
# Assume that the user changed the settings so that we can now find # Assume that the user changed the settings so that we can now find
# the path to all media files # the path to all media files
@ -137,13 +132,22 @@ class KodiMonitor(xbmc.Monitor):
data = loads(data, 'utf-8') data = loads(data, 'utf-8')
LOG.debug("Method: %s Data: %s", method, data) LOG.debug("Method: %s Data: %s", method, data)
# Hack
if not method == 'Player.OnStop':
self.hack_replay = None
if method == "Player.OnPlay": if method == "Player.OnPlay":
state.SUSPEND_SYNC = True state.SUSPEND_SYNC = True
self.PlayBackStart(data) self.PlayBackStart(data)
elif method == "Player.OnStop": elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck # Should refresh our video nodes, e.g. on deck
# xbmc.executebuiltin('ReloadSkin()') # xbmc.executebuiltin('ReloadSkin()')
if data.get('end'): if (self.hack_replay and not data.get('end') and
self.hack_replay == data['item']):
# Hack for add-on paths
self.hack_replay = None
self._hack_addon_paths_replay_video()
elif data.get('end'):
if state.PKC_CAUSED_STOP is True: if state.PKC_CAUSED_STOP is True:
state.PKC_CAUSED_STOP = False state.PKC_CAUSED_STOP = False
LOG.debug('PKC caused this playback stop - ignoring') LOG.debug('PKC caused this playback stop - ignoring')
@ -182,28 +186,60 @@ class KodiMonitor(xbmc.Monitor):
else: else:
# notify the server # notify the server
if playcount > 0: if playcount > 0:
scrobble(itemid, 'watched') PF.scrobble(itemid, 'watched')
else: else:
scrobble(itemid, 'unwatched') PF.scrobble(itemid, 'unwatched')
elif method == "VideoLibrary.OnRemove": elif method == "VideoLibrary.OnRemove":
pass pass
elif method == "System.OnSleep": elif method == "System.OnSleep":
# Connection is going to sleep # Connection is going to sleep
LOG.info("Marking the server as offline. SystemOnSleep activated.") LOG.info("Marking the server as offline. SystemOnSleep activated.")
window('plex_online', value="sleep") utils.window('plex_online', value="sleep")
elif method == "System.OnWake": elif method == "System.OnWake":
# Allow network to wake up # Allow network to wake up
xbmc.sleep(10000) xbmc.sleep(10000)
window('plex_online', value="false") utils.window('plex_online', value="false")
elif method == "GUI.OnScreensaverDeactivated": elif method == "GUI.OnScreensaverDeactivated":
if settings('dbSyncScreensaver') == "true": if utils.settings('dbSyncScreensaver') == "true":
xbmc.sleep(5000) xbmc.sleep(5000)
plex_command('RUN_LIB_SCAN', 'full') utils.plex_command('RUN_LIB_SCAN', 'full')
elif method == "System.OnQuit": elif method == "System.OnQuit":
LOG.info('Kodi OnQuit detected - shutting down') LOG.info('Kodi OnQuit detected - shutting down')
state.STOP_PKC = True state.STOP_PKC = True
@LOCKER.lockthis @staticmethod
@state.LOCKER_SUBSCRIBER.lockthis
def _hack_addon_paths_replay_video():
"""
Hack we need for RESUMABLE items because Kodi lost the path of the
last played item that is now being replayed (see playback.py's
Player().play()) Also see playqueue.py _compare_playqueues()
Needed if user re-starts the same video from the library using addon
paths. (Video is only added to playqueue, then immediately stoppen.
There is no playback initialized by Kodi.) Log excerpts:
Method: Playlist.OnAdd Data:
{u'item': {u'type': u'movie', u'id': 4},
u'playlistid': 1,
u'position': 0}
Now we would hack!
Method: Player.OnStop Data:
{u'item': {u'type': u'movie', u'id': 4},
u'end': False}
(within the same micro-second!)
"""
LOG.info('Detected re-start of playback of last item')
old = state.OLD_PLAYER_STATES[1]
kwargs = {
'plex_id': old['plex_id'],
'plex_type': old['plex_type'],
'path': old['file'],
'resolve': False
}
thread = Thread(target=playback.playback_triage, kwargs=kwargs)
thread.start()
@state.LOCKER_SUBSCRIBER.lockthis
def _playlist_onadd(self, data): def _playlist_onadd(self, data):
""" """
Called if an item is added to a Kodi playlist. Example data dict: Called if an item is added to a Kodi playlist. Example data dict:
@ -219,23 +255,12 @@ class KodiMonitor(xbmc.Monitor):
if 'id' not in data['item']: if 'id' not in data['item']:
return return
old = state.OLD_PLAYER_STATES[data['playlistid']] old = state.OLD_PLAYER_STATES[data['playlistid']]
if (not state.DIRECT_PATHS and data['position'] == 0 and if (not state.DIRECT_PATHS and
data['position'] == 0 and data['playlistid'] == 1 and
not PQ.PLAYQUEUES[data['playlistid']].items and not PQ.PLAYQUEUES[data['playlistid']].items and
data['item']['type'] == old['kodi_type'] and data['item']['type'] == old['kodi_type'] and
data['item']['id'] == old['kodi_id']): data['item']['id'] == old['kodi_id']):
# Hack we need for RESUMABLE items because Kodi lost the path of the self.hack_replay = data['item']
# last played item that is now being replayed (see playback.py's
# Player().play()) Also see playqueue.py _compare_playqueues()
LOG.info('Detected re-start of playback of last item')
kwargs = {
'plex_id': old['plex_id'],
'plex_type': old['plex_type'],
'path': old['file'],
'resolve': False
}
thread = Thread(target=playback_triage, kwargs=kwargs)
thread.start()
return
def _playlist_onremove(self, data): def _playlist_onremove(self, data):
""" """
@ -247,7 +272,7 @@ class KodiMonitor(xbmc.Monitor):
""" """
pass pass
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def _playlist_onclear(self, data): def _playlist_onclear(self, data):
""" """
Called if a Kodi playlist is cleared. Example data dict: Called if a Kodi playlist is cleared. Example data dict:
@ -271,7 +296,7 @@ class KodiMonitor(xbmc.Monitor):
plex_type = None plex_type = None
# If using direct paths and starting playback from a widget # If using direct paths and starting playback from a widget
if not kodi_id and kodi_type and path: if not kodi_id and kodi_type and path:
kodi_id, _ = kodiid_from_filename(path, kodi_type) kodi_id, _ = kodidb.kodiid_from_filename(path, kodi_type)
if kodi_id: if kodi_id:
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type) plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
@ -323,7 +348,7 @@ class KodiMonitor(xbmc.Monitor):
json_item.get('type'), json_item.get('type'),
json_item.get('file')) json_item.get('file'))
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def PlayBackStart(self, data): def PlayBackStart(self, data):
""" """
Called whenever playback is started. Example data: Called whenever playback is started. Example data:
@ -430,7 +455,7 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug('Set the player state: %s', status) LOG.debug('Set the player state: %s', status)
@thread_methods @utils.thread_methods
class SpecialMonitor(Thread): class SpecialMonitor(Thread):
""" """
Detect the resume dialog for widgets. Detect the resume dialog for widgets.
@ -439,8 +464,8 @@ class SpecialMonitor(Thread):
def run(self): def run(self):
LOG.info("----====# Starting Special Monitor #====----") LOG.info("----====# Starting Special Monitor #====----")
# "Start from beginning", "Play from beginning" # "Start from beginning", "Play from beginning"
strings = (try_encode(xbmc.getLocalizedString(12021)), strings = (utils.try_encode(xbmc.getLocalizedString(12021)),
try_encode(xbmc.getLocalizedString(12023))) utils.try_encode(xbmc.getLocalizedString(12023)))
while not self.stopped(): while not self.stopped():
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'): if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings: if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
@ -461,7 +486,7 @@ class SpecialMonitor(Thread):
LOG.info("#====---- Special Monitor Stopped ----====#") LOG.info("#====---- Special Monitor Stopped ----====#")
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def _playback_cleanup(ended=False): def _playback_cleanup(ended=False):
""" """
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
@ -505,12 +530,12 @@ def _record_playstate(status, ended):
# Item not (yet) in Kodi library # Item not (yet) in Kodi library
LOG.debug('No playstate update due to Plex id not found: %s', status) LOG.debug('No playstate update due to Plex id not found: %s', status)
return return
totaltime = float(kodi_time_to_millis(status['totaltime'])) / 1000 totaltime = float(utils.kodi_time_to_millis(status['totaltime'])) / 1000
if ended: if ended:
progress = 0.99 progress = 0.99
time = v.IGNORE_SECONDS_AT_START + 1 time = v.IGNORE_SECONDS_AT_START + 1
else: else:
time = float(kodi_time_to_millis(status['time'])) / 1000 time = float(utils.kodi_time_to_millis(status['time'])) / 1000
try: try:
progress = time / totaltime progress = time / totaltime
except ZeroDivisionError: except ZeroDivisionError:
@ -518,7 +543,7 @@ def _record_playstate(status, ended):
LOG.debug('Playback progress %s (%s of %s seconds)', LOG.debug('Playback progress %s (%s of %s seconds)',
progress, time, totaltime) progress, time, totaltime)
playcount = status['playcount'] playcount = status['playcount']
last_played = unix_date_to_kodi(unix_timestamp()) last_played = utils.unix_date_to_kodi(utils.unix_timestamp())
if playcount is None: if playcount is None:
LOG.debug('playcount not found, looking it up in the Kodi DB') LOG.debug('playcount not found, looking it up in the Kodi DB')
with kodidb.GetKodiDB('video') as kodi_db: with kodidb.GetKodiDB('video') as kodi_db:

View File

@ -2,15 +2,14 @@
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
from Queue import Empty from Queue import Empty
import xbmc import xbmc
from utils import thread_methods, settings, language as lang, dialog from .. import utils
import plexdb_functions as plexdb from .. import plexdb_functions as plexdb
import itemtypes from .. import itemtypes
from artwork import ArtworkSyncMessage from .. import artwork
import variables as v from .. import variables as v
import state from .. import state
############################################################################### ###############################################################################
@ -19,10 +18,10 @@ LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD', @utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN', 'DB_SCAN',
'STOP_SYNC', 'STOP_SYNC',
'SUSPEND_SYNC']) 'SUSPEND_SYNC'])
class ThreadedProcessFanart(Thread): class ThreadedProcessFanart(Thread):
""" """
Threaded download of additional fanart in the background Threaded download of additional fanart in the background
@ -68,17 +67,17 @@ class ThreadedProcessFanart(Thread):
'Window.IsVisible(DialogAddonSettings.xml)'): 'Window.IsVisible(DialogAddonSettings.xml)'):
# Avoid saving '0' all the time # Avoid saving '0' all the time
set_zero = True set_zero = True
settings('fanarttv_lookups', value='0') utils.settings('fanarttv_lookups', value='0')
xbmc.sleep(200) xbmc.sleep(200)
continue continue
set_zero = False set_zero = False
if isinstance(item, ArtworkSyncMessage): if isinstance(item, artwork.ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS: if state.IMAGE_SYNC_NOTIFICATIONS:
dialog('notification', utils.dialog('notification',
heading=lang(29999), heading=utils.lang(29999),
message=item.message, message=item.message,
icon='{plex}', icon='{plex}',
sound=False) sound=False)
queue.task_done() queue.task_done()
continue continue
@ -96,6 +95,6 @@ class ThreadedProcessFanart(Thread):
if (counter > 20 and not xbmc.getCondVisibility( if (counter > 20 and not xbmc.getCondVisibility(
'Window.IsVisible(DialogAddonSettings.xml)')): 'Window.IsVisible(DialogAddonSettings.xml)')):
counter = 0 counter = 0
settings('fanarttv_lookups', value=str(queue.qsize())) utils.settings('fanarttv_lookups', value=str(queue.qsize()))
queue.task_done() queue.task_done()
LOG.debug("---===### Stopped FanartSync ###===---") LOG.debug("---===### Stopped FanartSync ###===---")

View File

@ -2,12 +2,11 @@
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
from Queue import Empty from Queue import Empty
from xbmc import sleep from xbmc import sleep
from utils import thread_methods, window from .. import utils
from PlexFunctions import GetPlexMetadata, GetAllPlexChildren from .. import plex_functions as PF
import sync_info from . import sync_info
############################################################################### ###############################################################################
@ -16,9 +15,9 @@ LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', @utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC', 'STOP_SYNC',
'SUSPEND_SYNC']) 'SUSPEND_SYNC'])
class ThreadedGetMetadata(Thread): class ThreadedGetMetadata(Thread):
""" """
Threaded download of Plex XML metadata for a certain library item. Threaded download of Plex XML metadata for a certain library item.
@ -79,7 +78,7 @@ class ThreadedGetMetadata(Thread):
sleep(20) sleep(20)
continue continue
# Download Metadata # Download Metadata
xml = GetPlexMetadata(item['plex_id']) xml = PF.GetPlexMetadata(item['plex_id'])
if xml is None: if xml is None:
# Did not receive a valid XML - skip that item for now # Did not receive a valid XML - skip that item for now
LOG.error("Could not get metadata for %s. Skipping that item " LOG.error("Could not get metadata for %s. Skipping that item "
@ -93,14 +92,14 @@ class ThreadedGetMetadata(Thread):
elif xml == 401: elif xml == 401:
LOG.error('HTTP 401 returned by PMS. Too much strain? ' LOG.error('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now') 'Cancelling sync for now')
window('plex_scancrashed', value='401') utils.window('plex_scancrashed', value='401')
# Kill remaining items in queue (for main thread to cont.) # Kill remaining items in queue (for main thread to cont.)
queue.task_done() queue.task_done()
break break
item['xml'] = xml item['xml'] = xml
if item.get('get_children') is True: if item.get('get_children') is True:
children_xml = GetAllPlexChildren(item['plex_id']) children_xml = PF.GetAllPlexChildren(item['plex_id'])
try: try:
children_xml[0].attrib children_xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):

View File

@ -2,12 +2,11 @@
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
from Queue import Empty from Queue import Empty
from xbmc import sleep from xbmc import sleep
from utils import thread_methods from .. import utils
import itemtypes from .. import itemtypes
import sync_info from . import sync_info
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger("PLEX." + __name__)
@ -15,9 +14,9 @@ LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', @utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC', 'STOP_SYNC',
'SUSPEND_SYNC']) 'SUSPEND_SYNC'])
class ThreadedProcessMetadata(Thread): class ThreadedProcessMetadata(Thread):
""" """
Not yet implemented for more than 1 thread - if ever. Only to be called by Not yet implemented for more than 1 thread - if ever. Only to be called by

View File

@ -1,11 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from logging import getLogger from logging import getLogger
from threading import Thread, Lock from threading import Thread, Lock
from xbmc import sleep from xbmc import sleep
from xbmcgui import DialogProgressBG from xbmcgui import DialogProgressBG
from utils import thread_methods, language as lang from .. import utils
############################################################################### ###############################################################################
@ -19,9 +18,9 @@ LOCK = Lock()
############################################################################### ###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', @utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC', 'STOP_SYNC',
'SUSPEND_SYNC']) 'SUSPEND_SYNC'])
class ThreadedShowSyncInfo(Thread): class ThreadedShowSyncInfo(Thread):
""" """
Threaded class to show the Kodi statusbar of the metadata download. Threaded class to show the Kodi statusbar of the metadata download.
@ -44,7 +43,10 @@ class ThreadedShowSyncInfo(Thread):
total = self.total total = self.total
dialog = DialogProgressBG('dialoglogProgressBG') dialog = DialogProgressBG('dialoglogProgressBG')
dialog.create("%s %s: %s %s" dialog.create("%s %s: %s %s"
% (lang(39714), self.item_type, str(total), lang(39715))) % (utils.lang(39714),
self.item_type,
unicode(total),
utils.lang(39715)))
total = 2 * total total = 2 * total
total_progress = 0 total_progress = 0
@ -55,15 +57,15 @@ class ThreadedShowSyncInfo(Thread):
view_name = PROCESSING_VIEW_NAME view_name = PROCESSING_VIEW_NAME
total_progress = get_progress + process_progress total_progress = get_progress + process_progress
try: try:
percentage = int(float(total_progress) / float(total)*100.0) percentage = int(float(total_progress) / float(total) * 100.0)
except ZeroDivisionError: except ZeroDivisionError:
percentage = 0 percentage = 0
dialog.update(percentage, dialog.update(percentage,
message="%s %s. %s %s: %s" message="%s %s. %s %s: %s"
% (get_progress, % (get_progress,
lang(39712), utils.lang(39712),
process_progress, process_progress,
lang(39713), utils.lang(39713),
view_name)) view_name))
# Sleep for x milliseconds # Sleep for x milliseconds
sleep(200) sleep(200)

View File

@ -5,34 +5,27 @@ from threading import Thread
import Queue import Queue
from random import shuffle from random import shuffle
import copy import copy
import xbmc import xbmc
from xbmcvfs import exists from xbmcvfs import exists
import utils from . import utils
from utils import window, settings, dialog, language as lang, try_decode, \ from .downloadutils import DownloadUtils as DU
try_encode from . import itemtypes
from downloadutils import DownloadUtils as DU from . import plexdb_functions as plexdb
import itemtypes from . import kodidb_functions as kodidb
import plexdb_functions as plexdb from . import artwork
import kodidb_functions as kodidb from . import videonodes
import artwork from . import plex_functions as PF
import videonodes from .plex_api import API
import variables as v from .library_sync import get_metadata, process_metadata, fanart, sync_info
from . import music
import PlexFunctions as PF from . import playlists
import PlexAPI from . import variables as v
from library_sync.get_metadata import ThreadedGetMetadata from . import state
from library_sync.process_metadata import ThreadedProcessMetadata
import library_sync.sync_info as sync_info
from library_sync.fanart import ThreadedProcessFanart
import music
import playlists
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.librarysync')
############################################################################### ###############################################################################
@ -47,10 +40,10 @@ class LibrarySync(Thread):
self.views = [] self.views = []
self.session_keys = {} self.session_keys = {}
self.fanartqueue = Queue.Queue() self.fanartqueue = Queue.Queue()
self.fanartthread = ThreadedProcessFanart(self.fanartqueue) self.fanartthread = fanart.ThreadedProcessFanart(self.fanartqueue)
# How long should we wait at least to process new/changed PMS items? # How long should we wait at least to process new/changed PMS items?
self.vnodes = videonodes.VideoNodes() self.vnodes = videonodes.VideoNodes()
self.install_sync_done = settings('SyncInstallRunDone') == 'true' self.install_sync_done = utils.settings('SyncInstallRunDone') == 'true'
# Show sync dialog even if user deactivated? # Show sync dialog even if user deactivated?
self.force_dialog = True self.force_dialog = True
# Need to be set accordingly later # Need to be set accordingly later
@ -91,16 +84,16 @@ class LibrarySync(Thread):
if state.SYNC_DIALOG is not True and self.force_dialog is not True: if state.SYNC_DIALOG is not True and self.force_dialog is not True:
return return
if icon == "plex": if icon == "plex":
dialog('notification', utils.dialog('notification',
heading='{plex}', heading='{plex}',
message=message, message=message,
icon='{plex}', icon='{plex}',
sound=False) sound=False)
elif icon == "error": elif icon == "error":
dialog('notification', utils.dialog('notification',
heading='{plex}', heading='{plex}',
message=message, message=message,
icon='{error}') icon='{error}')
@staticmethod @staticmethod
def sync_pms_time(): def sync_pms_time():
@ -200,7 +193,8 @@ class LibrarySync(Thread):
# Calculate time offset Kodi-PMS # Calculate time offset Kodi-PMS
state.KODI_PLEX_TIME_OFFSET = float(koditime) - float(plextime) state.KODI_PLEX_TIME_OFFSET = float(koditime) - float(plextime)
settings('kodiplextimeoffset', value=str(state.KODI_PLEX_TIME_OFFSET)) utils.settings('kodiplextimeoffset',
value=str(state.KODI_PLEX_TIME_OFFSET))
LOG.info("Time offset Koditime - Plextime in seconds: %s", LOG.info("Time offset Koditime - Plextime in seconds: %s",
str(state.KODI_PLEX_TIME_OFFSET)) str(state.KODI_PLEX_TIME_OFFSET))
return True return True
@ -288,15 +282,15 @@ class LibrarySync(Thread):
if state.ENABLE_MUSIC: if state.ENABLE_MUSIC:
xbmc.executebuiltin('UpdateLibrary(music)') xbmc.executebuiltin('UpdateLibrary(music)')
if window('plex_scancrashed') == 'true': if utils.window('plex_scancrashed') == 'true':
# Show warning if itemtypes.py crashed at some point # Show warning if itemtypes.py crashed at some point
dialog('ok', heading='{plex}', line1=lang(39408)) utils.dialog('ok', heading='{plex}', line1=utils.lang(39408))
window('plex_scancrashed', clear=True) utils.window('plex_scancrashed', clear=True)
elif window('plex_scancrashed') == '401': elif utils.window('plex_scancrashed') == '401':
window('plex_scancrashed', clear=True) utils.window('plex_scancrashed', clear=True)
if state.PMS_STATUS not in ('401', 'Auth'): if state.PMS_STATUS not in ('401', 'Auth'):
# Plex server had too much and returned ERROR # Plex server had too much and returned ERROR
dialog('ok', heading='{plex}', line1=lang(39409)) utils.dialog('ok', heading='{plex}', line1=utils.lang(39409))
return True return True
def _process_view(self, folder_item, kodi_db, plex_db, totalnodes): def _process_view(self, folder_item, kodi_db, plex_db, totalnodes):
@ -495,7 +489,7 @@ class LibrarySync(Thread):
# totalnodes += 1 # totalnodes += 1
# Save total # Save total
window('Plex.nodes.total', str(totalnodes)) utils.window('Plex.nodes.total', str(totalnodes))
# Get rid of old items (view has been deleted on Plex side) # Get rid of old items (view has been deleted on Plex side)
if self.old_views: if self.old_views:
@ -524,11 +518,11 @@ class LibrarySync(Thread):
elif item['kodi_type'] in v.KODI_AUDIOTYPES: elif item['kodi_type'] in v.KODI_AUDIOTYPES:
delete_music.append(item) delete_music.append(item)
dialog('notification', utils.dialog('notification',
heading='{plex}', heading='{plex}',
message=lang(30052), message=utils.lang(30052),
icon='{plex}', icon='{plex}',
sound=False) sound=False)
for item in delete_movies: for item in delete_movies:
with itemtypes.Movies() as movie_db: with itemtypes.Movies() as movie_db:
movie_db.remove(item['plex_id']) movie_db.remove(item['plex_id'])
@ -638,8 +632,8 @@ class LibrarySync(Thread):
def process_updatelist(self, item_class): def process_updatelist(self, item_class):
""" """
Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes them Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes
by then calling item_classs.<item_class>() them by then calling item_classs.<item_class>()
Input: Input:
item_class: 'Movies', 'TVShows', ... item_class: 'Movies', 'TVShows', ...
@ -665,13 +659,15 @@ class LibrarySync(Thread):
# Spawn GetMetadata threads for downloading # Spawn GetMetadata threads for downloading
threads = [] threads = []
for _ in range(min(state.SYNC_THREAD_NUMBER, item_number)): for _ in range(min(state.SYNC_THREAD_NUMBER, item_number)):
thread = ThreadedGetMetadata(download_queue, process_queue) thread = get_metadata.ThreadedGetMetadata(download_queue,
process_queue)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
LOG.debug("%s download threads spawned", len(threads)) LOG.debug("%s download threads spawned", len(threads))
# Spawn one more thread to process Metadata, once downloaded # Spawn one more thread to process Metadata, once downloaded
thread = ThreadedProcessMetadata(process_queue, item_class) thread = process_metadata.ThreadedProcessMetadata(process_queue,
item_class)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
@ -702,7 +698,7 @@ class LibrarySync(Thread):
except: except:
pass pass
LOG.debug("Sync threads finished") LOG.debug("Sync threads finished")
if (settings('FanartTV') == 'true' and if (utils.settings('FanartTV') == 'true' and
item_class in ('Movies', 'TVShows')): item_class in ('Movies', 'TVShows')):
for item in self.updatelist: for item in self.updatelist:
if item['plex_type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW): if item['plex_type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
@ -1099,7 +1095,7 @@ class LibrarySync(Thread):
continue continue
else: else:
successful = self.process_newitems(item) successful = self.process_newitems(item)
if successful and settings('FanartTV') == 'true': if successful and utils.settings('FanartTV') == 'true':
if item['type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW): if item['type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
self.fanartqueue.put({ self.fanartqueue.put({
'plex_id': item['ratingKey'], 'plex_id': item['ratingKey'],
@ -1294,7 +1290,7 @@ class LibrarySync(Thread):
if kodi_info is None: if kodi_info is None:
# Item not (yet) in Kodi library # Item not (yet) in Kodi library
continue continue
if settings('plex_serverowned') == 'false': if utils.settings('plex_serverowned') == 'false':
# Not our PMS, we are not authorized to get the sessions # Not our PMS, we are not authorized to get the sessions
# On the bright side, it must be us playing :-) # On the bright side, it must be us playing :-)
self.session_keys[session_key] = {} self.session_keys[session_key] = {}
@ -1312,7 +1308,7 @@ class LibrarySync(Thread):
self.session_keys[session_key]['file_id'] = kodi_info[1] self.session_keys[session_key]['file_id'] = kodi_info[1]
self.session_keys[session_key]['kodi_type'] = kodi_info[4] self.session_keys[session_key]['kodi_type'] = kodi_info[4]
session = self.session_keys[session_key] session = self.session_keys[session_key]
if settings('plex_serverowned') != 'false': if utils.settings('plex_serverowned') != 'false':
# Identify the user - same one as signed on with PKC? Skip # Identify the user - same one as signed on with PKC? Skip
# update if neither session's username nor userid match # update if neither session's username nor userid match
# (Owner sometime's returns id '1', not always) # (Owner sometime's returns id '1', not always)
@ -1339,7 +1335,7 @@ class LibrarySync(Thread):
LOG.error('Could not get up-to-date xml for item %s', LOG.error('Could not get up-to-date xml for item %s',
plex_id) plex_id)
continue continue
api = PlexAPI.API(xml[0]) api = API(xml[0])
userdata = api.userdata() userdata = api.userdata()
session['duration'] = userdata['Runtime'] session['duration'] = userdata['Runtime']
session['viewCount'] = userdata['PlayCount'] session['viewCount'] = userdata['PlayCount']
@ -1378,7 +1374,8 @@ class LibrarySync(Thread):
resume, resume,
session['duration'], session['duration'],
session['file_id'], session['file_id'],
utils.unix_date_to_kodi(utils.unix_timestamp()), utils.unix_date_to_kodi(
utils.unix_timestamp()),
plex_type) plex_type)
def sync_fanart(self, missing_only=True, refresh=False): def sync_fanart(self, missing_only=True, refresh=False):
@ -1389,7 +1386,7 @@ class LibrarySync(Thread):
missing_only=True False will start look-up for EVERY item missing_only=True False will start look-up for EVERY item
refresh=False True will force refresh all external fanart refresh=False True will force refresh all external fanart
""" """
if settings('FanartTV') == 'false': if utils.settings('FanartTV') == 'false':
return return
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
if missing_only: if missing_only:
@ -1407,7 +1404,8 @@ class LibrarySync(Thread):
# Shuffle the list to not always start out identically # Shuffle the list to not always start out identically
shuffle(items) shuffle(items)
# Checking FanartTV for %s items # Checking FanartTV for %s items
self.fanartqueue.put(artwork.ArtworkSyncMessage(lang(30018) % len(items))) self.fanartqueue.put(artwork.ArtworkSyncMessage(
utils.lang(30018) % len(items)))
for i, item in enumerate(items): for i, item in enumerate(items):
self.fanartqueue.put({ self.fanartqueue.put({
'plex_id': item['plex_id'], 'plex_id': item['plex_id'],
@ -1415,7 +1413,7 @@ class LibrarySync(Thread):
'refresh': refresh 'refresh': refresh
}) })
# FanartTV lookup completed # FanartTV lookup completed
self.fanartqueue.put(artwork.ArtworkSyncMessage(lang(30019))) self.fanartqueue.put(artwork.ArtworkSyncMessage(utils.lang(30019)))
def triage_lib_scans(self): def triage_lib_scans(self):
""" """
@ -1424,27 +1422,27 @@ class LibrarySync(Thread):
""" """
if state.RUN_LIB_SCAN in ("full", "repair"): if state.RUN_LIB_SCAN in ("full", "repair"):
LOG.info('Full library scan requested, starting') LOG.info('Full library scan requested, starting')
window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
state.DB_SCAN = True state.DB_SCAN = True
success = self.maintain_views() success = self.maintain_views()
if success and state.RUN_LIB_SCAN == "full": if success and state.RUN_LIB_SCAN == "full":
success = self.full_sync() success = self.full_sync()
elif success: elif success:
success = self.full_sync(repair=True) success = self.full_sync(repair=True)
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
if success: if success:
# Full library sync finished # Full library sync finished
self.show_kodi_note(lang(39407)) self.show_kodi_note(utils.lang(39407))
elif not self.suspend_item_sync(): elif not self.suspend_item_sync():
self.force_dialog = True self.force_dialog = True
# ERROR in library sync # ERROR in library sync
self.show_kodi_note(lang(39410), icon='error') self.show_kodi_note(utils.lang(39410), icon='error')
self.force_dialog = False self.force_dialog = False
# Reset views was requested from somewhere else # Reset views was requested from somewhere else
elif state.RUN_LIB_SCAN == "views": elif state.RUN_LIB_SCAN == "views":
LOG.info('Refresh playlist and nodes requested, starting') LOG.info('Refresh playlist and nodes requested, starting')
window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
state.DB_SCAN = True state.DB_SCAN = True
# First remove playlists # First remove playlists
utils.delete_playlists() utils.delete_playlists()
@ -1455,28 +1453,28 @@ class LibrarySync(Thread):
# Ran successfully # Ran successfully
LOG.info("Refresh playlists/nodes completed") LOG.info("Refresh playlists/nodes completed")
# "Plex playlists/nodes refreshed" # "Plex playlists/nodes refreshed"
self.show_kodi_note(lang(39405)) self.show_kodi_note(utils.lang(39405))
else: else:
# Failed # Failed
LOG.error("Refresh playlists/nodes failed") LOG.error("Refresh playlists/nodes failed")
# "Plex playlists/nodes refresh failed" # "Plex playlists/nodes refresh failed"
self.show_kodi_note(lang(39406), icon="error") self.show_kodi_note(utils.lang(39406), icon="error")
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
elif state.RUN_LIB_SCAN == 'fanart': elif state.RUN_LIB_SCAN == 'fanart':
# Only look for missing fanart (No) # Only look for missing fanart (No)
# or refresh all fanart (Yes) # or refresh all fanart (Yes)
refresh = dialog('yesno', refresh = utils.dialog('yesno',
heading='{plex}', heading='{plex}',
line1=lang(39223), line1=utils.lang(39223),
nolabel=lang(39224), nolabel=utils.lang(39224),
yeslabel=lang(39225)) yeslabel=utils.lang(39225))
self.sync_fanart(missing_only=not refresh, refresh=refresh) self.sync_fanart(missing_only=not refresh, refresh=refresh)
elif state.RUN_LIB_SCAN == 'textures': elif state.RUN_LIB_SCAN == 'textures':
state.DB_SCAN = True state.DB_SCAN = True
window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
artwork.Artwork().fullTextureCacheSync() artwork.Artwork().fullTextureCacheSync()
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
else: else:
raise NotImplementedError('Library scan not defined: %s' raise NotImplementedError('Library scan not defined: %s'
@ -1489,12 +1487,12 @@ class LibrarySync(Thread):
self._run_internal() self._run_internal()
except Exception as e: except Exception as e:
state.DB_SCAN = False state.DB_SCAN = False
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
LOG.error('LibrarySync thread crashed. Error message: %s', e) LOG.error('LibrarySync thread crashed. Error message: %s', e)
import traceback import traceback
LOG.error("Traceback:\n%s", traceback.format_exc()) LOG.error("Traceback:\n%s", traceback.format_exc())
# Library sync thread has crashed # Library sync thread has crashed
dialog('ok', heading='{plex}', line1=lang(39400)) utils.dialog('ok', heading='{plex}', line1=utils.lang(39400))
raise raise
def _run_internal(self): def _run_internal(self):
@ -1504,21 +1502,21 @@ class LibrarySync(Thread):
last_sync = 0 last_sync = 0
last_processing = 0 last_processing = 0
last_time_sync = 0 last_time_sync = 0
one_day_in_seconds = 60*60*24 one_day_in_seconds = 60 * 60 * 24
# Link to Websocket queue # Link to Websocket queue
queue = state.WEBSOCKET_QUEUE queue = state.WEBSOCKET_QUEUE
if (not exists(try_encode(v.DB_VIDEO_PATH)) or if (not exists(utils.try_encode(v.DB_VIDEO_PATH)) or
not exists(try_encode(v.DB_TEXTURE_PATH)) or not exists(utils.try_encode(v.DB_TEXTURE_PATH)) or
(state.ENABLE_MUSIC and (state.ENABLE_MUSIC and
not exists(try_encode(v.DB_MUSIC_PATH)))): not exists(utils.try_encode(v.DB_MUSIC_PATH)))):
# Database does not exists # Database does not exists
LOG.error("The current Kodi version is incompatible " LOG.error("The current Kodi version is incompatible "
"to know which Kodi versions are supported.") "to know which Kodi versions are supported.")
LOG.error('Current Kodi version: %s', try_decode( LOG.error('Current Kodi version: %s', utils.try_decode(
xbmc.getInfoLabel('System.BuildVersion'))) xbmc.getInfoLabel('System.BuildVersion')))
# "Current Kodi version is unsupported, cancel lib sync" # "Current Kodi version is unsupported, cancel lib sync"
dialog('ok', heading='{plex}', line1=lang(39403)) utils.dialog('ok', heading='{plex}', line1=utils.lang(39403))
return return
# Do some initializing # Do some initializing
@ -1526,14 +1524,14 @@ class LibrarySync(Thread):
self.initialize_plex_db() self.initialize_plex_db()
# Run start up sync # Run start up sync
state.DB_SCAN = True state.DB_SCAN = True
window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
LOG.info("Db version: %s", settings('dbCreatedWithVersion')) LOG.info("Db version: %s", utils.settings('dbCreatedWithVersion'))
LOG.info('Refreshing video nodes and playlists now') LOG.info('Refreshing video nodes and playlists now')
# Setup the paths for addon-paths (even when using direct paths) # Setup the paths for addon-paths (even when using direct paths)
with kodidb.GetKodiDB('video') as kodi_db: with kodidb.GetKodiDB('video') as kodi_db:
kodi_db.setup_path_table() kodi_db.setup_path_table()
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
playlist_monitor = None playlist_monitor = None
@ -1549,7 +1547,7 @@ class LibrarySync(Thread):
if not self.install_sync_done: if not self.install_sync_done:
# Very first sync upon installation or reset of Kodi DB # Very first sync upon installation or reset of Kodi DB
state.DB_SCAN = True state.DB_SCAN = True
window('plex_dbScan', value='true') utils.window('plex_dbScan', value='true')
# Initialize time offset Kodi - PMS # Initialize time offset Kodi - PMS
self.sync_pms_time() self.sync_pms_time()
last_time_sync = utils.unix_timestamp() last_time_sync = utils.unix_timestamp()
@ -1562,9 +1560,9 @@ class LibrarySync(Thread):
LOG.error('Initial maintain_views not successful') LOG.error('Initial maintain_views not successful')
elif self.full_sync(): elif self.full_sync():
LOG.info('Initial start-up full sync successful') LOG.info('Initial start-up full sync successful')
settings('SyncInstallRunDone', value='true') utils.settings('SyncInstallRunDone', value='true')
self.install_sync_done = True self.install_sync_done = True
settings('dbCreatedWithVersion', v.ADDON_VERSION) utils.settings('dbCreatedWithVersion', v.ADDON_VERSION)
self.force_dialog = False self.force_dialog = False
initial_sync_done = True initial_sync_done = True
kodi_db_version_checked = True kodi_db_version_checked = True
@ -1576,27 +1574,29 @@ class LibrarySync(Thread):
else: else:
LOG.error('Initial start-up full sync unsuccessful') LOG.error('Initial start-up full sync unsuccessful')
xbmc.executebuiltin('InhibitIdleShutdown(false)') xbmc.executebuiltin('InhibitIdleShutdown(false)')
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
elif not kodi_db_version_checked: elif not kodi_db_version_checked:
# Install sync was already done, don't force-show dialogs # Install sync was already done, don't force-show dialogs
self.force_dialog = False self.force_dialog = False
# Verify the validity of the database # Verify the validity of the database
current_version = settings('dbCreatedWithVersion') current_version = utils.settings('dbCreatedWithVersion')
if not utils.compare_version(current_version, v.MIN_DB_VERSION): if not utils.compare_version(current_version,
v.MIN_DB_VERSION):
LOG.warn("Db version out of date: %s minimum version " LOG.warn("Db version out of date: %s minimum version "
"required: %s", current_version, v.MIN_DB_VERSION) "required: %s", current_version, v.MIN_DB_VERSION)
# DB out of date. Proceed to recreate? # DB out of date. Proceed to recreate?
resp = dialog('yesno', resp = utils.dialog('yesno',
heading=lang(29999), heading=utils.lang(29999),
line1=lang(39401)) line1=utils.lang(39401))
if not resp: if not resp:
LOG.warn("Db version out of date! USER IGNORED!") LOG.warn("Db version out of date! USER IGNORED!")
# PKC may not work correctly until reset # PKC may not work correctly until reset
dialog('ok', utils.dialog('ok',
heading='{plex}', heading='{plex}',
line1=lang(29999) + lang(39402)) line1='%s%s' % (utils.lang(29999),
utils.lang(39402)))
else: else:
utils.reset(ask_user=False) utils.reset(ask_user=False)
break break
@ -1606,7 +1606,7 @@ class LibrarySync(Thread):
# First sync upon PKC restart. Skipped if very first sync upon # First sync upon PKC restart. Skipped if very first sync upon
# PKC installation has been completed # PKC installation has been completed
state.DB_SCAN = True state.DB_SCAN = True
window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
LOG.info('Doing initial sync on Kodi startup') LOG.info('Doing initial sync on Kodi startup')
if state.SUSPEND_SYNC: if state.SUSPEND_SYNC:
LOG.warning('Forcing startup sync even if Kodi is playing') LOG.warning('Forcing startup sync even if Kodi is playing')
@ -1627,7 +1627,7 @@ class LibrarySync(Thread):
playlist_monitor = playlists.kodi_playlist_monitor() playlist_monitor = playlists.kodi_playlist_monitor()
else: else:
LOG.info('Startup sync has not yet been successful') LOG.info('Startup sync has not yet been successful')
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
# Currently no db scan, so we can start a new scan # Currently no db scan, so we can start a new scan
@ -1646,23 +1646,23 @@ class LibrarySync(Thread):
not self.suspend_item_sync()): not self.suspend_item_sync()):
LOG.info('Doing scheduled full library scan') LOG.info('Doing scheduled full library scan')
state.DB_SCAN = True state.DB_SCAN = True
window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
success = self.maintain_views() success = self.maintain_views()
if success: if success:
success = self.full_sync() success = self.full_sync()
if not success and not self.suspend_item_sync(): if not success and not self.suspend_item_sync():
LOG.error('Could not finish scheduled full sync') LOG.error('Could not finish scheduled full sync')
self.force_dialog = True self.force_dialog = True
self.show_kodi_note(lang(39410), self.show_kodi_note(utils.lang(39410),
icon='error') icon='error')
self.force_dialog = False self.force_dialog = False
elif success: elif success:
last_sync = now last_sync = now
# Full library sync finished successfully # Full library sync finished successfully
self.show_kodi_note(lang(39407)) self.show_kodi_note(utils.lang(39407))
else: else:
LOG.info('Full sync interrupted') LOG.info('Full sync interrupted')
window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False state.DB_SCAN = False
elif now - last_time_sync > one_day_in_seconds: elif now - last_time_sync > one_day_in_seconds:
LOG.info('Starting daily time sync') LOG.info('Starting daily time sync')

View File

@ -1,29 +1,31 @@
from logging import getLogger from logging import getLogger
import variables as v
from utils import compare_version, settings from . import variables as v
from . import utils
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger('PLEX.migration')
def check_migration(): def check_migration():
log.info('Checking whether we need to migrate something') LOG.info('Checking whether we need to migrate something')
last_migration = settings('last_migrated_PKC_version') last_migration = utils.settings('last_migrated_PKC_version')
if last_migration == v.ADDON_VERSION: if last_migration == v.ADDON_VERSION:
log.info('Already migrated to PKC version %s' % v.ADDON_VERSION) LOG.info('Already migrated to PKC version %s' % v.ADDON_VERSION)
# Ensure later migration if user downgraded PKC! # Ensure later migration if user downgraded PKC!
settings('last_migrated_PKC_version', value=v.ADDON_VERSION) utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
return return
if not compare_version(last_migration, '1.8.2'): if not utils.compare_version(last_migration, '1.8.2'):
log.info('Migrating to version 1.8.1') LOG.info('Migrating to version 1.8.1')
# Set the new PKC theMovieDB key # Set the new PKC theMovieDB key
settings('themoviedbAPIKey', value='19c90103adb9e98f2172c6a6a3d85dc4') utils.settings('themoviedbAPIKey',
value='19c90103adb9e98f2172c6a6a3d85dc4')
if not compare_version(last_migration, '2.0.25'): if not utils.compare_version(last_migration, '2.0.25'):
log.info('Migrating to version 2.0.24') LOG.info('Migrating to version 2.0.24')
# Need to re-connect with PMS to pick up on plex.direct URIs # Need to re-connect with PMS to pick up on plex.direct URIs
settings('ipaddress', value='') utils.settings('ipaddress', value='')
settings('port', value='') utils.settings('port', value='')
settings('last_migrated_PKC_version', value=v.ADDON_VERSION) utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)

View File

@ -3,12 +3,12 @@ from logging import getLogger
from re import compile as re_compile from re import compile as re_compile
from xml.etree.ElementTree import ParseError from xml.etree.ElementTree import ParseError
from utils import XmlKodiSetting, reboot_kodi, language as lang from . import utils
from PlexAPI import API from .plex_api import API
import variables as v from . import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.music')
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''') REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
############################################################################### ###############################################################################
@ -37,9 +37,10 @@ def excludefromscan_music_folders(xml):
omit_check=True) omit_check=True)
paths.append(__turn_to_regex(path)) paths.append(__turn_to_regex(path))
try: try:
with XmlKodiSetting('advancedsettings.xml', with utils.XmlKodiSetting(
force_create=True, 'advancedsettings.xml',
top_element='advancedsettings') as xml_file: force_create=True,
top_element='advancedsettings') as xml_file:
parent = xml_file.set_setting(['audio', 'excludefromscan']) parent = xml_file.set_setting(['audio', 'excludefromscan'])
for path in paths: for path in paths:
for element in parent: for element in parent:
@ -71,7 +72,7 @@ def excludefromscan_music_folders(xml):
if reboot is True: if reboot is True:
# 'New Plex music library detected. Sorry, but we need to # 'New Plex music library detected. Sorry, but we need to
# restart Kodi now due to the changes made.' # restart Kodi now due to the changes made.'
reboot_kodi(lang(39711)) utils.reboot_kodi(utils.lang(39711))
def __turn_to_regex(path): def __turn_to_regex(path):

View File

@ -1,13 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from cPickle import dumps, loads from cPickle import dumps, loads
from xbmcgui import Window from xbmcgui import Window
from xbmc import log, LOGDEBUG from xbmc import log, LOGDEBUG
############################################################################### ###############################################################################
WINDOW = Window(10000) WINDOW = Window(10000)
PREFIX = 'PLEX.%s: ' % __name__ PREFIX = 'PLEX.pickler: '
############################################################################### ###############################################################################

View File

@ -4,27 +4,25 @@ Used to kick off Kodi playback
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
from os.path import join from os.path import join
from xbmc import Player, sleep from xbmc import Player, sleep
from PlexAPI import API from .plex_api import API
from PlexFunctions import GetPlexMetadata, init_plex_playqueue from . import plex_functions as PF
from downloadutils import DownloadUtils as DU from . import utils
import plexdb_functions as plexdb from .downloadutils import DownloadUtils as DU
import kodidb_functions as kodidb from . import plexdb_functions as plexdb
import playlist_func as PL from . import kodidb_functions as kodidb
import playqueue as PQ from . import playlist_func as PL
from playutils import PlayUtils from . import playqueue as PQ
from PKC_listitem import PKC_ListItem from . import json_rpc as js
from pickler import pickle_me, Playback_Successful from . import pickler
import json_rpc as js from .playutils import PlayUtils
from utils import settings, dialog, language as lang, try_encode from .pkc_listitem import PKCListItem
from plexbmchelper.subscribers import LOCKER from . import variables as v
import variables as v from . import state
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.playback')
# Do we need to return ultimately with a setResolvedUrl? # Do we need to return ultimately with a setResolvedUrl?
RESOLVE = True RESOLVE = True
# We're "failing" playback with a video of 0 length # We're "failing" playback with a video of 0 length
@ -32,13 +30,13 @@ NULL_VIDEO = join(v.ADDON_FOLDER, 'addons', v.ADDON_ID, 'empty_video.mp4')
############################################################################### ###############################################################################
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True): def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
""" """
Hit this function for addon path playback, Plex trailers, etc. Hit this function for addon path playback, Plex trailers, etc.
Will setup playback first, then on second call complete playback. Will setup playback first, then on second call complete playback.
Will set Playback_Successful() with potentially a PKC_ListItem() attached Will set Playback_Successful() with potentially a PKCListItem() attached
(to be consumed by setResolvedURL in default.py) (to be consumed by setResolvedURL in default.py)
If trailers or additional (movie-)parts are added, default.py is released If trailers or additional (movie-)parts are added, default.py is released
@ -50,14 +48,14 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
service.py Python instance service.py Python instance
""" """
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, ' LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, '
'resolve %s,', plex_id, plex_type, path, resolve) 'resolve %s', plex_id, plex_type, path, resolve)
global RESOLVE global RESOLVE
# If started via Kodi context menu, we never resolve # If started via Kodi context menu, we never resolve
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False
if not state.AUTHENTICATED: if not state.AUTHENTICATED:
LOG.error('Not yet authenticated for PMS, abort starting playback') LOG.error('Not yet authenticated for PMS, abort starting playback')
# "Unauthorized for PMS" # "Unauthorized for PMS"
dialog('notification', lang(29999), lang(30017)) utils.dialog('notification', utils.lang(29999), utils.lang(30017))
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
@ -74,7 +72,10 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
except KeyError: except KeyError:
LOG.error('Still no position - abort') LOG.error('Still no position - abort')
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
# HACK to detect playback of playlists for add-on paths # HACK to detect playback of playlists for add-on paths
@ -97,9 +98,15 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
try: try:
item = playqueue.items[pos] item = playqueue.items[pos]
except IndexError: except IndexError:
LOG.debug('PKC playqueue yet empty, need to initialize playback')
initiate = True initiate = True
else: else:
initiate = True if item.plex_id != plex_id else False if item.plex_id != plex_id:
LOG.debug('Received new plex_id %s, expected %s. Init playback',
plex_id, item.plex_id)
initiate = True
else:
initiate = False
if initiate: if initiate:
_playback_init(plex_id, plex_type, playqueue, pos) _playback_init(plex_id, plex_type, playqueue, pos)
else: else:
@ -124,13 +131,16 @@ def _playlist_playback(plex_id, plex_type):
for the next item in line :-) for the next item in line :-)
(by the way: trying to get active Kodi player id will return []) (by the way: trying to get active Kodi player id will return [])
""" """
xml = GetPlexMetadata(plex_id) xml = PF.GetPlexMetadata(plex_id)
try: try:
xml[0].attrib xml[0].attrib
except (IndexError, TypeError, AttributeError): except (IndexError, TypeError, AttributeError):
LOG.error('Could not get a PMS xml for plex id %s', plex_id) LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
# Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback # Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback
@ -150,13 +160,16 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
Playback setup if Kodi starts playing an item for the first time. Playback setup if Kodi starts playing an item for the first time.
""" """
LOG.info('Initializing PKC playback') LOG.info('Initializing PKC playback')
xml = GetPlexMetadata(plex_id) xml = PF.GetPlexMetadata(plex_id)
try: try:
xml[0].attrib xml[0].attrib
except (IndexError, TypeError, AttributeError): except (IndexError, TypeError, AttributeError):
LOG.error('Could not get a PMS xml for plex id %s', plex_id) LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
if playqueue.kodi_pl.size() > 1: if playqueue.kodi_pl.size() > 1:
@ -179,10 +192,12 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
api = API(xml[0]) api = API(xml[0])
trailers = False trailers = False
if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and
settings('enableCinema') == "true"): utils.settings('enableCinema') == "true"):
if settings('askCinema') == "true": if utils.settings('askCinema') == "true":
# "Play trailers?" # "Play trailers?"
trailers = dialog('yesno', lang(29999), lang(33016)) trailers = utils.dialog('yesno',
utils.lang(29999),
utils.lang(33016))
trailers = True if trailers else False trailers = True if trailers else False
else: else:
trailers = True trailers = True
@ -198,15 +213,18 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
playqueue.clear() playqueue.clear()
if plex_type != v.PLEX_TYPE_CLIP: if plex_type != v.PLEX_TYPE_CLIP:
# Post to the PMS to create a playqueue - in any case due to Companion # Post to the PMS to create a playqueue - in any case due to Companion
xml = init_plex_playqueue(plex_id, xml = PF.init_plex_playqueue(plex_id,
xml.attrib.get('librarySectionUUID'), xml.attrib.get('librarySectionUUID'),
mediatype=plex_type, mediatype=plex_type,
trailers=trailers) trailers=trailers)
if xml is None: if xml is None:
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s', LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
plex_id, xml.attrib.get('librarySectionUUID')) plex_id, xml.attrib.get('librarySectionUUID'))
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
# Do NOT use _ensure_resolve() because we resolved above already # Do NOT use _ensure_resolve() because we resolved above already
state.CONTEXT_MENU_PLAY = False state.CONTEXT_MENU_PLAY = False
state.FORCE_TRANSCODE = False state.FORCE_TRANSCODE = False
@ -253,9 +271,13 @@ def _ensure_resolve(abort=False):
# Because playback won't start with context menu play # Because playback won't start with context menu play
state.PKC_CAUSED_STOP = True state.PKC_CAUSED_STOP = True
state.PKC_CAUSED_STOP_DONE = False state.PKC_CAUSED_STOP_DONE = False
result = Playback_Successful() if not abort:
result.listitem = PKC_ListItem(path=NULL_VIDEO) result = pickler.Playback_Successful()
pickle_me(result) result.listitem = PKCListItem(path=NULL_VIDEO)
pickler.pickle_me(result)
else:
# Shows PKC error message
pickler.pickle_me(None)
if abort: if abort:
# Reset some playback variables # Reset some playback variables
state.CONTEXT_MENU_PLAY = False state.CONTEXT_MENU_PLAY = False
@ -308,7 +330,7 @@ def _prep_playlist_stack(xml):
# Need to redirect again to PKC to conclude playback # Need to redirect again to PKC to conclude playback
path = api.path() path = api.path()
listitem = api.create_listitem() listitem = api.create_listitem()
listitem.setPath(try_encode(path)) listitem.setPath(utils.try_encode(path))
else: else:
# Will add directly via the Kodi DB # Will add directly via the Kodi DB
path = None path = None
@ -373,8 +395,8 @@ 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 = Playback_Successful() result = pickler.Playback_Successful()
listitem = PKC_ListItem() 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
@ -385,7 +407,7 @@ def _conclude_playback(playqueue, pos):
playurl = playutils.getPlayUrl() playurl = playutils.getPlayUrl()
else: else:
playurl = item.file playurl = item.file
listitem.setPath(try_encode(playurl)) listitem.setPath(utils.try_encode(playurl))
if item.playmethod == 'DirectStream': if item.playmethod == 'DirectStream':
listitem.setSubtitles(api.cache_external_subs()) listitem.setSubtitles(api.cache_external_subs())
elif item.playmethod == 'Transcode': elif item.playmethod == 'Transcode':
@ -405,7 +427,7 @@ def _conclude_playback(playqueue, pos):
listitem.setProperty('resumetime', str(item.offset)) listitem.setProperty('resumetime', str(item.offset))
# Reset the resumable flag # Reset the resumable flag
result.listitem = listitem result.listitem = listitem
pickle_me(result) pickler.pickle_me(result)
LOG.info('Done concluding playback') LOG.info('Done concluding playback')
@ -424,7 +446,7 @@ def process_indirect(key, offset, resolve=True):
key, offset, resolve) key, offset, resolve)
global RESOLVE global RESOLVE
RESOLVE = resolve RESOLVE = resolve
result = Playback_Successful() 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'):
@ -441,7 +463,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 = PKC_ListItem() listitem = 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()])
@ -462,14 +484,14 @@ def process_indirect(key, offset, resolve=True):
return return
playurl = xml[0].attrib['key'] playurl = xml[0].attrib['key']
item.file = playurl item.file = playurl
listitem.setPath(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 result.listitem = listitem
pickle_me(result) pickler.pickle_me(result)
else: else:
thread = Thread(target=Player().play, thread = Thread(target=Player().play,
args={'item': try_encode(playurl), args={'item': utils.try_encode(playurl),
'listitem': listitem}) 'listitem': listitem})
thread.setDaemon(True) thread.setDaemon(True)
LOG.info('Done initializing PKC playback, starting Kodi player') LOG.info('Done initializing PKC playback, starting Kodi player')

View File

@ -4,16 +4,16 @@ from logging import getLogger
from threading import Thread from threading import Thread
from urlparse import parse_qsl from urlparse import parse_qsl
import playback from . import playback
from context_entry import ContextMenu from . import context_entry
import state from . import json_rpc as js
import json_rpc as js from . import pickler
from pickler import pickle_me, Playback_Successful from . import kodidb_functions as kodidb
import kodidb_functions as kodidb from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.playback_starter')
############################################################################### ###############################################################################
@ -29,7 +29,7 @@ class PlaybackStarter(Thread):
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')
pickle_me(Playback_Successful()) pickler.pickle_me(pickler.Playback_Successful())
return return
params = dict(parse_qsl(params)) params = dict(parse_qsl(params))
mode = params.get('mode') mode = params.get('mode')
@ -54,10 +54,10 @@ class PlaybackStarter(Thread):
else: else:
LOG.error('Could not find tv show id for %s', item) LOG.error('Could not find tv show id for %s', item)
if resolve: if resolve:
pickle_me(Playback_Successful()) pickler.pickle_me(pickler.Playback_Successful())
elif mode == 'context_menu': elif mode == 'context_menu':
ContextMenu(kodi_id=params.get('kodi_id'), context_entry.ContextMenu(kodi_id=params.get('kodi_id'),
kodi_type=params.get('kodi_type')) kodi_type=params.get('kodi_type'))
def run(self): def run(self):
queue = state.COMMAND_PIPELINE_QUEUE queue = state.COMMAND_PIPELINE_QUEUE

View File

@ -8,18 +8,18 @@ import urllib
from urlparse import parse_qsl, urlsplit from urlparse import parse_qsl, urlsplit
from re import compile as re_compile from re import compile as re_compile
import plexdb_functions as plexdb from .plex_api import API
from downloadutils import DownloadUtils as DU from . import plex_functions as PF
from utils import try_decode, try_encode from . import plexdb_functions as plexdb
from PlexAPI import API from . import kodidb_functions as kodidb
from PlexFunctions import GetPlexMetadata from .downloadutils import DownloadUtils as DU
from kodidb_functions import kodiid_from_filename from . import utils
import json_rpc as js from . import json_rpc as js
import variables as v from . import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.playlist_func')
REGEX = re_compile(r'''metadata%2F(\d+)''') REGEX = re_compile(r'''metadata%2F(\d+)''')
############################################################################### ###############################################################################
@ -51,13 +51,14 @@ class PlaylistObjectBaseclase(object):
continue continue
if isinstance(getattr(self, key), str): if isinstance(getattr(self, key), str):
answ += '\'%s\': \'%s\', ' % (key, answ += '\'%s\': \'%s\', ' % (key,
try_decode(getattr(self, key))) utils.try_decode(getattr(self,
key)))
elif isinstance(getattr(self, key), unicode): elif isinstance(getattr(self, key), unicode):
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key)) answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
else: else:
# e.g. int # e.g. int
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key))) answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
return try_encode(answ + '}}') return utils.try_encode(answ + '}}')
class Playlist_Object(PlaylistObjectBaseclase): class Playlist_Object(PlaylistObjectBaseclase):
@ -228,7 +229,8 @@ class Playlist_Item(object):
continue continue
if isinstance(getattr(self, key), str): if isinstance(getattr(self, key), str):
answ += '\'%s\': \'%s\', ' % (key, answ += '\'%s\': \'%s\', ' % (key,
try_decode(getattr(self, key))) utils.try_decode(getattr(self,
key)))
elif isinstance(getattr(self, key), unicode): elif isinstance(getattr(self, key), unicode):
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key)) answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
else: else:
@ -238,7 +240,7 @@ class Playlist_Item(object):
answ += '\'xml\': None}}' answ += '\'xml\': None}}'
else: else:
answ += '\'xml\': \'%s\'}}' % self.xml.tag answ += '\'xml\': \'%s\'}}' % self.xml.tag
return try_encode(answ) return utils.try_encode(answ)
def plex_stream_index(self, kodi_stream_index, stream_type): def plex_stream_index(self, kodi_stream_index, stream_type):
""" """
@ -317,7 +319,7 @@ def playlist_item_from_kodi(kodi_item):
item.plex_type = query.get('itemType') item.plex_type = query.get('itemType')
if item.plex_id is None and item.file is not None: if item.plex_id is None and item.file is not None:
item.uri = ('library://whatever/item/%s' item.uri = ('library://whatever/item/%s'
% urllib.quote(try_encode(item.file), safe='')) % urllib.quote(utils.try_encode(item.file), safe=''))
else: else:
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER # TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
@ -343,17 +345,20 @@ def verify_kodi_item(plex_id, kodi_item):
# Need more info since we don't have kodi_id nor type. Use file path. # Need more info since we don't have kodi_id nor type. Use file path.
if (kodi_item['file'].startswith('plugin') or if (kodi_item['file'].startswith('plugin') or
kodi_item['file'].startswith('http')): kodi_item['file'].startswith('http')):
raise PlaylistError('kodi_item cannot be used for Plex playback') LOG.error('kodi_item %s cannot be used for Plex playback', kodi_item)
raise PlaylistError
LOG.debug('Starting research for Kodi id since we didnt get one: %s', LOG.debug('Starting research for Kodi id since we didnt get one: %s',
kodi_item) kodi_item)
kodi_id, _ = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_MOVIE) kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_MOVIE)
kodi_item['type'] = v.KODI_TYPE_MOVIE kodi_item['type'] = v.KODI_TYPE_MOVIE
if kodi_id is None: if kodi_id is None:
kodi_id, _ = kodiid_from_filename(kodi_item['file'], kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_EPISODE) v.KODI_TYPE_EPISODE)
kodi_item['type'] = v.KODI_TYPE_EPISODE kodi_item['type'] = v.KODI_TYPE_EPISODE
if kodi_id is None: if kodi_id is None:
kodi_id, _ = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_SONG) kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG)
kodi_item['type'] = v.KODI_TYPE_SONG kodi_item['type'] = v.KODI_TYPE_SONG
kodi_item['id'] = kodi_id kodi_item['id'] = kodi_id
kodi_item['type'] = None if kodi_id is None else kodi_item['type'] kodi_item['type'] = None if kodi_id is None else kodi_item['type']
@ -519,8 +524,9 @@ def init_plex_playqueue(playlist, plex_id=None, kodi_item=None):
# Need to get the details for the playlist item # Need to get the details for the playlist item
item = playlist_item_from_xml(xml[0]) item = playlist_item_from_xml(xml[0])
except (KeyError, IndexError, TypeError): except (KeyError, IndexError, TypeError):
raise PlaylistError('Could not init Plex playlist with plex_id %s and ' LOG.error('Could not init Plex playlist: plex_id %s, kodi_item %s',
'kodi_item %s' % (plex_id, kodi_item)) plex_id, kodi_item)
raise PlaylistError
playlist.items.append(item) playlist.items.append(item)
LOG.debug('Initialized the playqueue on the Plex side: %s', playlist) LOG.debug('Initialized the playqueue on the Plex side: %s', playlist)
return item return item
@ -683,7 +689,7 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
item = playlist_item_from_kodi( item = playlist_item_from_kodi(
{'id': kodi_id, 'type': kodi_type, 'file': file}) {'id': kodi_id, 'type': kodi_type, 'file': file})
if item.plex_id is not None: if item.plex_id is not None:
xml = GetPlexMetadata(item.plex_id) xml = PF.GetPlexMetadata(item.plex_id)
item.xml = xml[-1] item.xml = xml[-1]
playlist.items.insert(pos, item) playlist.items.insert(pos, item)
return item return item
@ -863,7 +869,7 @@ def get_plextype_from_xml(xml):
except IndexError: except IndexError:
LOG.error('Could not get plex_id from xml: %s', xml.attrib) LOG.error('Could not get plex_id from xml: %s', xml.attrib)
return return
new_xml = GetPlexMetadata(plex_id) new_xml = PF.GetPlexMetadata(plex_id)
try: try:
new_xml[0].attrib new_xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):

View File

@ -2,28 +2,21 @@
from logging import getLogger from logging import getLogger
import os import os
import sys import sys
from threading import Lock
from xbmcvfs import exists from xbmcvfs import exists
from watchdog.events import FileSystemEventHandler from .watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer from .watchdog.observers import Observer
import playlist_func as PL from . import playlist_func as PL
from PlexAPI import API from .plex_api import API
import kodidb_functions as kodidb from . import kodidb_functions as kodidb
import plexdb_functions as plexdb from . import plexdb_functions as plexdb
import utils from . import utils
import variables as v from . import variables as v
import state from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.playlists')
# Necessary to temporarily hold back librarysync/websocket listener when doing
# a full sync
LOCK = Lock()
LOCKER = utils.LockFunction(LOCK)
# Which playlist formates are supported by PKC? # Which playlist formates are supported by PKC?
SUPPORTED_FILETYPES = ( SUPPORTED_FILETYPES = (
@ -274,7 +267,7 @@ def _kodi_playlist_identical(xml_element):
pass pass
@LOCKER.lockthis @state.LOCKER_PLAYLISTS.lockthis
def process_websocket(plex_id, updated_at, state): def process_websocket(plex_id, updated_at, state):
""" """
Hit by librarysync to process websocket messages concerning playlists Hit by librarysync to process websocket messages concerning playlists
@ -302,7 +295,7 @@ def process_websocket(plex_id, updated_at, state):
pass pass
@LOCKER.lockthis @state.LOCKER_PLAYLISTS.lockthis
def full_sync(): def full_sync():
""" """
Full sync of playlists between Kodi and Plex. Returns True is successful, Full sync of playlists between Kodi and Plex. Returns True is successful,
@ -438,7 +431,7 @@ class PlaylistEventhandler(FileSystemEventHandler):
EVENT_TYPE_DELETED: self.on_deleted, EVENT_TYPE_DELETED: self.on_deleted,
} }
event_type = event.event_type event_type = event.event_type
with LOCK: with state.LOCK_PLAYLISTS:
_method_map[event_type](event) _method_map[event_type](event)
def on_created(self, event): def on_created(self, event):

View File

@ -4,21 +4,18 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
from re import compile as re_compile from re import compile as re_compile
import xbmc
from xbmc import Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO, sleep from . import utils
from . import playlist_func as PL
from utils import thread_methods from . import plex_functions as PF
import playlist_func as PL from .plex_api import API
from PlexFunctions import GetAllPlexChildren from . import json_rpc as js
from PlexAPI import API from . import variables as v
from plexbmchelper.subscribers import LOCK from . import state
from playback import play_xml
import json_rpc as js
import variables as v
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.playqueue')
PLUGIN = 'plugin://%s' % v.ADDON_ID PLUGIN = 'plugin://%s' % v.ADDON_ID
REGEX = re_compile(r'''plex_id=(\d+)''') REGEX = re_compile(r'''plex_id=(\d+)''')
@ -37,7 +34,7 @@ def init_playqueues():
LOG.debug('Playqueues have already been initialized') LOG.debug('Playqueues have already been initialized')
return return
# Initialize Kodi playqueues # Initialize Kodi playqueues
with LOCK: with state.LOCK_SUBSCRIBER:
for i in (0, 1, 2): for i in (0, 1, 2):
# Just in case the Kodi response is not sorted correctly # Just in case the Kodi response is not sorted correctly
for queue in js.get_playlists(): for queue in js.get_playlists():
@ -48,12 +45,12 @@ def init_playqueues():
playqueue.type = queue['type'] playqueue.type = queue['type']
# Initialize each Kodi playlist # Initialize each Kodi playlist
if playqueue.type == v.KODI_TYPE_AUDIO: if playqueue.type == v.KODI_TYPE_AUDIO:
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC) playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
elif playqueue.type == v.KODI_TYPE_VIDEO: elif playqueue.type == v.KODI_TYPE_VIDEO:
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO) playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
else: else:
# Currently, only video or audio playqueues available # Currently, only video or audio playqueues available
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO) playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
# Overwrite 'picture' with 'photo' # Overwrite 'picture' with 'photo'
playqueue.type = v.KODI_TYPE_PHOTO playqueue.type = v.KODI_TYPE_PHOTO
PLAYQUEUES.append(playqueue) PLAYQUEUES.append(playqueue)
@ -65,7 +62,7 @@ def get_playqueue_from_type(kodi_playlist_type):
Returns the playqueue according to the kodi_playlist_type ('video', Returns the playqueue according to the kodi_playlist_type ('video',
'audio', 'picture') passed in 'audio', 'picture') passed in
""" """
with LOCK: with state.LOCK_SUBSCRIBER:
for playqueue in PLAYQUEUES: for playqueue in PLAYQUEUES:
if playqueue.type == kodi_playlist_type: if playqueue.type == kodi_playlist_type:
break break
@ -81,7 +78,7 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
Returns the Playlist_Object Returns the Playlist_Object
""" """
xml = GetAllPlexChildren(plex_id) xml = PF.GetAllPlexChildren(plex_id)
try: try:
xml[0].attrib xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
@ -95,41 +92,11 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id()) PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id())
playqueue.plex_transient_token = transient_token playqueue.plex_transient_token = transient_token
LOG.debug('Firing up Kodi player') LOG.debug('Firing up Kodi player')
Player().play(playqueue.kodi_pl, None, False, 0) xbmc.Player().play(playqueue.kodi_pl, None, False, 0)
return playqueue return playqueue
def update_playqueue_from_PMS(playqueue, @utils.thread_methods(add_suspends=['PMS_STATUS'])
playqueue_id=None,
repeat=None,
offset=None,
transient_token=None):
"""
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
in playqueue_id if we need to fetch a new playqueue
repeat = 0, 1, 2
offset = time offset in Plextime (milliseconds)
"""
LOG.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s', playqueue_id, offset, repeat)
# Safe transient token from being deleted
if transient_token is None:
transient_token = playqueue.plex_transient_token
with LOCK:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear()
try:
PL.get_playlist_details_from_xml(playqueue, xml)
except PL.PlaylistError:
LOG.error('Could not get playqueue ID %s', playqueue_id)
return
playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token
play_xml(playqueue, xml, offset)
@thread_methods(add_suspends=['PMS_STATUS'])
class PlayqueueMonitor(Thread): class PlayqueueMonitor(Thread):
""" """
Unfortunately, Kodi does not tell if items within a Kodi playqueue Unfortunately, Kodi does not tell if items within a Kodi playqueue
@ -222,8 +189,8 @@ class PlayqueueMonitor(Thread):
while suspended(): while suspended():
if stopped(): if stopped():
break break
sleep(1000) xbmc.sleep(1000)
with LOCK: with state.LOCK_SUBSCRIBER:
for playqueue in PLAYQUEUES: for playqueue in PLAYQUEUES:
kodi_pl = js.playlist_get_items(playqueue.playlistid) kodi_pl = js.playlist_get_items(playqueue.playlistid)
if playqueue.old_kodi_pl != kodi_pl: if playqueue.old_kodi_pl != kodi_pl:
@ -236,5 +203,5 @@ class PlayqueueMonitor(Thread):
# compare old and new playqueue # compare old and new playqueue
self._compare_playqueues(playqueue, kodi_pl) self._compare_playqueues(playqueue, kodi_pl)
playqueue.old_kodi_pl = list(kodi_pl) playqueue.old_kodi_pl = list(kodi_pl)
sleep(200) xbmc.sleep(200)
LOG.info("----===## PlayqueueMonitor stopped ##===----") LOG.info("----===## PlayqueueMonitor stopped ##===----")

View File

@ -2,13 +2,13 @@
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from downloadutils import DownloadUtils as DU
from utils import window, settings, language as lang, dialog, try_encode from .downloadutils import DownloadUtils as DU
import variables as v from . import utils
from . import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.playutils')
############################################################################### ###############################################################################
@ -46,7 +46,8 @@ class PlayUtils():
'maxVideoBitrate': self.get_bitrate(), 'maxVideoBitrate': self.get_bitrate(),
'videoResolution': self.get_resolution(), 'videoResolution': self.get_resolution(),
'videoQuality': '100', 'videoQuality': '100',
'mediaBufferSize': int(settings('kodi_video_cache'))/1024, 'mediaBufferSize': int(
utils.settings('kodi_video_cache')) / 1024,
}) })
self.item.playmethod = 'Transcode' self.item.playmethod = 'Transcode'
LOG.info("The playurl is: %s", playurl) LOG.info("The playurl is: %s", playurl)
@ -71,15 +72,15 @@ class PlayUtils():
return playurl return playurl
# set to either 'Direct Stream=1' or 'Transcode=2' # set to either 'Direct Stream=1' or 'Transcode=2'
# and NOT to 'Direct Play=0' # and NOT to 'Direct Play=0'
if settings('playType') != "0": if utils.settings('playType') != "0":
# User forcing to play via HTTP # User forcing to play via HTTP
LOG.info("User chose to not direct play") LOG.info("User chose to not direct play")
return return
if self.mustTranscode(): if self.mustTranscode():
return return
return self.api.validate_playurl(path, return self.api.validate_playurl(path,
self.api.plex_type(), self.api.plex_type(),
force_check=True) force_check=True)
def mustTranscode(self): def mustTranscode(self):
""" """
@ -106,7 +107,7 @@ class PlayUtils():
# e.g. trailers. Avoids TypeError with "'h265' in codec" # e.g. trailers. Avoids TypeError with "'h265' in codec"
LOG.info('No codec from PMS, not transcoding.') LOG.info('No codec from PMS, not transcoding.')
return False return False
if ((settings('transcodeHi10P') == 'true' and if ((utils.settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10') and videoCodec['bitDepth'] == '10') and
('h264' in codec)): ('h264' in codec)):
LOG.info('Option to transcode 10bit h264 video content enabled.') LOG.info('Option to transcode 10bit h264 video content enabled.')
@ -139,7 +140,7 @@ class PlayUtils():
if self.api.plex_type() == 'track': if self.api.plex_type() == 'track':
return True return True
# set to 'Transcode=2' # set to 'Transcode=2'
if settings('playType') == "2": if utils.settings('playType') == "2":
# User forcing to play via HTTP # User forcing to play via HTTP
LOG.info("User chose to transcode") LOG.info("User chose to transcode")
return False return False
@ -149,7 +150,7 @@ class PlayUtils():
def get_max_bitrate(self): def get_max_bitrate(self):
# get the addon video quality # get the addon video quality
videoQuality = settings('maxVideoQualities') videoQuality = utils.settings('maxVideoQualities')
bitrate = { bitrate = {
'0': 320, '0': 320,
'1': 720, '1': 720,
@ -180,13 +181,13 @@ class PlayUtils():
'2': 720, '2': 720,
'3': 1080 '3': 1080
} }
return H265[settings('transcodeH265')] return H265[utils.settings('transcodeH265')]
def get_bitrate(self): def get_bitrate(self):
""" """
Get the desired transcoding bitrate from the settings Get the desired transcoding bitrate from the settings
""" """
videoQuality = settings('transcoderVideoQualities') videoQuality = utils.settings('transcoderVideoQualities')
bitrate = { bitrate = {
'0': 320, '0': 320,
'1': 720, '1': 720,
@ -207,7 +208,7 @@ class PlayUtils():
""" """
Get the desired transcoding resolutions from the settings Get the desired transcoding resolutions from the settings
""" """
chosen = settings('transcoderVideoQualities') chosen = utils.settings('transcoderVideoQualities')
res = { res = {
'0': '420x420', '0': '420x420',
'1': '576x320', '1': '576x320',
@ -244,7 +245,7 @@ class PlayUtils():
audio_streams = [] audio_streams = []
subtitle_streams_list = [] subtitle_streams_list = []
# No subtitles as an option # No subtitles as an option
subtitle_streams = [lang(39706)] subtitle_streams = [utils.lang(39706)]
downloadable_streams = [] downloadable_streams = []
download_subs = [] download_subs = []
# selectAudioIndex = "" # selectAudioIndex = ""
@ -264,35 +265,35 @@ class PlayUtils():
codec = stream.attrib.get('codec') codec = stream.attrib.get('codec')
channellayout = stream.attrib.get('audioChannelLayout', "") channellayout = stream.attrib.get('audioChannelLayout', "")
try: try:
track = "%s %s - %s %s" % (audio_numb+1, track = "%s %s - %s %s" % (audio_numb + 1,
stream.attrib['language'], stream.attrib['language'],
codec, codec,
channellayout) channellayout)
except KeyError: except KeyError:
track = "%s %s - %s %s" % (audio_numb+1, track = "%s %s - %s %s" % (audio_numb + 1,
lang(39707), # unknown utils.lang(39707), # unknown
codec, codec,
channellayout) channellayout)
audio_streams_list.append(index) audio_streams_list.append(index)
audio_streams.append(try_encode(track)) audio_streams.append(utils.try_encode(track))
audio_numb += 1 audio_numb += 1
# Subtitles # Subtitles
elif typus == "3": elif typus == "3":
try: try:
track = "%s %s" % (sub_num+1, stream.attrib['language']) track = "%s %s" % (sub_num + 1, stream.attrib['language'])
except KeyError: except KeyError:
track = "%s %s (%s)" % (sub_num+1, track = "%s %s (%s)" % (sub_num + 1,
lang(39707), # unknown utils.lang(39707), # unknown
stream.attrib.get('codec')) stream.attrib.get('codec'))
default = stream.attrib.get('default') default = stream.attrib.get('default')
forced = stream.attrib.get('forced') forced = stream.attrib.get('forced')
downloadable = stream.attrib.get('key') downloadable = stream.attrib.get('key')
if default: if default:
track = "%s - %s" % (track, lang(39708)) # Default track = "%s - %s" % (track, utils.lang(39708)) # Default
if forced: if forced:
track = "%s - %s" % (track, lang(39709)) # Forced track = "%s - %s" % (track, utils.lang(39709)) # Forced
if downloadable: if downloadable:
# We do know the language - temporarily download # We do know the language - temporarily download
if 'language' in stream.attrib: if 'language' in stream.attrib:
@ -303,23 +304,23 @@ class PlayUtils():
# We don't know the language - no need to download # We don't know the language - no need to download
else: else:
path = self.api.attach_plex_token_to_url( path = self.api.attach_plex_token_to_url(
"%s%s" % (window('pms_server'), "%s%s" % (utils.window('pms_server'),
stream.attrib['key'])) stream.attrib['key']))
downloadable_streams.append(index) downloadable_streams.append(index)
download_subs.append(try_encode(path)) download_subs.append(utils.try_encode(path))
else: else:
track = "%s (%s)" % (track, lang(39710)) # burn-in track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
if stream.attrib.get('selected') == '1' and downloadable: if stream.attrib.get('selected') == '1' and downloadable:
# Only show subs without asking user if they can be # Only show subs without asking user if they can be
# turned off # turned off
default_sub = index default_sub = index
subtitle_streams_list.append(index) subtitle_streams_list.append(index)
subtitle_streams.append(try_encode(track)) subtitle_streams.append(utils.try_encode(track))
sub_num += 1 sub_num += 1
if audio_numb > 1: if audio_numb > 1:
resp = dialog('select', lang(33013), audio_streams) resp = utils.dialog('select', utils.lang(33013), audio_streams)
if resp > -1: if resp > -1:
# User selected some audio track # User selected some audio track
args = { args = {
@ -335,14 +336,14 @@ class PlayUtils():
return return
select_subs_index = None select_subs_index = None
if (settings('pickPlexSubtitles') == 'true' and if (utils.settings('pickPlexSubtitles') == 'true' and
default_sub is not None): default_sub is not None):
LOG.info('Using default Plex subtitle: %s', default_sub) LOG.info('Using default Plex subtitle: %s', default_sub)
select_subs_index = default_sub select_subs_index = default_sub
else: else:
resp = dialog('select', lang(33014), subtitle_streams) resp = utils.dialog('select', utils.lang(33014), subtitle_streams)
if resp > 0: if resp > 0:
select_subs_index = subtitle_streams_list[resp-1] select_subs_index = subtitle_streams_list[resp - 1]
else: else:
# User selected no subtitles or backed out of dialog # User selected no subtitles or backed out of dialog
select_subs_index = '' select_subs_index = ''

View File

@ -32,30 +32,27 @@ http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-pyt
from logging import getLogger from logging import getLogger
from re import compile as re_compile, sub from re import compile as re_compile, sub
from urllib import urlencode, unquote from urllib import urlencode, unquote
from os.path import basename, join import os
from os import makedirs
from xbmcgui import ListItem from xbmcgui import ListItem
from xbmcvfs import exists from xbmcvfs import exists
import clientinfo as client from .downloadutils import DownloadUtils as DU
from downloadutils import DownloadUtils as DU from . import clientinfo
from utils import window, settings, language as lang, try_decode, try_encode, \ from . import utils
unix_date_to_kodi, exists_dir, slugify, dialog, escape_html from . import plex_functions as PF
import PlexFunctions as PF from . import plexdb_functions as plexdb
import plexdb_functions as plexdb from . import kodidb_functions as kodidb
import kodidb_functions as kodidb from . import variables as v
import variables as v from . import state
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.plex_api')
REGEX_IMDB = re_compile(r'''/(tt\d+)''') REGEX_IMDB = re_compile(r'''/(tt\d+)''')
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''') REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
if not exists_dir(v.EXTERNAL_SUBTITLE_TEMP_PATH): if not utils.exists_dir(v.EXTERNAL_SUBTITLE_TEMP_PATH):
makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH) os.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
############################################################################### ###############################################################################
@ -73,7 +70,7 @@ class API(object):
# which media part in the XML response shall we look at? # which media part in the XML response shall we look at?
self.part = 0 self.part = 0
self.mediastream = None self.mediastream = None
self.server = window('pms_server') self.server = utils.window('pms_server')
def set_part_number(self, number=None): def set_part_number(self, number=None):
""" """
@ -206,7 +203,7 @@ class API(object):
ans = None ans = None
if ans is not None: if ans is not None:
try: try:
ans = try_decode(unquote(ans)) ans = utils.try_decode(unquote(ans))
except UnicodeDecodeError: except UnicodeDecodeError:
# Sometimes, Plex seems to have encoded in latin1 # Sometimes, Plex seems to have encoded in latin1
ans = unquote(ans).decode('latin1') ans = unquote(ans).decode('latin1')
@ -218,23 +215,23 @@ class API(object):
Will always use addon paths, never direct paths Will always use addon paths, never direct paths
""" """
extension = self.item[0][0].attrib['key'][self.item[0][0].attrib['key'].rfind('.'):].lower() extension = self.item[0][0].attrib['key'][self.item[0][0].attrib['key'].rfind('.'):].lower()
if (window('plex_force_transcode_pix') == 'true' or if (utils.window('plex_force_transcode_pix') == 'true' or
extension not in v.KODI_SUPPORTED_IMAGES): extension not in v.KODI_SUPPORTED_IMAGES):
# Let Plex transcode # Let Plex transcode
# max width/height supported by plex image transcoder is 1920x1080 # max width/height supported by plex image transcoder is 1920x1080
path = self.server + PF.transcode_image_path( path = self.server + PF.transcode_image_path(
self.item[0][0].get('key'), self.item[0][0].get('key'),
window('pms_token'), utils.window('pms_token'),
"%s%s" % (self.server, self.item[0][0].get('key')), "%s%s" % (self.server, self.item[0][0].get('key')),
1920, 1920,
1080) 1080)
else: else:
path = self.attach_plex_token_to_url( path = self.attach_plex_token_to_url(
'%s%s' % (window('pms_server'), '%s%s' % (utils.window('pms_server'),
self.item[0][0].attrib['key'])) self.item[0][0].attrib['key']))
# Attach Plex id to url to let it be picked up by our playqueue agent # Attach Plex id to url to let it be picked up by our playqueue agent
# later # later
return try_encode('%s&plex_id=%s' % (path, self.plex_id())) return utils.try_encode('%s&plex_id=%s' % (path, self.plex_id()))
def tv_show_path(self): def tv_show_path(self):
""" """
@ -261,7 +258,7 @@ class API(object):
""" """
res = self.item.get('addedAt') res = self.item.get('addedAt')
if res is not None: if res is not None:
res = unix_date_to_kodi(res) res = utils.unix_date_to_kodi(res)
else: else:
res = '2000-01-01 10:00:00' res = '2000-01-01 10:00:00'
return res return res
@ -298,7 +295,7 @@ class API(object):
played = True if playcount else False played = True if playcount else False
try: try:
last_played = unix_date_to_kodi(int(item['lastViewedAt'])) last_played = utils.unix_date_to_kodi(int(item['lastViewedAt']))
except (KeyError, ValueError): except (KeyError, ValueError):
last_played = None last_played = None
@ -426,7 +423,7 @@ class API(object):
""" """
answ = self.item.get('guid') answ = self.item.get('guid')
if answ is not None: if answ is not None:
answ = escape_html(answ) answ = utils.escape_html(answ)
return answ return answ
def provider(self, providername=None): def provider(self, providername=None):
@ -459,7 +456,7 @@ class API(object):
""" """
Returns the title of the element as unicode or 'Missing Title Name' Returns the title of the element as unicode or 'Missing Title Name'
""" """
return try_decode(self.item.get('title', 'Missing Title Name')) return utils.try_decode(self.item.get('title', 'Missing Title Name'))
def titles(self): def titles(self):
""" """
@ -660,12 +657,12 @@ class API(object):
url may or may not already contain a '?' url may or may not already contain a '?'
""" """
if window('pms_token') == '': if utils.window('pms_token') == '':
return url return url
if '?' not in url: if '?' not in url:
url = "%s?X-Plex-Token=%s" % (url, window('pms_token')) url = "%s?X-Plex-Token=%s" % (url, utils.window('pms_token'))
else: else:
url = "%s&X-Plex-Token=%s" % (url, window('pms_token')) url = "%s&X-Plex-Token=%s" % (url, utils.window('pms_token'))
return url return url
def item_id(self): def item_id(self):
@ -811,12 +808,12 @@ class API(object):
track['channels'] = stream.get('channels') track['channels'] = stream.get('channels')
# 'unknown' if we cannot get language # 'unknown' if we cannot get language
track['language'] = stream.get( track['language'] = stream.get(
'languageCode', lang(39310)).lower() 'languageCode', utils.lang(39310)).lower()
audiotracks.append(track) audiotracks.append(track)
elif media_type == 3: # Subtitle streams elif media_type == 3: # Subtitle streams
# 'unknown' if we cannot get language # 'unknown' if we cannot get language
subtitlelanguages.append( subtitlelanguages.append(
stream.get('languageCode', lang(39310)).lower()) stream.get('languageCode', utils.lang(39310)).lower())
return { return {
'video': videotracks, 'video': videotracks,
'audio': audiotracks, 'audio': audiotracks,
@ -969,7 +966,7 @@ class API(object):
LOG.info('Start movie set/collection lookup on themoviedb with %s', LOG.info('Start movie set/collection lookup on themoviedb with %s',
item.get('title', '')) item.get('title', ''))
api_key = settings('themoviedbAPIKey') api_key = utils.settings('themoviedbAPIKey')
if media_type == v.PLEX_TYPE_SHOW: if media_type == v.PLEX_TYPE_SHOW:
media_type = 'tv' media_type = 'tv'
title = item.get('title', '') title = item.get('title', '')
@ -980,7 +977,7 @@ class API(object):
parameters = { parameters = {
'api_key': api_key, 'api_key': api_key,
'language': v.KODILANGUAGE, 'language': v.KODILANGUAGE,
'query': try_encode(title) 'query': utils.try_encode(title)
} }
data = DU().downloadUrl(url, data = DU().downloadUrl(url,
authenticate=False, authenticate=False,
@ -1106,8 +1103,8 @@ class API(object):
try: try:
data.get('poster_path') data.get('poster_path')
except AttributeError: except AttributeError:
LOG.debug('Could not find TheMovieDB poster paths for %s in ' LOG.debug('Could not find TheMovieDB poster paths for %s'
'the language %s', title, language) ' in the language %s', title, language)
continue continue
if not poster and data.get('poster_path'): if not poster and data.get('poster_path'):
poster = ('https://image.tmdb.org/t/p/original%s' % poster = ('https://image.tmdb.org/t/p/original%s' %
@ -1123,7 +1120,7 @@ class API(object):
media_id: IMDB id for movies, tvdb id for TV shows media_id: IMDB id for movies, tvdb id for TV shows
""" """
api_key = settings('FanArtTVAPIKey') api_key = utils.settings('FanArtTVAPIKey')
typus = self.plex_type() typus = self.plex_type()
if typus == v.PLEX_TYPE_SHOW: if typus == v.PLEX_TYPE_SHOW:
typus = 'tv' typus = 'tv'
@ -1239,17 +1236,17 @@ class API(object):
count += 1 count += 1
if (count > 1 and ( if (count > 1 and (
(self.plex_type() != 'clip' and (self.plex_type() != 'clip' and
settings('bestQuality') == 'false') utils.settings('bestQuality') == 'false')
or or
(self.plex_type() == 'clip' and (self.plex_type() == 'clip' and
settings('bestTrailer') == 'false'))): utils.settings('bestTrailer') == 'false'))):
# Several streams/files available. # Several streams/files available.
dialoglist = [] dialoglist = []
for entry in self.item.iterfind('./Media'): for entry in self.item.iterfind('./Media'):
# Get additional info (filename / languages) # Get additional info (filename / languages)
filename = None filename = None
if 'file' in entry[0].attrib: if 'file' in entry[0].attrib:
filename = basename(entry[0].attrib['file']) filename = os.path.basename(entry[0].attrib['file'])
# Languages of audio streams # Languages of audio streams
languages = [] languages = []
for stream in entry[0]: for stream in entry[0]:
@ -1258,12 +1255,13 @@ class API(object):
languages.append(stream.attrib['language']) languages.append(stream.attrib['language'])
languages = ', '.join(languages) languages = ', '.join(languages)
if filename: if filename:
option = try_encode(filename) option = utils.try_encode(filename)
if languages: if languages:
if option: if option:
option = '%s (%s): ' % (option, try_encode(languages)) option = '%s (%s): ' % (option,
utils.try_encode(languages))
else: else:
option = '%s: ' % try_encode(languages) option = '%s: ' % utils.try_encode(languages)
if 'videoResolution' in entry.attrib: if 'videoResolution' in entry.attrib:
option = '%s%sp ' % (option, option = '%s%sp ' % (option,
entry.get('videoResolution')) entry.get('videoResolution'))
@ -1278,7 +1276,7 @@ class API(object):
option = '%s%s ' % (option, option = '%s%s ' % (option,
entry.get('audioCodec')) entry.get('audioCodec'))
dialoglist.append(option) dialoglist.append(option)
media = dialog('select', 'Select stream', dialoglist) media = utils.dialog('select', 'Select stream', dialoglist)
else: else:
media = 0 media = 0
self.mediastream = media self.mediastream = media
@ -1309,7 +1307,7 @@ class API(object):
self.mediastream_number() self.mediastream_number()
if quality is None: if quality is None:
quality = {} quality = {}
xargs = client.getXArgsDeviceInfo() xargs = clientinfo.getXArgsDeviceInfo()
# For DirectPlay, path/key of PART is needed # For DirectPlay, path/key of PART is needed
# trailers are 'clip' with PMS xmls # trailers are 'clip' with PMS xmls
if action == "DirectStream": if action == "DirectStream":
@ -1334,19 +1332,19 @@ class API(object):
transcode_path = self.server + \ transcode_path = self.server + \
'/video/:/transcode/universal/start.m3u8?' '/video/:/transcode/universal/start.m3u8?'
args = { args = {
'audioBoost': settings('audioBoost'), 'audioBoost': utils.settings('audioBoost'),
'autoAdjustQuality': 0, 'autoAdjustQuality': 0,
'directPlay': 0, 'directPlay': 0,
'directStream': 1, 'directStream': 1,
'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls' 'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
'session': window('plex_client_Id'), 'session': utils.window('plex_client_Id'),
'fastSeek': 1, 'fastSeek': 1,
'path': path, 'path': path,
'mediaIndex': self.mediastream, 'mediaIndex': self.mediastream,
'partIndex': self.part, 'partIndex': self.part,
'hasMDE': 1, 'hasMDE': 1,
'location': 'lan', 'location': 'lan',
'subtitleSize': settings('subtitleSize') 'subtitleSize': utils.settings('subtitleSize')
} }
# Look like Android to let the PMS use the transcoding profile # Look like Android to let the PMS use the transcoding profile
xargs.update(headers) xargs.update(headers)
@ -1403,7 +1401,7 @@ class API(object):
Returns the path to the downloaded subtitle or None Returns the path to the downloaded subtitle or None
""" """
path = join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename) path = os.path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
response = DU().downloadUrl(url, return_response=True) response = DU().downloadUrl(url, return_response=True)
try: try:
response.status_code response.status_code
@ -1417,7 +1415,7 @@ class API(object):
filer.write(response.content) filer.write(response.content)
except UnicodeEncodeError: except UnicodeEncodeError:
LOG.debug('Need to slugify the filename %s', path) LOG.debug('Need to slugify the filename %s', path)
path = slugify(path) path = utils.slugify(path)
with open(path, 'wb') as filer: with open(path, 'wb') as filer:
filer.write(response.content) filer.write(response.content)
return path return path
@ -1561,7 +1559,8 @@ class API(object):
listitem.setInfo('video', infoLabels=metadata) listitem.setInfo('video', infoLabels=metadata)
try: try:
# Add context menu entry for information screen # Add context menu entry for information screen
listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)]) listitem.addContextMenuItems([(utils.lang(30032),
'XBMC.Action(Info)',)])
except TypeError: except TypeError:
# Kodi fuck-up # Kodi fuck-up
pass pass
@ -1607,20 +1606,20 @@ class API(object):
# exist() needs a / or \ at the end to work for directories # exist() needs a / or \ at the end to work for directories
if folder is False: if folder is False:
# files # files
check = exists(try_encode(path)) check = exists(utils.try_encode(path))
else: else:
# directories # directories
if "\\" in path: if "\\" in path:
if not path.endswith('\\'): if not path.endswith('\\'):
# Add the missing backslash # Add the missing backslash
check = exists_dir(path + "\\") check = utils.exists_dir(path + "\\")
else: else:
check = exists_dir(path) check = utils.exists_dir(path)
else: else:
if not path.endswith('/'): if not path.endswith('/'):
check = exists_dir(path + "/") check = utils.exists_dir(path + "/")
else: else:
check = exists_dir(path) check = utils.exists_dir(path)
if not check: if not check:
if force_check is False: if force_check is False:
@ -1649,25 +1648,11 @@ class API(object):
LOG.warn('Cannot access file: %s', url) LOG.warn('Cannot access file: %s', url)
# Kodi cannot locate the file #s. Please verify your PKC settings. Stop # Kodi cannot locate the file #s. Please verify your PKC settings. Stop
# syncing? # syncing?
resp = dialog('yesno', heading='{plex}', line1=lang(39031) % url) resp = utils.dialog('yesno',
heading='{plex}',
line1=utils.lang(39031) % url)
return resp return resp
def set_listitem_artwork(self, listitem):
"""
Set all artwork to the listitem
"""
allartwork = self.artwork()
listitem.setArt(self.artwork())
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
# Backdrop is a list, grab the first backdrop
self._set_listitem_artprop(listitem,
arttype,
allartwork[art][0])
else:
self._set_listitem_artprop(listitem, arttype, allartwork[art])
@staticmethod @staticmethod
def _set_listitem_artprop(listitem, arttype, path): def _set_listitem_artprop(listitem, arttype, path):
if arttype in ( if arttype in (

View File

@ -6,30 +6,57 @@ from threading import Thread
from Queue import Empty from Queue import Empty
from socket import SHUT_RDWR from socket import SHUT_RDWR
from urllib import urlencode from urllib import urlencode
from xbmc import sleep, executebuiltin, Player from xbmc import sleep, executebuiltin, Player
from utils import settings, thread_methods, language as lang, dialog from .plexbmchelper import listener, plexgdm, subscribers, httppersist
from plexbmchelper import listener, plexgdm, subscribers, httppersist from .plex_api import API
from plexbmchelper.subscribers import LOCKER from . import utils
from PlexFunctions import ParseContainerKey, GetPlexMetadata, DownloadChunks from . import plex_functions as PF
from PlexAPI import API from . import playlist_func as PL
from playlist_func import get_pms_playqueue, get_plextype_from_xml, \ from . import playback
get_playlist_details_from_xml from . import json_rpc as js
from playback import playback_triage, play_xml from . import playqueue as PQ
import json_rpc as js from . import variables as v
import variables as v from . import state
import state
import playqueue as PQ
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.plex_companion')
############################################################################### ###############################################################################
@thread_methods(add_suspends=['PMS_STATUS']) def update_playqueue_from_PMS(playqueue,
playqueue_id=None,
repeat=None,
offset=None,
transient_token=None):
"""
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
in playqueue_id if we need to fetch a new playqueue
repeat = 0, 1, 2
offset = time offset in Plextime (milliseconds)
"""
LOG.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s', playqueue_id, offset, repeat)
# Safe transient token from being deleted
if transient_token is None:
transient_token = playqueue.plex_transient_token
with state.LOCK_SUBSCRIBER:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear()
try:
PL.get_playlist_details_from_xml(playqueue, xml)
except PL.PlaylistError:
LOG.error('Could not get playqueue ID %s', playqueue_id)
return
playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token
playback.play_xml(playqueue, xml, offset)
@utils.thread_methods(add_suspends=['PMS_STATUS'])
class PlexCompanion(Thread): class PlexCompanion(Thread):
""" """
Plex Companion monitoring class. Invoke only once Plex Companion monitoring class. Invoke only once
@ -47,9 +74,9 @@ class PlexCompanion(Thread):
self.subscription_manager = None self.subscription_manager = None
Thread.__init__(self) Thread.__init__(self)
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def _process_alexa(self, data): def _process_alexa(self, data):
xml = GetPlexMetadata(data['key']) xml = PF.GetPlexMetadata(data['key'])
try: try:
xml[0].attrib xml[0].attrib
except (AttributeError, IndexError, TypeError): except (AttributeError, IndexError, TypeError):
@ -62,28 +89,33 @@ class PlexCompanion(Thread):
api.plex_id(), api.plex_id(),
transient_token=data.get('token')) transient_token=data.get('token'))
elif data['containerKey'].startswith('/playQueues/'): elif data['containerKey'].startswith('/playQueues/'):
_, container_key, _ = ParseContainerKey(data['containerKey']) _, container_key, _ = PF.ParseContainerKey(data['containerKey'])
xml = DownloadChunks('{server}/playQueues/%s?' % container_key) xml = PF.DownloadChunks('{server}/playQueues/%s?' % container_key)
if xml is None: if xml is None:
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128), icon='{error}') utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
return return
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()])
playqueue.clear() playqueue.clear()
get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
playqueue.plex_transient_token = data.get('token') playqueue.plex_transient_token = data.get('token')
if data.get('offset') != '0': if data.get('offset') != '0':
offset = float(data['offset']) / 1000.0 offset = float(data['offset']) / 1000.0
else: else:
offset = None offset = None
play_xml(playqueue, xml, offset) playback.play_xml(playqueue, xml, offset)
else: else:
state.PLEX_TRANSIENT_TOKEN = data.get('token') state.PLEX_TRANSIENT_TOKEN = data.get('token')
if data.get('offset') != '0': if data.get('offset') != '0':
state.RESUMABLE = True state.RESUMABLE = True
state.RESUME_PLAYBACK = True state.RESUME_PLAYBACK = True
playback_triage(api.plex_id(), api.plex_type(), resolve=False) playback.playback_triage(api.plex_id(),
api.plex_type(),
resolve=False)
@staticmethod @staticmethod
def _process_node(data): def _process_node(data):
@ -99,17 +131,17 @@ class PlexCompanion(Thread):
executebuiltin('RunPlugin(plugin://%s?%s)' executebuiltin('RunPlugin(plugin://%s?%s)'
% (v.ADDON_ID, urlencode(params))) % (v.ADDON_ID, urlencode(params)))
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def _process_playlist(self, data): def _process_playlist(self, data):
# Get the playqueue ID # Get the playqueue ID
_, container_key, query = ParseContainerKey(data['containerKey']) _, container_key, query = PF.ParseContainerKey(data['containerKey'])
try: try:
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
except KeyError: except KeyError:
# E.g. Plex web does not supply the media type # E.g. Plex web does not supply the media type
# Still need to figure out the type (video vs. music vs. pix) # Still need to figure out the type (video vs. music vs. pix)
xml = GetPlexMetadata(data['key']) xml = PF.GetPlexMetadata(data['key'])
try: try:
xml[0].attrib xml[0].attrib
except (AttributeError, IndexError, TypeError): except (AttributeError, IndexError, TypeError):
@ -118,14 +150,13 @@ class PlexCompanion(Thread):
api = API(xml[0]) api = API(xml[0])
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()])
PQ.update_playqueue_from_PMS( update_playqueue_from_PMS(playqueue,
playqueue, playqueue_id=container_key,
playqueue_id=container_key, repeat=query.get('repeat'),
repeat=query.get('repeat'), offset=data.get('offset'),
offset=data.get('offset'), transient_token=data.get('token'))
transient_token=data.get('token'))
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def _process_streams(self, data): def _process_streams(self, data):
""" """
Plex Companion client adjusted audio or subtitle stream Plex Companion client adjusted audio or subtitle stream
@ -147,17 +178,17 @@ class PlexCompanion(Thread):
else: else:
LOG.error('Unknown setStreams command: %s', data) LOG.error('Unknown setStreams command: %s', data)
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def _process_refresh(self, data): def _process_refresh(self, data):
""" """
example data: {'playQueueID': '8475', 'commandID': '11'} example data: {'playQueueID': '8475', 'commandID': '11'}
""" """
xml = get_pms_playqueue(data['playQueueID']) xml = PL.get_pms_playqueue(data['playQueueID'])
if xml is None: if xml is None:
return return
if len(xml) == 0: if len(xml) == 0:
LOG.debug('Empty playqueue received - clearing playqueue') LOG.debug('Empty playqueue received - clearing playqueue')
plex_type = get_plextype_from_xml(xml) plex_type = PL.get_plextype_from_xml(xml)
if plex_type is None: if plex_type is None:
return return
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
@ -166,7 +197,7 @@ class PlexCompanion(Thread):
return return
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
PQ.update_playqueue_from_PMS(playqueue, data['playQueueID']) update_playqueue_from_PMS(playqueue, data['playQueueID'])
def _process_tasks(self, task): def _process_tasks(self, task):
""" """
@ -231,7 +262,7 @@ class PlexCompanion(Thread):
self.player) self.player)
self.subscription_manager = subscription_manager self.subscription_manager = subscription_manager
if settings('plexCompanion') == 'true': if utils.settings('plexCompanion') == 'true':
# Start up httpd # Start up httpd
start_count = 0 start_count = 0
while True: while True:

View File

@ -7,18 +7,17 @@ from re import compile as re_compile
from copy import deepcopy from copy import deepcopy
from time import time from time import time
from threading import Thread from threading import Thread
from xbmc import sleep from xbmc import sleep
from downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from utils import settings, try_encode, try_decode from . import utils
from variables import PLEX_TO_KODI_TIMEFACTOR from . import plex_tv
import plex_tv from . import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.plex_functions')
CONTAINERSIZE = int(settings('limitindex')) CONTAINERSIZE = int(utils.settings('limitindex'))
REGEX_PLEX_KEY = re_compile(r'''/(.+)/(\d+)$''') REGEX_PLEX_KEY = re_compile(r'''/(.+)/(\d+)$''')
REGEX_PLEX_DIRECT = re_compile(r'''\.plex\.direct:\d+$''') REGEX_PLEX_DIRECT = re_compile(r'''\.plex\.direct:\d+$''')
@ -36,7 +35,7 @@ def ConvertPlexToKodiTime(plexTime):
""" """
if plexTime is None: if plexTime is None:
return None return None
return int(float(plexTime) * PLEX_TO_KODI_TIMEFACTOR) return int(float(plexTime) * v.PLEX_TO_KODI_TIMEFACTOR)
def GetPlexKeyNumber(plexKey): def GetPlexKeyNumber(plexKey):
@ -90,13 +89,13 @@ def GetMethodFromPlexType(plexType):
def GetPlexLoginFromSettings(): def GetPlexLoginFromSettings():
""" """
Returns a dict: Returns a dict:
'plexLogin': settings('plexLogin'), 'plexLogin': utils.settings('plexLogin'),
'plexToken': settings('plexToken'), 'plexToken': utils.settings('plexToken'),
'plexhome': settings('plexhome'), 'plexhome': utils.settings('plexhome'),
'plexid': settings('plexid'), 'plexid': utils.settings('plexid'),
'myplexlogin': settings('myplexlogin'), 'myplexlogin': utils.settings('myplexlogin'),
'plexAvatar': settings('plexAvatar'), 'plexAvatar': utils.settings('plexAvatar'),
'plexHomeSize': settings('plexHomeSize') 'plexHomeSize': utils.settings('plexHomeSize')
Returns strings or unicode Returns strings or unicode
@ -106,13 +105,13 @@ def GetPlexLoginFromSettings():
plexhome is 'true' if plex home is used (the default) plexhome is 'true' if plex home is used (the default)
""" """
return { return {
'plexLogin': settings('plexLogin'), 'plexLogin': utils.settings('plexLogin'),
'plexToken': settings('plexToken'), 'plexToken': utils.settings('plexToken'),
'plexhome': settings('plexhome'), 'plexhome': utils.settings('plexhome'),
'plexid': settings('plexid'), 'plexid': utils.settings('plexid'),
'myplexlogin': settings('myplexlogin'), 'myplexlogin': utils.settings('myplexlogin'),
'plexAvatar': settings('plexAvatar'), 'plexAvatar': utils.settings('plexAvatar'),
'plexHomeSize': settings('plexHomeSize') 'plexHomeSize': utils.settings('plexHomeSize')
} }
@ -140,7 +139,7 @@ def check_connection(url, token=None, verifySSL=None):
if token is not None: if token is not None:
header_options = {'X-Plex-Token': token} header_options = {'X-Plex-Token': token}
if verifySSL is True: if verifySSL is True:
verifySSL = None if settings('sslverify') == 'true' else False verifySSL = None if utils.settings('sslverify') == 'true' else False
if 'plex.tv' in url: if 'plex.tv' in url:
url = 'https://plex.tv/api/home/users' url = 'https://plex.tv/api/home/users'
LOG.debug("Checking connection to server %s with verifySSL=%s", LOG.debug("Checking connection to server %s with verifySSL=%s",
@ -303,11 +302,11 @@ def _plex_gdm():
} }
for line in response['data'].split('\n'): for line in response['data'].split('\n'):
if 'Content-Type:' in line: if 'Content-Type:' in line:
pms['product'] = try_decode(line.split(':')[1].strip()) pms['product'] = utils.try_decode(line.split(':')[1].strip())
elif 'Host:' in line: elif 'Host:' in line:
pms['baseURL'] = line.split(':')[1].strip() pms['baseURL'] = line.split(':')[1].strip()
elif 'Name:' in line: elif 'Name:' in line:
pms['name'] = try_decode(line.split(':')[1].strip()) pms['name'] = utils.try_decode(line.split(':')[1].strip())
elif 'Port:' in line: elif 'Port:' in line:
pms['port'] = line.split(':')[1].strip() pms['port'] = line.split(':')[1].strip()
elif 'Resource-Identifier:' in line: elif 'Resource-Identifier:' in line:
@ -336,7 +335,7 @@ def _pms_list_from_plex_tv(token):
queue = Queue() queue = Queue()
thread_queue = [] thread_queue = []
max_age_in_seconds = 2*60*60*24 max_age_in_seconds = 2 * 60 * 60 * 24
for device in xml.findall('Device'): for device in xml.findall('Device'):
if 'server' not in device.get('provides'): if 'server' not in device.get('provides'):
# No PMS - skip # No PMS - skip
@ -355,7 +354,7 @@ def _pms_list_from_plex_tv(token):
'token': device.get('accessToken'), 'token': device.get('accessToken'),
'ownername': device.get('sourceTitle'), 'ownername': device.get('sourceTitle'),
'product': device.get('product'), # e.g. 'Plex Media Server' 'product': device.get('product'), # e.g. 'Plex Media Server'
'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e...' 'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e..'
'device': device.get('device'), # e.g. 'PC' or 'Windows' 'device': device.get('device'), # e.g. 'PC' or 'Windows'
'platform': device.get('platform'), # e.g. 'Windows', 'Android' 'platform': device.get('platform'), # e.g. 'Windows', 'Android'
'local': device.get('publicAddressMatches') == '1', 'local': device.get('publicAddressMatches') == '1',
@ -634,7 +633,7 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
'repeat': '0' 'repeat': '0'
} }
if trailers is True: if trailers is True:
args['extrasPrefixCount'] = settings('trailerNumber') args['extrasPrefixCount'] = utils.settings('trailerNumber')
xml = DU().downloadUrl(url + '?' + urlencode(args), action_type="POST") xml = DU().downloadUrl(url + '?' + urlencode(args), action_type="POST")
try: try:
xml[0].tag xml[0].tag
@ -791,7 +790,7 @@ def GetUserArtworkURL(username):
Returns the URL for the user's Avatar. Or False if something went Returns the URL for the user's Avatar. Or False if something went
wrong. wrong.
""" """
users = plex_tv.list_home_users(settings('plexToken')) users = plex_tv.list_home_users(utils.settings('plexToken'))
url = '' url = ''
# If an error is encountered, set to False # If an error is encountered, set to False
if not users: if not users:
@ -818,13 +817,13 @@ def transcode_image_path(key, AuthToken, path, width, height):
final path to image file final path to image file
""" """
# external address - can we get a transcoding request for external images? # external address - can we get a transcoding request for external images?
if key.startswith('http://') or key.startswith('https://'): if key.startswith('http://') or key.startswith('https://'):
path = key path = key
elif key.startswith('/'): # internal full path. elif key.startswith('/'): # internal full path.
path = 'http://127.0.0.1:32400' + key path = 'http://127.0.0.1:32400' + key
else: # internal path, add-on else: # internal path, add-on
path = 'http://127.0.0.1:32400' + path + '/' + key path = 'http://127.0.0.1:32400' + path + '/' + key
path = try_encode(path) path = utils.try_encode(path)
# This is bogus (note the extra path component) but ATV is stupid when it # This is bogus (note the extra path component) but ATV is stupid when it
# comes to caching images, it doesn't use querystrings. Fortunately PMS is # comes to caching images, it doesn't use querystrings. Fortunately PMS is
# lenient... # lenient...

View File

@ -1,15 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from logging import getLogger from logging import getLogger
from xbmc import sleep, executebuiltin from xbmc import sleep, executebuiltin
from downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from utils import dialog, language as lang, settings, try_encode from . import utils
import variables as v from . import variables as v
import state from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.plex_tx')
############################################################################### ###############################################################################
@ -39,7 +38,7 @@ def choose_home_user(token):
username = user['title'] username = user['title']
userlist.append(username) userlist.append(username)
# To take care of non-ASCII usernames # To take care of non-ASCII usernames
userlist_coded.append(try_encode(username)) userlist_coded.append(utils.try_encode(username))
usernumber = len(userlist) usernumber = len(userlist)
username = '' username = ''
usertoken = '' usertoken = ''
@ -47,12 +46,14 @@ def choose_home_user(token):
while trials < 3: while trials < 3:
if usernumber > 1: if usernumber > 1:
# Select user # Select user
user_select = dialog('select', lang(29999) + lang(39306), user_select = utils.dialog(
userlist_coded) 'select',
'%s%s' % (utils.lang(29999), utils.lang(39306)),
userlist_coded)
if user_select == -1: if user_select == -1:
LOG.info("No user selected.") LOG.info("No user selected.")
settings('username', value='') utils.settings('username', value='')
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID) executebuiltin('Addon.Openutils.settings(%s)' % v.ADDON_ID)
return False return False
# Only 1 user received, choose that one # Only 1 user received, choose that one
else: else:
@ -64,11 +65,11 @@ def choose_home_user(token):
pin = None pin = None
if user['protected'] == '1': if user['protected'] == '1':
LOG.debug('Asking for users PIN') LOG.debug('Asking for users PIN')
pin = dialog('input', pin = utils.dialog('input',
lang(39307) + selected_user, '%s%s' % (utils.lang(39307), selected_user),
'', '',
type='{numeric}', type='{numeric}',
option='{hide}') option='{hide}')
# User chose to cancel # User chose to cancel
# Plex bug: don't call url for protected user with empty PIN # Plex bug: don't call url for protected user with empty PIN
if not pin: if not pin:
@ -78,7 +79,7 @@ def choose_home_user(token):
result = switch_home_user(user['id'], result = switch_home_user(user['id'],
pin, pin,
token, token,
settings('plex_machineIdentifier')) utils.settings('plex_machineIdentifier'))
if result: if result:
# Successfully retrieved username: break out of while loop # Successfully retrieved username: break out of while loop
username = result['username'] username = result['username']
@ -88,15 +89,16 @@ def choose_home_user(token):
else: else:
trials += 1 trials += 1
# Could not login user, please try again # Could not login user, please try again
if not dialog('yesno', if not utils.dialog('yesno',
heading='{plex}', heading='{plex}',
line1=lang(39308) + selected_user, line1='%s%s' % (utils.lang(39308),
line2=lang(39309)): selected_user),
line2=utils.lang(39309)):
# User chose to cancel # User chose to cancel
break break
if not username: if not username:
LOG.error('Failed signing in a user to plex.tv') LOG.error('Failed signing in a user to plex.tv')
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID) executebuiltin('Addon.Openutils.settings(%s)' % v.ADDON_ID)
return False return False
return { return {
'username': username, 'username': username,
@ -123,7 +125,7 @@ def switch_home_user(userid, pin, token, machineIdentifier):
for the machineIdentifier that was chosen for the machineIdentifier that was chosen
} }
settings('userid') and settings('username') with new plex token utils.settings('userid') and utils.settings('username') with new plex token
""" """
LOG.info('Switching to user %s', userid) LOG.info('Switching to user %s', userid)
url = 'https://plex.tv/api/home/users/' + userid + '/switch' url = 'https://plex.tv/api/home/users/' + userid + '/switch'
@ -143,12 +145,12 @@ def switch_home_user(userid, pin, token, machineIdentifier):
token = answer.attrib.get('authenticationToken', '') token = answer.attrib.get('authenticationToken', '')
# Write to settings file # Write to settings file
settings('username', username) utils.settings('username', username)
settings('accessToken', token) utils.settings('accessToken', token)
settings('userid', answer.attrib.get('id', '')) utils.settings('userid', answer.attrib.get('id', ''))
settings('plex_restricteduser', utils.settings('plex_restricteduser',
'true' if answer.attrib.get('restricted', '0') == '1' 'true' if answer.attrib.get('restricted', '0') == '1'
else 'false') else 'false')
state.RESTRICTED_USER = True if \ state.RESTRICTED_USER = True if \
answer.attrib.get('restricted', '0') == '1' else False answer.attrib.get('restricted', '0') == '1' else False
@ -239,15 +241,15 @@ def sign_in_with_pin():
code, identifier = get_pin() code, identifier = get_pin()
if not code: if not code:
# Problems trying to contact plex.tv. Try again later # Problems trying to contact plex.tv. Try again later
dialog('ok', heading='{plex}', line1=lang(39303)) utils.dialog('ok', heading='{plex}', line1=utils.lang(39303))
return False return False
# Go to https://plex.tv/pin and enter the code: # Go to https://plex.tv/pin and enter the code:
# Or press No to cancel the sign in. # Or press No to cancel the sign in.
answer = dialog('yesno', answer = utils.dialog('yesno',
heading='{plex}', heading='{plex}',
line1=lang(39304) + "\n\n", line1='%s%s' % (utils.lang(39304), "\n\n"),
line2=code + "\n\n", line2='%s%s' % (code, "\n\n"),
line3=lang(39311)) line3=utils.lang(39311))
if not answer: if not answer:
return False return False
count = 0 count = 0
@ -261,7 +263,7 @@ def sign_in_with_pin():
count += 1 count += 1
if xml is False: if xml is False:
# Could not sign in to plex.tv Try again later # Could not sign in to plex.tv Try again later
dialog('ok', heading='{plex}', line1=lang(39305)) utils.dialog('ok', heading='{plex}', line1=utils.lang(39305))
return False return False
# Parse xml # Parse xml
userid = xml.attrib.get('id') userid = xml.attrib.get('id')
@ -282,15 +284,15 @@ def sign_in_with_pin():
'plexid': userid, 'plexid': userid,
'homesize': home_size 'homesize': home_size
} }
settings('plexLogin', username) utils.settings('plexLogin', username)
settings('plexToken', token) utils.settings('plexToken', token)
settings('plexhome', home) utils.settings('plexhome', home)
settings('plexid', userid) utils.settings('plexid', userid)
settings('plexAvatar', avatar) utils.settings('plexAvatar', avatar)
settings('plexHomeSize', home_size) utils.settings('plexHomeSize', home_size)
# Let Kodi log into plex.tv on startup from now on # Let Kodi log into plex.tv on startup from now on
settings('myplexlogin', 'true') utils.settings('myplexlogin', 'true')
settings('plex_status', value=lang(39227)) utils.settings('plex_status', value=utils.lang(39227))
return result return result

View File

@ -7,7 +7,7 @@ from socket import error as socket_error
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.httppersist')
############################################################################### ###############################################################################

View File

@ -6,19 +6,18 @@ from re import sub
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urlparse import urlparse, parse_qs from urlparse import urlparse, parse_qs
import xbmc
from xbmc import sleep, Player, Monitor from .. import companion
from .. import json_rpc as js
from companion import process_command from .. import clientinfo
import json_rpc as js from .. import variables as v
from clientinfo import getXArgsDeviceInfo
import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.listener')
PLAYER = Player() PLAYER = xbmc.Player()
MONITOR = Monitor() MONITOR = xbmc.Monitor()
# Hack we need in order to keep track of the open connections from Plex Web # Hack we need in order to keep track of the open connections from Plex Web
CLIENT_DICT = {} CLIENT_DICT = {}
@ -122,7 +121,7 @@ class MyHandler(BaseHTTPRequestHandler):
RESOURCES_XML.format( RESOURCES_XML.format(
title=v.DEVICENAME, title=v.DEVICENAME,
machineIdentifier=v.PKC_MACHINE_IDENTIFIER), machineIdentifier=v.PKC_MACHINE_IDENTIFIER),
getXArgsDeviceInfo(include_token=False)) clientinfo.getXArgsDeviceInfo(include_token=False))
elif request_path == 'player/timeline/poll': elif request_path == 'player/timeline/poll':
# Plex web does polling if connected to PKC via Companion # Plex web does polling if connected to PKC via Companion
# Only reply if there is indeed something playing # Only reply if there is indeed something playing
@ -188,7 +187,7 @@ class MyHandler(BaseHTTPRequestHandler):
code=500) code=500)
elif "/subscribe" in request_path: elif "/subscribe" in request_path:
self.response(v.COMPANION_OK_MESSAGE, self.response(v.COMPANION_OK_MESSAGE,
getXArgsDeviceInfo(include_token=False)) clientinfo.getXArgsDeviceInfo(include_token=False))
protocol = params.get('protocol') protocol = params.get('protocol')
host = self.client_address[0] host = self.client_address[0]
port = params.get('port') port = params.get('port')
@ -201,14 +200,14 @@ class MyHandler(BaseHTTPRequestHandler):
command_id) command_id)
elif "/unsubscribe" in request_path: elif "/unsubscribe" in request_path:
self.response(v.COMPANION_OK_MESSAGE, self.response(v.COMPANION_OK_MESSAGE,
getXArgsDeviceInfo(include_token=False)) clientinfo.getXArgsDeviceInfo(include_token=False))
uuid = self.headers.get('X-Plex-Client-Identifier') \ uuid = self.headers.get('X-Plex-Client-Identifier') \
or self.client_address[0] or self.client_address[0]
sub_mgr.remove_subscriber(uuid) sub_mgr.remove_subscriber(uuid)
else: else:
# Throw it to companion.py # Throw it to companion.py
process_command(request_path, params) companion.process_command(request_path, params)
self.response('', getXArgsDeviceInfo(include_token=False)) self.response('', clientinfo.getXArgsDeviceInfo(include_token=False))
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):

View File

@ -25,16 +25,15 @@ import logging
import socket import socket
import threading import threading
import time import time
from xbmc import sleep from xbmc import sleep
import downloadutils from ..downloadutils import DownloadUtils as DU
from utils import window, settings, dialog, language from .. import utils
import variables as v from .. import variables as v
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) log = logging.getLogger('PLEX.plexgdm')
############################################################################### ###############################################################################
@ -49,7 +48,7 @@ class plexgdm:
self._multicast_address = '239.0.0.250' self._multicast_address = '239.0.0.250'
self.discover_group = (self._multicast_address, 32414) self.discover_group = (self._multicast_address, 32414)
self.client_register_group = (self._multicast_address, 32413) self.client_register_group = (self._multicast_address, 32413)
self.client_update_port = int(settings('companionUpdatePort')) self.client_update_port = int(utils.settings('companionUpdatePort'))
self.server_list = [] self.server_list = []
self.discovery_interval = 120 self.discovery_interval = 120
@ -58,7 +57,7 @@ class plexgdm:
self._registration_is_running = False self._registration_is_running = False
self.client_registered = False self.client_registered = False
self.download = downloadutils.DownloadUtils().downloadUrl self.download = DU().downloadUrl
def clientDetails(self): def clientDetails(self):
self.client_data = ( self.client_data = (
@ -120,14 +119,15 @@ class plexgdm:
log.error("Unable to bind to port [%s] - Plex Companion will not " log.error("Unable to bind to port [%s] - Plex Companion will not "
"be registered. Change the Plex Companion update port!" "be registered. Change the Plex Companion update port!"
% self.client_update_port) % self.client_update_port)
if settings('companion_show_gdm_port_warning') == 'true': if utils.settings('companion_show_gdm_port_warning') == 'true':
if dialog('yesno', if utils.dialog('yesno',
language(29999), utils.lang(29999),
'Port %s' % self.client_update_port, 'Port %s' % self.client_update_port,
language(39079), utils.lang(39079),
yeslabel=language(30013), # Never show again yeslabel=utils.lang(30013), # Never show again
nolabel=language(30012)): # OK nolabel=utils.lang(30012)): # OK
settings('companion_show_gdm_port_warning', value='false') utils.settings('companion_show_gdm_port_warning',
value='false')
from xbmc import executebuiltin from xbmc import executebuiltin
executebuiltin( executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)') 'Addon.OpenSettings(plugin.video.plexkodiconnect)')
@ -189,7 +189,7 @@ class plexgdm:
log.info("Server list is empty. Unable to check") log.info("Server list is empty. Unable to check")
return False return False
for server in self.server_list: for server in self.server_list:
if server['uuid'] == window('plex_machineIdentifier'): if server['uuid'] == utils.window('plex_machineIdentifier'):
media_server = server['server'] media_server = server['server']
media_port = server['port'] media_port = server['port']
scheme = server['protocol'] scheme = server['protocol']
@ -223,7 +223,7 @@ class plexgdm:
return self.server_list return self.server_list
def discover(self): def discover(self):
currServer = window('pms_server') currServer = utils.window('pms_server')
if not currServer: if not currServer:
return return
currServerProt, currServerIP, currServerPort = \ currServerProt, currServerIP, currServerPort = \
@ -240,9 +240,9 @@ class plexgdm:
'owned': '1', 'owned': '1',
'role': 'master', 'role': 'master',
'server': currServerIP, 'server': currServerIP,
'serverName': window('plex_servername'), 'serverName': utils.window('plex_servername'),
'updated': int(time.time()), 'updated': int(time.time()),
'uuid': window('plex_machineIdentifier'), 'uuid': utils.window('plex_machineIdentifier'),
'version': 'irrelevant' 'version': 'irrelevant'
}] }]
@ -305,5 +305,5 @@ class plexgdm:
def start_all(self, daemon=False): def start_all(self, daemon=False):
self.start_discovery(daemon) self.start_discovery(daemon)
if settings('plexCompanion') == 'true': if utils.settings('plexCompanion') == 'true':
self.start_registration(daemon) self.start_registration(daemon)

View File

@ -3,22 +3,17 @@ Manages getting playstate from Kodi and sending it to the PMS as well as
subscribed Plex Companion clients. subscribed Plex Companion clients.
""" """
from logging import getLogger from logging import getLogger
from threading import Thread, RLock from threading import Thread
from downloadutils import DownloadUtils as DU from ..downloadutils import DownloadUtils as DU
from utils import window, kodi_time_to_millis, LockFunction from .. import utils
import state from .. import state
import variables as v from .. import variables as v
import json_rpc as js from .. import json_rpc as js
import playqueue as PQ from .. import playqueue as PQ
############################################################################### ###############################################################################
LOG = getLogger('PLEX.subscribers')
LOG = getLogger("PLEX." + __name__)
# Need to lock all methods and functions messing with subscribers or state
LOCK = RLock()
LOCKER = LockFunction(LOCK)
############################################################################### ###############################################################################
# What is Companion controllable? # What is Companion controllable?
@ -150,7 +145,7 @@ class SubscriptionMgr(object):
position = info['position'] position = info['position']
return position return position
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def msg(self, players): def msg(self, players):
""" """
Returns a timeline xml as str Returns a timeline xml as str
@ -206,12 +201,12 @@ class SubscriptionMgr(object):
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO, v.PLEX_PLAYLIST_TYPE_PHOTO): if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO, v.PLEX_PLAYLIST_TYPE_PHOTO):
self.location = 'fullScreenVideo' self.location = 'fullScreenVideo'
self.stop_sent_to_web = False self.stop_sent_to_web = False
pbmc_server = window('pms_server') pbmc_server = utils.window('pms_server')
if pbmc_server: if pbmc_server:
(self.protocol, self.server, self.port) = pbmc_server.split(':') (self.protocol, self.server, self.port) = pbmc_server.split(':')
self.server = self.server.replace('/', '') self.server = self.server.replace('/', '')
status = 'paused' if int(info['speed']) == 0 else 'playing' status = 'paused' if int(info['speed']) == 0 else 'playing'
duration = kodi_time_to_millis(info['totaltime']) duration = utils.kodi_time_to_millis(info['totaltime'])
shuffle = '1' if info['shuffled'] else '0' shuffle = '1' if info['shuffled'] else '0'
mute = '1' if info['muted'] is True else '0' mute = '1' if info['muted'] is True else '0'
answ = { answ = {
@ -219,11 +214,11 @@ class SubscriptionMgr(object):
'protocol': self.protocol, 'protocol': self.protocol,
'address': self.server, 'address': self.server,
'port': self.port, 'port': self.port,
'machineIdentifier': window('plex_machineIdentifier'), 'machineIdentifier': utils.window('plex_machineIdentifier'),
'state': status, 'state': status,
'type': ptype, 'type': ptype,
'itemType': ptype, 'itemType': ptype,
'time': kodi_time_to_millis(info['time']), 'time': utils.kodi_time_to_millis(info['time']),
'duration': duration, 'duration': duration,
'seekRange': '0-%s' % duration, 'seekRange': '0-%s' % duration,
'shuffle': shuffle, 'shuffle': shuffle,
@ -231,7 +226,7 @@ class SubscriptionMgr(object):
'volume': info['volume'], 'volume': info['volume'],
'mute': mute, 'mute': mute,
'mediaIndex': 0, # Still to implement from here 'mediaIndex': 0, # Still to implement from here
'partIndex':0, 'partIndex': 0,
'partCount': 1, 'partCount': 1,
'providerIdentifier': 'com.plexapp.plugins.library', 'providerIdentifier': 'com.plexapp.plugins.library',
} }
@ -302,7 +297,7 @@ class SubscriptionMgr(object):
return playqueue.items[position].plex_stream_index( return playqueue.items[position].plex_stream_index(
info[STREAM_DETAILS[stream_type]]['index'], stream_type) info[STREAM_DETAILS[stream_type]]['index'], stream_type)
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def update_command_id(self, uuid, command_id): def update_command_id(self, uuid, command_id):
""" """
Updates the Plex Companien client with the machine identifier uuid with Updates the Plex Companien client with the machine identifier uuid with
@ -320,8 +315,6 @@ class SubscriptionMgr(object):
for player in players.values(): for player in players.values():
info = state.PLAYER_STATES[player['playerid']] info = state.PLAYER_STATES[player['playerid']]
playqueue = PQ.PLAYQUEUES[player['playerid']] playqueue = PQ.PLAYQUEUES[player['playerid']]
LOG.debug('playqueue is: %s', playqueue)
LOG.debug('info is: %s', info)
position = self._get_correct_position(info, playqueue) position = self._get_correct_position(info, playqueue)
try: try:
item = playqueue.items[position] item = playqueue.items[position]
@ -334,7 +327,7 @@ class SubscriptionMgr(object):
return False return False
return True return True
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def notify(self): def notify(self):
""" """
Causes PKC to tell the PMS and Plex Companion players to receive a Causes PKC to tell the PMS and Plex Companion players to receive a
@ -360,8 +353,6 @@ class SubscriptionMgr(object):
def _notify_server(self, players): def _notify_server(self, players):
for typus, player in players.iteritems(): for typus, player in players.iteritems():
LOG.debug('player is %s', player)
LOG.debug('typus is %s', typus)
self._send_pms_notification( self._send_pms_notification(
player['playerid'], self._get_pms_params(player['playerid'])) player['playerid'], self._get_pms_params(player['playerid']))
try: try:
@ -386,8 +377,8 @@ class SubscriptionMgr(object):
'state': status, 'state': status,
'ratingKey': item.plex_id, 'ratingKey': item.plex_id,
'key': '/library/metadata/%s' % item.plex_id, 'key': '/library/metadata/%s' % item.plex_id,
'time': kodi_time_to_millis(info['time']), 'time': utils.kodi_time_to_millis(info['time']),
'duration': kodi_time_to_millis(info['totaltime']) 'duration': utils.kodi_time_to_millis(info['totaltime'])
} }
if info['container_key'] is not None: if info['container_key'] is not None:
# params['containerKey'] = info['container_key'] # params['containerKey'] = info['container_key']
@ -419,7 +410,7 @@ class SubscriptionMgr(object):
LOG.debug("Sent server notification with parameters: %s to %s", LOG.debug("Sent server notification with parameters: %s to %s",
xargs, url) xargs, url)
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def add_subscriber(self, protocol, host, port, uuid, command_id): def add_subscriber(self, protocol, host, port, uuid, command_id):
""" """
Adds a new Plex Companion subscriber to PKC. Adds a new Plex Companion subscriber to PKC.
@ -434,7 +425,7 @@ class SubscriptionMgr(object):
self.subscribers[subscriber.uuid] = subscriber self.subscribers[subscriber.uuid] = subscriber
return subscriber return subscriber
@LOCKER.lockthis @state.LOCKER_SUBSCRIBER.lockthis
def remove_subscriber(self, uuid): def remove_subscriber(self, uuid):
""" """
Removes a connected Plex Companion subscriber with machine identifier Removes a connected Plex Companion subscriber with machine identifier

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from . import utils
from utils import kodi_sql from . import variables as v
import variables as v
############################################################################### ###############################################################################
@ -17,7 +15,7 @@ class Get_Plex_DB():
and the db gets closed and the db gets closed
""" """
def __enter__(self): def __enter__(self):
self.plexconn = kodi_sql('plex') self.plexconn = utils.kodi_sql('plex')
return Plex_DB_Functions(self.plexconn.cursor()) return Plex_DB_Functions(self.plexconn.cursor())
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):

View File

@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
import sys
import xbmc
from . import utils
from . import userclient
from . import initialsetup
from . import kodimonitor
from . import librarysync
from . import websocket_client
from . import plex_companion
from . import plex_functions as PF
from . import command_pipeline
from . import playback_starter
from . import playqueue
from . import artwork
from . import variables as v
from . import state
from . import loghandler
###############################################################################
loghandler.config()
LOG = logging.getLogger("PLEX.service_entry")
###############################################################################
class Service():
server_online = True
warn_auth = True
user = None
ws = None
library = None
plexcompanion = None
user_running = False
ws_running = False
alexa_running = False
library_running = False
plexcompanion_running = False
kodimonitor_running = False
playback_starter_running = False
image_cache_thread_running = False
def __init__(self):
# Initial logging
LOG.info("======== START %s ========", v.ADDON_NAME)
LOG.info("Platform: %s", v.PLATFORM)
LOG.info("KODI Version: %s", v.KODILONGVERSION)
LOG.info("%s Version: %s", v.ADDON_NAME, v.ADDON_VERSION)
LOG.info("PKC Direct Paths: %s",
utils.settings('useDirectPaths') == '1')
LOG.info("Number of sync threads: %s",
utils.settings('syncThreadNumber'))
LOG.info("Full sys.argv received: %s", sys.argv)
self.monitor = xbmc.Monitor()
# Load/Reset PKC entirely - important for user/Kodi profile switch
initialsetup.reload_pkc()
def _stop_pkc(self):
"""
Kodi's abortRequested is really unreliable :-(
"""
return self.monitor.abortRequested() or state.STOP_PKC
def ServiceEntryPoint(self):
# Important: Threads depending on abortRequest will not trigger
# if profile switch happens more than once.
_stop_pkc = self._stop_pkc
monitor = self.monitor
# Server auto-detect
initialsetup.InitialSetup().setup()
# Detect playback start early on
self.command_pipeline = command_pipeline.Monitor_Window()
self.command_pipeline.start()
# Initialize important threads, handing over self for callback purposes
self.user = userclient.UserClient()
self.ws = websocket_client.PMS_Websocket()
self.alexa = websocket_client.Alexa_Websocket()
self.library = librarysync.LibrarySync()
self.plexcompanion = plex_companion.PlexCompanion()
self.specialmonitor = kodimonitor.SpecialMonitor()
self.playback_starter = playback_starter.PlaybackStarter()
self.playqueue = playqueue.PlayqueueMonitor()
if utils.settings('enableTextureCache') == "true":
self.image_cache_thread = artwork.Image_Cache_Thread()
welcome_msg = True
counter = 0
while not _stop_pkc():
if utils.window('plex_kodiProfile') != v.KODI_PROFILE:
# Profile change happened, terminate this thread and others
LOG.info("Kodi profile was: %s and changed to: %s. "
"Terminating old PlexKodiConnect thread.",
v.KODI_PROFILE, utils.window('plex_kodiProfile'))
break
# Before proceeding, need to make sure:
# 1. Server is online
# 2. User is set
# 3. User has access to the server
if utils.window('plex_online') == "true":
# Plex server is online
# Verify if user is set and has access to the server
if (self.user.user is not None) and self.user.has_access:
if not self.kodimonitor_running:
# Start up events
self.warn_auth = True
if welcome_msg is True:
# Reset authentication warnings
welcome_msg = False
utils.dialog('notification',
utils.lang(29999),
"%s %s" % (utils.lang(33000),
self.user.user),
icon='{plex}',
time=2000,
sound=False)
# Start monitoring kodi events
self.kodimonitor_running = kodimonitor.KodiMonitor()
self.specialmonitor.start()
# Start the Websocket Client
if not self.ws_running:
self.ws_running = True
self.ws.start()
# Start the Alexa thread
if (not self.alexa_running and
utils.settings('enable_alexa') == 'true'):
self.alexa_running = True
self.alexa.start()
# Start the syncing thread
if not self.library_running:
self.library_running = True
self.library.start()
# Start the Plex Companion thread
if not self.plexcompanion_running:
self.plexcompanion_running = True
self.plexcompanion.start()
if not self.playback_starter_running:
self.playback_starter_running = True
self.playback_starter.start()
self.playqueue.start()
if (not self.image_cache_thread_running and
utils.settings('enableTextureCache') == "true"):
self.image_cache_thread_running = True
self.image_cache_thread.start()
else:
if (self.user.user is None) and self.warn_auth:
# Alert user is not authenticated and suppress future
# warning
self.warn_auth = False
LOG.warn("Not authenticated yet.")
# User access is restricted.
# Keep verifying until access is granted
# unless server goes offline or Kodi is shut down.
while self.user.has_access is False:
# Verify access with an API call
self.user.check_access()
if utils.window('plex_online') != "true":
# Server went offline
break
if monitor.waitForAbort(3):
# Abort was requested while waiting. We should exit
break
else:
# Wait until Plex server is online
# or Kodi is shut down.
while not self._stop_pkc():
server = self.user.get_server()
if server is False:
# No server info set in add-on settings
pass
elif PF.check_connection(server, verifySSL=True) is False:
# Server is offline or cannot be reached
# Alert the user and suppress future warning
if self.server_online:
self.server_online = False
utils.window('plex_online', value="false")
# Suspend threads
state.SUSPEND_LIBRARY_THREAD = True
LOG.error("Plex Media Server went offline")
if utils.settings('show_pms_offline') == 'true':
utils.dialog('notification',
utils.lang(33001),
"%s %s" % (utils.lang(29999),
utils.lang(33002)),
icon='{plex}',
sound=False)
counter += 1
# Periodically check if the IP changed, e.g. per minute
if counter > 20:
counter = 0
setup = initialsetup.InitialSetup()
tmp = setup.pick_pms()
if tmp is not None:
setup.write_pms_to_settings(tmp)
else:
# Server is online
counter = 0
if not self.server_online:
# Server was offline when Kodi started.
# Wait for server to be fully established.
if monitor.waitForAbort(5):
# Abort was requested while waiting.
break
self.server_online = True
# Alert the user that server is online.
if (welcome_msg is False and
utils.settings('show_pms_offline') == 'true'):
utils.dialog('notification',
utils.lang(29999),
utils.lang(33003),
icon='{plex}',
time=5000,
sound=False)
LOG.info("Server %s is online and ready.", server)
utils.window('plex_online', value="true")
if state.AUTHENTICATED:
# Server got offline when we were authenticated.
# Hence resume threads
state.SUSPEND_LIBRARY_THREAD = False
# Start the userclient thread
if not self.user_running:
self.user_running = True
self.user.start()
break
if monitor.waitForAbort(3):
# Abort was requested while waiting.
break
if monitor.waitForAbort(0.05):
# Abort was requested while waiting. We should exit
break
# Terminating PlexKodiConnect
# Tell all threads to terminate (e.g. several lib sync threads)
state.STOP_PKC = True
utils.window('plex_service_started', clear=True)
LOG.info("======== STOP %s ========", v.ADDON_NAME)
def start():
# Safety net - Kody starts PKC twice upon first installation!
if utils.window('plex_service_started') == 'true':
EXIT = True
else:
utils.window('plex_service_started', value='true')
EXIT = False
# Delay option
DELAY = int(utils.settings('startupDelay'))
LOG.info("Delaying Plex startup by: %s sec...", DELAY)
if EXIT:
LOG.error('PKC service.py already started - exiting this instance')
elif DELAY and xbmc.Monitor().waitForAbort(DELAY):
# Start the service
LOG.info("Abort requested while waiting. PKC not started.")
else:
Service().ServiceEntryPoint()

View File

@ -1,5 +1,48 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# THREAD SAFE # THREAD SAFE
from threading import Lock, RLock
from functools import wraps
class LockFunction(object):
"""
Decorator for class methods and functions to lock them with lock.
Initialize this class first
lockfunction = LockFunction(lock), where lock is a threading.Lock() object
To then lock a function or method:
@lockfunction.lockthis
def some_function(args, kwargs)
"""
def __init__(self, lock):
self.lock = lock
def lockthis(self, func):
"""
Use this method to actually lock a function or method
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""
Wrapper construct
"""
with self.lock:
result = func(*args, **kwargs)
return result
return wrapper
# LOCKS
####################
# Need to lock all methods and functions messing with subscribers
LOCK_SUBSCRIBER = RLock()
LOCKER_SUBSCRIBER = LockFunction(LOCK_SUBSCRIBER)
# Necessary to temporarily hold back librarysync/websocket listener when doing
# a full sync
LOCK_PLAYLISTS = Lock()
LOCKER_PLAYLISTS = LockFunction(LOCK_PLAYLISTS)
# Quit PKC # Quit PKC
STOP_PKC = False STOP_PKC = False

View File

@ -7,20 +7,20 @@ from xbmc import sleep, executebuiltin, translatePath
import xbmcaddon import xbmcaddon
from xbmcvfs import exists from xbmcvfs import exists
from utils import window, settings, language as lang, thread_methods, dialog from .downloadutils import DownloadUtils as DU
from downloadutils import DownloadUtils as DU from . import utils
import plex_tv from . import plex_tv
import PlexFunctions as PF from . import plex_functions as PF
import state from . import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.userclient')
############################################################################### ###############################################################################
@thread_methods(add_suspends=['SUSPEND_USER_CLIENT']) @utils.thread_methods(add_suspends=['SUSPEND_USER_CLIENT'])
class UserClient(Thread): class UserClient(Thread):
""" """
Manage Plex users Manage Plex users
@ -54,11 +54,11 @@ class UserClient(Thread):
Get the current PMS' URL Get the current PMS' URL
""" """
# Original host # Original host
self.server_name = settings('plex_servername') self.server_name = utils.settings('plex_servername')
https = settings('https') == "true" https = utils.settings('https') == "true"
host = settings('ipaddress') host = utils.settings('ipaddress')
port = settings('port') port = utils.settings('port')
self.machine_identifier = settings('plex_machineIdentifier') self.machine_identifier = utils.settings('plex_machineIdentifier')
if not host: if not host:
LOG.debug("No server information saved.") LOG.debug("No server information saved.")
return False return False
@ -74,7 +74,8 @@ class UserClient(Thread):
self.machine_identifier = PF.GetMachineIdentifier(server) self.machine_identifier = PF.GetMachineIdentifier(server)
if not self.machine_identifier: if not self.machine_identifier:
self.machine_identifier = '' self.machine_identifier = ''
settings('plex_machineIdentifier', value=self.machine_identifier) utils.settings('plex_machineIdentifier',
value=self.machine_identifier)
LOG.debug('Returning active server: %s', server) LOG.debug('Returning active server: %s', server)
return server return server
@ -84,15 +85,15 @@ class UserClient(Thread):
Do we need to verify the SSL certificate? Return None if that is the Do we need to verify the SSL certificate? Return None if that is the
case, else False case, else False
""" """
return None if settings('sslverify') == 'true' else False return None if utils.settings('sslverify') == 'true' else False
@staticmethod @staticmethod
def get_ssl_certificate(): def get_ssl_certificate():
""" """
Client side certificate Client side certificate
""" """
return None if settings('sslcert') == 'None' \ return None if utils.settings('sslcert') == 'None' \
else settings('sslcert') else utils.settings('sslcert')
def set_user_prefs(self): def set_user_prefs(self):
""" """
@ -103,7 +104,7 @@ class UserClient(Thread):
if self.token: if self.token:
url = PF.GetUserArtworkURL(self.user) url = PF.GetUserArtworkURL(self.user)
if url: if url:
window('PlexUserImage', value=url) utils.window('PlexUserImage', value=url)
@staticmethod @staticmethod
def check_access(): def check_access():
@ -141,29 +142,32 @@ class UserClient(Thread):
state.PLEX_USER_ID = user_id or None state.PLEX_USER_ID = user_id or None
state.PLEX_USERNAME = username state.PLEX_USERNAME = username
# This is the token for the current PMS (might also be '') # This is the token for the current PMS (might also be '')
window('pms_token', value=usertoken) utils.window('pms_token', value=usertoken)
state.PMS_TOKEN = usertoken state.PMS_TOKEN = usertoken
# This is the token for plex.tv for the current user # This is the token for plex.tv for the current user
# Is only '' if user is not signed in to plex.tv # Is only '' if user is not signed in to plex.tv
window('plex_token', value=settings('plexToken')) utils.window('plex_token', value=utils.settings('plexToken'))
state.PLEX_TOKEN = settings('plexToken') or None state.PLEX_TOKEN = utils.settings('plexToken') or None
window('plex_restricteduser', value=settings('plex_restricteduser')) utils.window('plex_restricteduser',
value=utils.settings('plex_restricteduser'))
state.RESTRICTED_USER = True \ state.RESTRICTED_USER = True \
if settings('plex_restricteduser') == 'true' else False if utils.settings('plex_restricteduser') == 'true' else False
window('pms_server', value=self.server) utils.window('pms_server', value=self.server)
window('plex_machineIdentifier', value=self.machine_identifier) utils.window('plex_machineIdentifier', value=self.machine_identifier)
window('plex_servername', value=self.server_name) utils.window('plex_servername', value=self.server_name)
window('plex_authenticated', value='true') utils.window('plex_authenticated', value='true')
state.AUTHENTICATED = True state.AUTHENTICATED = True
window('useDirectPaths', value='true' utils.window('useDirectPaths',
if settings('useDirectPaths') == "1" else 'false') value='true' if utils.settings('useDirectPaths') == "1"
state.DIRECT_PATHS = True if settings('useDirectPaths') == "1" \ else 'false')
state.DIRECT_PATHS = True if utils.settings('useDirectPaths') == "1" \
else False else False
state.INDICATE_MEDIA_VERSIONS = True \ state.INDICATE_MEDIA_VERSIONS = True \
if settings('indicate_media_versions') == "true" else False if utils.settings('indicate_media_versions') == "true" else False
window('plex_force_transcode_pix', value='true' utils.window('plex_force_transcode_pix',
if settings('force_transcode_pix') == "1" else 'false') value='true' if utils.settings('force_transcode_pix') == "1"
else 'false')
# Start DownloadUtils session # Start DownloadUtils session
self.do_utils = DU() self.do_utils = DU()
@ -173,9 +177,9 @@ class UserClient(Thread):
self.set_user_prefs() self.set_user_prefs()
# Writing values to settings file # Writing values to settings file
settings('username', value=username) utils.settings('username', value=username)
settings('userid', value=user_id) utils.settings('userid', value=user_id)
settings('accessToken', value=usertoken) utils.settings('accessToken', value=usertoken)
return True return True
def authenticate(self): def authenticate(self):
@ -188,9 +192,9 @@ class UserClient(Thread):
if self.retry >= 2: if self.retry >= 2:
LOG.error("Too many retries to login.") LOG.error("Too many retries to login.")
state.PMS_STATUS = 'Stop' state.PMS_STATUS = 'Stop'
dialog('ok', lang(33001), lang(39023)) utils.dialog('ok', utils.lang(33001), utils.lang(39023))
executebuiltin( executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)') 'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
return False return False
# Get /profile/addon_data # Get /profile/addon_data
@ -209,10 +213,10 @@ class UserClient(Thread):
return False return False
# If there is a username in the settings, try authenticating # If there is a username in the settings, try authenticating
username = settings('username') username = utils.settings('username')
userId = settings('userid') userId = utils.settings('userid')
usertoken = settings('accessToken') usertoken = utils.settings('accessToken')
enforceLogin = settings('enforceUserLogin') enforceLogin = utils.settings('enforceUserLogin')
# Found a user in the settings, try to authenticate # Found a user in the settings, try to authenticate
if username and enforceLogin == 'false': if username and enforceLogin == 'false':
LOG.debug('Trying to authenticate with old settings') LOG.debug('Trying to authenticate with old settings')
@ -225,15 +229,15 @@ class UserClient(Thread):
return True return True
elif answ == 401: elif answ == 401:
LOG.error("User token no longer valid. Sign user out") LOG.error("User token no longer valid. Sign user out")
settings('username', value='') utils.settings('username', value='')
settings('userid', value='') utils.settings('userid', value='')
settings('accessToken', value='') utils.settings('accessToken', value='')
else: else:
LOG.debug("Could not yet authenticate user") LOG.debug("Could not yet authenticate user")
return False return False
# Could not use settings - try to get Plex user list from plex.tv # Could not use settings - try to get Plex user list from plex.tv
plextoken = settings('plexToken') plextoken = utils.settings('plexToken')
if plextoken: if plextoken:
LOG.info("Trying to connect to plex.tv to get a user list") LOG.info("Trying to connect to plex.tv to get a user list")
userInfo = plex_tv.choose_home_user(plextoken) userInfo = plex_tv.choose_home_user(plextoken)
@ -268,24 +272,24 @@ class UserClient(Thread):
self.do_utils.stopSession() self.do_utils.stopSession()
except AttributeError: except AttributeError:
pass pass
window('plex_authenticated', clear=True) utils.window('plex_authenticated', clear=True)
state.AUTHENTICATED = False state.AUTHENTICATED = False
window('pms_token', clear=True) utils.window('pms_token', clear=True)
state.PLEX_TOKEN = None state.PLEX_TOKEN = None
state.PLEX_TRANSIENT_TOKEN = None state.PLEX_TRANSIENT_TOKEN = None
state.PMS_TOKEN = None state.PMS_TOKEN = None
window('plex_token', clear=True) utils.window('plex_token', clear=True)
window('pms_server', clear=True) utils.window('pms_server', clear=True)
window('plex_machineIdentifier', clear=True) utils.window('plex_machineIdentifier', clear=True)
window('plex_servername', clear=True) utils.window('plex_servername', clear=True)
state.PLEX_USER_ID = None state.PLEX_USER_ID = None
state.PLEX_USERNAME = None state.PLEX_USERNAME = None
window('plex_restricteduser', clear=True) utils.window('plex_restricteduser', clear=True)
state.RESTRICTED_USER = False state.RESTRICTED_USER = False
settings('username', value='') utils.settings('username', value='')
settings('userid', value='') utils.settings('userid', value='')
settings('accessToken', value='') utils.settings('accessToken', value='')
self.token = None self.token = None
self.auth = True self.auth = True
@ -313,7 +317,7 @@ class UserClient(Thread):
elif state.PMS_STATUS == "401": elif state.PMS_STATUS == "401":
# Unauthorized access, revoke token # Unauthorized access, revoke token
state.PMS_STATUS = 'Auth' state.PMS_STATUS = 'Auth'
window('plex_serverStatus', value='Auth') utils.window('plex_serverStatus', value='Auth')
self.reset_client() self.reset_client()
sleep(3000) sleep(3000)
@ -330,7 +334,7 @@ class UserClient(Thread):
LOG.info("Current userId: %s", state.PLEX_USER_ID) LOG.info("Current userId: %s", state.PLEX_USER_ID)
self.retry = 0 self.retry = 0
state.SUSPEND_LIBRARY_THREAD = False state.SUSPEND_LIBRARY_THREAD = False
window('plex_serverStatus', clear=True) utils.window('plex_serverStatus', clear=True)
state.PMS_STATUS = False state.PMS_STATUS = False
if not self.auth and (self.user is None): if not self.auth and (self.user is None):

View File

@ -4,6 +4,10 @@ Various functions and decorators for PKC
""" """
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
import xbmc
import xbmcaddon
import xbmcgui
from xbmcvfs import exists, delete
import os import os
from cProfile import Profile from cProfile import Profile
from pstats import Stats from pstats import Stats
@ -20,17 +24,12 @@ import hashlib
import re import re
import unicodedata import unicodedata
import xbmc from . import variables as v
import xbmcaddon from . import state
import xbmcgui
from xbmcvfs import exists, delete
import variables as v
import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.utils')
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect') ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
@ -51,7 +50,7 @@ def reboot_kodi(message=None):
Set optional custom message Set optional custom message
""" """
message = message or language(33033) message = message or lang(33033)
dialog('ok', heading='{plex}', line1=message) dialog('ok', heading='{plex}', line1=message)
xbmc.executebuiltin('RestartApp') xbmc.executebuiltin('RestartApp')
@ -130,7 +129,7 @@ def exists_dir(path):
return answ return answ
def language(stringid): def lang(stringid):
""" """
Central string retrieval from strings.po Central string retrieval from strings.po
""" """
@ -194,7 +193,7 @@ def dialog(typus, *args, **kwargs):
kwargs['option'] = types[kwargs['option']] kwargs['option'] = types[kwargs['option']]
if 'heading' in kwargs: if 'heading' in kwargs:
kwargs['heading'] = kwargs['heading'].replace("{plex}", kwargs['heading'] = kwargs['heading'].replace("{plex}",
language(29999)) lang(29999))
dia = xbmcgui.Dialog() dia = xbmcgui.Dialog()
types = { types = {
'yesno': dia.yesno, 'yesno': dia.yesno,
@ -487,8 +486,8 @@ def wipe_database():
connection.commit() connection.commit()
cursor.close() cursor.close()
# Reset the artwork sync status in the PKC settings # Reset the artwork sync status in the PKC settings
settings('caching_artwork_count', value=language(39310)) settings('caching_artwork_count', value=lang(39310))
settings('fanarttv_lookups', value=language(39310)) settings('fanarttv_lookups', value=lang(39310))
# reset the install run flag # reset the install run flag
settings('SyncInstallRunDone', value="false") settings('SyncInstallRunDone', value="false")
@ -500,8 +499,8 @@ def reset(ask_user=True):
""" """
# Are you sure you want to reset your local Kodi database? # Are you sure you want to reset your local Kodi database?
if ask_user and not dialog('yesno', if ask_user and not dialog('yesno',
heading='{plex} %s ' % language(30132), heading='{plex} %s ' % lang(30132),
line1=language(39600)): line1=lang(39600)):
return return
# first stop any db sync # first stop any db sync
@ -513,8 +512,8 @@ def reset(ask_user=True):
if count == 0: if count == 0:
# Could not stop the database from running. Please try again later. # Could not stop the database from running. Please try again later.
dialog('ok', dialog('ok',
heading='{plex} %s' % language(30132), heading='{plex} %s' % lang(30132),
line1=language(39601)) line1=lang(39601))
return return
xbmc.sleep(1000) xbmc.sleep(1000)
@ -524,8 +523,8 @@ def reset(ask_user=True):
# Reset all PlexKodiConnect Addon settings? (this is usually NOT # Reset all PlexKodiConnect Addon settings? (this is usually NOT
# recommended and unnecessary!) # recommended and unnecessary!)
if ask_user and dialog('yesno', if ask_user and dialog('yesno',
heading='{plex} %s ' % language(30132), heading='{plex} %s ' % lang(30132),
line1=language(39603)): line1=lang(39603)):
# Delete the settings # Delete the settings
addon = xbmcaddon.Addon() addon = xbmcaddon.Addon()
addondir = try_decode(xbmc.translatePath(addon.getAddonInfo('profile'))) addondir = try_decode(xbmc.translatePath(addon.getAddonInfo('profile')))
@ -708,7 +707,7 @@ class XmlKodiSetting(object):
LOG.error('Error parsing %s', self.path) LOG.error('Error parsing %s', self.path)
# "Kodi cannot parse {0}. PKC will not function correctly. Please # "Kodi cannot parse {0}. PKC will not function correctly. Please
# visit {1} and correct your file!" # visit {1} and correct your file!"
dialog('ok', language(29999), language(39716).format( dialog('ok', lang(29999), lang(39716).format(
self.filename, self.filename,
'http://kodi.wiki')) 'http://kodi.wiki'))
self.__exit__(etree.ParseError, None, None) self.__exit__(etree.ParseError, None, None)
@ -861,7 +860,7 @@ def passwords_xml():
LOG.error('Error parsing %s', xmlpath) LOG.error('Error parsing %s', xmlpath)
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit # "Kodi cannot parse {0}. PKC will not function correctly. Please visit
# {1} and correct your file!" # {1} and correct your file!"
dialog('ok', language(29999), language(39716).format( dialog('ok', lang(29999), lang(39716).format(
'passwords.xml', 'http://forum.kodi.tv/')) 'passwords.xml', 'http://forum.kodi.tv/'))
return return
else: else:
@ -1192,33 +1191,3 @@ def thread_methods(cls=None, add_stops=None, add_suspends=None):
# Return class to render this a decorator # Return class to render this a decorator
return cls return cls
class LockFunction(object):
"""
Decorator for class methods and functions to lock them with lock.
Initialize this class first
lockfunction = LockFunction(lock), where lock is a threading.Lock() object
To then lock a function or method:
@lockfunction.lockthis
def some_function(args, kwargs)
"""
def __init__(self, lock):
self.lock = lock
def lockthis(self, func):
"""
Use this method to actually lock a function or method
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""
Wrapper construct
"""
with self.lock:
result = func(*args, **kwargs)
return result
return wrapper

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import xbmc import xbmc
from xbmcaddon import Addon from xbmcaddon import Addon

View File

@ -4,18 +4,16 @@ from logging import getLogger
from distutils import dir_util from distutils import dir_util
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from os import makedirs from os import makedirs
import xbmc import xbmc
from xbmcvfs import exists from xbmcvfs import exists
from utils import window, settings, language as lang, try_encode, indent, \ from . import utils
normalize_nodes, exists_dir, try_decode from . import variables as v
import variables as v from . import state
import state
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger('PLEX.videonodes')
############################################################################### ###############################################################################
# Paths are strings, NOT unicode! # Paths are strings, NOT unicode!
@ -30,21 +28,26 @@ class VideoNodes(object):
root = etree.Element('node', attrib={'order': "%s" % order}) root = etree.Element('node', attrib={'order': "%s" % order})
elif roottype == 1: elif roottype == 1:
# Filter # Filter
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"}) root = etree.Element('node',
attrib={'order': "%s" % order, 'type': "filter"})
etree.SubElement(root, 'match').text = "all" etree.SubElement(root, 'match').text = "all"
# Add tag rule # Add tag rule
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"}) rule = etree.SubElement(root,
'rule',
attrib={'field': "tag", 'operator': "is"})
etree.SubElement(rule, 'value').text = tagname etree.SubElement(rule, 'value').text = tagname
else: else:
# Folder # Folder
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"}) root = etree.Element('node',
attrib={'order': "%s" % order, 'type': "folder"})
etree.SubElement(root, 'label').text = label etree.SubElement(root, 'label').text = label
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.plexkodiconnect/icon.png" etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.plexkodiconnect/icon.png"
return root return root
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid,
delete=False):
# Plex: reassign mediatype due to Kodi inner workings # Plex: reassign mediatype due to Kodi inner workings
# How many items do we get at most? # How many items do we get at most?
limit = state.FETCH_PMS_ITEM_NUMBER limit = state.FETCH_PMS_ITEM_NUMBER
@ -63,32 +66,32 @@ class VideoNodes(object):
dirname = viewid dirname = viewid
# Returns strings # Returns strings
path = try_decode(xbmc.translatePath( path = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/")) "special://profile/library/video/"))
nodepath = try_decode(xbmc.translatePath( nodepath = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/Plex-%s/" % dirname)) "special://profile/library/video/Plex-%s/" % dirname))
if delete: if delete:
if exists_dir(nodepath): if utils.exists_dir(nodepath):
from shutil import rmtree from shutil import rmtree
rmtree(nodepath) rmtree(nodepath)
log.info("Sucessfully removed videonode: %s." % tagname) LOG.info("Sucessfully removed videonode: %s." % tagname)
return return
# Verify the video directory # Verify the video directory
if not exists_dir(path): if not utils.exists_dir(path):
dir_util.copy_tree( dir_util.copy_tree(
src=try_decode( src=utils.try_decode(
xbmc.translatePath("special://xbmc/system/library/video")), xbmc.translatePath("special://xbmc/system/library/video")),
dst=try_decode( dst=utils.try_decode(
xbmc.translatePath("special://profile/library/video")), xbmc.translatePath("special://profile/library/video")),
preserve_mode=0) # do not copy permission bits! preserve_mode=0) # do not copy permission bits!
# Create the node directory # Create the node directory
if mediatype != "photos": if mediatype != "photos":
if not exists_dir(nodepath): if not utils.exists_dir(nodepath):
# folder does not exist yet # folder does not exist yet
log.debug('Creating folder %s' % nodepath) LOG.debug('Creating folder %s' % nodepath)
makedirs(nodepath) makedirs(nodepath)
# Create index entry # Create index entry
@ -97,13 +100,13 @@ class VideoNodes(object):
path = "library://video/Plex-%s/" % dirname path = "library://video/Plex-%s/" % dirname
for i in range(1, indexnumber): for i in range(1, indexnumber):
# Verify to make sure we don't create duplicates # Verify to make sure we don't create duplicates
if window('Plex.nodes.%s.index' % i) == path: if utils.window('Plex.nodes.%s.index' % i) == path:
return return
if mediatype == "photos": if mediatype == "photos":
path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid) path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid)
window('Plex.nodes.%s.index' % indexnumber, value=path) utils.window('Plex.nodes.%s.index' % indexnumber, value=path)
# Root # Root
if not mediatype == "photos": if not mediatype == "photos":
@ -119,7 +122,7 @@ class VideoNodes(object):
tagname=tagname, tagname=tagname,
roottype=0) roottype=0)
try: try:
indent(root) utils.indent(root)
except: except:
pass pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8") etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
@ -222,14 +225,15 @@ class VideoNodes(object):
# Get label # Get label
stringid = nodes[node] stringid = nodes[node]
if node != "1": if node != "1":
label = lang(stringid) label = utils.lang(stringid)
if not label: if not label:
label = xbmc.getLocalizedString(stringid) label = xbmc.getLocalizedString(stringid)
else: else:
label = stringid label = stringid
# Set window properties # Set window properties
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all": if ((mediatype == "homevideos" or mediatype == "photos") and
nodetype == "all"):
# Custom query # Custom query
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s" path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s"
% (viewid, mediatype)) % (viewid, mediatype))
@ -278,34 +282,39 @@ class VideoNodes(object):
templabel = label templabel = label
embynode = "Plex.nodes.%s" % indexnumber embynode = "Plex.nodes.%s" % indexnumber
window('%s.title' % embynode, value=templabel) utils.window('%s.title' % embynode, value=templabel)
window('%s.path' % embynode, value=windowpath) utils.window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path) utils.window('%s.content' % embynode, value=path)
window('%s.type' % embynode, value=mediatype) utils.window('%s.type' % embynode, value=mediatype)
else: else:
embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype) embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype)
window('%s.title' % embynode, value=label) utils.window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath) utils.window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path) utils.window('%s.content' % embynode, value=path)
if mediatype == "photos": if mediatype == "photos":
# For photos, we do not create a node in videos but we do want the window props # For photos, we do not create a node in videos but we do want
# to be created. # the window props to be created. To do: add our photos nodes to
# To do: add our photos nodes to kodi picture sources somehow # kodi picture sources somehow
continue continue
if exists(try_encode(nodeXML)): if exists(utils.try_encode(nodeXML)):
# Don't recreate xml if already exists # Don't recreate xml if already exists
continue continue
# Create the root # Create the root
if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"): if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"):
# Folder type with plugin path # Folder type with plugin path
root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname, roottype=2) root = self.commonRoot(order=sortorder[node],
label=label,
tagname=tagname,
roottype=2)
etree.SubElement(root, 'path').text = path etree.SubElement(root, 'path').text = path
etree.SubElement(root, 'content').text = "episodes" etree.SubElement(root, 'content').text = "episodes"
else: else:
root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname) root = self.commonRoot(order=sortorder[node],
label=label,
tagname=tagname)
if nodetype in ('recentepisodes', 'inprogressepisodes'): if nodetype in ('recentepisodes', 'inprogressepisodes'):
etree.SubElement(root, 'content').text = "episodes" etree.SubElement(root, 'content').text = "episodes"
else: else:
@ -313,20 +322,24 @@ class VideoNodes(object):
# Elements per nodetype # Elements per nodetype
if nodetype == "all": if nodetype == "all":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root,
'order',
{'direction': "ascending"}).text = "sorttitle"
elif nodetype == "recent": elif nodetype == "recent":
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" etree.SubElement(root,
'order',
{'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'limit').text = limit
if settings('MovieShowWatched') == 'false': if utils.settings('MovieShowWatched') == 'false':
rule = etree.SubElement(root, rule = etree.SubElement(root,
'rule', 'rule',
{'field': "playcount", {'field': "playcount",
'operator': "is"}) 'operator': "is"})
etree.SubElement(rule, 'value').text = "0" etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogress": elif nodetype == "inprogress":
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"}) etree.SubElement(root,
'rule',
{'field': "inprogress", 'operator': "true"})
etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'limit').text = limit
etree.SubElement( etree.SubElement(
root, root,
@ -335,54 +348,67 @@ class VideoNodes(object):
).text = 'lastplayed' ).text = 'lastplayed'
elif nodetype == "genres": elif nodetype == "genres":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root,
'order',
{'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'group').text = "genres" etree.SubElement(root, 'group').text = "genres"
elif nodetype == "unwatched": elif nodetype == "unwatched":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root,
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"}) 'order',
{'direction': "ascending"}).text = "sorttitle"
rule = etree.SubElement(root,
"rule",
{'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0" etree.SubElement(rule, 'value').text = "0"
elif nodetype == "sets": elif nodetype == "sets":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root,
'order',
{'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'group').text = "tags" etree.SubElement(root, 'group').text = "tags"
elif nodetype == "random": elif nodetype == "random":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random" etree.SubElement(root,
'order',
{'direction': "ascending"}).text = "random"
etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'limit').text = limit
elif nodetype == "recommended": elif nodetype == "recommended":
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating" etree.SubElement(root,
'order',
{'direction': "descending"}).text = "rating"
etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) rule = etree.SubElement(root,
'rule',
{'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0" etree.SubElement(rule, 'value').text = "0"
rule2 = etree.SubElement(root, 'rule', rule2 = etree.SubElement(root,
attrib={'field': "rating", 'operator': "greaterthan"}) 'rule',
attrib={'field': "rating", 'operator': "greaterthan"})
etree.SubElement(rule2, 'value').text = "7" etree.SubElement(rule2, 'value').text = "7"
elif nodetype == "recentepisodes": elif nodetype == "recentepisodes":
# Kodi Isengard, Jarvis # Kodi Isengard, Jarvis
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" etree.SubElement(root,
'order',
{'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) rule = etree.SubElement(root,
'rule',
{'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0" etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogressepisodes": elif nodetype == "inprogressepisodes":
# Kodi Isengard, Jarvis # Kodi Isengard, Jarvis
etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', rule = etree.SubElement(root,
attrib={'field': "inprogress", 'operator':"true"}) 'rule',
attrib={'field': "inprogress", 'operator':"true"})
try: try:
indent(root) utils.indent(root)
except: except:
pass pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8") etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
def singleNode(self, indexnumber, tagname, mediatype, itemtype): def singleNode(self, indexnumber, tagname, mediatype, itemtype):
tagname = try_encode(tagname) tagname = utils.try_encode(tagname)
cleantagname = try_decode(normalize_nodes(tagname)) cleantagname = utils.try_decode(utils.normalize_nodes(tagname))
nodepath = try_decode(xbmc.translatePath( nodepath = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/")) "special://profile/library/video/"))
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
path = "library://video/plex_%s.xml" % cleantagname path = "library://video/plex_%s.xml" % cleantagname
@ -393,12 +419,12 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Video,%s,return)" % path windowpath = "ActivateWindow(Video,%s,return)" % path
# Create the video node directory # Create the video node directory
if not exists_dir(nodepath): if not utils.exists_dir(nodepath):
# We need to copy over the default items # We need to copy over the default items
dir_util.copy_tree( dir_util.copy_tree(
src=try_decode( src=utils.try_decode(
xbmc.translatePath("special://xbmc/system/library/video")), xbmc.translatePath("special://xbmc/system/library/video")),
dst=try_decode( dst=utils.try_decode(
xbmc.translatePath("special://profile/library/video")), xbmc.translatePath("special://profile/library/video")),
preserve_mode=0) # do not copy permission bits! preserve_mode=0) # do not copy permission bits!
@ -407,14 +433,14 @@ class VideoNodes(object):
'Favorite tvshows': 30181, 'Favorite tvshows': 30181,
'channels': 30173 'channels': 30173
} }
label = lang(labels[tagname]) label = utils.lang(labels[tagname])
embynode = "Plex.nodes.%s" % indexnumber embynode = "Plex.nodes.%s" % indexnumber
window('%s.title' % embynode, value=label) utils.window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath) utils.window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path) utils.window('%s.content' % embynode, value=path)
window('%s.type' % embynode, value=itemtype) utils.window('%s.type' % embynode, value=itemtype)
if exists(try_encode(nodeXML)): if exists(utils.try_encode(nodeXML)):
# Don't recreate xml if already exists # Don't recreate xml if already exists
return return
@ -423,23 +449,26 @@ class VideoNodes(object):
label=label, label=label,
tagname=tagname, tagname=tagname,
roottype=2) roottype=2)
etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels" etree.SubElement(root,
'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels"
else: else:
root = self.commonRoot(order=1, label=label, tagname=tagname) root = self.commonRoot(order=1, label=label, tagname=tagname)
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root,
'order',
{'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'content').text = mediatype etree.SubElement(root, 'content').text = mediatype
try: try:
indent(root) utils.indent(root)
except: except:
pass pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8") etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
def clearProperties(self): def clearProperties(self):
log.info("Clearing nodes properties.") LOG.info("Clearing nodes properties.")
plexprops = window('Plex.nodes.total') plexprops = utils.window('Plex.nodes.total')
propnames = [ propnames = [
"index","path","title","content", "index","path","title","content",
"inprogress.content","inprogress.title", "inprogress.content","inprogress.title",
@ -457,4 +486,5 @@ class VideoNodes(object):
totalnodes = int(plexprops) totalnodes = int(plexprops)
for i in range(totalnodes): for i in range(totalnodes):
for prop in propnames: for prop in propnames:
window('Plex.nodes.%s.%s' % (str(i), prop), clear=True) utils.window('Plex.nodes.%s.%s' % (str(i), prop),
clear=True)

View File

@ -88,9 +88,9 @@ Event Handler Classes
import os.path import os.path
import logging import logging
import re import re
from pathtools.patterns import match_any_paths from ..pathtools.patterns import match_any_paths
from watchdog.utils import has_attribute from .utils import has_attribute
from watchdog.utils import unicode_paths from .utils import unicode_paths
EVENT_TYPE_MOVED = 'moved' EVENT_TYPE_MOVED = 'moved'

View File

@ -55,8 +55,8 @@ Class Platforms Note
""" """
import warnings import warnings
from watchdog.utils import platform from ..utils import platform
from watchdog.utils import UnsupportedLibc from ..utils import UnsupportedLibc
if platform.is_linux(): if platform.is_linux():
try: try:

View File

@ -18,9 +18,9 @@
from __future__ import with_statement from __future__ import with_statement
import threading import threading
from watchdog.utils import BaseThread from ..utils import BaseThread
from watchdog.utils.compat import queue from ..utils.compat import queue
from watchdog.utils.bricks import SkipRepeatsQueue from ..utils.bricks import SkipRepeatsQueue
DEFAULT_EMITTER_TIMEOUT = 1 # in seconds. DEFAULT_EMITTER_TIMEOUT = 1 # in seconds.
DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds. DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds.

View File

@ -30,7 +30,7 @@ import threading
import unicodedata import unicodedata
import _watchdog_fsevents as _fsevents import _watchdog_fsevents as _fsevents
from watchdog.events import ( from ..events import (
FileDeletedEvent, FileDeletedEvent,
FileModifiedEvent, FileModifiedEvent,
FileCreatedEvent, FileCreatedEvent,
@ -41,8 +41,8 @@ from watchdog.events import (
DirMovedEvent DirMovedEvent
) )
from watchdog.utils.dirsnapshot import DirectorySnapshot from ..utils.dirsnapshot import DirectorySnapshot
from watchdog.observers.api import ( from ..observers.api import (
BaseObserver, BaseObserver,
EventEmitter, EventEmitter,
DEFAULT_EMITTER_TIMEOUT, DEFAULT_EMITTER_TIMEOUT,

View File

@ -24,9 +24,9 @@ import os
import logging import logging
import unicodedata import unicodedata
from threading import Thread from threading import Thread
from watchdog.utils.compat import queue from ..utils.compat import queue
from watchdog.events import ( from ..events import (
FileDeletedEvent, FileDeletedEvent,
FileModifiedEvent, FileModifiedEvent,
FileCreatedEvent, FileCreatedEvent,
@ -36,7 +36,7 @@ from watchdog.events import (
DirCreatedEvent, DirCreatedEvent,
DirMovedEvent DirMovedEvent
) )
from watchdog.observers.api import ( from ..observers.api import (
BaseObserver, BaseObserver,
EventEmitter, EventEmitter,
DEFAULT_EMITTER_TIMEOUT, DEFAULT_EMITTER_TIMEOUT,

View File

@ -73,14 +73,14 @@ import os
import threading import threading
from .inotify_buffer import InotifyBuffer from .inotify_buffer import InotifyBuffer
from watchdog.observers.api import ( from ..observers.api import (
EventEmitter, EventEmitter,
BaseObserver, BaseObserver,
DEFAULT_EMITTER_TIMEOUT, DEFAULT_EMITTER_TIMEOUT,
DEFAULT_OBSERVER_TIMEOUT DEFAULT_OBSERVER_TIMEOUT
) )
from watchdog.events import ( from ..events import (
DirDeletedEvent, DirDeletedEvent,
DirModifiedEvent, DirModifiedEvent,
DirMovedEvent, DirMovedEvent,
@ -92,7 +92,7 @@ from watchdog.events import (
generate_sub_moved_events, generate_sub_moved_events,
generate_sub_created_events, generate_sub_created_events,
) )
from watchdog.utils import unicode_paths from ..utils import unicode_paths
class InotifyEmitter(EventEmitter): class InotifyEmitter(EventEmitter):

View File

@ -15,9 +15,9 @@
# limitations under the License. # limitations under the License.
import logging import logging
from watchdog.utils import BaseThread from ..utils import BaseThread
from watchdog.utils.delayed_queue import DelayedQueue from ..utils.delayed_queue import DelayedQueue
from watchdog.observers.inotify_c import Inotify from ..observers.inotify_c import Inotify
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -24,8 +24,8 @@ import ctypes
import ctypes.util import ctypes.util
from functools import reduce from functools import reduce
from ctypes import c_int, c_char_p, c_uint32 from ctypes import c_int, c_char_p, c_uint32
from watchdog.utils import has_attribute from ..utils import has_attribute
from watchdog.utils import UnsupportedLibc from ..utils import UnsupportedLibc
def _load_libc(): def _load_libc():

View File

@ -78,7 +78,7 @@ Collections and Utility Classes
""" """
from __future__ import with_statement from __future__ import with_statement
from watchdog.utils import platform from ..utils import platform
import threading import threading
import errno import errno
@ -94,18 +94,18 @@ if sys.version_info < (2, 7, 0):
else: else:
import select import select
from pathtools.path import absolute_path from ...pathtools.path import absolute_path
from watchdog.observers.api import ( from ..observers.api import (
BaseObserver, BaseObserver,
EventEmitter, EventEmitter,
DEFAULT_OBSERVER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT,
DEFAULT_EMITTER_TIMEOUT DEFAULT_EMITTER_TIMEOUT
) )
from watchdog.utils.dirsnapshot import DirectorySnapshot from ..utils.dirsnapshot import DirectorySnapshot
from watchdog.events import ( from ..events import (
DirMovedEvent, DirMovedEvent,
DirDeletedEvent, DirDeletedEvent,
DirCreatedEvent, DirCreatedEvent,

View File

@ -38,16 +38,17 @@ from __future__ import with_statement
import os import os
import threading import threading
from functools import partial from functools import partial
from watchdog.utils import stat as default_stat from ..utils import stat as default_stat
from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff from ..utils.dirsnapshot import DirectorySnapshot, \
from watchdog.observers.api import ( DirectorySnapshotDiff
from ..observers.api import (
EventEmitter, EventEmitter,
BaseObserver, BaseObserver,
DEFAULT_OBSERVER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT,
DEFAULT_EMITTER_TIMEOUT DEFAULT_EMITTER_TIMEOUT
) )
from watchdog.events import ( from ..events import (
DirMovedEvent, DirMovedEvent,
DirDeletedEvent, DirDeletedEvent,
DirCreatedEvent, DirCreatedEvent,

View File

@ -24,7 +24,7 @@ import threading
import os.path import os.path
import time import time
from watchdog.events import ( from ..events import (
DirCreatedEvent, DirCreatedEvent,
DirDeletedEvent, DirDeletedEvent,
DirMovedEvent, DirMovedEvent,
@ -37,14 +37,14 @@ from watchdog.events import (
generate_sub_created_events, generate_sub_created_events,
) )
from watchdog.observers.api import ( from ..observers.api import (
EventEmitter, EventEmitter,
BaseObserver, BaseObserver,
DEFAULT_OBSERVER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT,
DEFAULT_EMITTER_TIMEOUT DEFAULT_EMITTER_TIMEOUT
) )
from watchdog.observers.winapi import ( from ..observers.winapi import (
read_events, read_events,
get_directory_handle, get_directory_handle,
close_directory_handle, close_directory_handle,

View File

@ -22,8 +22,8 @@ import signal
import subprocess import subprocess
import time import time
from watchdog.utils import echo, has_attribute from ..utils import echo, has_attribute
from watchdog.events import PatternMatchingEventHandler from ..events import PatternMatchingEventHandler
class Trick(PatternMatchingEventHandler): class Trick(PatternMatchingEventHandler):

View File

@ -33,9 +33,8 @@ Classes
import os import os
import sys import sys
import threading import threading
import watchdog.utils.platform from . import platform
from watchdog.utils.compat import Event from .compat import Event
from collections import namedtuple
if sys.version_info[0] == 2 and platform.is_windows(): if sys.version_info[0] == 2 and platform.is_windows():

View File

@ -24,6 +24,6 @@ except ImportError:
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
from watchdog.utils.event_backport import Event from .event_backport import Event
else: else:
from threading import Event from threading import Event

View File

@ -47,8 +47,7 @@ Classes
import errno import errno
import os import os
from stat import S_ISDIR from stat import S_ISDIR
from watchdog.utils import platform from . import stat as default_stat
from watchdog.utils import stat as default_stat
class DirectorySnapshotDiff(object): class DirectorySnapshotDiff(object):

View File

@ -24,7 +24,7 @@
import sys import sys
from watchdog.utils import platform from . import platform
try: try:
# Python 2 # Python 2

View File

@ -37,8 +37,8 @@ except ImportError:
from io import StringIO from io import StringIO
from argh import arg, aliases, ArghParser, expects_obj from argh import arg, aliases, ArghParser, expects_obj
from watchdog.version import VERSION_STRING from .version import VERSION_STRING
from watchdog.utils import load_class from .utils import load_class
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)

View File

@ -48,11 +48,11 @@ import logging
import traceback import traceback
import sys import sys
import utils from . import utils
############################################################################### ###############################################################################
LOG = logging.getLogger("PLEX." + __name__) LOG = logging.getLogger('PLEX.websocket')
############################################################################### ###############################################################################

View File

@ -7,17 +7,16 @@ from json import loads
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from threading import Thread from threading import Thread
from ssl import CERT_NONE from ssl import CERT_NONE
from xbmc import sleep from xbmc import sleep
from utils import window, settings, thread_methods from . import utils
from companion import process_command from . import companion
import state from . import state
import variables as v from . import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger('PLEX.websocket_client')
############################################################################### ###############################################################################
@ -140,14 +139,14 @@ class WebSocket(Thread):
LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__) LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD', @utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'BACKGROUND_SYNC_DISABLED']) 'BACKGROUND_SYNC_DISABLED'])
class PMS_Websocket(WebSocket): class PMS_Websocket(WebSocket):
""" """
Websocket connection with the PMS for Plex Companion Websocket connection with the PMS for Plex Companion
""" """
def getUri(self): def getUri(self):
server = window('pms_server') server = utils.window('pms_server')
# Get the appropriate prefix for the websocket # Get the appropriate prefix for the websocket
if server.startswith('https'): if server.startswith('https'):
server = "wss%s" % server[5:] server = "wss%s" % server[5:]
@ -158,7 +157,7 @@ class PMS_Websocket(WebSocket):
if state.PLEX_TOKEN: if state.PLEX_TOKEN:
uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN
sslopt = {} sslopt = {}
if settings('sslverify') == "false": if utils.settings('sslverify') == "false":
sslopt["cert_reqs"] = CERT_NONE sslopt["cert_reqs"] = CERT_NONE
LOG.debug("%s: Uri: %s, sslopt: %s", LOG.debug("%s: Uri: %s, sslopt: %s",
self.__class__.__name__, uri, sslopt) self.__class__.__name__, uri, sslopt)
@ -206,7 +205,7 @@ class Alexa_Websocket(WebSocket):
""" """
Websocket connection to talk to Amazon Alexa. Websocket connection to talk to Amazon Alexa.
Can't use thread_methods! Can't use utils.thread_methods!
""" """
thread_stopped = False thread_stopped = False
thread_suspended = False thread_suspended = False
@ -244,9 +243,9 @@ class Alexa_Websocket(WebSocket):
LOG.error('%s: Could not parse Alexa message', LOG.error('%s: Could not parse Alexa message',
self.__class__.__name__) self.__class__.__name__)
return return
process_command(message.attrib['path'][1:], message.attrib) companion.process_command(message.attrib['path'][1:], message.attrib)
# Path in thread_methods # Path in utils.thread_methods
def stop(self): def stop(self):
self.thread_stopped = True self.thread_stopped = True

View File

@ -1,298 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from __future__ import unicode_literals # nice to fix os.walk unicode from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from resources.lib import service_entry
from os import path as os_path
from sys import path as sys_path, argv
from xbmc import translatePath, Monitor
from xbmcaddon import Addon
###############################################################################
_ADDON = Addon(id='plugin.video.plexkodiconnect')
try:
_ADDON_PATH = _ADDON.getAddonInfo('path').decode('utf-8')
except TypeError:
_ADDON_PATH = _ADDON.getAddonInfo('path').decode()
try:
_BASE_RESOURCE = translatePath(os_path.join(
_ADDON_PATH,
'resources',
'lib')).decode('utf-8')
except TypeError:
_BASE_RESOURCE = translatePath(os_path.join(
_ADDON_PATH,
'resources',
'lib')).decode()
sys_path.append(_BASE_RESOURCE)
###############################################################################
from utils import settings, window, language as lang, dialog
from userclient import UserClient
import initialsetup
from kodimonitor import KodiMonitor, SpecialMonitor
from librarysync import LibrarySync
from websocket_client import PMS_Websocket, Alexa_Websocket
from PlexFunctions import check_connection
from PlexCompanion import PlexCompanion
from command_pipeline import Monitor_Window
from playback_starter import PlaybackStarter
from playqueue import PlayqueueMonitor
from artwork import Image_Cache_Thread
import variables as v
import state
###############################################################################
import loghandler
loghandler.config()
LOG = getLogger("PLEX.service")
###############################################################################
class Service(): if __name__ == "__main__":
service_entry.start()
server_online = True
warn_auth = True
user = None
ws = None
library = None
plexCompanion = None
user_running = False
ws_running = False
alexa_running = False
library_running = False
plexCompanion_running = False
kodimonitor_running = False
playback_starter_running = False
image_cache_thread_running = False
def __init__(self):
# Initial logging
LOG.info("======== START %s ========", v.ADDON_NAME)
LOG.info("Platform: %s", v.PLATFORM)
LOG.info("KODI Version: %s", v.KODILONGVERSION)
LOG.info("%s Version: %s", v.ADDON_NAME, v.ADDON_VERSION)
LOG.info("PKC Direct Paths: %s", settings('useDirectPaths') == '1')
LOG.info("Number of sync threads: %s", settings('syncThreadNumber'))
LOG.info("Full sys.argv received: %s", argv)
self.monitor = Monitor()
# Load/Reset PKC entirely - important for user/Kodi profile switch
initialsetup.reload_pkc()
def __stop_PKC(self):
"""
Kodi's abortRequested is really unreliable :-(
"""
return self.monitor.abortRequested() or state.STOP_PKC
def ServiceEntryPoint(self):
# Important: Threads depending on abortRequest will not trigger
# if profile switch happens more than once.
__stop_PKC = self.__stop_PKC
monitor = self.monitor
kodiProfile = v.KODI_PROFILE
# Server auto-detect
initialsetup.InitialSetup().setup()
# Detect playback start early on
self.command_pipeline = Monitor_Window()
self.command_pipeline.start()
# Initialize important threads, handing over self for callback purposes
self.user = UserClient()
self.ws = PMS_Websocket()
self.alexa = Alexa_Websocket()
self.library = LibrarySync()
self.plexCompanion = PlexCompanion()
self.specialMonitor = SpecialMonitor()
self.playback_starter = PlaybackStarter()
self.playqueue = PlayqueueMonitor()
if settings('enableTextureCache') == "true":
self.image_cache_thread = Image_Cache_Thread()
welcome_msg = True
counter = 0
while not __stop_PKC():
if window('plex_kodiProfile') != kodiProfile:
# Profile change happened, terminate this thread and others
LOG.info("Kodi profile was: %s and changed to: %s. "
"Terminating old PlexKodiConnect thread.",
kodiProfile, window('plex_kodiProfile'))
break
# Before proceeding, need to make sure:
# 1. Server is online
# 2. User is set
# 3. User has access to the server
if window('plex_online') == "true":
# Plex server is online
# Verify if user is set and has access to the server
if (self.user.user is not None) and self.user.has_access:
if not self.kodimonitor_running:
# Start up events
self.warn_auth = True
if welcome_msg is True:
# Reset authentication warnings
welcome_msg = False
dialog('notification',
lang(29999),
"%s %s" % (lang(33000),
self.user.user),
icon='{plex}',
time=2000,
sound=False)
# Start monitoring kodi events
self.kodimonitor_running = KodiMonitor()
self.specialMonitor.start()
# Start the Websocket Client
if not self.ws_running:
self.ws_running = True
self.ws.start()
# Start the Alexa thread
if (not self.alexa_running and
settings('enable_alexa') == 'true'):
self.alexa_running = True
self.alexa.start()
# Start the syncing thread
if not self.library_running:
self.library_running = True
self.library.start()
# Start the Plex Companion thread
if not self.plexCompanion_running:
self.plexCompanion_running = True
self.plexCompanion.start()
if not self.playback_starter_running:
self.playback_starter_running = True
self.playback_starter.start()
self.playqueue.start()
if (not self.image_cache_thread_running and
settings('enableTextureCache') == "true"):
self.image_cache_thread_running = True
self.image_cache_thread.start()
else:
if (self.user.user is None) and self.warn_auth:
# Alert user is not authenticated and suppress future
# warning
self.warn_auth = False
LOG.warn("Not authenticated yet.")
# User access is restricted.
# Keep verifying until access is granted
# unless server goes offline or Kodi is shut down.
while self.user.has_access is False:
# Verify access with an API call
self.user.check_access()
if window('plex_online') != "true":
# Server went offline
break
if monitor.waitForAbort(3):
# Abort was requested while waiting. We should exit
break
else:
# Wait until Plex server is online
# or Kodi is shut down.
while not self.__stop_PKC():
server = self.user.get_server()
if server is False:
# No server info set in add-on settings
pass
elif check_connection(server, verifySSL=True) is False:
# Server is offline or cannot be reached
# Alert the user and suppress future warning
if self.server_online:
self.server_online = False
window('plex_online', value="false")
# Suspend threads
state.SUSPEND_LIBRARY_THREAD = True
LOG.error("Plex Media Server went offline")
if settings('show_pms_offline') == 'true':
dialog('notification',
lang(33001),
"%s %s" % (lang(29999), lang(33002)),
icon='{plex}',
sound=False)
counter += 1
# Periodically check if the IP changed, e.g. per minute
if counter > 20:
counter = 0
setup = initialsetup.InitialSetup()
tmp = setup.pick_pms()
if tmp is not None:
setup.write_pms_to_settings(tmp)
else:
# Server is online
counter = 0
if not self.server_online:
# Server was offline when Kodi started.
# Wait for server to be fully established.
if monitor.waitForAbort(5):
# Abort was requested while waiting.
break
self.server_online = True
# Alert the user that server is online.
if (welcome_msg is False and
settings('show_pms_offline') == 'true'):
dialog('notification',
lang(29999),
lang(33003),
icon='{plex}',
time=5000,
sound=False)
LOG.info("Server %s is online and ready.", server)
window('plex_online', value="true")
if state.AUTHENTICATED:
# Server got offline when we were authenticated.
# Hence resume threads
state.SUSPEND_LIBRARY_THREAD = False
# Start the userclient thread
if not self.user_running:
self.user_running = True
self.user.start()
break
if monitor.waitForAbort(3):
# Abort was requested while waiting.
break
if monitor.waitForAbort(0.05):
# Abort was requested while waiting. We should exit
break
# Terminating PlexKodiConnect
# Tell all threads to terminate (e.g. several lib sync threads)
state.STOP_PKC = True
window('plex_service_started', clear=True)
LOG.info("======== STOP %s ========", v.ADDON_NAME)
# Safety net - Kody starts PKC twice upon first installation!
if window('plex_service_started') == 'true':
EXIT = True
else:
window('plex_service_started', value='true')
EXIT = False
# Delay option
DELAY = int(settings('startupDelay'))
LOG.info("Delaying Plex startup by: %s sec...", DELAY)
if EXIT:
LOG.error('PKC service.py already started - exiting this instance')
elif DELAY and Monitor().waitForAbort(DELAY):
# Start the service
LOG.info("Abort requested while waiting. PKC not started.")
else:
Service().ServiceEntryPoint()