diff --git a/addon.xml b/addon.xml
index 4803b5a2..703d6adc 100644
--- a/addon.xml
+++ b/addon.xml
@@ -3,8 +3,8 @@
-
-
+
+
video audio image
diff --git a/default.py b/default.py
index 71fc5cc7..96ae547a 100644
--- a/default.py
+++ b/default.py
@@ -1,47 +1,19 @@
# -*- coding: utf-8 -*-
###############################################################################
-from __future__ import unicode_literals # nice to fix os.walk unicode
+from __future__ import absolute_import, division, unicode_literals
import logging
-from os import path as os_path
-from sys import path as sys_path, argv
+from sys import argv
from urlparse import parse_qsl
-
-from xbmc import translatePath, sleep, executebuiltin
-from xbmcaddon import Addon
+from xbmc import sleep, executebuiltin
from xbmcgui import ListItem
from xbmcplugin import setResolvedUrl
-_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 resources.lib import entrypoint, utils, pickler, pkc_listitem, \
+ variables as v, loghandler
###############################################################################
-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()
log = logging.getLogger('PLEX.default')
@@ -104,7 +76,7 @@ class Main():
entrypoint.create_new_pms()
elif mode == 'reset':
- reset()
+ utils.reset()
elif mode == 'togglePlexTV':
entrypoint.toggle_plex_tv_sign_in()
@@ -113,41 +85,41 @@ class Main():
entrypoint.reset_authorization()
elif mode == 'passwords':
- passwords_xml()
+ utils.passwords_xml()
elif mode == 'switchuser':
entrypoint.switch_plex_user()
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
- dialog('ok', lang(29999), lang(39205))
+ utils.dialog('ok', utils.lang(29999), utils.lang(39205))
log.error('Not connected to a PMS.')
else:
if mode == 'repair':
log.info('Requesting repair lib sync')
- plex_command('RUN_LIB_SCAN', 'repair')
+ utils.plex_command('RUN_LIB_SCAN', 'repair')
elif mode == 'manualsync':
log.info('Requesting full library scan')
- plex_command('RUN_LIB_SCAN', 'full')
+ utils.plex_command('RUN_LIB_SCAN', 'full')
elif mode == 'texturecache':
log.info('Requesting texture caching of all textures')
- plex_command('RUN_LIB_SCAN', 'textures')
+ utils.plex_command('RUN_LIB_SCAN', 'textures')
elif mode == 'chooseServer':
entrypoint.choose_pms_server()
elif mode == 'refreshplaylist':
log.info('Requesting playlist/nodes refresh')
- plex_command('RUN_LIB_SCAN', 'views')
+ utils.plex_command('RUN_LIB_SCAN', 'views')
elif mode == 'deviceid':
self.deviceid()
elif mode == 'fanart':
log.info('User requested fanarttv refresh')
- plex_command('RUN_LIB_SCAN', 'fanart')
+ utils.plex_command('RUN_LIB_SCAN', 'fanart')
elif '/extrafanart' in argv[0]:
plexpath = argv[2][1:]
@@ -171,40 +143,40 @@ class Main():
"""
request = '%s&handle=%s' % (argv[2], HANDLE)
# Put the request into the 'queue'
- plex_command('PLAY', request)
+ utils.plex_command('PLAY', request)
if HANDLE == -1:
# Handle -1 received, not waiting for main thread
return
# Wait for the result
- while not pickl_window('plex_result'):
+ while not pickler.pickl_window('plex_result'):
sleep(50)
- result = unpickle_me()
+ result = pickler.unpickle_me()
if result is None:
log.error('Error encountered, aborting')
- dialog('notification',
- heading='{plex}',
- message=lang(30128),
- icon='{error}',
- time=3000)
+ utils.dialog('notification',
+ heading='{plex}',
+ message=utils.lang(30128),
+ icon='{error}',
+ time=3000)
setResolvedUrl(HANDLE, False, ListItem())
elif result.listitem:
- listitem = convert_PKC_to_listitem(result.listitem)
+ listitem = pkc_listitem.convert_pkc_to_listitem(result.listitem)
setResolvedUrl(HANDLE, True, listitem)
@staticmethod
def deviceid():
- deviceId_old = window('plex_client_Id')
+ deviceId_old = pickler.pickl_window('plex_client_Id')
from clientinfo import getDeviceId
try:
deviceId = getDeviceId(reset=True)
except Exception as 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:
log.info('Successfully removed old device ID: %s New deviceId:'
'%s' % (deviceId_old, deviceId))
# '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')
diff --git a/resources/lib/PKC_listitem.py b/resources/lib/PKC_listitem.py
index e34a0019..00956888 100644
--- a/resources/lib/PKC_listitem.py
+++ b/resources/lib/PKC_listitem.py
@@ -3,11 +3,11 @@
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'),
label2=data.get('label2'),
path=data.get('path'))
@@ -26,7 +26,7 @@ def convert_PKC_to_listitem(PKC_listitem):
return listitem
-class PKC_ListItem(object):
+class PKCListItem(object):
"""
Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data
when pickling!
diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py
index d778ad6e..c6256a63 100644
--- a/resources/lib/artwork.py
+++ b/resources/lib/artwork.py
@@ -12,18 +12,17 @@ import requests
import xbmc
from xbmcvfs import exists
-from utils import settings, language as lang, kodi_sql, try_encode, try_decode,\
- thread_methods, dialog, exists_dir
-import state
+from . import utils
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.artwork')
# Disable annoying requests warnings
requests.packages.urllib3.disable_warnings()
ARTWORK_QUEUE = Queue()
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')
###############################################################################
@@ -37,7 +36,7 @@ def double_urldecode(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):
sleep_between = 50
# Potentially issues with limited number of threads
@@ -73,20 +72,20 @@ class Image_Cache_Thread(Thread):
'Window.IsVisible(DialogAddonSettings.xml)'):
# Avoid saving '0' all the time
set_zero = True
- settings('caching_artwork_count', value='0')
+ utils.settings('caching_artwork_count', value='0')
xbmc.sleep(1000)
continue
set_zero = False
if isinstance(url, ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS:
- dialog('notification',
- heading=lang(29999),
- message=url.message,
- icon='{plex}',
- sound=False)
+ utils.dialog('notification',
+ heading=utils.lang(29999),
+ message=url.message,
+ icon='{plex}',
+ sound=False)
queue.task_done()
continue
- url = double_urlencode(try_encode(url))
+ url = double_urlencode(utils.try_encode(url))
sleeptime = 0
while True:
try:
@@ -133,14 +132,15 @@ class Image_Cache_Thread(Thread):
if (counter > 20 and not xbmc.getCondVisibility(
'Window.IsVisible(DialogAddonSettings.xml)')):
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
xbmc.sleep(sleep_between)
LOG.info("---===### Stopped Image_Cache_Thread ###===---")
class Artwork():
- enableTextureCache = settings('enableTextureCache') == "true"
+ enableTextureCache = utils.settings('enableTextureCache') == "true"
if enableTextureCache:
queue = ARTWORK_QUEUE
@@ -156,7 +156,7 @@ class Artwork():
artworks = list()
# Get all posters and fanart/background for video and music
for kind in ('video', 'music'):
- connection = kodi_sql(kind)
+ connection = utils.kodi_sql(kind)
cursor = connection.cursor()
for typus in ('poster', 'fanart'):
cursor.execute('SELECT url FROM art WHERE type == ?',
@@ -164,7 +164,7 @@ class Artwork():
artworks.extend(cursor.fetchall())
connection.close()
artworks_to_cache = list()
- connection = kodi_sql('texture')
+ connection = utils.kodi_sql('texture')
cursor = connection.cursor()
for url in artworks:
query = 'SELECT url FROM texture WHERE url == ? LIMIT 1'
@@ -175,40 +175,41 @@ class Artwork():
if not artworks_to_cache:
LOG.info('Caching of major images to Kodi texture cache done')
# Set to "None"
- settings('caching_artwork_count', value=lang(30069))
+ utils.settings('caching_artwork_count', value=utils.lang(30069))
return
length = len(artworks_to_cache)
LOG.info('Caching has not been completed - caching %s major images',
length)
- settings('caching_artwork_count', value=str(length))
+ utils.settings('caching_artwork_count', value=str(length))
# 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):
self.queue.put(url[0])
# Plex image caching done
- self.queue.put(ArtworkSyncMessage(lang(30007)))
+ self.queue.put(ArtworkSyncMessage(utils.lang(30007)))
def fullTextureCacheSync(self):
"""
This method will sync all Kodi artwork to textures13.db
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
LOG.info("Doing Image Cache Sync")
# 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")
# Remove all existing textures first
- path = try_decode(xbmc.translatePath("special://thumbnails/"))
- if exists_dir(path):
+ path = utils.try_decode(
+ xbmc.translatePath("special://thumbnails/"))
+ if utils.exists_dir(path):
rmtree(path, ignore_errors=True)
self.restore_cache_directories()
# remove all existing data from texture DB
- connection = kodi_sql('texture')
+ connection = utils.kodi_sql('texture')
cursor = connection.cursor()
query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
cursor.execute(query, ('table', ))
@@ -221,7 +222,7 @@ class Artwork():
connection.close()
# Cache all entries in video DB
- connection = kodi_sql('video')
+ connection = utils.kodi_sql('video')
cursor = connection.cursor()
# dont include actors
query = "SELECT url FROM art WHERE media_type != ?"
@@ -234,7 +235,7 @@ class Artwork():
for url in result:
self.cache_texture(url[0])
# Cache all entries in music DB
- connection = kodi_sql('music')
+ connection = utils.kodi_sql('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
@@ -309,7 +310,7 @@ class Artwork():
"""
Deleted the cached artwork with path url (if it exists)
"""
- connection = kodi_sql('texture')
+ connection = utils.kodi_sql('texture')
cursor = connection.cursor()
try:
cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
@@ -323,7 +324,7 @@ class Artwork():
path = xbmc.translatePath("special://thumbnails/%s" % cachedurl)
LOG.debug("Deleting cached thumbnail: %s", 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,))
connection.commit()
finally:
@@ -336,8 +337,8 @@ class Artwork():
"a", "b", "c", "d", "e", "f",
"Video", "plex")
for path in paths:
- makedirs(try_decode(xbmc.translatePath("special://thumbnails/%s"
- % path)))
+ makedirs(utils.try_decode(
+ xbmc.translatePath("special://thumbnails/%s" % path)))
class ArtworkSyncMessage(object):
diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py
index 05294313..edf67f0e 100644
--- a/resources/lib/clientinfo.py
+++ b/resources/lib/clientinfo.py
@@ -3,12 +3,12 @@
###############################################################################
from logging import getLogger
-from utils import window, settings
-import variables as v
+from . import utils
+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-Provides': 'client,controller,player,pubsub-player',
}
- if include_token and window('pms_token'):
- xargs['X-Plex-Token'] = window('pms_token')
+ if include_token and utils.window('pms_token'):
+ xargs['X-Plex-Token'] = utils.window('pms_token')
if options is not None:
xargs.update(options)
return xargs
@@ -60,26 +60,26 @@ def getDeviceId(reset=False):
"""
if reset is True:
v.PKC_MACHINE_IDENTIFIER = None
- window('plex_client_Id', clear=True)
- settings('plex_client_Id', value="")
+ utils.window('plex_client_Id', clear=True)
+ utils.settings('plex_client_Id', value="")
client_id = v.PKC_MACHINE_IDENTIFIER
if 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!!
if client_id != "" and reset is False:
v.PKC_MACHINE_IDENTIFIER = client_id
- window('plex_client_Id', value=client_id)
- log.info("Unique device Id plex_client_Id loaded: %s", client_id)
+ utils.window('plex_client_Id', value=client_id)
+ LOG.info("Unique device Id plex_client_Id loaded: %s", client_id)
return client_id
- log.info("Generating a new deviceid.")
+ LOG.info("Generating a new deviceid.")
from uuid import 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
- window('plex_client_Id', value=client_id)
- log.info("Unique device Id plex_client_Id generated: %s", client_id)
+ utils.window('plex_client_Id', value=client_id)
+ LOG.info("Unique device Id plex_client_Id generated: %s", client_id)
return client_id
diff --git a/resources/lib/command_pipeline.py b/resources/lib/command_pipeline.py
index 6b889445..9604a0e1 100644
--- a/resources/lib/command_pipeline.py
+++ b/resources/lib/command_pipeline.py
@@ -2,18 +2,17 @@
###############################################################################
import logging
from threading import Thread
-
from xbmc import sleep
-from utils import window, thread_methods
-import state
+from . import utils
+from . import state
###############################################################################
-LOG = logging.getLogger("PLEX." + __name__)
+LOG = logging.getLogger('PLEX.command_pipeline')
###############################################################################
-@thread_methods
+@utils.thread_methods
class Monitor_Window(Thread):
"""
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
LOG.info("----===## Starting Kodi_Play_Client ##===----")
while not stopped():
- if window('plex_command'):
- value = window('plex_command')
- window('plex_command', clear=True)
+ if utils.window('plex_command'):
+ value = utils.window('plex_command')
+ utils.window('plex_command', clear=True)
if value.startswith('PLAY-'):
queue.put(value.replace('PLAY-', ''))
elif value == 'SUSPEND_LIBRARY_THREAD-True':
diff --git a/resources/lib/companion.py b/resources/lib/companion.py
index 60aa29ce..d48aedb1 100644
--- a/resources/lib/companion.py
+++ b/resources/lib/companion.py
@@ -2,18 +2,17 @@
Processes Plex companion inputs from the plexbmchelper to Kodi commands
"""
from logging import getLogger
-
from xbmc import Player
-from variables import ALEXA_TO_COMPANION
-import playqueue as PQ
-from PlexFunctions import GetPlexKeyNumber
-import json_rpc as js
-import state
+from . import playqueue as PQ
+from . import plex_functions as PF
+from . import json_rpc as js
+from . import variables as v
+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!
"""
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',
playqueue_item_id, plex_id)
found = True
@@ -51,8 +50,8 @@ def convert_alexa_to_companion(dictionary):
The params passed by Alexa must first be converted to Companion talk
"""
for key in dictionary:
- if key in ALEXA_TO_COMPANION:
- dictionary[ALEXA_TO_COMPANION[key]] = dictionary[key]
+ if key in v.ALEXA_TO_COMPANION:
+ dictionary[v.ALEXA_TO_COMPANION[key]] = dictionary[key]
del dictionary[key]
diff --git a/resources/lib/context.py b/resources/lib/context.py
index ef9f67c9..3f629689 100644
--- a/resources/lib/context.py
+++ b/resources/lib/context.py
@@ -2,15 +2,14 @@
###############################################################################
from logging import getLogger
from os.path import join
-
import xbmcgui
from xbmcaddon import Addon
-from utils import window
+from . import utils
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.context')
ADDON = Addon('plugin.video.plexkodiconnect')
ACTION_PARENT_DIR = 9
@@ -44,8 +43,8 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
return self.selected_option
def onInit(self):
- if window('PlexUserImage'):
- self.getControl(USER_IMAGE).setImage(window('PlexUserImage'))
+ if utils.window('PlexUserImage'):
+ self.getControl(USER_IMAGE).setImage(utils.window('PlexUserImage'))
height = 479 + (len(self._options) * 55)
LOG.debug("options: %s", self._options)
self.list_ = self.getControl(LIST)
diff --git a/resources/lib/context_entry.py b/resources/lib/context_entry.py
index f9b4a2ca..65d80d80 100644
--- a/resources/lib/context_entry.py
+++ b/resources/lib/context_entry.py
@@ -1,35 +1,33 @@
# -*- coding: utf-8 -*-
###############################################################################
from logging import getLogger
-
from xbmcaddon import Addon
import xbmc
-import xbmcplugin
import xbmcgui
-import context
-import plexdb_functions as plexdb
-from utils import window, settings, dialog, language as lang
-import PlexFunctions as PF
-from PlexAPI import API
-import playqueue as PQ
-import variables as v
-import state
+from . import context
+from . import plexdb_functions as plexdb
+from . import utils
+from . import plex_functions as PF
+from .plex_api import API
+from . import playqueue as PQ
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.context_entry')
OPTIONS = {
- 'Refresh': lang(30410),
- 'Delete': lang(30409),
- 'Addon': lang(30408),
- # 'AddFav': lang(30405),
- # 'RemoveFav': lang(30406),
- # 'RateSong': lang(30407),
- 'Transcode': lang(30412),
- 'PMS_Play': lang(30415), # Use PMS to start playback
- 'Extras': lang(30235)
+ 'Refresh': utils.lang(30410),
+ 'Delete': utils.lang(30409),
+ 'Addon': utils.lang(30408),
+ # 'AddFav': utils.lang(30405),
+ # 'RemoveFav': utils.lang(30406),
+ # 'RateSong': utils.lang(30407),
+ 'Transcode': utils.lang(30412),
+ 'PMS_Play': utils.lang(30415), # Use PMS to start playback
+ 'Extras': utils.lang(30235)
}
###############################################################################
@@ -98,8 +96,8 @@ class ContextMenu(object):
options.append(OPTIONS['Transcode'])
# Delete item, only if the Plex Home main user is logged in
- if (window('plex_restricteduser') != 'true' and
- window('plex_allows_mediaDeletion') == 'true'):
+ if (utils.window('plex_restricteduser') != 'true' and
+ utils.window('plex_allows_mediaDeletion') == 'true'):
options.append(OPTIONS['Delete'])
# Addon settings
options.append(OPTIONS['Addon'])
@@ -138,14 +136,14 @@ class ContextMenu(object):
Delete item on PMS
"""
delete = True
- if settings('skipContextMenu') != "true":
- if not dialog("yesno", heading="{plex}", line1=lang(33041)):
+ if utils.settings('skipContextMenu') != "true":
+ if not utils.dialog("yesno", heading="{plex}", line1=utils.lang(33041)):
LOG.info("User skipped deletion for: %s", self.plex_id)
delete = False
if delete:
LOG.info("Deleting Plex item with id %s", self.plex_id)
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):
"""
diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py
index 3c28ab11..420b7f33 100644
--- a/resources/lib/downloadutils.py
+++ b/resources/lib/downloadutils.py
@@ -5,10 +5,9 @@ from logging import getLogger
import xml.etree.ElementTree as etree
import requests
-from utils import window, language as lang, dialog
-import clientinfo as client
-
-import state
+from . import utils
+from . import clientinfo
+from . import state
###############################################################################
@@ -16,7 +15,7 @@ import state
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.downloadutils')
###############################################################################
@@ -75,22 +74,22 @@ class DownloadUtils():
# Start session
self.s = requests.Session()
- self.deviceId = client.getDeviceId()
+ self.deviceId = clientinfo.getDeviceId()
# Attach authenticated header to the session
- self.s.headers = client.getXArgsDeviceInfo()
+ self.s.headers = clientinfo.getXArgsDeviceInfo()
self.s.encoding = 'utf-8'
# Set SSL settings
self.setSSL()
# Set other stuff
- self.setServer(window('pms_server'))
+ self.setServer(utils.window('pms_server'))
# Counters to declare PMS dead or unauthorized
# Use window variables because start of movies will be called with a
# new plugin instance - it's impossible to share data otherwise
if reset is True:
- window('countUnauthorized', value='0')
- window('countError', value='0')
+ utils.window('countUnauthorized', value='0')
+ utils.window('countError', value='0')
# Retry connections to the server
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
@@ -110,7 +109,7 @@ class DownloadUtils():
LOG.info('Request session stopped')
def getHeader(self, options=None):
- header = client.getXArgsDeviceInfo()
+ header = clientinfo.getXArgsDeviceInfo()
if options is not None:
header.update(options)
return header
@@ -227,9 +226,9 @@ class DownloadUtils():
else:
# We COULD contact the PMS, hence it ain't dead
if authenticate is True:
- window('countError', value='0')
+ utils.window('countError', value='0')
if r.status_code != 401:
- window('countUnauthorized', value='0')
+ utils.window('countUnauthorized', value='0')
if r.status_code == 204:
# No body in the response
@@ -247,9 +246,10 @@ class DownloadUtils():
LOG.info(r.text)
if '401 Unauthorized' in r.text:
# Truly unauthorized
- window('countUnauthorized',
- value=str(int(window('countUnauthorized')) + 1))
- if (int(window('countUnauthorized')) >=
+ utils.window(
+ 'countUnauthorized',
+ value=str(int(utils.window('countUnauthorized')) + 1))
+ if (int(utils.window('countUnauthorized')) >=
self.unauthorizedAttempts):
LOG.warn('We seem to be truly unauthorized for PMS'
' %s ', url)
@@ -258,11 +258,11 @@ class DownloadUtils():
LOG.debug('Setting PMS server status to '
'unauthorized')
state.PMS_STATUS = '401'
- window('plex_serverStatus', value="401")
- dialog('notification',
- lang(29999),
- lang(30017),
- icon='{error}')
+ utils.window('plex_serverStatus', value="401")
+ utils.dialog('notification',
+ utils.lang(29999),
+ utils.lang(30017),
+ icon='{error}')
else:
# there might be other 401 where e.g. PMS under strain
LOG.info('PMS might only be under strain')
@@ -312,12 +312,12 @@ class DownloadUtils():
if authenticate is True:
# Make the addon aware of status
try:
- window('countError',
- value=str(int(window('countError')) + 1))
- if int(window('countError')) >= self.connectionAttempts:
+ utils.window('countError',
+ value=str(int(utils.window('countError')) + 1))
+ if int(utils.window('countError')) >= self.connectionAttempts:
LOG.warn('Failed to connect to %s too many times. '
'Declare PMS dead', url)
- window('plex_online', value="false")
+ utils.window('plex_online', value="false")
except ValueError:
# 'countError' not yet set
pass
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
index febc2993..1c78436a 100644
--- a/resources/lib/entrypoint.py
+++ b/resources/lib/entrypoint.py
@@ -10,23 +10,19 @@ from os import walk, makedirs
from os.path import basename, join
from sys import argv
from urllib import urlencode
-
import xbmcplugin
from xbmc import sleep, executebuiltin, translatePath
from xbmcgui import ListItem
-from utils import window, settings, language as lang, dialog, try_encode, \
- catch_exceptions, exists_dir, plex_command, try_decode
-import downloadutils
-
-from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
- GetMachineIdentifier
-from PlexAPI import API
-import json_rpc as js
-import variables as v
+from . import utils
+from .downloadutils import DownloadUtils as DU
+from .plex_api import API
+from . import plex_functions as PF
+from . import json_rpc as js
+from . import variables as v
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.entrypoint')
try:
HANDLE = int(argv[1])
@@ -47,8 +43,8 @@ def choose_pms_server():
server = setup.pick_pms(showDialog=True)
if server is None:
LOG.error('We did not connect to a new PMS, aborting')
- plex_command('SUSPEND_USER_CLIENT', 'False')
- plex_command('SUSPEND_LIBRARY_THREAD', 'False')
+ utils.plex_command('SUSPEND_USER_CLIENT', 'False')
+ utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
return
LOG.info("User chose server %s", server['name'])
@@ -65,12 +61,12 @@ def choose_pms_server():
_log_in()
LOG.info("Choosing new PMS complete")
# ' connected'
- dialog('notification',
- lang(29999),
- '%s %s' % (server['name'], lang(39220)),
- icon='{plex}',
- time=3000,
- sound=False)
+ utils.dialog('notification',
+ utils.lang(29999),
+ '%s %s' % (server['name'], utils.lang(39220)),
+ icon='{plex}',
+ time=3000,
+ sound=False)
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.
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')
- settings('plexLogin', value="")
- settings('plexToken', value="")
- settings('plexid', value="")
- settings('plexHomeSize', value="1")
- settings('plexAvatar', value="")
- settings('plex_status', value=lang(39226))
+ utils.settings('plexLogin', value="")
+ utils.settings('plexToken', value="")
+ utils.settings('plexid', value="")
+ utils.settings('plexHomeSize', value="1")
+ utils.settings('plexAvatar', value="")
+ utils.settings('plex_status', value=utils.lang(39226))
- window('plex_token', clear=True)
- plex_command('PLEX_TOKEN', '')
- plex_command('PLEX_USERNAME', '')
+ utils.window('plex_token', clear=True)
+ utils.plex_command('PLEX_TOKEN', '')
+ utils.plex_command('PLEX_USERNAME', '')
else:
LOG.info('Login to plex.tv')
import initialsetup
initialsetup.InitialSetup().plex_tv_sign_in()
- dialog('notification',
- lang(29999),
- lang(39221),
- icon='{plex}',
- time=3000,
- sound=False)
+ utils.dialog('notification',
+ utils.lang(29999),
+ utils.lang(39221),
+ icon='{plex}',
+ time=3000,
+ sound=False)
def reset_authorization():
"""
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:
LOG.info("Reset login attempts.")
- plex_command('PMS_STATUS', 'Auth')
+ utils.plex_command('PMS_STATUS', 'Auth')
else:
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)
xbmcplugin.setContent(HANDLE, 'files')
# Get emby nodes from the window props
- plexprops = window('Plex.nodes.total')
+ plexprops = utils.window('Plex.nodes.total')
if plexprops:
totalnodes = int(plexprops)
for i in range(totalnodes):
- path = window('Plex.nodes.%s.index' % i)
+ path = utils.window('Plex.nodes.%s.index' % i)
if not path:
- path = window('Plex.nodes.%s.content' % i)
+ path = utils.window('Plex.nodes.%s.content' % i)
if not path:
continue
- label = window('Plex.nodes.%s.title' % i)
- node_type = window('Plex.nodes.%s.type' % i)
+ label = utils.window('Plex.nodes.%s.title' % i)
+ node_type = utils.window('Plex.nodes.%s.type' % i)
# because we do not use seperate entrypoints for each content type,
# 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
@@ -161,21 +157,19 @@ def show_main_menu(content_type=None):
# Plex Watch later
if content_type not in ('image', 'audio'):
- directory_item(lang(39211),
+ directory_item(utils.lang(39211),
"plugin://%s?mode=watchlater" % v.ADDON_ID)
# Plex Channels
- directory_item(lang(30173),
- "plugin://%s?mode=channels" % v.ADDON_ID)
+ directory_item(utils.lang(30173), "plugin://%s?mode=channels" % v.ADDON_ID)
# 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)
# some extra entries for settings and stuff
- directory_item(lang(39201),
- "plugin://%s?mode=settings" % v.ADDON_ID)
- directory_item(lang(39203),
+ directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID)
+ directory_item(utils.lang(39203),
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
- directory_item(lang(39204),
+ directory_item(utils.lang(39204),
"plugin://%s?mode=manualsync" % v.ADDON_ID)
xbmcplugin.endOfDirectory(HANDLE)
@@ -188,7 +182,7 @@ def switch_plex_user():
# Guess these user avatars are a future feature. Skipping for now
# Delete any userimages. Since there's always only 1 user: position = 0
# position = 0
- # window('EmbyAdditionalUserImage.%s' % position, clear=True)
+ # utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
LOG.info("Plex home user switch requested")
if not _log_out():
return
@@ -258,7 +252,8 @@ def create_listitem(item, append_show_title=False, append_sxxexx=False):
listitem.setArt({'icon': 'DefaultTVShows.png'})
listitem.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
try:
- listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)])
+ listitem.addContextMenuItems([(utils.lang(30032),
+ 'XBMC.Action(Info)',)])
except TypeError:
# Kodi fuck-up
pass
@@ -287,7 +282,7 @@ def next_up_episodes(tagname, limit):
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
}
for item in js.get_tv_shows(params):
- if settings('ignoreSpecialsNextEpisodes') == "true":
+ if utils.settings('ignoreSpecialsNextEpisodes') == "true":
params = {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
@@ -383,8 +378,8 @@ def recent_episodes(mediatype, tagname, limit):
# if the addon is called with recentepisodes parameter,
# we return the recentepisodes list of the given tagname
xbmcplugin.setContent(HANDLE, 'episodes')
- append_show_title = settings('RecentTvAppendShow') == 'true'
- append_sxxexx = settings('RecentTvAppendSeason') == 'true'
+ append_show_title = utils.settings('RecentTvAppendShow') == 'true'
+ append_sxxexx = utils.settings('RecentTvAppendSeason') == 'true'
# First we get a list of all the TV shows - filtered by tag
show_ids = set()
params = {
@@ -401,7 +396,7 @@ def recent_episodes(mediatype, tagname, limit):
"dateadded", "lastplayed"],
"limits": {"end": limit}
}
- if settings('TVShowWatched') == 'false':
+ if utils.settings('TVShowWatched') == 'false':
params['filter'] = {
'operator': "lessthan",
'field': "playcount",
@@ -444,7 +439,7 @@ def get_video_files(plex_id, params):
LOG.info('No Plex ID found, abort getting Extras')
return xbmcplugin.endOfDirectory(HANDLE)
- item = GetPlexMetadata(plex_id)
+ item = PF.GetPlexMetadata(plex_id)
try:
path = item[0][0][0].attrib['file']
except (TypeError, IndexError, AttributeError, KeyError):
@@ -459,17 +454,17 @@ def get_video_files(plex_id, params):
path = path.replace('\\', '\\\\')
# Directory only, get rid of filename
path = path.replace(basename(path), '')
- if exists_dir(path):
+ if utils.exists_dir(path):
for root, dirs, files in walk(path):
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)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=item_path,
listitem=listitem,
isFolder=True)
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)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=file,
@@ -480,7 +475,7 @@ def get_video_files(plex_id, params):
xbmcplugin.endOfDirectory(HANDLE)
-@catch_exceptions(warnuser=False)
+@utils.catch_exceptions(warnuser=False)
def extra_fanart(plex_id, plex_path):
"""
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
# because of the caching system in xbmc
- fanart_dir = try_decode(translatePath(
+ fanart_dir = utils.try_decode(translatePath(
"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
makedirs(fanart_dir)
- xml = GetPlexMetadata(plex_id)
+ xml = PF.GetPlexMetadata(plex_id)
if xml is None:
LOG.error('Could not download metadata for %s', plex_id)
return xbmcplugin.endOfDirectory(HANDLE)
@@ -511,19 +506,20 @@ def extra_fanart(plex_id, plex_path):
backdrops = api.artwork()['Backdrop']
for count, backdrop in enumerate(backdrops):
# 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)
xbmcplugin.addDirectoryItem(
handle=HANDLE,
url=art_file,
listitem=listitem)
- copyfile(backdrop, try_decode(art_file))
+ copyfile(backdrop, utils.try_decode(art_file))
else:
LOG.info("Found cached backdrop.")
# Use existing cached images
for root, _, files in walk(fanart_dir):
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)
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=art_file,
@@ -541,13 +537,13 @@ def on_deck_episodes(viewid, tagname, limit):
limit: Max. number of items to retrieve, e.g. 50
"""
xbmcplugin.setContent(HANDLE, 'episodes')
- append_show_title = settings('OnDeckTvAppendShow') == 'true'
- append_sxxexx = settings('OnDeckTvAppendSeason') == 'true'
- if settings('OnDeckTVextended') == 'false':
+ append_show_title = utils.settings('OnDeckTvAppendShow') == 'true'
+ append_sxxexx = utils.settings('OnDeckTvAppendSeason') == 'true'
+ if utils.settings('OnDeckTVextended') == 'false':
# Chances are that this view is used on Kodi startup
# Wait till we've connected to a PMS. At most 30s
counter = 0
- while window('plex_authenticated') != 'true':
+ while utils.window('plex_authenticated') != 'true':
counter += 1
if counter == 300:
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)
return
sleep(100)
- xml = downloadutils.DownloadUtils().downloadUrl(
- '{server}/library/sections/%s/onDeck' % viewid)
+ xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid)
if xml in (None, 401):
LOG.error('Could not download PMS xml for view %s', viewid)
xbmcplugin.endOfDirectory(HANDLE, False)
return
- direct_paths = settings('useDirectPaths') == '1'
+ direct_paths = utils.settings('useDirectPaths') == '1'
counter = 0
for item in xml:
api = API(item)
@@ -580,7 +575,7 @@ def on_deck_episodes(viewid, tagname, limit):
break
xbmcplugin.endOfDirectory(
handle=HANDLE,
- cacheToDisc=settings('enableTextureCache') == 'true')
+ cacheToDisc=utils.settings('enableTextureCache') == 'true')
return
# if the addon is called with nextup parameter,
@@ -610,7 +605,7 @@ def on_deck_episodes(viewid, tagname, limit):
"dateadded", "lastplayed"
],
}
- if settings('ignoreSpecialsNextEpisodes') == "true":
+ if utils.settings('ignoreSpecialsNextEpisodes') == "true":
params['filter'] = {
'and': [
{'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)
"""
- if window('plex_token') == '':
+ if utils.window('plex_token') == '':
LOG.error('No watch later - not signed in to plex.tv')
return xbmcplugin.endOfDirectory(HANDLE, False)
- if window('plex_restricteduser') == 'true':
+ if utils.window('plex_restricteduser') == 'true':
LOG.error('No watch later - restricted user')
return xbmcplugin.endOfDirectory(HANDLE, False)
- xml = downloadutils.DownloadUtils().downloadUrl(
- 'https://plex.tv/pms/playlists/queue/all',
- authenticate=False,
- headerOptions={'X-Plex-Token': window('plex_token')})
+ xml = DU().downloadUrl('https://plex.tv/pms/playlists/queue/all',
+ authenticate=False,
+ headerOptions={'X-Plex-Token': utils.window('plex_token')})
if xml in (None, 401):
LOG.error('Could not download watch later list from plex.tv')
return xbmcplugin.endOfDirectory(HANDLE, False)
-
LOG.info('Displaying watch later plex.tv items')
xbmcplugin.setContent(HANDLE, 'movies')
- direct_paths = settings('useDirectPaths') == '1'
+ direct_paths = utils.settings('useDirectPaths') == '1'
for item in xml:
__build_item(item, direct_paths)
-
xbmcplugin.endOfDirectory(
handle=HANDLE,
- cacheToDisc=settings('enableTextureCache') == 'true')
+ cacheToDisc=utils.settings('enableTextureCache') == 'true')
def channels():
"""
Listing for Plex Channels
"""
- xml = downloadutils.DownloadUtils().downloadUrl('{server}/channels/all')
+ xml = DU().downloadUrl('{server}/channels/all')
try:
xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError):
@@ -708,7 +700,7 @@ def channels():
__build_folder(item)
xbmcplugin.endOfDirectory(
handle=HANDLE,
- cacheToDisc=settings('enableTextureCache') == 'true')
+ cacheToDisc=utils.settings('enableTextureCache') == 'true')
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}) or the plex_section_id
"""
if key:
- xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key)
+ xml = DU().downloadUrl('{server}%s' % key)
else:
- xml = GetPlexSectionResults(plex_section_id)
+ xml = PF.GetPlexSectionResults(plex_section_id)
try:
xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError):
@@ -735,7 +727,7 @@ def browse_plex(key=None, plex_section_id=None):
artists = False
albums = False
musicvideos = False
- direct_paths = settings('useDirectPaths') == '1'
+ direct_paths = utils.settings('useDirectPaths') == '1'
for item in xml:
if item.tag == 'Directory':
__build_folder(item, plex_section_id=plex_section_id)
@@ -802,7 +794,7 @@ def browse_plex(key=None, plex_section_id=None):
xbmcplugin.endOfDirectory(
handle=HANDLE,
- cacheToDisc=settings('enableTextureCache') == 'true')
+ cacheToDisc=utils.settings('enableTextureCache') == 'true')
def __build_folder(xml_element, plex_section_id=None):
@@ -854,7 +846,7 @@ def extras(plex_id):
Lists all extras for plex_id
"""
xbmcplugin.setContent(HANDLE, 'movies')
- xml = GetPlexMetadata(plex_id)
+ xml = PF.GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (TypeError, IndexError, KeyError):
@@ -874,41 +866,47 @@ def create_new_pms():
Opens dialogs for the user the plug in the PMS details
"""
# "Enter your Plex Media Server's IP or URL. Examples are:"
- dialog('ok', lang(29999), lang(39215), '192.168.1.2', 'plex.myServer.org')
- address = dialog('input', "Enter PMS IP or URL")
+ utils.dialog('ok',
+ utils.lang(29999),
+ utils.lang(39215),
+ '192.168.1.2',
+ 'plex.myServer.org')
+ address = utils.dialog('input', "Enter PMS IP or URL")
if address == '':
return
- port = dialog('input', "Enter PMS port", '32400', type='{numeric}')
+ port = utils.dialog('input', "Enter PMS port", '32400', type='{numeric}')
if port == '':
return
url = '%s:%s' % (address, port)
# "Does your Plex Media Server support SSL connections?
# (https instead of http)"
- https = dialog('yesno', lang(29999), lang(39217))
+ https = utils.dialog('yesno', utils.lang(29999), utils.lang(39217))
if https:
url = 'https://%s' % url
else:
url = 'http://%s' % url
https = 'true' if https else 'false'
- machine_identifier = GetMachineIdentifier(url)
+ machine_identifier = PF.GetMachineIdentifier(url)
if machine_identifier is None:
# "Error contacting url
# Abort (Yes) or save address anyway (No)"
- if dialog('yesno',
- lang(29999),
- '%s %s. %s' % (lang(39218), url, lang(39219))):
+ if utils.dialog('yesno',
+ utils.lang(29999),
+ '%s %s. %s' % (utils.lang(39218),
+ url,
+ utils.lang(39219))):
return
else:
- settings('plex_machineIdentifier', '')
+ utils.settings('plex_machineIdentifier', '')
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',
https, address, port, machine_identifier)
- settings('https', value=https)
- settings('ipaddress', value=address)
- settings('port', value=port)
+ utils.settings('https', value=https)
+ utils.settings('ipaddress', value=address)
+ utils.settings('port', value=port)
# 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
if _log_out():
@@ -923,9 +921,9 @@ def _log_in():
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
out!
"""
- plex_command('RUN_LIB_SCAN', 'full')
+ utils.plex_command('RUN_LIB_SCAN', 'full')
# Restart user client
- plex_command('SUSPEND_USER_CLIENT', 'False')
+ utils.plex_command('SUSPEND_USER_CLIENT', 'False')
def _log_out():
@@ -935,22 +933,22 @@ def _log_out():
Returns True if successfully signed out, False otherwise
"""
# Resetting, please wait
- dialog('notification',
- lang(29999),
- lang(39207),
- icon='{plex}',
- time=3000,
- sound=False)
+ utils.dialog('notification',
+ utils.lang(29999),
+ utils.lang(39207),
+ icon='{plex}',
+ time=3000,
+ sound=False)
# 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
counter = 0
- while window('plex_dbScan') == 'true':
+ while utils.window('plex_dbScan') == 'true':
if counter > 200:
# 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
- plex_command('SUSPEND_LIBRARY_THREAD', 'False')
+ utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
LOG.error("Could not stop library sync, aborting")
return False
counter += 1
@@ -959,17 +957,17 @@ def _log_out():
counter = 0
# Log out currently signed in user:
- window('plex_serverStatus', value='401')
- plex_command('PMS_STATUS', '401')
+ utils.window('plex_serverStatus', value='401')
+ utils.plex_command('PMS_STATUS', '401')
# Above method needs to have run its course! Hence wait
- while window('plex_serverStatus') == "401":
+ while utils.window('plex_serverStatus') == "401":
if counter > 100:
# '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")
return False
counter += 1
sleep(50)
# Suspend the user client during procedure
- plex_command('SUSPEND_USER_CLIENT', 'True')
+ utils.plex_command('SUSPEND_USER_CLIENT', 'True')
return True
diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py
index 259d5c15..04c4cb4a 100644
--- a/resources/lib/initialsetup.py
+++ b/resources/lib/initialsetup.py
@@ -6,23 +6,22 @@ import xml.etree.ElementTree as etree
from xbmc import executebuiltin, translatePath
-from utils import settings, window, language as lang, try_decode, dialog, \
- XmlKodiSetting, reboot_kodi
-from migration import check_migration
-from downloadutils import DownloadUtils as DU
-from userclient import UserClient
-from clientinfo import getDeviceId
-import PlexFunctions as PF
-import plex_tv
-import json_rpc as js
-import playqueue as PQ
-from videonodes import VideoNodes
-import state
-import variables as v
+from . import utils
+from . import migration
+from .downloadutils import DownloadUtils as DU
+from . import videonodes
+from . import userclient
+from . import clientinfo
+from . import plex_functions as PF
+from . import plex_tv
+from . import json_rpc as js
+from . import playqueue as PQ
+from . import state
+from . import variables as v
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.initialsetup')
###############################################################################
@@ -48,27 +47,28 @@ def reload_pkc():
reload(state)
# Reset window props
for prop in WINDOW_PROPERTIES:
- window(prop, clear=True)
+ utils.window(prop, clear=True)
# Clear video nodes properties
- VideoNodes().clearProperties()
+ videonodes.VideoNodes().clearProperties()
# Initializing
- state.VERIFY_SSL_CERT = settings('sslverify') == 'true'
- state.SSL_CERT_PATH = settings('sslcert') \
- if settings('sslcert') != 'None' else None
- state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
- state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
- state.SYNC_DIALOG = settings('dbSyncIndicator') == 'true'
- state.ENABLE_MUSIC = settings('enableMusic') == 'true'
- state.BACKGROUND_SYNC_DISABLED = settings(
+ state.VERIFY_SSL_CERT = utils.settings('sslverify') == 'true'
+ state.SSL_CERT_PATH = utils.settings('sslcert') \
+ if utils.settings('sslcert') != 'None' else None
+ state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
+ state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
+ state.SYNC_DIALOG = utils.settings('dbSyncIndicator') == 'true'
+ state.ENABLE_MUSIC = utils.settings('enableMusic') == 'true'
+ state.BACKGROUND_SYNC_DISABLED = utils.settings(
'enableBackgroundSync') == 'false'
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
- settings('backgroundsync_saftyMargin'))
- state.REPLACE_SMB_PATH = settings('replaceSMB') == 'true'
- state.REMAP_PATH = settings('remapSMB') == 'true'
- state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
- state.FETCH_PMS_ITEM_NUMBER = settings('fetch_pms_item_number')
- state.FORCE_RELOAD_SKIN = settings('forceReloadSkinOnPlaybackStop') == 'true'
+ utils.settings('backgroundsync_saftyMargin'))
+ state.REPLACE_SMB_PATH = utils.settings('replaceSMB') == 'true'
+ state.REMAP_PATH = utils.settings('remapSMB') == 'true'
+ state.KODI_PLEX_TIME_OFFSET = float(utils.settings('kodiplextimeoffset'))
+ state.FETCH_PMS_ITEM_NUMBER = utils.settings('fetch_pms_item_number')
+ state.FORCE_RELOAD_SKIN = \
+ utils.settings('forceReloadSkinOnPlaybackStop') == 'true'
# Init some Queues()
state.COMMAND_PIPELINE_QUEUE = Queue()
state.COMPANION_QUEUE = Queue(maxsize=100)
@@ -76,9 +76,9 @@ def reload_pkc():
set_replace_paths()
set_webserver()
# To detect Kodi profile switches
- window('plex_kodiProfile',
- value=try_decode(translatePath("special://profile")))
- getDeviceId()
+ utils.window('plex_kodiProfile',
+ value=utils.try_decode(translatePath("special://profile")))
+ clientinfo.getDeviceId()
# Initialize the PKC playqueues
PQ.init_playqueues()
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 arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg)
- value = settings(key)
+ value = utils.settings(key)
if '://' in value:
protocol = value.split('://', 1)[0]
value = value.replace(protocol, protocol.lower())
@@ -129,8 +129,8 @@ def _write_pms_settings(url, token):
for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion':
value = 'true' if entry.get('value', '1') == '1' else 'false'
- settings('plex_allows_mediaDeletion', value=value)
- window('plex_allows_mediaDeletion', value=value)
+ utils.settings('plex_allows_mediaDeletion', value=value)
+ utils.window('plex_allows_mediaDeletion', value=value)
class InitialSetup(object):
@@ -140,8 +140,8 @@ class InitialSetup(object):
"""
def __init__(self):
LOG.debug('Entering initialsetup class')
- self.server = UserClient().get_server()
- self.serverid = settings('plex_machineIdentifier')
+ self.server = userclient.UserClient().get_server()
+ self.serverid = utils.settings('plex_machineIdentifier')
# Get Plex credentials from settings file, if they exist
plexdict = PF.GetPlexLoginFromSettings()
self.myplexlogin = plexdict['myplexlogin'] == 'true'
@@ -149,7 +149,7 @@ class InitialSetup(object):
self.plex_token = plexdict['plexToken']
self.plexid = plexdict['plexid']
# Token for the PMS, not plex.tv
- self.pms_token = settings('accessToken')
+ self.pms_token = utils.settings('accessToken')
if self.plex_token:
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
LOG.info('plex.tv connection returned HTTP %s', str(chk))
# Delete token in the settings
- settings('plexToken', value='')
- settings('plexLogin', value='')
+ utils.settings('plexToken', value='')
+ utils.settings('plexLogin', value='')
# 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()
elif chk is False or chk >= 400:
# Problems connecting to plex.tv. Network or internet issue?
LOG.info('Problems connecting to plex.tv; connection returned '
'HTTP %s', str(chk))
- dialog('ok', lang(29999), lang(39010))
+ utils.dialog('ok', utils.lang(29999), utils.lang(39010))
answer = False
else:
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
xml = DU().downloadUrl('https://plex.tv/users/account',
authenticate=False,
@@ -202,11 +202,12 @@ class InitialSetup(object):
except (AttributeError, KeyError):
LOG.error('Failed to update Plex info from plex.tv')
else:
- settings('plexLogin', value=self.plex_login)
+ utils.settings('plexLogin', value=self.plex_login)
home = 'true' if xml.attrib.get('home') == '1' else 'false'
- settings('plexhome', value=home)
- settings('plexAvatar', value=xml.attrib.get('thumb'))
- settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
+ utils.settings('plexhome', value=home)
+ utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
+ utils.settings('plexHomeSize',
+ value=xml.attrib.get('homeSize', '1'))
LOG.info('Updated Plex info from plex.tv')
return answer
@@ -233,7 +234,7 @@ class InitialSetup(object):
LOG.warn('Could not retrieve machineIdentifier')
answer = False
else:
- settings('plex_machineIdentifier', value=self.serverid)
+ utils.settings('plex_machineIdentifier', value=self.serverid)
elif answer is True:
temp_server_id = PF.GetMachineIdentifier(self.server)
if temp_server_id != self.serverid:
@@ -325,7 +326,7 @@ class InitialSetup(object):
if item.get('machineIdentifier') == self.serverid:
server = item
if server is None:
- name = settings('plex_servername')
+ name = utils.settings('plex_servername')
LOG.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is '
'offline', self.serverid, name)
@@ -356,18 +357,18 @@ class InitialSetup(object):
"""
https_updated = False
# Searching for PMS
- dialog('notification',
- heading='{plex}',
- message=lang(30001),
- icon='{plex}',
- time=5000)
+ utils.dialog('notification',
+ heading='{plex}',
+ message=utils.lang(30001),
+ icon='{plex}',
+ time=5000)
while True:
if https_updated is False:
serverlist = PF.discover_pms(self.plex_token)
# Exit if no servers found
if not serverlist:
LOG.warn('No plex media servers found!')
- dialog('ok', lang(29999), lang(39011))
+ utils.dialog('ok', utils.lang(29999), utils.lang(39011))
return
# Get a nicer list
dialoglist = []
@@ -375,10 +376,10 @@ class InitialSetup(object):
if server['local']:
# server is in the same network as client.
# Add"local"
- msg = lang(39022)
+ msg = utils.lang(39022)
else:
# Add 'remote'
- msg = lang(39054)
+ msg = utils.lang(39054)
if server.get('ownername'):
# Display username if its not our PMS
dialoglist.append('%s (%s, %s)'
@@ -389,7 +390,7 @@ class InitialSetup(object):
dialoglist.append('%s (%s)'
% (server['name'], msg))
# Let user pick server from a list
- resp = dialog('select', lang(39012), dialoglist)
+ resp = utils.dialog('select', utils.lang(39012), dialoglist)
if resp == -1:
# User cancelled
return
@@ -406,17 +407,19 @@ class InitialSetup(object):
LOG.warn('Not yet authorized for Plex server %s',
server['name'])
# Please sign in to plex.tv
- dialog('ok',
- lang(29999),
- lang(39013) + server['name'],
- lang(39014))
+ utils.dialog('ok',
+ utils.lang(29999),
+ utils.lang(39013) + server['name'],
+ utils.lang(39014))
if self.plex_tv_sign_in() is False:
# Exit while loop if user cancels
return
# Problems connecting
elif chk >= 400 or chk is False:
# 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
if not answ:
return
@@ -429,30 +432,31 @@ class InitialSetup(object):
"""
Saves server to file settings
"""
- settings('plex_machineIdentifier', server['machineIdentifier'])
- settings('plex_servername', server['name'])
- settings('plex_serverowned', 'true' if server['owned'] else 'false')
+ utils.settings('plex_machineIdentifier', server['machineIdentifier'])
+ utils.settings('plex_servername', server['name'])
+ utils.settings('plex_serverowned',
+ 'true' if server['owned'] else 'false')
# Careful to distinguish local from remote PMS
if server['local']:
scheme = server['scheme']
- settings('ipaddress', server['ip'])
- settings('port', server['port'])
+ utils.settings('ipaddress', server['ip'])
+ utils.settings('port', server['port'])
LOG.debug("Setting SSL verify to false, because server is "
"local")
- settings('sslverify', 'false')
+ utils.settings('sslverify', 'false')
else:
baseURL = server['baseURL'].split(':')
scheme = baseURL[0]
- settings('ipaddress', baseURL[1].replace('//', ''))
- settings('port', baseURL[2])
+ utils.settings('ipaddress', baseURL[1].replace('//', ''))
+ utils.settings('port', baseURL[2])
LOG.debug("Setting SSL verify to true, because server is not "
"local")
- settings('sslverify', 'true')
+ utils.settings('sslverify', 'true')
if scheme == 'https':
- settings('https', 'true')
+ utils.settings('https', 'true')
else:
- settings('https', 'false')
+ utils.settings('https', 'false')
# And finally do some logging
LOG.debug("Writing to Kodi user settings file")
LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ",
@@ -468,9 +472,9 @@ class InitialSetup(object):
"""
LOG.info("Initial setup called.")
try:
- with XmlKodiSetting('advancedsettings.xml',
- force_create=True,
- top_element='advancedsettings') as xml:
+ with utils.XmlKodiSetting('advancedsettings.xml',
+ force_create=True,
+ top_element='advancedsettings') as xml:
# Get current Kodi video cache setting
cache = xml.get_setting(['cache', 'memorysize'])
# Disable foreground "Loading media information from files"
@@ -493,13 +497,13 @@ class InitialSetup(object):
# Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520'
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
try:
- with XmlKodiSetting('sources.xml',
- force_create=True,
- top_element='sources') as xml:
+ with utils.XmlKodiSetting('sources.xml',
+ force_create=True,
+ top_element='sources') as xml:
root = xml.set_setting(['video'])
count = 2
for source in root.findall('.//path'):
@@ -526,21 +530,21 @@ class InitialSetup(object):
pass
# Do we need to migrate stuff?
- check_migration()
+ migration.check_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
# breaking playback reporting for PKC
if js.settings_getsettingvalue('videoplayer.autoplaynextitem'):
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
- settings('warned_setting_videoplayer.autoplaynextitem',
- value='true')
+ utils.settings('warned_setting_videoplayer.autoplaynextitem',
+ value='true')
# Warning: Kodi setting "Play next video automatically" is
# 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',
False)
# Set any video library updates to happen in the background in order to
@@ -556,7 +560,7 @@ class InitialSetup(object):
self.server, self.serverid)
_write_pms_settings(self.server, self.pms_token)
if reboot is True:
- reboot_kodi()
+ utils.reboot_kodi()
return
# If not already retrieved myplex info, optionally let user sign in
@@ -570,78 +574,91 @@ class InitialSetup(object):
self.write_pms_to_settings(server)
# User already answered the installation questions
- if settings('InstallQuestionsAnswered') == 'true':
+ if utils.settings('InstallQuestionsAnswered') == 'true':
if reboot is True:
- reboot_kodi()
+ utils.reboot_kodi()
return
# Additional settings where the user needs to choose
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
goto_settings = False
- if dialog('yesno',
- lang(29999),
- lang(39027),
- lang(39028),
- nolabel="Addon (Default)",
- yeslabel="Native (Direct Paths)"):
+ if utils.dialog('yesno',
+ utils.lang(29999),
+ utils.lang(39027),
+ utils.lang(39028),
+ nolabel="Addon (Default)",
+ yeslabel="Native (Direct Paths)"):
LOG.debug("User opted to use direct paths.")
- settings('useDirectPaths', value="1")
+ utils.settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True
# Are you on a system where you would like to replace paths
# \\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")
else:
- settings('replaceSMB', value="false")
+ utils.settings('replaceSMB', value="false")
# 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")
- settings('remapSMB', value="true")
+ utils.settings('remapSMB', value="true")
# Please enter your custom smb paths in the settings under
# "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
# Go to network credentials?
- if dialog('yesno',
- heading=lang(29999),
- line1=lang(39029),
- line2=lang(39030)):
+ if utils.dialog('yesno',
+ heading=utils.lang(29999),
+ line1=utils.lang(39029),
+ line2=utils.lang(39030)):
LOG.debug("Presenting network credentials dialog.")
from utils import passwords_xml
passwords_xml()
# 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.")
- settings('enableMusic', value="false")
+ utils.settings('enableMusic', value="false")
# 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")
- settings('FanartTV', value="true")
+ utils.settings('FanartTV', value="true")
# Do you want to replace your custom user ratings with an indicator of
# 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")
- 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
# "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
- # 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
- settings('InstallQuestionsAnswered', value='true')
+ utils.settings('InstallQuestionsAnswered', value='true')
if goto_settings is False:
# Open Settings page now? You will need to restart!
- goto_settings = dialog('yesno',
- heading=lang(29999),
- line1=lang(39017))
+ goto_settings = utils.dialog('yesno',
+ heading=utils.lang(29999),
+ line1=utils.lang(39017))
if goto_settings:
state.PMS_STATUS = 'Stop'
- executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
+ executebuiltin(
+ 'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
elif reboot is True:
- reboot_kodi()
+ utils.reboot_kodi()
diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py
index 37372cce..0facc6e9 100644
--- a/resources/lib/itemtypes.py
+++ b/resources/lib/itemtypes.py
@@ -4,18 +4,17 @@ from logging import getLogger
from ntpath import dirname
from datetime import datetime
-from artwork import Artwork
-from utils import window, kodi_sql, catch_exceptions
-import plexdb_functions as plexdb
-import kodidb_functions as kodidb
-
-from PlexAPI import API
-from PlexFunctions import GetPlexMetadata
-import variables as v
-import state
+from . import artwork
+from . import utils
+from . import plexdb_functions as plexdb
+from . import kodidb_functions as kodidb
+from .plex_api import API
+from . import plex_functions as PF
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.itemtypes')
# Note: always use same order of URL arguments, NOT urlencode:
# plex_id=&plex_type=&mode=play
@@ -32,8 +31,8 @@ class Items(object):
kodiType: optional argument; e.g. 'video' or 'music'
"""
def __init__(self):
- self.artwork = Artwork()
- self.server = window('pms_server')
+ self.artwork = artwork.Artwork()
+ self.server = utils.window('pms_server')
self.plexconn = None
self.plexcursor = None
self.kodiconn = None
@@ -45,9 +44,9 @@ class Items(object):
"""
Open DB connections and cursors
"""
- self.plexconn = kodi_sql('plex')
+ self.plexconn = utils.kodi_sql('plex')
self.plexcursor = self.plexconn.cursor()
- self.kodiconn = kodi_sql('video')
+ self.kodiconn = utils.kodi_sql('video')
self.kodicursor = self.kodiconn.cursor()
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
@@ -63,7 +62,7 @@ class Items(object):
self.kodiconn.close()
return self
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def getfanart(self, plex_id, refresh=False):
"""
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)
return True
- xml = GetPlexMetadata(plex_id)
+ xml = PF.GetPlexMetadata(plex_id)
if xml is None:
# Did not receive a valid XML - skip that item for now
LOG.error("Could not get metadata for %s. Skipping that item "
@@ -183,7 +182,7 @@ class Movies(Items):
"""
Used for plex library-type movies
"""
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def add_update(self, item, viewtag=None, viewid=None):
"""
Process single movie
@@ -513,7 +512,7 @@ class TVShows(Items):
"""
For Plex library-type TV shows
"""
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def add_update(self, item, viewtag=None, viewid=None):
"""
Process a single show
@@ -722,7 +721,7 @@ class TVShows(Items):
tags.extend(collections)
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):
"""
Process a single season of a certain tv show
@@ -768,7 +767,7 @@ class TVShows(Items):
view_id=viewid,
checksum=checksum)
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def add_updateEpisode(self, item, viewtag=None, viewid=None):
"""
Process single episode
@@ -998,7 +997,7 @@ class TVShows(Items):
runtime,
playcount,
dateplayed,
- None) # Do send None, we check here
+ None) # Do send None, we check here
if not state.DIRECT_PATHS:
# need to set a SECOND file entry for a path without plex show id
filename = api.file_name(force_first_media=True)
@@ -1014,9 +1013,9 @@ class TVShows(Items):
runtime,
playcount,
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):
"""
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.
Open DB connections and cursors
"""
- self.plexconn = kodi_sql('plex')
+ self.plexconn = utils.kodi_sql('plex')
self.plexcursor = self.plexconn.cursor()
# Here it is, not 'video' but 'music'
- self.kodiconn = kodi_sql('music')
+ self.kodiconn = utils.kodi_sql('music')
self.kodicursor = self.kodiconn.cursor()
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
return self
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def add_updateArtist(self, item, viewtag=None, viewid=None):
"""
Adds a single artist
@@ -1236,7 +1235,7 @@ class Music(Items):
v.KODI_TYPE_ARTIST,
kodicursor)
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None,
scan_children=True):
"""
@@ -1362,7 +1361,7 @@ class Music(Items):
artist_id = plex_db.getItem_byId(parent_id)[0]
except TypeError:
LOG.info('Artist %s does not yet exist in Plex DB', parent_id)
- artist = GetPlexMetadata(parent_id)
+ artist = PF.GetPlexMetadata(parent_id)
try:
artist[0].attrib
except (TypeError, IndexError, AttributeError):
@@ -1393,13 +1392,16 @@ class Music(Items):
self.genres,
v.KODI_TYPE_ALBUM)
# 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
if scan_children:
for child in children:
self.add_updateSong(child, viewtag, viewid)
- @catch_exceptions(warnuser=True)
+ @utils.catch_exceptions(warnuser=True)
def add_updateSong(self, item, viewtag=None, viewid=None):
"""
Process single song
@@ -1459,7 +1461,7 @@ class Music(Items):
if disc == 1:
track = tracknumber
else:
- track = disc*2**16 + tracknumber
+ track = disc * 2 ** 16 + tracknumber
year = api.year()
_, duration = api.resume_runtime()
rating = userdata['UserRating']
@@ -1573,7 +1575,7 @@ class Music(Items):
# No album found. Let's create it
LOG.info("Album database entry missing.")
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:
LOG.error('Could not download album, abort')
return
@@ -1664,7 +1666,8 @@ class Music(Items):
idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
VALUES (?, ?, ?, ?, ?)
'''
- kodicursor.execute(query, (songid, albumid, track, title, duration))
+ kodicursor.execute(query,
+ (songid, albumid, track, title, duration))
# Link song to artists
artist_loop = [{
'Name': api.grandparent_title(),
@@ -1680,7 +1683,7 @@ class Music(Items):
artistid = artist_edb[0]
except TypeError:
# 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:
LOG.error('Error getting artist, abort')
return
@@ -1718,9 +1721,12 @@ class Music(Items):
artwork.modify_artwork(artworks, songid, v.KODI_TYPE_SONG, kodicursor)
if item.get('parentKey') is None:
# 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):
"""
Completely remove the item with plex_id from the Kodi and Plex DBs.
@@ -1768,7 +1774,8 @@ class Music(Items):
##### IF ARTIST #####
elif kodi_type == v.KODI_TYPE_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:
songs = self.plex_db.getItem_byParentId(album[1],
v.KODI_TYPE_SONG)
diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py
index bc1589d0..b989c97b 100644
--- a/resources/lib/json_rpc.py
+++ b/resources/lib/json_rpc.py
@@ -3,9 +3,10 @@ Collection of functions using the Kodi JSON RPC interface.
See http://kodi.wiki/view/JSON-RPC_API
"""
from json import loads, dumps
-from utils import millis_to_kodi_time
from xbmc import executeJSONRPC
+from . import utils
+
class JsonRPC(object):
"""
@@ -152,7 +153,7 @@ def seek_to(offset):
for playerid in get_player_ids():
JsonRPC("Player.Seek").execute(
{"playerid": playerid,
- "value": millis_to_kodi_time(offset)})
+ "value": utils.millis_to_kodi_time(offset)})
def smallforward():
diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py
index f4ccd5fa..c021df0a 100644
--- a/resources/lib/kodidb_functions.py
+++ b/resources/lib/kodidb_functions.py
@@ -7,14 +7,14 @@ from logging import getLogger
from ntpath import dirname
from sqlite3 import IntegrityError
-import artwork
-from utils import kodi_sql, try_decode, unix_timestamp, unix_date_to_kodi
-import variables as v
-import state
+from . import artwork
+from . import utils
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.kodidb_functions')
###############################################################################
@@ -35,7 +35,7 @@ class GetKodiDB(object):
self.db_type = db_type
def __enter__(self):
- self.kodiconn = kodi_sql(self.db_type)
+ self.kodiconn = utils.kodi_sql(self.db_type)
kodi_db = KodiDBMethods(self.kodiconn.cursor())
return kodi_db
@@ -118,7 +118,7 @@ class KodiDBMethods(object):
if pathid is None:
self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path")
pathid = self.cursor.fetchone()[0] + 1
- datetime = unix_date_to_kodi(unix_timestamp())
+ datetime = utils.unix_date_to_kodi(utils.unix_timestamp())
query = '''
INSERT INTO path(idPath, strPath, dateAdded)
VALUES (?, ?, ?)
@@ -209,13 +209,14 @@ class KodiDBMethods(object):
INSERT INTO files(idFile, idPath, strFilename, dateAdded)
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
def obsolete_file_ids(self):
"""
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'
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, if not possible
"""
kodi_id = None
- path = try_decode(path)
+ path = utils.try_decode(path)
try:
filename = path.rsplit('/', 1)[1]
path = path.rsplit('/', 1)[0] + '/'
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
index 23114773..e20302dd 100644
--- a/resources/lib/kodimonitor.py
+++ b/resources/lib/kodimonitor.py
@@ -5,29 +5,25 @@ from logging import getLogger
from json import loads
from threading import Thread
import copy
-
import xbmc
from xbmcgui import Window
-import plexdb_functions as plexdb
-import kodidb_functions as kodidb
-from utils import window, settings, plex_command, thread_methods, try_encode, \
- kodi_time_to_millis, unix_date_to_kodi, unix_timestamp
-from PlexFunctions import scrobble
-from downloadutils import DownloadUtils as DU
-from kodidb_functions import kodiid_from_filename
-from plexbmchelper.subscribers import LOCKER
-from playback import playback_triage
-from initialsetup import set_replace_paths
-import playqueue as PQ
-import json_rpc as js
-import playlist_func as PL
-import state
-import variables as v
+from . import plexdb_functions as plexdb
+from . import kodidb_functions as kodidb
+from . import utils
+from . import plex_functions as PF
+from .downloadutils import DownloadUtils as DU
+from . import playback
+from . import initialsetup
+from . import playqueue as PQ
+from . import json_rpc as js
+from . import playlist_func as PL
+from . import state
+from . import variables as v
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.kodimonitor')
# settings: window-variable
WINDOW_SETTINGS = {
@@ -65,6 +61,7 @@ class KodiMonitor(xbmc.Monitor):
def __init__(self):
self.xbmcplayer = xbmc.Player()
self._already_slept = False
+ self.hack_replay = None
xbmc.Monitor.__init__(self)
for playerid in state.PLAYER_STATES:
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
@@ -91,14 +88,14 @@ class KodiMonitor(xbmc.Monitor):
changed = False
# Reset the window variables from the settings variables
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
LOG.debug('PKC window settings changed: %s is now %s',
- settings_value, settings(settings_value))
- window(window_value, value=settings(settings_value))
+ settings_value, utils.settings(settings_value))
+ utils.window(window_value, value=utils.settings(settings_value))
# Reset the state variables in state.py
for settings_value, state_name in STATE_SETTINGS.iteritems():
- new = settings(settings_value)
+ new = utils.settings(settings_value)
if new == 'true':
new = True
elif new == 'false':
@@ -110,19 +107,17 @@ class KodiMonitor(xbmc.Monitor):
setattr(state, state_name, new)
if state_name == 'FETCH_PMS_ITEM_NUMBER':
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
- set_replace_paths()
- state.BACKGROUND_SYNC_DISABLED = settings(
+ initialsetup.set_replace_paths()
+ state.BACKGROUND_SYNC_DISABLED = utils.settings(
'enableBackgroundSync') == 'false'
- state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
+ state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
- settings('backgroundsync_saftyMargin'))
- state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
- state.SSL_CERT_PATH = settings('sslcert') \
- if settings('sslcert') != 'None' else None
- # Never set through the user
- # state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
+ utils.settings('backgroundsync_saftyMargin'))
+ state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
+ state.SSL_CERT_PATH = utils.settings('sslcert') \
+ if utils.settings('sslcert') != 'None' else None
if changed is True:
# Assume that the user changed the settings so that we can now find
# the path to all media files
@@ -137,13 +132,22 @@ class KodiMonitor(xbmc.Monitor):
data = loads(data, 'utf-8')
LOG.debug("Method: %s Data: %s", method, data)
+ # Hack
+ if not method == 'Player.OnStop':
+ self.hack_replay = None
+
if method == "Player.OnPlay":
state.SUSPEND_SYNC = True
self.PlayBackStart(data)
elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck
# 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:
state.PKC_CAUSED_STOP = False
LOG.debug('PKC caused this playback stop - ignoring')
@@ -182,28 +186,60 @@ class KodiMonitor(xbmc.Monitor):
else:
# notify the server
if playcount > 0:
- scrobble(itemid, 'watched')
+ PF.scrobble(itemid, 'watched')
else:
- scrobble(itemid, 'unwatched')
+ PF.scrobble(itemid, 'unwatched')
elif method == "VideoLibrary.OnRemove":
pass
elif method == "System.OnSleep":
# Connection is going to sleep
LOG.info("Marking the server as offline. SystemOnSleep activated.")
- window('plex_online', value="sleep")
+ utils.window('plex_online', value="sleep")
elif method == "System.OnWake":
# Allow network to wake up
xbmc.sleep(10000)
- window('plex_online', value="false")
+ utils.window('plex_online', value="false")
elif method == "GUI.OnScreensaverDeactivated":
- if settings('dbSyncScreensaver') == "true":
+ if utils.settings('dbSyncScreensaver') == "true":
xbmc.sleep(5000)
- plex_command('RUN_LIB_SCAN', 'full')
+ utils.plex_command('RUN_LIB_SCAN', 'full')
elif method == "System.OnQuit":
LOG.info('Kodi OnQuit detected - shutting down')
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):
"""
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']:
return
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
data['item']['type'] == old['kodi_type'] and
data['item']['id'] == old['kodi_id']):
- # 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()
- 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
+ self.hack_replay = data['item']
def _playlist_onremove(self, data):
"""
@@ -247,7 +272,7 @@ class KodiMonitor(xbmc.Monitor):
"""
pass
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def _playlist_onclear(self, data):
"""
Called if a Kodi playlist is cleared. Example data dict:
@@ -271,7 +296,7 @@ class KodiMonitor(xbmc.Monitor):
plex_type = None
# If using direct paths and starting playback from a widget
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:
with plexdb.Get_Plex_DB() as plex_db:
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('file'))
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def PlayBackStart(self, data):
"""
Called whenever playback is started. Example data:
@@ -430,7 +455,7 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug('Set the player state: %s', status)
-@thread_methods
+@utils.thread_methods
class SpecialMonitor(Thread):
"""
Detect the resume dialog for widgets.
@@ -439,8 +464,8 @@ class SpecialMonitor(Thread):
def run(self):
LOG.info("----====# Starting Special Monitor #====----")
# "Start from beginning", "Play from beginning"
- strings = (try_encode(xbmc.getLocalizedString(12021)),
- try_encode(xbmc.getLocalizedString(12023)))
+ strings = (utils.try_encode(xbmc.getLocalizedString(12021)),
+ utils.try_encode(xbmc.getLocalizedString(12023)))
while not self.stopped():
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
@@ -461,7 +486,7 @@ class SpecialMonitor(Thread):
LOG.info("#====---- Special Monitor Stopped ----====#")
-@LOCKER.lockthis
+@state.LOCKER_SUBSCRIBER.lockthis
def _playback_cleanup(ended=False):
"""
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
LOG.debug('No playstate update due to Plex id not found: %s', status)
return
- totaltime = float(kodi_time_to_millis(status['totaltime'])) / 1000
+ totaltime = float(utils.kodi_time_to_millis(status['totaltime'])) / 1000
if ended:
progress = 0.99
time = v.IGNORE_SECONDS_AT_START + 1
else:
- time = float(kodi_time_to_millis(status['time'])) / 1000
+ time = float(utils.kodi_time_to_millis(status['time'])) / 1000
try:
progress = time / totaltime
except ZeroDivisionError:
@@ -518,7 +543,7 @@ def _record_playstate(status, ended):
LOG.debug('Playback progress %s (%s of %s seconds)',
progress, time, totaltime)
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:
LOG.debug('playcount not found, looking it up in the Kodi DB')
with kodidb.GetKodiDB('video') as kodi_db:
diff --git a/resources/lib/library_sync/fanart.py b/resources/lib/library_sync/fanart.py
index a13aa956..9c9088b8 100644
--- a/resources/lib/library_sync/fanart.py
+++ b/resources/lib/library_sync/fanart.py
@@ -2,15 +2,14 @@
from logging import getLogger
from threading import Thread
from Queue import Empty
-
import xbmc
-from utils import thread_methods, settings, language as lang, dialog
-import plexdb_functions as plexdb
-import itemtypes
-from artwork import ArtworkSyncMessage
-import variables as v
-import state
+from .. import utils
+from .. import plexdb_functions as plexdb
+from .. import itemtypes
+from .. import artwork
+from .. import variables as v
+from .. import state
###############################################################################
@@ -19,10 +18,10 @@ LOG = getLogger("PLEX." + __name__)
###############################################################################
-@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
- 'DB_SCAN',
- 'STOP_SYNC',
- 'SUSPEND_SYNC'])
+@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
+ 'DB_SCAN',
+ 'STOP_SYNC',
+ 'SUSPEND_SYNC'])
class ThreadedProcessFanart(Thread):
"""
Threaded download of additional fanart in the background
@@ -68,17 +67,17 @@ class ThreadedProcessFanart(Thread):
'Window.IsVisible(DialogAddonSettings.xml)'):
# Avoid saving '0' all the time
set_zero = True
- settings('fanarttv_lookups', value='0')
+ utils.settings('fanarttv_lookups', value='0')
xbmc.sleep(200)
continue
set_zero = False
- if isinstance(item, ArtworkSyncMessage):
+ if isinstance(item, artwork.ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS:
- dialog('notification',
- heading=lang(29999),
- message=item.message,
- icon='{plex}',
- sound=False)
+ utils.dialog('notification',
+ heading=utils.lang(29999),
+ message=item.message,
+ icon='{plex}',
+ sound=False)
queue.task_done()
continue
@@ -96,6 +95,6 @@ class ThreadedProcessFanart(Thread):
if (counter > 20 and not xbmc.getCondVisibility(
'Window.IsVisible(DialogAddonSettings.xml)')):
counter = 0
- settings('fanarttv_lookups', value=str(queue.qsize()))
+ utils.settings('fanarttv_lookups', value=str(queue.qsize()))
queue.task_done()
LOG.debug("---===### Stopped FanartSync ###===---")
diff --git a/resources/lib/library_sync/get_metadata.py b/resources/lib/library_sync/get_metadata.py
index 2850d74a..fb9a8097 100644
--- a/resources/lib/library_sync/get_metadata.py
+++ b/resources/lib/library_sync/get_metadata.py
@@ -2,12 +2,11 @@
from logging import getLogger
from threading import Thread
from Queue import Empty
-
from xbmc import sleep
-from utils import thread_methods, window
-from PlexFunctions import GetPlexMetadata, GetAllPlexChildren
-import sync_info
+from .. import utils
+from .. import plex_functions as PF
+from . import sync_info
###############################################################################
@@ -16,9 +15,9 @@ LOG = getLogger("PLEX." + __name__)
###############################################################################
-@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
- 'STOP_SYNC',
- 'SUSPEND_SYNC'])
+@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
+ 'STOP_SYNC',
+ 'SUSPEND_SYNC'])
class ThreadedGetMetadata(Thread):
"""
Threaded download of Plex XML metadata for a certain library item.
@@ -79,7 +78,7 @@ class ThreadedGetMetadata(Thread):
sleep(20)
continue
# Download Metadata
- xml = GetPlexMetadata(item['plex_id'])
+ xml = PF.GetPlexMetadata(item['plex_id'])
if xml is None:
# Did not receive a valid XML - skip that item for now
LOG.error("Could not get metadata for %s. Skipping that item "
@@ -93,14 +92,14 @@ class ThreadedGetMetadata(Thread):
elif xml == 401:
LOG.error('HTTP 401 returned by PMS. Too much strain? '
'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.)
queue.task_done()
break
item['xml'] = xml
if item.get('get_children') is True:
- children_xml = GetAllPlexChildren(item['plex_id'])
+ children_xml = PF.GetAllPlexChildren(item['plex_id'])
try:
children_xml[0].attrib
except (TypeError, IndexError, AttributeError):
diff --git a/resources/lib/library_sync/process_metadata.py b/resources/lib/library_sync/process_metadata.py
index 7ce4f5f7..e7930a95 100644
--- a/resources/lib/library_sync/process_metadata.py
+++ b/resources/lib/library_sync/process_metadata.py
@@ -2,12 +2,11 @@
from logging import getLogger
from threading import Thread
from Queue import Empty
-
from xbmc import sleep
-from utils import thread_methods
-import itemtypes
-import sync_info
+from .. import utils
+from .. import itemtypes
+from . import sync_info
###############################################################################
LOG = getLogger("PLEX." + __name__)
@@ -15,9 +14,9 @@ LOG = getLogger("PLEX." + __name__)
###############################################################################
-@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
- 'STOP_SYNC',
- 'SUSPEND_SYNC'])
+@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
+ 'STOP_SYNC',
+ 'SUSPEND_SYNC'])
class ThreadedProcessMetadata(Thread):
"""
Not yet implemented for more than 1 thread - if ever. Only to be called by
diff --git a/resources/lib/library_sync/sync_info.py b/resources/lib/library_sync/sync_info.py
index a292bd7a..fd22520b 100644
--- a/resources/lib/library_sync/sync_info.py
+++ b/resources/lib/library_sync/sync_info.py
@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from threading import Thread, Lock
-
from xbmc import sleep
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',
- 'STOP_SYNC',
- 'SUSPEND_SYNC'])
+@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
+ 'STOP_SYNC',
+ 'SUSPEND_SYNC'])
class ThreadedShowSyncInfo(Thread):
"""
Threaded class to show the Kodi statusbar of the metadata download.
@@ -44,7 +43,10 @@ class ThreadedShowSyncInfo(Thread):
total = self.total
dialog = DialogProgressBG('dialoglogProgressBG')
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_progress = 0
@@ -55,15 +57,15 @@ class ThreadedShowSyncInfo(Thread):
view_name = PROCESSING_VIEW_NAME
total_progress = get_progress + process_progress
try:
- percentage = int(float(total_progress) / float(total)*100.0)
+ percentage = int(float(total_progress) / float(total) * 100.0)
except ZeroDivisionError:
percentage = 0
dialog.update(percentage,
message="%s %s. %s %s: %s"
% (get_progress,
- lang(39712),
+ utils.lang(39712),
process_progress,
- lang(39713),
+ utils.lang(39713),
view_name))
# Sleep for x milliseconds
sleep(200)
diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py
index 898fb093..931999ed 100644
--- a/resources/lib/librarysync.py
+++ b/resources/lib/librarysync.py
@@ -5,34 +5,27 @@ from threading import Thread
import Queue
from random import shuffle
import copy
-
import xbmc
from xbmcvfs import exists
-import utils
-from utils import window, settings, dialog, language as lang, try_decode, \
- try_encode
-from downloadutils import DownloadUtils as DU
-import itemtypes
-import plexdb_functions as plexdb
-import kodidb_functions as kodidb
-import artwork
-import videonodes
-import variables as v
-
-import PlexFunctions as PF
-import PlexAPI
-from library_sync.get_metadata import ThreadedGetMetadata
-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
+from . import utils
+from .downloadutils import DownloadUtils as DU
+from . import itemtypes
+from . import plexdb_functions as plexdb
+from . import kodidb_functions as kodidb
+from . import artwork
+from . import videonodes
+from . import plex_functions as PF
+from .plex_api import API
+from .library_sync import get_metadata, process_metadata, fanart, sync_info
+from . import music
+from . import playlists
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.librarysync')
###############################################################################
@@ -47,10 +40,10 @@ class LibrarySync(Thread):
self.views = []
self.session_keys = {}
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?
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?
self.force_dialog = True
# 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:
return
if icon == "plex":
- dialog('notification',
- heading='{plex}',
- message=message,
- icon='{plex}',
- sound=False)
+ utils.dialog('notification',
+ heading='{plex}',
+ message=message,
+ icon='{plex}',
+ sound=False)
elif icon == "error":
- dialog('notification',
- heading='{plex}',
- message=message,
- icon='{error}')
+ utils.dialog('notification',
+ heading='{plex}',
+ message=message,
+ icon='{error}')
@staticmethod
def sync_pms_time():
@@ -200,7 +193,8 @@ class LibrarySync(Thread):
# Calculate time offset Kodi-PMS
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",
str(state.KODI_PLEX_TIME_OFFSET))
return True
@@ -288,15 +282,15 @@ class LibrarySync(Thread):
if state.ENABLE_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
- dialog('ok', heading='{plex}', line1=lang(39408))
- window('plex_scancrashed', clear=True)
- elif window('plex_scancrashed') == '401':
- window('plex_scancrashed', clear=True)
+ utils.dialog('ok', heading='{plex}', line1=utils.lang(39408))
+ utils.window('plex_scancrashed', clear=True)
+ elif utils.window('plex_scancrashed') == '401':
+ utils.window('plex_scancrashed', clear=True)
if state.PMS_STATUS not in ('401', 'Auth'):
# 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
def _process_view(self, folder_item, kodi_db, plex_db, totalnodes):
@@ -495,7 +489,7 @@ class LibrarySync(Thread):
# totalnodes += 1
# 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)
if self.old_views:
@@ -524,11 +518,11 @@ class LibrarySync(Thread):
elif item['kodi_type'] in v.KODI_AUDIOTYPES:
delete_music.append(item)
- dialog('notification',
- heading='{plex}',
- message=lang(30052),
- icon='{plex}',
- sound=False)
+ utils.dialog('notification',
+ heading='{plex}',
+ message=utils.lang(30052),
+ icon='{plex}',
+ sound=False)
for item in delete_movies:
with itemtypes.Movies() as movie_db:
movie_db.remove(item['plex_id'])
@@ -638,8 +632,8 @@ class LibrarySync(Thread):
def process_updatelist(self, item_class):
"""
- Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes them
- by then calling item_classs.()
+ Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes
+ them by then calling item_classs.()
Input:
item_class: 'Movies', 'TVShows', ...
@@ -665,13 +659,15 @@ class LibrarySync(Thread):
# Spawn GetMetadata threads for downloading
threads = []
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.start()
threads.append(thread)
LOG.debug("%s download threads spawned", len(threads))
# 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.start()
threads.append(thread)
@@ -702,7 +698,7 @@ class LibrarySync(Thread):
except:
pass
LOG.debug("Sync threads finished")
- if (settings('FanartTV') == 'true' and
+ if (utils.settings('FanartTV') == 'true' and
item_class in ('Movies', 'TVShows')):
for item in self.updatelist:
if item['plex_type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
@@ -1099,7 +1095,7 @@ class LibrarySync(Thread):
continue
else:
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):
self.fanartqueue.put({
'plex_id': item['ratingKey'],
@@ -1294,7 +1290,7 @@ class LibrarySync(Thread):
if kodi_info is None:
# Item not (yet) in Kodi library
continue
- if settings('plex_serverowned') == 'false':
+ if utils.settings('plex_serverowned') == 'false':
# Not our PMS, we are not authorized to get the sessions
# On the bright side, it must be us playing :-)
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]['kodi_type'] = kodi_info[4]
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
# update if neither session's username nor userid match
# (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',
plex_id)
continue
- api = PlexAPI.API(xml[0])
+ api = API(xml[0])
userdata = api.userdata()
session['duration'] = userdata['Runtime']
session['viewCount'] = userdata['PlayCount']
@@ -1378,7 +1374,8 @@ class LibrarySync(Thread):
resume,
session['duration'],
session['file_id'],
- utils.unix_date_to_kodi(utils.unix_timestamp()),
+ utils.unix_date_to_kodi(
+ utils.unix_timestamp()),
plex_type)
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
refresh=False True will force refresh all external fanart
"""
- if settings('FanartTV') == 'false':
+ if utils.settings('FanartTV') == 'false':
return
with plexdb.Get_Plex_DB() as plex_db:
if missing_only:
@@ -1407,7 +1404,8 @@ class LibrarySync(Thread):
# Shuffle the list to not always start out identically
shuffle(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):
self.fanartqueue.put({
'plex_id': item['plex_id'],
@@ -1415,7 +1413,7 @@ class LibrarySync(Thread):
'refresh': refresh
})
# FanartTV lookup completed
- self.fanartqueue.put(artwork.ArtworkSyncMessage(lang(30019)))
+ self.fanartqueue.put(artwork.ArtworkSyncMessage(utils.lang(30019)))
def triage_lib_scans(self):
"""
@@ -1424,27 +1422,27 @@ class LibrarySync(Thread):
"""
if state.RUN_LIB_SCAN in ("full", "repair"):
LOG.info('Full library scan requested, starting')
- window('plex_dbScan', value="true")
+ utils.window('plex_dbScan', value="true")
state.DB_SCAN = True
success = self.maintain_views()
if success and state.RUN_LIB_SCAN == "full":
success = self.full_sync()
elif success:
success = self.full_sync(repair=True)
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
if success:
# Full library sync finished
- self.show_kodi_note(lang(39407))
+ self.show_kodi_note(utils.lang(39407))
elif not self.suspend_item_sync():
self.force_dialog = True
# 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
# Reset views was requested from somewhere else
elif state.RUN_LIB_SCAN == "views":
LOG.info('Refresh playlist and nodes requested, starting')
- window('plex_dbScan', value="true")
+ utils.window('plex_dbScan', value="true")
state.DB_SCAN = True
# First remove playlists
utils.delete_playlists()
@@ -1455,28 +1453,28 @@ class LibrarySync(Thread):
# Ran successfully
LOG.info("Refresh playlists/nodes completed")
# "Plex playlists/nodes refreshed"
- self.show_kodi_note(lang(39405))
+ self.show_kodi_note(utils.lang(39405))
else:
# Failed
LOG.error("Refresh playlists/nodes failed")
# "Plex playlists/nodes refresh failed"
- self.show_kodi_note(lang(39406), icon="error")
- window('plex_dbScan', clear=True)
+ self.show_kodi_note(utils.lang(39406), icon="error")
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
elif state.RUN_LIB_SCAN == 'fanart':
# Only look for missing fanart (No)
# or refresh all fanart (Yes)
- refresh = dialog('yesno',
- heading='{plex}',
- line1=lang(39223),
- nolabel=lang(39224),
- yeslabel=lang(39225))
+ refresh = utils.dialog('yesno',
+ heading='{plex}',
+ line1=utils.lang(39223),
+ nolabel=utils.lang(39224),
+ yeslabel=utils.lang(39225))
self.sync_fanart(missing_only=not refresh, refresh=refresh)
elif state.RUN_LIB_SCAN == 'textures':
state.DB_SCAN = True
- window('plex_dbScan', value="true")
+ utils.window('plex_dbScan', value="true")
artwork.Artwork().fullTextureCacheSync()
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
else:
raise NotImplementedError('Library scan not defined: %s'
@@ -1489,12 +1487,12 @@ class LibrarySync(Thread):
self._run_internal()
except Exception as e:
state.DB_SCAN = False
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
LOG.error('LibrarySync thread crashed. Error message: %s', e)
import traceback
LOG.error("Traceback:\n%s", traceback.format_exc())
# Library sync thread has crashed
- dialog('ok', heading='{plex}', line1=lang(39400))
+ utils.dialog('ok', heading='{plex}', line1=utils.lang(39400))
raise
def _run_internal(self):
@@ -1504,21 +1502,21 @@ class LibrarySync(Thread):
last_sync = 0
last_processing = 0
last_time_sync = 0
- one_day_in_seconds = 60*60*24
+ one_day_in_seconds = 60 * 60 * 24
# Link to Websocket queue
queue = state.WEBSOCKET_QUEUE
- if (not exists(try_encode(v.DB_VIDEO_PATH)) or
- not exists(try_encode(v.DB_TEXTURE_PATH)) or
+ if (not exists(utils.try_encode(v.DB_VIDEO_PATH)) or
+ not exists(utils.try_encode(v.DB_TEXTURE_PATH)) or
(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
LOG.error("The current Kodi version is incompatible "
"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')))
# "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
# Do some initializing
@@ -1526,14 +1524,14 @@ class LibrarySync(Thread):
self.initialize_plex_db()
# Run start up sync
state.DB_SCAN = True
- window('plex_dbScan', value="true")
- LOG.info("Db version: %s", settings('dbCreatedWithVersion'))
+ utils.window('plex_dbScan', value="true")
+ LOG.info("Db version: %s", utils.settings('dbCreatedWithVersion'))
LOG.info('Refreshing video nodes and playlists now')
# Setup the paths for addon-paths (even when using direct paths)
with kodidb.GetKodiDB('video') as kodi_db:
kodi_db.setup_path_table()
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
playlist_monitor = None
@@ -1549,7 +1547,7 @@ class LibrarySync(Thread):
if not self.install_sync_done:
# Very first sync upon installation or reset of Kodi DB
state.DB_SCAN = True
- window('plex_dbScan', value='true')
+ utils.window('plex_dbScan', value='true')
# Initialize time offset Kodi - PMS
self.sync_pms_time()
last_time_sync = utils.unix_timestamp()
@@ -1562,9 +1560,9 @@ class LibrarySync(Thread):
LOG.error('Initial maintain_views not successful')
elif self.full_sync():
LOG.info('Initial start-up full sync successful')
- settings('SyncInstallRunDone', value='true')
+ utils.settings('SyncInstallRunDone', value='true')
self.install_sync_done = True
- settings('dbCreatedWithVersion', v.ADDON_VERSION)
+ utils.settings('dbCreatedWithVersion', v.ADDON_VERSION)
self.force_dialog = False
initial_sync_done = True
kodi_db_version_checked = True
@@ -1576,27 +1574,29 @@ class LibrarySync(Thread):
else:
LOG.error('Initial start-up full sync unsuccessful')
xbmc.executebuiltin('InhibitIdleShutdown(false)')
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
elif not kodi_db_version_checked:
# Install sync was already done, don't force-show dialogs
self.force_dialog = False
# Verify the validity of the database
- current_version = settings('dbCreatedWithVersion')
- if not utils.compare_version(current_version, v.MIN_DB_VERSION):
+ current_version = utils.settings('dbCreatedWithVersion')
+ if not utils.compare_version(current_version,
+ v.MIN_DB_VERSION):
LOG.warn("Db version out of date: %s minimum version "
"required: %s", current_version, v.MIN_DB_VERSION)
# DB out of date. Proceed to recreate?
- resp = dialog('yesno',
- heading=lang(29999),
- line1=lang(39401))
+ resp = utils.dialog('yesno',
+ heading=utils.lang(29999),
+ line1=utils.lang(39401))
if not resp:
LOG.warn("Db version out of date! USER IGNORED!")
# PKC may not work correctly until reset
- dialog('ok',
- heading='{plex}',
- line1=lang(29999) + lang(39402))
+ utils.dialog('ok',
+ heading='{plex}',
+ line1='%s%s' % (utils.lang(29999),
+ utils.lang(39402)))
else:
utils.reset(ask_user=False)
break
@@ -1606,7 +1606,7 @@ class LibrarySync(Thread):
# First sync upon PKC restart. Skipped if very first sync upon
# PKC installation has been completed
state.DB_SCAN = True
- window('plex_dbScan', value="true")
+ utils.window('plex_dbScan', value="true")
LOG.info('Doing initial sync on Kodi startup')
if state.SUSPEND_SYNC:
LOG.warning('Forcing startup sync even if Kodi is playing')
@@ -1627,7 +1627,7 @@ class LibrarySync(Thread):
playlist_monitor = playlists.kodi_playlist_monitor()
else:
LOG.info('Startup sync has not yet been successful')
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
# Currently no db scan, so we can start a new scan
@@ -1646,23 +1646,23 @@ class LibrarySync(Thread):
not self.suspend_item_sync()):
LOG.info('Doing scheduled full library scan')
state.DB_SCAN = True
- window('plex_dbScan', value="true")
+ utils.window('plex_dbScan', value="true")
success = self.maintain_views()
if success:
success = self.full_sync()
if not success and not self.suspend_item_sync():
LOG.error('Could not finish scheduled full sync')
self.force_dialog = True
- self.show_kodi_note(lang(39410),
+ self.show_kodi_note(utils.lang(39410),
icon='error')
self.force_dialog = False
elif success:
last_sync = now
# Full library sync finished successfully
- self.show_kodi_note(lang(39407))
+ self.show_kodi_note(utils.lang(39407))
else:
LOG.info('Full sync interrupted')
- window('plex_dbScan', clear=True)
+ utils.window('plex_dbScan', clear=True)
state.DB_SCAN = False
elif now - last_time_sync > one_day_in_seconds:
LOG.info('Starting daily time sync')
diff --git a/resources/lib/migration.py b/resources/lib/migration.py
index 41aceb02..00c15228 100644
--- a/resources/lib/migration.py
+++ b/resources/lib/migration.py
@@ -1,29 +1,31 @@
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():
- log.info('Checking whether we need to migrate something')
- last_migration = settings('last_migrated_PKC_version')
+ LOG.info('Checking whether we need to migrate something')
+ last_migration = utils.settings('last_migrated_PKC_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!
- settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
+ utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
return
- if not compare_version(last_migration, '1.8.2'):
- log.info('Migrating to version 1.8.1')
+ if not utils.compare_version(last_migration, '1.8.2'):
+ LOG.info('Migrating to version 1.8.1')
# Set the new PKC theMovieDB key
- settings('themoviedbAPIKey', value='19c90103adb9e98f2172c6a6a3d85dc4')
+ utils.settings('themoviedbAPIKey',
+ value='19c90103adb9e98f2172c6a6a3d85dc4')
- if not compare_version(last_migration, '2.0.25'):
- log.info('Migrating to version 2.0.24')
+ if not utils.compare_version(last_migration, '2.0.25'):
+ LOG.info('Migrating to version 2.0.24')
# Need to re-connect with PMS to pick up on plex.direct URIs
- settings('ipaddress', value='')
- settings('port', value='')
+ utils.settings('ipaddress', value='')
+ utils.settings('port', value='')
- settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
+ utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
diff --git a/resources/lib/music.py b/resources/lib/music.py
index 30e7ab61..518f4747 100644
--- a/resources/lib/music.py
+++ b/resources/lib/music.py
@@ -3,12 +3,12 @@ from logging import getLogger
from re import compile as re_compile
from xml.etree.ElementTree import ParseError
-from utils import XmlKodiSetting, reboot_kodi, language as lang
-from PlexAPI import API
-import variables as v
+from . import utils
+from .plex_api import API
+from . import variables as v
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.music')
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
###############################################################################
@@ -37,9 +37,10 @@ def excludefromscan_music_folders(xml):
omit_check=True)
paths.append(__turn_to_regex(path))
try:
- with XmlKodiSetting('advancedsettings.xml',
- force_create=True,
- top_element='advancedsettings') as xml_file:
+ with utils.XmlKodiSetting(
+ 'advancedsettings.xml',
+ force_create=True,
+ top_element='advancedsettings') as xml_file:
parent = xml_file.set_setting(['audio', 'excludefromscan'])
for path in paths:
for element in parent:
@@ -71,7 +72,7 @@ def excludefromscan_music_folders(xml):
if reboot is True:
# 'New Plex music library detected. Sorry, but we need to
# restart Kodi now due to the changes made.'
- reboot_kodi(lang(39711))
+ utils.reboot_kodi(utils.lang(39711))
def __turn_to_regex(path):
diff --git a/resources/lib/pickler.py b/resources/lib/pickler.py
index 3e277104..d7450b27 100644
--- a/resources/lib/pickler.py
+++ b/resources/lib/pickler.py
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
###############################################################################
from cPickle import dumps, loads
-
from xbmcgui import Window
from xbmc import log, LOGDEBUG
###############################################################################
WINDOW = Window(10000)
-PREFIX = 'PLEX.%s: ' % __name__
+PREFIX = 'PLEX.pickler: '
###############################################################################
diff --git a/resources/lib/playback.py b/resources/lib/playback.py
index 87d0e1ec..7be1f5d4 100644
--- a/resources/lib/playback.py
+++ b/resources/lib/playback.py
@@ -4,27 +4,25 @@ Used to kick off Kodi playback
from logging import getLogger
from threading import Thread
from os.path import join
-
from xbmc import Player, sleep
-from PlexAPI import API
-from PlexFunctions import GetPlexMetadata, init_plex_playqueue
-from downloadutils import DownloadUtils as DU
-import plexdb_functions as plexdb
-import kodidb_functions as kodidb
-import playlist_func as PL
-import playqueue as PQ
-from playutils import PlayUtils
-from PKC_listitem import PKC_ListItem
-from pickler import pickle_me, Playback_Successful
-import json_rpc as js
-from utils import settings, dialog, language as lang, try_encode
-from plexbmchelper.subscribers import LOCKER
-import variables as v
-import state
+from .plex_api import API
+from . import plex_functions as PF
+from . import utils
+from .downloadutils import DownloadUtils as DU
+from . import plexdb_functions as plexdb
+from . import kodidb_functions as kodidb
+from . import playlist_func as PL
+from . import playqueue as PQ
+from . import json_rpc as js
+from . import pickler
+from .playutils import PlayUtils
+from .pkc_listitem import PKCListItem
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.playback')
# Do we need to return ultimately with a setResolvedUrl?
RESOLVE = True
# 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):
"""
Hit this function for addon path playback, Plex trailers, etc.
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)
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
"""
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
# If started via Kodi context menu, we never resolve
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False
if not state.AUTHENTICATED:
LOG.error('Not yet authenticated for PMS, abort starting playback')
# "Unauthorized for PMS"
- dialog('notification', lang(29999), lang(30017))
+ utils.dialog('notification', utils.lang(29999), utils.lang(30017))
_ensure_resolve(abort=True)
return
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:
LOG.error('Still no position - abort')
# "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)
return
# 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:
item = playqueue.items[pos]
except IndexError:
+ LOG.debug('PKC playqueue yet empty, need to initialize playback')
initiate = True
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:
_playback_init(plex_id, plex_type, playqueue, pos)
else:
@@ -124,13 +131,16 @@ def _playlist_playback(plex_id, plex_type):
for the next item in line :-)
(by the way: trying to get active Kodi player id will return [])
"""
- xml = GetPlexMetadata(plex_id)
+ xml = PF.GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (IndexError, TypeError, AttributeError):
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "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)
return
# 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.
"""
LOG.info('Initializing PKC playback')
- xml = GetPlexMetadata(plex_id)
+ xml = PF.GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (IndexError, TypeError, AttributeError):
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "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)
return
if playqueue.kodi_pl.size() > 1:
@@ -179,10 +192,12 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
api = API(xml[0])
trailers = False
if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and
- settings('enableCinema') == "true"):
- if settings('askCinema') == "true":
+ utils.settings('enableCinema') == "true"):
+ if utils.settings('askCinema') == "true":
# "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
else:
trailers = True
@@ -198,15 +213,18 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
playqueue.clear()
if plex_type != v.PLEX_TYPE_CLIP:
# Post to the PMS to create a playqueue - in any case due to Companion
- xml = init_plex_playqueue(plex_id,
- xml.attrib.get('librarySectionUUID'),
- mediatype=plex_type,
- trailers=trailers)
+ xml = PF.init_plex_playqueue(plex_id,
+ xml.attrib.get('librarySectionUUID'),
+ mediatype=plex_type,
+ trailers=trailers)
if xml is None:
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
plex_id, xml.attrib.get('librarySectionUUID'))
# "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
state.CONTEXT_MENU_PLAY = False
state.FORCE_TRANSCODE = False
@@ -253,9 +271,13 @@ def _ensure_resolve(abort=False):
# Because playback won't start with context menu play
state.PKC_CAUSED_STOP = True
state.PKC_CAUSED_STOP_DONE = False
- result = Playback_Successful()
- result.listitem = PKC_ListItem(path=NULL_VIDEO)
- pickle_me(result)
+ if not abort:
+ result = pickler.Playback_Successful()
+ result.listitem = PKCListItem(path=NULL_VIDEO)
+ pickler.pickle_me(result)
+ else:
+ # Shows PKC error message
+ pickler.pickle_me(None)
if abort:
# Reset some playback variables
state.CONTEXT_MENU_PLAY = False
@@ -308,7 +330,7 @@ def _prep_playlist_stack(xml):
# Need to redirect again to PKC to conclude playback
path = api.path()
listitem = api.create_listitem()
- listitem.setPath(try_encode(path))
+ listitem.setPath(utils.try_encode(path))
else:
# Will add directly via the Kodi DB
path = None
@@ -373,8 +395,8 @@ def _conclude_playback(playqueue, pos):
return PKC listitem attached to result
"""
LOG.info('Concluding playback for playqueue position %s', pos)
- result = Playback_Successful()
- listitem = PKC_ListItem()
+ result = pickler.Playback_Successful()
+ listitem = PKCListItem()
item = playqueue.items[pos]
if item.xml is not None:
# Got a Plex element
@@ -385,7 +407,7 @@ def _conclude_playback(playqueue, pos):
playurl = playutils.getPlayUrl()
else:
playurl = item.file
- listitem.setPath(try_encode(playurl))
+ listitem.setPath(utils.try_encode(playurl))
if item.playmethod == 'DirectStream':
listitem.setSubtitles(api.cache_external_subs())
elif item.playmethod == 'Transcode':
@@ -405,7 +427,7 @@ def _conclude_playback(playqueue, pos):
listitem.setProperty('resumetime', str(item.offset))
# Reset the resumable flag
result.listitem = listitem
- pickle_me(result)
+ pickler.pickle_me(result)
LOG.info('Done concluding playback')
@@ -424,7 +446,7 @@ def process_indirect(key, offset, resolve=True):
key, offset, resolve)
global RESOLVE
RESOLVE = resolve
- result = Playback_Successful()
+ result = pickler.Playback_Successful()
if key.startswith('http') or key.startswith('{server}'):
xml = DU().downloadUrl(key)
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))
# Todo: implement offset
api = API(xml[0])
- listitem = PKC_ListItem()
+ listitem = PKCListItem()
api.create_listitem(listitem)
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
@@ -462,14 +484,14 @@ def process_indirect(key, offset, resolve=True):
return
playurl = xml[0].attrib['key']
item.file = playurl
- listitem.setPath(try_encode(playurl))
+ listitem.setPath(utils.try_encode(playurl))
playqueue.items.append(item)
if resolve is True:
result.listitem = listitem
- pickle_me(result)
+ pickler.pickle_me(result)
else:
thread = Thread(target=Player().play,
- args={'item': try_encode(playurl),
+ args={'item': utils.try_encode(playurl),
'listitem': listitem})
thread.setDaemon(True)
LOG.info('Done initializing PKC playback, starting Kodi player')
diff --git a/resources/lib/playback_starter.py b/resources/lib/playback_starter.py
index 86433bcd..ff20b519 100644
--- a/resources/lib/playback_starter.py
+++ b/resources/lib/playback_starter.py
@@ -4,16 +4,16 @@ from logging import getLogger
from threading import Thread
from urlparse import parse_qsl
-import playback
-from context_entry import ContextMenu
-import state
-import json_rpc as js
-from pickler import pickle_me, Playback_Successful
-import kodidb_functions as kodidb
+from . import playback
+from . import context_entry
+from . import json_rpc as js
+from . import pickler
+from . 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:
# E.g. other add-ons scanning for Extras folder
LOG.debug('Detected 3rd party add-on call - ignoring')
- pickle_me(Playback_Successful())
+ pickler.pickle_me(pickler.Playback_Successful())
return
params = dict(parse_qsl(params))
mode = params.get('mode')
@@ -54,10 +54,10 @@ class PlaybackStarter(Thread):
else:
LOG.error('Could not find tv show id for %s', item)
if resolve:
- pickle_me(Playback_Successful())
+ pickler.pickle_me(pickler.Playback_Successful())
elif mode == 'context_menu':
- ContextMenu(kodi_id=params.get('kodi_id'),
- kodi_type=params.get('kodi_type'))
+ context_entry.ContextMenu(kodi_id=params.get('kodi_id'),
+ kodi_type=params.get('kodi_type'))
def run(self):
queue = state.COMMAND_PIPELINE_QUEUE
diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py
index 0c587551..83ee8653 100644
--- a/resources/lib/playlist_func.py
+++ b/resources/lib/playlist_func.py
@@ -8,18 +8,18 @@ import urllib
from urlparse import parse_qsl, urlsplit
from re import compile as re_compile
-import plexdb_functions as plexdb
-from downloadutils import DownloadUtils as DU
-from utils import try_decode, try_encode
-from PlexAPI import API
-from PlexFunctions import GetPlexMetadata
-from kodidb_functions import kodiid_from_filename
-import json_rpc as js
-import variables as v
+from .plex_api import API
+from . import plex_functions as PF
+from . import plexdb_functions as plexdb
+from . import kodidb_functions as kodidb
+from .downloadutils import DownloadUtils as DU
+from . import utils
+from . import json_rpc as js
+from . import variables as v
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.playlist_func')
REGEX = re_compile(r'''metadata%2F(\d+)''')
###############################################################################
@@ -51,13 +51,14 @@ class PlaylistObjectBaseclase(object):
continue
if isinstance(getattr(self, key), str):
answ += '\'%s\': \'%s\', ' % (key,
- try_decode(getattr(self, key)))
+ utils.try_decode(getattr(self,
+ key)))
elif isinstance(getattr(self, key), unicode):
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
else:
# e.g. int
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
- return try_encode(answ + '}}')
+ return utils.try_encode(answ + '}}')
class Playlist_Object(PlaylistObjectBaseclase):
@@ -228,7 +229,8 @@ class Playlist_Item(object):
continue
if isinstance(getattr(self, key), str):
answ += '\'%s\': \'%s\', ' % (key,
- try_decode(getattr(self, key)))
+ utils.try_decode(getattr(self,
+ key)))
elif isinstance(getattr(self, key), unicode):
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
else:
@@ -238,7 +240,7 @@ class Playlist_Item(object):
answ += '\'xml\': None}}'
else:
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):
"""
@@ -317,7 +319,7 @@ def playlist_item_from_kodi(kodi_item):
item.plex_type = query.get('itemType')
if item.plex_id is None and item.file is not None:
item.uri = ('library://whatever/item/%s'
- % urllib.quote(try_encode(item.file), safe=''))
+ % urllib.quote(utils.try_encode(item.file), safe=''))
else:
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
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.
if (kodi_item['file'].startswith('plugin') or
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',
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
if kodi_id is None:
- kodi_id, _ = kodiid_from_filename(kodi_item['file'],
- v.KODI_TYPE_EPISODE)
+ kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
+ v.KODI_TYPE_EPISODE)
kodi_item['type'] = v.KODI_TYPE_EPISODE
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['id'] = kodi_id
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
item = playlist_item_from_xml(xml[0])
except (KeyError, IndexError, TypeError):
- raise PlaylistError('Could not init Plex playlist with plex_id %s and '
- 'kodi_item %s' % (plex_id, kodi_item))
+ LOG.error('Could not init Plex playlist: plex_id %s, kodi_item %s',
+ plex_id, kodi_item)
+ raise PlaylistError
playlist.items.append(item)
LOG.debug('Initialized the playqueue on the Plex side: %s', playlist)
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(
{'id': kodi_id, 'type': kodi_type, 'file': file})
if item.plex_id is not None:
- xml = GetPlexMetadata(item.plex_id)
+ xml = PF.GetPlexMetadata(item.plex_id)
item.xml = xml[-1]
playlist.items.insert(pos, item)
return item
@@ -863,7 +869,7 @@ def get_plextype_from_xml(xml):
except IndexError:
LOG.error('Could not get plex_id from xml: %s', xml.attrib)
return
- new_xml = GetPlexMetadata(plex_id)
+ new_xml = PF.GetPlexMetadata(plex_id)
try:
new_xml[0].attrib
except (TypeError, IndexError, AttributeError):
diff --git a/resources/lib/playlists.py b/resources/lib/playlists.py
index ea7880c4..f0c99ded 100644
--- a/resources/lib/playlists.py
+++ b/resources/lib/playlists.py
@@ -2,28 +2,21 @@
from logging import getLogger
import os
import sys
-from threading import Lock
-
from xbmcvfs import exists
-from watchdog.events import FileSystemEventHandler
-from watchdog.observers import Observer
-import playlist_func as PL
-from PlexAPI import API
-import kodidb_functions as kodidb
-import plexdb_functions as plexdb
-import utils
-import variables as v
-import state
+from .watchdog.events import FileSystemEventHandler
+from .watchdog.observers import Observer
+from . import playlist_func as PL
+from .plex_api import API
+from . import kodidb_functions as kodidb
+from . import plexdb_functions as plexdb
+from . import utils
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
-
-# Necessary to temporarily hold back librarysync/websocket listener when doing
-# a full sync
-LOCK = Lock()
-LOCKER = utils.LockFunction(LOCK)
+LOG = getLogger('PLEX.playlists')
# Which playlist formates are supported by PKC?
SUPPORTED_FILETYPES = (
@@ -274,7 +267,7 @@ def _kodi_playlist_identical(xml_element):
pass
-@LOCKER.lockthis
+@state.LOCKER_PLAYLISTS.lockthis
def process_websocket(plex_id, updated_at, state):
"""
Hit by librarysync to process websocket messages concerning playlists
@@ -302,7 +295,7 @@ def process_websocket(plex_id, updated_at, state):
pass
-@LOCKER.lockthis
+@state.LOCKER_PLAYLISTS.lockthis
def full_sync():
"""
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 = event.event_type
- with LOCK:
+ with state.LOCK_PLAYLISTS:
_method_map[event_type](event)
def on_created(self, event):
diff --git a/resources/lib/playqueue.py b/resources/lib/playqueue.py
index 2ef97858..232caf0a 100644
--- a/resources/lib/playqueue.py
+++ b/resources/lib/playqueue.py
@@ -4,21 +4,18 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
from logging import getLogger
from threading import Thread
from re import compile as re_compile
+import xbmc
-from xbmc import Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO, sleep
-
-from utils import thread_methods
-import playlist_func as PL
-from PlexFunctions import GetAllPlexChildren
-from PlexAPI import API
-from plexbmchelper.subscribers import LOCK
-from playback import play_xml
-import json_rpc as js
-import variables as v
-import state
+from . import utils
+from . import playlist_func as PL
+from . import plex_functions as PF
+from .plex_api import API
+from . import json_rpc as js
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.playqueue')
PLUGIN = 'plugin://%s' % v.ADDON_ID
REGEX = re_compile(r'''plex_id=(\d+)''')
@@ -37,7 +34,7 @@ def init_playqueues():
LOG.debug('Playqueues have already been initialized')
return
# Initialize Kodi playqueues
- with LOCK:
+ with state.LOCK_SUBSCRIBER:
for i in (0, 1, 2):
# Just in case the Kodi response is not sorted correctly
for queue in js.get_playlists():
@@ -48,12 +45,12 @@ def init_playqueues():
playqueue.type = queue['type']
# Initialize each Kodi playlist
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:
- playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
+ playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
else:
# 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'
playqueue.type = v.KODI_TYPE_PHOTO
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',
'audio', 'picture') passed in
"""
- with LOCK:
+ with state.LOCK_SUBSCRIBER:
for playqueue in PLAYQUEUES:
if playqueue.type == kodi_playlist_type:
break
@@ -81,7 +78,7 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
Returns the Playlist_Object
"""
- xml = GetAllPlexChildren(plex_id)
+ xml = PF.GetAllPlexChildren(plex_id)
try:
xml[0].attrib
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())
playqueue.plex_transient_token = transient_token
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
-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 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'])
+@utils.thread_methods(add_suspends=['PMS_STATUS'])
class PlayqueueMonitor(Thread):
"""
Unfortunately, Kodi does not tell if items within a Kodi playqueue
@@ -222,8 +189,8 @@ class PlayqueueMonitor(Thread):
while suspended():
if stopped():
break
- sleep(1000)
- with LOCK:
+ xbmc.sleep(1000)
+ with state.LOCK_SUBSCRIBER:
for playqueue in PLAYQUEUES:
kodi_pl = js.playlist_get_items(playqueue.playlistid)
if playqueue.old_kodi_pl != kodi_pl:
@@ -236,5 +203,5 @@ class PlayqueueMonitor(Thread):
# compare old and new playqueue
self._compare_playqueues(playqueue, kodi_pl)
playqueue.old_kodi_pl = list(kodi_pl)
- sleep(200)
+ xbmc.sleep(200)
LOG.info("----===## PlayqueueMonitor stopped ##===----")
diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py
index cad9b47e..536cfaa0 100644
--- a/resources/lib/playutils.py
+++ b/resources/lib/playutils.py
@@ -2,13 +2,13 @@
###############################################################################
from logging import getLogger
-from downloadutils import DownloadUtils as DU
-from utils import window, settings, language as lang, dialog, try_encode
-import variables as v
+from .downloadutils import DownloadUtils as DU
+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(),
'videoResolution': self.get_resolution(),
'videoQuality': '100',
- 'mediaBufferSize': int(settings('kodi_video_cache'))/1024,
+ 'mediaBufferSize': int(
+ utils.settings('kodi_video_cache')) / 1024,
})
self.item.playmethod = 'Transcode'
LOG.info("The playurl is: %s", playurl)
@@ -71,15 +72,15 @@ class PlayUtils():
return playurl
# set to either 'Direct Stream=1' or 'Transcode=2'
# and NOT to 'Direct Play=0'
- if settings('playType') != "0":
+ if utils.settings('playType') != "0":
# User forcing to play via HTTP
LOG.info("User chose to not direct play")
return
if self.mustTranscode():
return
return self.api.validate_playurl(path,
- self.api.plex_type(),
- force_check=True)
+ self.api.plex_type(),
+ force_check=True)
def mustTranscode(self):
"""
@@ -106,7 +107,7 @@ class PlayUtils():
# e.g. trailers. Avoids TypeError with "'h265' in codec"
LOG.info('No codec from PMS, not transcoding.')
return False
- if ((settings('transcodeHi10P') == 'true' and
+ if ((utils.settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10') and
('h264' in codec)):
LOG.info('Option to transcode 10bit h264 video content enabled.')
@@ -139,7 +140,7 @@ class PlayUtils():
if self.api.plex_type() == 'track':
return True
# set to 'Transcode=2'
- if settings('playType') == "2":
+ if utils.settings('playType') == "2":
# User forcing to play via HTTP
LOG.info("User chose to transcode")
return False
@@ -149,7 +150,7 @@ class PlayUtils():
def get_max_bitrate(self):
# get the addon video quality
- videoQuality = settings('maxVideoQualities')
+ videoQuality = utils.settings('maxVideoQualities')
bitrate = {
'0': 320,
'1': 720,
@@ -180,13 +181,13 @@ class PlayUtils():
'2': 720,
'3': 1080
}
- return H265[settings('transcodeH265')]
+ return H265[utils.settings('transcodeH265')]
def get_bitrate(self):
"""
Get the desired transcoding bitrate from the settings
"""
- videoQuality = settings('transcoderVideoQualities')
+ videoQuality = utils.settings('transcoderVideoQualities')
bitrate = {
'0': 320,
'1': 720,
@@ -207,7 +208,7 @@ class PlayUtils():
"""
Get the desired transcoding resolutions from the settings
"""
- chosen = settings('transcoderVideoQualities')
+ chosen = utils.settings('transcoderVideoQualities')
res = {
'0': '420x420',
'1': '576x320',
@@ -244,7 +245,7 @@ class PlayUtils():
audio_streams = []
subtitle_streams_list = []
# No subtitles as an option
- subtitle_streams = [lang(39706)]
+ subtitle_streams = [utils.lang(39706)]
downloadable_streams = []
download_subs = []
# selectAudioIndex = ""
@@ -264,35 +265,35 @@ class PlayUtils():
codec = stream.attrib.get('codec')
channellayout = stream.attrib.get('audioChannelLayout', "")
try:
- track = "%s %s - %s %s" % (audio_numb+1,
+ track = "%s %s - %s %s" % (audio_numb + 1,
stream.attrib['language'],
codec,
channellayout)
except KeyError:
- track = "%s %s - %s %s" % (audio_numb+1,
- lang(39707), # unknown
+ track = "%s %s - %s %s" % (audio_numb + 1,
+ utils.lang(39707), # unknown
codec,
channellayout)
audio_streams_list.append(index)
- audio_streams.append(try_encode(track))
+ audio_streams.append(utils.try_encode(track))
audio_numb += 1
# Subtitles
elif typus == "3":
try:
- track = "%s %s" % (sub_num+1, stream.attrib['language'])
+ track = "%s %s" % (sub_num + 1, stream.attrib['language'])
except KeyError:
- track = "%s %s (%s)" % (sub_num+1,
- lang(39707), # unknown
+ track = "%s %s (%s)" % (sub_num + 1,
+ utils.lang(39707), # unknown
stream.attrib.get('codec'))
default = stream.attrib.get('default')
forced = stream.attrib.get('forced')
downloadable = stream.attrib.get('key')
if default:
- track = "%s - %s" % (track, lang(39708)) # Default
+ track = "%s - %s" % (track, utils.lang(39708)) # Default
if forced:
- track = "%s - %s" % (track, lang(39709)) # Forced
+ track = "%s - %s" % (track, utils.lang(39709)) # Forced
if downloadable:
# We do know the language - temporarily download
if 'language' in stream.attrib:
@@ -303,23 +304,23 @@ class PlayUtils():
# We don't know the language - no need to download
else:
path = self.api.attach_plex_token_to_url(
- "%s%s" % (window('pms_server'),
+ "%s%s" % (utils.window('pms_server'),
stream.attrib['key']))
downloadable_streams.append(index)
- download_subs.append(try_encode(path))
+ download_subs.append(utils.try_encode(path))
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:
# Only show subs without asking user if they can be
# turned off
default_sub = index
subtitle_streams_list.append(index)
- subtitle_streams.append(try_encode(track))
+ subtitle_streams.append(utils.try_encode(track))
sub_num += 1
if audio_numb > 1:
- resp = dialog('select', lang(33013), audio_streams)
+ resp = utils.dialog('select', utils.lang(33013), audio_streams)
if resp > -1:
# User selected some audio track
args = {
@@ -335,14 +336,14 @@ class PlayUtils():
return
select_subs_index = None
- if (settings('pickPlexSubtitles') == 'true' and
+ if (utils.settings('pickPlexSubtitles') == 'true' and
default_sub is not None):
LOG.info('Using default Plex subtitle: %s', default_sub)
select_subs_index = default_sub
else:
- resp = dialog('select', lang(33014), subtitle_streams)
+ resp = utils.dialog('select', utils.lang(33014), subtitle_streams)
if resp > 0:
- select_subs_index = subtitle_streams_list[resp-1]
+ select_subs_index = subtitle_streams_list[resp - 1]
else:
# User selected no subtitles or backed out of dialog
select_subs_index = ''
diff --git a/resources/lib/PlexAPI.py b/resources/lib/plex_api.py
similarity index 94%
rename from resources/lib/PlexAPI.py
rename to resources/lib/plex_api.py
index c6416d2f..5bec86c5 100644
--- a/resources/lib/PlexAPI.py
+++ b/resources/lib/plex_api.py
@@ -32,30 +32,27 @@ http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-pyt
from logging import getLogger
from re import compile as re_compile, sub
from urllib import urlencode, unquote
-from os.path import basename, join
-from os import makedirs
-
+import os
from xbmcgui import ListItem
from xbmcvfs import exists
-import clientinfo as client
-from downloadutils import DownloadUtils as DU
-from utils import window, settings, language as lang, try_decode, try_encode, \
- unix_date_to_kodi, exists_dir, slugify, dialog, escape_html
-import PlexFunctions as PF
-import plexdb_functions as plexdb
-import kodidb_functions as kodidb
-import variables as v
-import state
+from .downloadutils import DownloadUtils as DU
+from . import clientinfo
+from . import utils
+from . import plex_functions as PF
+from . import plexdb_functions as plexdb
+from . import kodidb_functions as kodidb
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.plex_api')
REGEX_IMDB = re_compile(r'''/(tt\d+)''')
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
-if not exists_dir(v.EXTERNAL_SUBTITLE_TEMP_PATH):
- makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
+if not utils.exists_dir(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?
self.part = 0
self.mediastream = None
- self.server = window('pms_server')
+ self.server = utils.window('pms_server')
def set_part_number(self, number=None):
"""
@@ -206,7 +203,7 @@ class API(object):
ans = None
if ans is not None:
try:
- ans = try_decode(unquote(ans))
+ ans = utils.try_decode(unquote(ans))
except UnicodeDecodeError:
# Sometimes, Plex seems to have encoded in latin1
ans = unquote(ans).decode('latin1')
@@ -218,23 +215,23 @@ class API(object):
Will always use addon paths, never direct paths
"""
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):
# Let Plex transcode
# max width/height supported by plex image transcoder is 1920x1080
path = self.server + PF.transcode_image_path(
self.item[0][0].get('key'),
- window('pms_token'),
+ utils.window('pms_token'),
"%s%s" % (self.server, self.item[0][0].get('key')),
1920,
1080)
else:
path = self.attach_plex_token_to_url(
- '%s%s' % (window('pms_server'),
+ '%s%s' % (utils.window('pms_server'),
self.item[0][0].attrib['key']))
# Attach Plex id to url to let it be picked up by our playqueue agent
# 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):
"""
@@ -261,7 +258,7 @@ class API(object):
"""
res = self.item.get('addedAt')
if res is not None:
- res = unix_date_to_kodi(res)
+ res = utils.unix_date_to_kodi(res)
else:
res = '2000-01-01 10:00:00'
return res
@@ -298,7 +295,7 @@ class API(object):
played = True if playcount else False
try:
- last_played = unix_date_to_kodi(int(item['lastViewedAt']))
+ last_played = utils.unix_date_to_kodi(int(item['lastViewedAt']))
except (KeyError, ValueError):
last_played = None
@@ -426,7 +423,7 @@ class API(object):
"""
answ = self.item.get('guid')
if answ is not None:
- answ = escape_html(answ)
+ answ = utils.escape_html(answ)
return answ
def provider(self, providername=None):
@@ -459,7 +456,7 @@ class API(object):
"""
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):
"""
@@ -660,12 +657,12 @@ class API(object):
url may or may not already contain a '?'
"""
- if window('pms_token') == '':
+ if utils.window('pms_token') == '':
return 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:
- url = "%s&X-Plex-Token=%s" % (url, window('pms_token'))
+ url = "%s&X-Plex-Token=%s" % (url, utils.window('pms_token'))
return url
def item_id(self):
@@ -811,12 +808,12 @@ class API(object):
track['channels'] = stream.get('channels')
# 'unknown' if we cannot get language
track['language'] = stream.get(
- 'languageCode', lang(39310)).lower()
+ 'languageCode', utils.lang(39310)).lower()
audiotracks.append(track)
elif media_type == 3: # Subtitle streams
# 'unknown' if we cannot get language
subtitlelanguages.append(
- stream.get('languageCode', lang(39310)).lower())
+ stream.get('languageCode', utils.lang(39310)).lower())
return {
'video': videotracks,
'audio': audiotracks,
@@ -969,7 +966,7 @@ class API(object):
LOG.info('Start movie set/collection lookup on themoviedb with %s',
item.get('title', ''))
- api_key = settings('themoviedbAPIKey')
+ api_key = utils.settings('themoviedbAPIKey')
if media_type == v.PLEX_TYPE_SHOW:
media_type = 'tv'
title = item.get('title', '')
@@ -980,7 +977,7 @@ class API(object):
parameters = {
'api_key': api_key,
'language': v.KODILANGUAGE,
- 'query': try_encode(title)
+ 'query': utils.try_encode(title)
}
data = DU().downloadUrl(url,
authenticate=False,
@@ -1106,8 +1103,8 @@ class API(object):
try:
data.get('poster_path')
except AttributeError:
- LOG.debug('Could not find TheMovieDB poster paths for %s in '
- 'the language %s', title, language)
+ LOG.debug('Could not find TheMovieDB poster paths for %s'
+ ' in the language %s', title, language)
continue
if not poster and data.get('poster_path'):
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
"""
- api_key = settings('FanArtTVAPIKey')
+ api_key = utils.settings('FanArtTVAPIKey')
typus = self.plex_type()
if typus == v.PLEX_TYPE_SHOW:
typus = 'tv'
@@ -1239,17 +1236,17 @@ class API(object):
count += 1
if (count > 1 and (
(self.plex_type() != 'clip' and
- settings('bestQuality') == 'false')
+ utils.settings('bestQuality') == 'false')
or
(self.plex_type() == 'clip' and
- settings('bestTrailer') == 'false'))):
+ utils.settings('bestTrailer') == 'false'))):
# Several streams/files available.
dialoglist = []
for entry in self.item.iterfind('./Media'):
# Get additional info (filename / languages)
filename = None
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 = []
for stream in entry[0]:
@@ -1258,12 +1255,13 @@ class API(object):
languages.append(stream.attrib['language'])
languages = ', '.join(languages)
if filename:
- option = try_encode(filename)
+ option = utils.try_encode(filename)
if languages:
if option:
- option = '%s (%s): ' % (option, try_encode(languages))
+ option = '%s (%s): ' % (option,
+ utils.try_encode(languages))
else:
- option = '%s: ' % try_encode(languages)
+ option = '%s: ' % utils.try_encode(languages)
if 'videoResolution' in entry.attrib:
option = '%s%sp ' % (option,
entry.get('videoResolution'))
@@ -1278,7 +1276,7 @@ class API(object):
option = '%s%s ' % (option,
entry.get('audioCodec'))
dialoglist.append(option)
- media = dialog('select', 'Select stream', dialoglist)
+ media = utils.dialog('select', 'Select stream', dialoglist)
else:
media = 0
self.mediastream = media
@@ -1309,7 +1307,7 @@ class API(object):
self.mediastream_number()
if quality is None:
quality = {}
- xargs = client.getXArgsDeviceInfo()
+ xargs = clientinfo.getXArgsDeviceInfo()
# For DirectPlay, path/key of PART is needed
# trailers are 'clip' with PMS xmls
if action == "DirectStream":
@@ -1334,19 +1332,19 @@ class API(object):
transcode_path = self.server + \
'/video/:/transcode/universal/start.m3u8?'
args = {
- 'audioBoost': settings('audioBoost'),
+ 'audioBoost': utils.settings('audioBoost'),
'autoAdjustQuality': 0,
'directPlay': 0,
'directStream': 1,
'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
- 'session': window('plex_client_Id'),
+ 'session': utils.window('plex_client_Id'),
'fastSeek': 1,
'path': path,
'mediaIndex': self.mediastream,
'partIndex': self.part,
'hasMDE': 1,
'location': 'lan',
- 'subtitleSize': settings('subtitleSize')
+ 'subtitleSize': utils.settings('subtitleSize')
}
# Look like Android to let the PMS use the transcoding profile
xargs.update(headers)
@@ -1403,7 +1401,7 @@ class API(object):
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)
try:
response.status_code
@@ -1417,7 +1415,7 @@ class API(object):
filer.write(response.content)
except UnicodeEncodeError:
LOG.debug('Need to slugify the filename %s', path)
- path = slugify(path)
+ path = utils.slugify(path)
with open(path, 'wb') as filer:
filer.write(response.content)
return path
@@ -1561,7 +1559,8 @@ class API(object):
listitem.setInfo('video', infoLabels=metadata)
try:
# Add context menu entry for information screen
- listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)])
+ listitem.addContextMenuItems([(utils.lang(30032),
+ 'XBMC.Action(Info)',)])
except TypeError:
# Kodi fuck-up
pass
@@ -1607,20 +1606,20 @@ class API(object):
# exist() needs a / or \ at the end to work for directories
if folder is False:
# files
- check = exists(try_encode(path))
+ check = exists(utils.try_encode(path))
else:
# directories
if "\\" in path:
if not path.endswith('\\'):
# Add the missing backslash
- check = exists_dir(path + "\\")
+ check = utils.exists_dir(path + "\\")
else:
- check = exists_dir(path)
+ check = utils.exists_dir(path)
else:
if not path.endswith('/'):
- check = exists_dir(path + "/")
+ check = utils.exists_dir(path + "/")
else:
- check = exists_dir(path)
+ check = utils.exists_dir(path)
if not check:
if force_check is False:
@@ -1649,25 +1648,11 @@ class API(object):
LOG.warn('Cannot access file: %s', url)
# Kodi cannot locate the file #s. Please verify your PKC settings. Stop
# syncing?
- resp = dialog('yesno', heading='{plex}', line1=lang(39031) % url)
+ resp = utils.dialog('yesno',
+ heading='{plex}',
+ line1=utils.lang(39031) % url)
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
def _set_listitem_artprop(listitem, arttype, path):
if arttype in (
diff --git a/resources/lib/PlexCompanion.py b/resources/lib/plex_companion.py
similarity index 76%
rename from resources/lib/PlexCompanion.py
rename to resources/lib/plex_companion.py
index 89d5a7a9..49969759 100644
--- a/resources/lib/PlexCompanion.py
+++ b/resources/lib/plex_companion.py
@@ -6,30 +6,57 @@ from threading import Thread
from Queue import Empty
from socket import SHUT_RDWR
from urllib import urlencode
-
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.subscribers import LOCKER
-from PlexFunctions import ParseContainerKey, GetPlexMetadata, DownloadChunks
-from PlexAPI import API
-from playlist_func import get_pms_playqueue, get_plextype_from_xml, \
- get_playlist_details_from_xml
-from playback import playback_triage, play_xml
-import json_rpc as js
-import variables as v
-import state
-import playqueue as PQ
+from .plexbmchelper import listener, plexgdm, subscribers, httppersist
+from .plex_api import API
+from . import utils
+from . import plex_functions as PF
+from . import playlist_func as PL
+from . import playback
+from . import json_rpc as js
+from . import playqueue as PQ
+from . import variables as v
+from . import state
###############################################################################
-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):
"""
Plex Companion monitoring class. Invoke only once
@@ -47,9 +74,9 @@ class PlexCompanion(Thread):
self.subscription_manager = None
Thread.__init__(self)
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def _process_alexa(self, data):
- xml = GetPlexMetadata(data['key'])
+ xml = PF.GetPlexMetadata(data['key'])
try:
xml[0].attrib
except (AttributeError, IndexError, TypeError):
@@ -62,28 +89,33 @@ class PlexCompanion(Thread):
api.plex_id(),
transient_token=data.get('token'))
elif data['containerKey'].startswith('/playQueues/'):
- _, container_key, _ = ParseContainerKey(data['containerKey'])
- xml = DownloadChunks('{server}/playQueues/%s?' % container_key)
+ _, container_key, _ = PF.ParseContainerKey(data['containerKey'])
+ xml = PF.DownloadChunks('{server}/playQueues/%s?' % container_key)
if xml is None:
# "Play error"
- dialog('notification', lang(29999), lang(30128), icon='{error}')
+ utils.dialog('notification',
+ utils.lang(29999),
+ utils.lang(30128),
+ icon='{error}')
return
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
playqueue.clear()
- get_playlist_details_from_xml(playqueue, xml)
+ PL.get_playlist_details_from_xml(playqueue, xml)
playqueue.plex_transient_token = data.get('token')
if data.get('offset') != '0':
offset = float(data['offset']) / 1000.0
else:
offset = None
- play_xml(playqueue, xml, offset)
+ playback.play_xml(playqueue, xml, offset)
else:
state.PLEX_TRANSIENT_TOKEN = data.get('token')
if data.get('offset') != '0':
state.RESUMABLE = 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
def _process_node(data):
@@ -99,17 +131,17 @@ class PlexCompanion(Thread):
executebuiltin('RunPlugin(plugin://%s?%s)'
% (v.ADDON_ID, urlencode(params)))
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def _process_playlist(self, data):
# Get the playqueue ID
- _, container_key, query = ParseContainerKey(data['containerKey'])
+ _, container_key, query = PF.ParseContainerKey(data['containerKey'])
try:
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
except KeyError:
# E.g. Plex web does not supply the media type
# Still need to figure out the type (video vs. music vs. pix)
- xml = GetPlexMetadata(data['key'])
+ xml = PF.GetPlexMetadata(data['key'])
try:
xml[0].attrib
except (AttributeError, IndexError, TypeError):
@@ -118,14 +150,13 @@ class PlexCompanion(Thread):
api = API(xml[0])
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
- PQ.update_playqueue_from_PMS(
- playqueue,
- playqueue_id=container_key,
- repeat=query.get('repeat'),
- offset=data.get('offset'),
- transient_token=data.get('token'))
+ update_playqueue_from_PMS(playqueue,
+ playqueue_id=container_key,
+ repeat=query.get('repeat'),
+ offset=data.get('offset'),
+ transient_token=data.get('token'))
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def _process_streams(self, data):
"""
Plex Companion client adjusted audio or subtitle stream
@@ -147,17 +178,17 @@ class PlexCompanion(Thread):
else:
LOG.error('Unknown setStreams command: %s', data)
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def _process_refresh(self, data):
"""
example data: {'playQueueID': '8475', 'commandID': '11'}
"""
- xml = get_pms_playqueue(data['playQueueID'])
+ xml = PL.get_pms_playqueue(data['playQueueID'])
if xml is None:
return
if len(xml) == 0:
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:
return
playqueue = PQ.get_playqueue_from_type(
@@ -166,7 +197,7 @@ class PlexCompanion(Thread):
return
playqueue = PQ.get_playqueue_from_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):
"""
@@ -231,7 +262,7 @@ class PlexCompanion(Thread):
self.player)
self.subscription_manager = subscription_manager
- if settings('plexCompanion') == 'true':
+ if utils.settings('plexCompanion') == 'true':
# Start up httpd
start_count = 0
while True:
diff --git a/resources/lib/PlexFunctions.py b/resources/lib/plex_functions.py
similarity index 94%
rename from resources/lib/PlexFunctions.py
rename to resources/lib/plex_functions.py
index de165fb1..89da000b 100644
--- a/resources/lib/PlexFunctions.py
+++ b/resources/lib/plex_functions.py
@@ -7,18 +7,17 @@ from re import compile as re_compile
from copy import deepcopy
from time import time
from threading import Thread
-
from xbmc import sleep
-from downloadutils import DownloadUtils as DU
-from utils import settings, try_encode, try_decode
-from variables import PLEX_TO_KODI_TIMEFACTOR
-import plex_tv
+from .downloadutils import DownloadUtils as DU
+from . import utils
+from . 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_DIRECT = re_compile(r'''\.plex\.direct:\d+$''')
@@ -36,7 +35,7 @@ def ConvertPlexToKodiTime(plexTime):
"""
if plexTime is None:
return None
- return int(float(plexTime) * PLEX_TO_KODI_TIMEFACTOR)
+ return int(float(plexTime) * v.PLEX_TO_KODI_TIMEFACTOR)
def GetPlexKeyNumber(plexKey):
@@ -90,13 +89,13 @@ def GetMethodFromPlexType(plexType):
def GetPlexLoginFromSettings():
"""
Returns a dict:
- 'plexLogin': settings('plexLogin'),
- 'plexToken': settings('plexToken'),
- 'plexhome': settings('plexhome'),
- 'plexid': settings('plexid'),
- 'myplexlogin': settings('myplexlogin'),
- 'plexAvatar': settings('plexAvatar'),
- 'plexHomeSize': settings('plexHomeSize')
+ 'plexLogin': utils.settings('plexLogin'),
+ 'plexToken': utils.settings('plexToken'),
+ 'plexhome': utils.settings('plexhome'),
+ 'plexid': utils.settings('plexid'),
+ 'myplexlogin': utils.settings('myplexlogin'),
+ 'plexAvatar': utils.settings('plexAvatar'),
+ 'plexHomeSize': utils.settings('plexHomeSize')
Returns strings or unicode
@@ -106,13 +105,13 @@ def GetPlexLoginFromSettings():
plexhome is 'true' if plex home is used (the default)
"""
return {
- 'plexLogin': settings('plexLogin'),
- 'plexToken': settings('plexToken'),
- 'plexhome': settings('plexhome'),
- 'plexid': settings('plexid'),
- 'myplexlogin': settings('myplexlogin'),
- 'plexAvatar': settings('plexAvatar'),
- 'plexHomeSize': settings('plexHomeSize')
+ 'plexLogin': utils.settings('plexLogin'),
+ 'plexToken': utils.settings('plexToken'),
+ 'plexhome': utils.settings('plexhome'),
+ 'plexid': utils.settings('plexid'),
+ 'myplexlogin': utils.settings('myplexlogin'),
+ 'plexAvatar': utils.settings('plexAvatar'),
+ 'plexHomeSize': utils.settings('plexHomeSize')
}
@@ -140,7 +139,7 @@ def check_connection(url, token=None, verifySSL=None):
if token is not None:
header_options = {'X-Plex-Token': token}
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:
url = 'https://plex.tv/api/home/users'
LOG.debug("Checking connection to server %s with verifySSL=%s",
@@ -303,11 +302,11 @@ def _plex_gdm():
}
for line in response['data'].split('\n'):
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:
pms['baseURL'] = line.split(':')[1].strip()
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:
pms['port'] = line.split(':')[1].strip()
elif 'Resource-Identifier:' in line:
@@ -336,7 +335,7 @@ def _pms_list_from_plex_tv(token):
queue = 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'):
if 'server' not in device.get('provides'):
# No PMS - skip
@@ -355,7 +354,7 @@ def _pms_list_from_plex_tv(token):
'token': device.get('accessToken'),
'ownername': device.get('sourceTitle'),
'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'
'platform': device.get('platform'), # e.g. 'Windows', 'Android'
'local': device.get('publicAddressMatches') == '1',
@@ -634,7 +633,7 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
'repeat': '0'
}
if trailers is True:
- args['extrasPrefixCount'] = settings('trailerNumber')
+ args['extrasPrefixCount'] = utils.settings('trailerNumber')
xml = DU().downloadUrl(url + '?' + urlencode(args), action_type="POST")
try:
xml[0].tag
@@ -791,7 +790,7 @@ def GetUserArtworkURL(username):
Returns the URL for the user's Avatar. Or False if something went
wrong.
"""
- users = plex_tv.list_home_users(settings('plexToken'))
+ users = plex_tv.list_home_users(utils.settings('plexToken'))
url = ''
# If an error is encountered, set to False
if not users:
@@ -818,13 +817,13 @@ def transcode_image_path(key, AuthToken, path, width, height):
final path to image file
"""
# 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
elif key.startswith('/'): # internal full path.
path = 'http://127.0.0.1:32400' + key
else: # internal path, add-on
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
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
# lenient...
diff --git a/resources/lib/plex_tv.py b/resources/lib/plex_tv.py
index fa28cb67..4dccd592 100644
--- a/resources/lib/plex_tv.py
+++ b/resources/lib/plex_tv.py
@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-
from logging import getLogger
-
from xbmc import sleep, executebuiltin
-from downloadutils import DownloadUtils as DU
-from utils import dialog, language as lang, settings, try_encode
-import variables as v
-import state
+from .downloadutils import DownloadUtils as DU
+from . import utils
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.plex_tx')
###############################################################################
@@ -39,7 +38,7 @@ def choose_home_user(token):
username = user['title']
userlist.append(username)
# To take care of non-ASCII usernames
- userlist_coded.append(try_encode(username))
+ userlist_coded.append(utils.try_encode(username))
usernumber = len(userlist)
username = ''
usertoken = ''
@@ -47,12 +46,14 @@ def choose_home_user(token):
while trials < 3:
if usernumber > 1:
# Select user
- user_select = dialog('select', lang(29999) + lang(39306),
- userlist_coded)
+ user_select = utils.dialog(
+ 'select',
+ '%s%s' % (utils.lang(29999), utils.lang(39306)),
+ userlist_coded)
if user_select == -1:
LOG.info("No user selected.")
- settings('username', value='')
- executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
+ utils.settings('username', value='')
+ executebuiltin('Addon.Openutils.settings(%s)' % v.ADDON_ID)
return False
# Only 1 user received, choose that one
else:
@@ -64,11 +65,11 @@ def choose_home_user(token):
pin = None
if user['protected'] == '1':
LOG.debug('Asking for users PIN')
- pin = dialog('input',
- lang(39307) + selected_user,
- '',
- type='{numeric}',
- option='{hide}')
+ pin = utils.dialog('input',
+ '%s%s' % (utils.lang(39307), selected_user),
+ '',
+ type='{numeric}',
+ option='{hide}')
# User chose to cancel
# Plex bug: don't call url for protected user with empty PIN
if not pin:
@@ -78,7 +79,7 @@ def choose_home_user(token):
result = switch_home_user(user['id'],
pin,
token,
- settings('plex_machineIdentifier'))
+ utils.settings('plex_machineIdentifier'))
if result:
# Successfully retrieved username: break out of while loop
username = result['username']
@@ -88,15 +89,16 @@ def choose_home_user(token):
else:
trials += 1
# Could not login user, please try again
- if not dialog('yesno',
- heading='{plex}',
- line1=lang(39308) + selected_user,
- line2=lang(39309)):
+ if not utils.dialog('yesno',
+ heading='{plex}',
+ line1='%s%s' % (utils.lang(39308),
+ selected_user),
+ line2=utils.lang(39309)):
# User chose to cancel
break
if not username:
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 {
'username': username,
@@ -123,7 +125,7 @@ def switch_home_user(userid, pin, token, machineIdentifier):
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)
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', '')
# Write to settings file
- settings('username', username)
- settings('accessToken', token)
- settings('userid', answer.attrib.get('id', ''))
- settings('plex_restricteduser',
- 'true' if answer.attrib.get('restricted', '0') == '1'
- else 'false')
+ utils.settings('username', username)
+ utils.settings('accessToken', token)
+ utils.settings('userid', answer.attrib.get('id', ''))
+ utils.settings('plex_restricteduser',
+ 'true' if answer.attrib.get('restricted', '0') == '1'
+ else 'false')
state.RESTRICTED_USER = True if \
answer.attrib.get('restricted', '0') == '1' else False
@@ -239,15 +241,15 @@ def sign_in_with_pin():
code, identifier = get_pin()
if not code:
# 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
# Go to https://plex.tv/pin and enter the code:
# Or press No to cancel the sign in.
- answer = dialog('yesno',
- heading='{plex}',
- line1=lang(39304) + "\n\n",
- line2=code + "\n\n",
- line3=lang(39311))
+ answer = utils.dialog('yesno',
+ heading='{plex}',
+ line1='%s%s' % (utils.lang(39304), "\n\n"),
+ line2='%s%s' % (code, "\n\n"),
+ line3=utils.lang(39311))
if not answer:
return False
count = 0
@@ -261,7 +263,7 @@ def sign_in_with_pin():
count += 1
if xml is False:
# 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
# Parse xml
userid = xml.attrib.get('id')
@@ -282,15 +284,15 @@ def sign_in_with_pin():
'plexid': userid,
'homesize': home_size
}
- settings('plexLogin', username)
- settings('plexToken', token)
- settings('plexhome', home)
- settings('plexid', userid)
- settings('plexAvatar', avatar)
- settings('plexHomeSize', home_size)
+ utils.settings('plexLogin', username)
+ utils.settings('plexToken', token)
+ utils.settings('plexhome', home)
+ utils.settings('plexid', userid)
+ utils.settings('plexAvatar', avatar)
+ utils.settings('plexHomeSize', home_size)
# Let Kodi log into plex.tv on startup from now on
- settings('myplexlogin', 'true')
- settings('plex_status', value=lang(39227))
+ utils.settings('myplexlogin', 'true')
+ utils.settings('plex_status', value=utils.lang(39227))
return result
diff --git a/resources/lib/plexbmchelper/httppersist.py b/resources/lib/plexbmchelper/httppersist.py
index 2d65bb60..3b23b1f6 100644
--- a/resources/lib/plexbmchelper/httppersist.py
+++ b/resources/lib/plexbmchelper/httppersist.py
@@ -7,7 +7,7 @@ from socket import error as socket_error
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.httppersist')
###############################################################################
diff --git a/resources/lib/plexbmchelper/listener.py b/resources/lib/plexbmchelper/listener.py
index 5e86257e..a3b03fd8 100644
--- a/resources/lib/plexbmchelper/listener.py
+++ b/resources/lib/plexbmchelper/listener.py
@@ -6,19 +6,18 @@ from re import sub
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urlparse import urlparse, parse_qs
+import xbmc
-from xbmc import sleep, Player, Monitor
-
-from companion import process_command
-import json_rpc as js
-from clientinfo import getXArgsDeviceInfo
-import variables as v
+from .. import companion
+from .. import json_rpc as js
+from .. import clientinfo
+from .. import variables as v
###############################################################################
-LOG = getLogger("PLEX." + __name__)
-PLAYER = Player()
-MONITOR = Monitor()
+LOG = getLogger('PLEX.listener')
+PLAYER = xbmc.Player()
+MONITOR = xbmc.Monitor()
# Hack we need in order to keep track of the open connections from Plex Web
CLIENT_DICT = {}
@@ -122,7 +121,7 @@ class MyHandler(BaseHTTPRequestHandler):
RESOURCES_XML.format(
title=v.DEVICENAME,
machineIdentifier=v.PKC_MACHINE_IDENTIFIER),
- getXArgsDeviceInfo(include_token=False))
+ clientinfo.getXArgsDeviceInfo(include_token=False))
elif request_path == 'player/timeline/poll':
# Plex web does polling if connected to PKC via Companion
# Only reply if there is indeed something playing
@@ -188,7 +187,7 @@ class MyHandler(BaseHTTPRequestHandler):
code=500)
elif "/subscribe" in request_path:
self.response(v.COMPANION_OK_MESSAGE,
- getXArgsDeviceInfo(include_token=False))
+ clientinfo.getXArgsDeviceInfo(include_token=False))
protocol = params.get('protocol')
host = self.client_address[0]
port = params.get('port')
@@ -201,14 +200,14 @@ class MyHandler(BaseHTTPRequestHandler):
command_id)
elif "/unsubscribe" in request_path:
self.response(v.COMPANION_OK_MESSAGE,
- getXArgsDeviceInfo(include_token=False))
+ clientinfo.getXArgsDeviceInfo(include_token=False))
uuid = self.headers.get('X-Plex-Client-Identifier') \
or self.client_address[0]
sub_mgr.remove_subscriber(uuid)
else:
# Throw it to companion.py
- process_command(request_path, params)
- self.response('', getXArgsDeviceInfo(include_token=False))
+ companion.process_command(request_path, params)
+ self.response('', clientinfo.getXArgsDeviceInfo(include_token=False))
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
diff --git a/resources/lib/plexbmchelper/plexgdm.py b/resources/lib/plexbmchelper/plexgdm.py
index 4c88998c..d430acac 100644
--- a/resources/lib/plexbmchelper/plexgdm.py
+++ b/resources/lib/plexbmchelper/plexgdm.py
@@ -25,16 +25,15 @@ import logging
import socket
import threading
import time
-
from xbmc import sleep
-import downloadutils
-from utils import window, settings, dialog, language
-import variables as v
+from ..downloadutils import DownloadUtils as DU
+from .. import utils
+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.discover_group = (self._multicast_address, 32414)
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.discovery_interval = 120
@@ -58,7 +57,7 @@ class plexgdm:
self._registration_is_running = False
self.client_registered = False
- self.download = downloadutils.DownloadUtils().downloadUrl
+ self.download = DU().downloadUrl
def clientDetails(self):
self.client_data = (
@@ -120,14 +119,15 @@ class plexgdm:
log.error("Unable to bind to port [%s] - Plex Companion will not "
"be registered. Change the Plex Companion update port!"
% self.client_update_port)
- if settings('companion_show_gdm_port_warning') == 'true':
- if dialog('yesno',
- language(29999),
- 'Port %s' % self.client_update_port,
- language(39079),
- yeslabel=language(30013), # Never show again
- nolabel=language(30012)): # OK
- settings('companion_show_gdm_port_warning', value='false')
+ if utils.settings('companion_show_gdm_port_warning') == 'true':
+ if utils.dialog('yesno',
+ utils.lang(29999),
+ 'Port %s' % self.client_update_port,
+ utils.lang(39079),
+ yeslabel=utils.lang(30013), # Never show again
+ nolabel=utils.lang(30012)): # OK
+ utils.settings('companion_show_gdm_port_warning',
+ value='false')
from xbmc import executebuiltin
executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
@@ -189,7 +189,7 @@ class plexgdm:
log.info("Server list is empty. Unable to check")
return False
for server in self.server_list:
- if server['uuid'] == window('plex_machineIdentifier'):
+ if server['uuid'] == utils.window('plex_machineIdentifier'):
media_server = server['server']
media_port = server['port']
scheme = server['protocol']
@@ -223,7 +223,7 @@ class plexgdm:
return self.server_list
def discover(self):
- currServer = window('pms_server')
+ currServer = utils.window('pms_server')
if not currServer:
return
currServerProt, currServerIP, currServerPort = \
@@ -240,9 +240,9 @@ class plexgdm:
'owned': '1',
'role': 'master',
'server': currServerIP,
- 'serverName': window('plex_servername'),
+ 'serverName': utils.window('plex_servername'),
'updated': int(time.time()),
- 'uuid': window('plex_machineIdentifier'),
+ 'uuid': utils.window('plex_machineIdentifier'),
'version': 'irrelevant'
}]
@@ -305,5 +305,5 @@ class plexgdm:
def start_all(self, daemon=False):
self.start_discovery(daemon)
- if settings('plexCompanion') == 'true':
+ if utils.settings('plexCompanion') == 'true':
self.start_registration(daemon)
diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py
index 2f01c462..3dffcd1e 100644
--- a/resources/lib/plexbmchelper/subscribers.py
+++ b/resources/lib/plexbmchelper/subscribers.py
@@ -3,22 +3,17 @@ Manages getting playstate from Kodi and sending it to the PMS as well as
subscribed Plex Companion clients.
"""
from logging import getLogger
-from threading import Thread, RLock
+from threading import Thread
-from downloadutils import DownloadUtils as DU
-from utils import window, kodi_time_to_millis, LockFunction
-import state
-import variables as v
-import json_rpc as js
-import playqueue as PQ
+from ..downloadutils import DownloadUtils as DU
+from .. import utils
+from .. import state
+from .. import variables as v
+from .. import json_rpc as js
+from .. import playqueue as PQ
###############################################################################
-
-LOG = getLogger("PLEX." + __name__)
-# Need to lock all methods and functions messing with subscribers or state
-LOCK = RLock()
-LOCKER = LockFunction(LOCK)
-
+LOG = getLogger('PLEX.subscribers')
###############################################################################
# What is Companion controllable?
@@ -150,7 +145,7 @@ class SubscriptionMgr(object):
position = info['position']
return position
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def msg(self, players):
"""
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):
self.location = 'fullScreenVideo'
self.stop_sent_to_web = False
- pbmc_server = window('pms_server')
+ pbmc_server = utils.window('pms_server')
if pbmc_server:
(self.protocol, self.server, self.port) = pbmc_server.split(':')
self.server = self.server.replace('/', '')
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'
mute = '1' if info['muted'] is True else '0'
answ = {
@@ -219,11 +214,11 @@ class SubscriptionMgr(object):
'protocol': self.protocol,
'address': self.server,
'port': self.port,
- 'machineIdentifier': window('plex_machineIdentifier'),
+ 'machineIdentifier': utils.window('plex_machineIdentifier'),
'state': status,
'type': ptype,
'itemType': ptype,
- 'time': kodi_time_to_millis(info['time']),
+ 'time': utils.kodi_time_to_millis(info['time']),
'duration': duration,
'seekRange': '0-%s' % duration,
'shuffle': shuffle,
@@ -231,7 +226,7 @@ class SubscriptionMgr(object):
'volume': info['volume'],
'mute': mute,
'mediaIndex': 0, # Still to implement from here
- 'partIndex':0,
+ 'partIndex': 0,
'partCount': 1,
'providerIdentifier': 'com.plexapp.plugins.library',
}
@@ -302,7 +297,7 @@ class SubscriptionMgr(object):
return playqueue.items[position].plex_stream_index(
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def update_command_id(self, uuid, command_id):
"""
Updates the Plex Companien client with the machine identifier uuid with
@@ -320,8 +315,6 @@ class SubscriptionMgr(object):
for player in players.values():
info = state.PLAYER_STATES[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)
try:
item = playqueue.items[position]
@@ -334,7 +327,7 @@ class SubscriptionMgr(object):
return False
return True
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def notify(self):
"""
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):
for typus, player in players.iteritems():
- LOG.debug('player is %s', player)
- LOG.debug('typus is %s', typus)
self._send_pms_notification(
player['playerid'], self._get_pms_params(player['playerid']))
try:
@@ -386,8 +377,8 @@ class SubscriptionMgr(object):
'state': status,
'ratingKey': item.plex_id,
'key': '/library/metadata/%s' % item.plex_id,
- 'time': kodi_time_to_millis(info['time']),
- 'duration': kodi_time_to_millis(info['totaltime'])
+ 'time': utils.kodi_time_to_millis(info['time']),
+ 'duration': utils.kodi_time_to_millis(info['totaltime'])
}
if info['container_key'] is not None:
# params['containerKey'] = info['container_key']
@@ -419,7 +410,7 @@ class SubscriptionMgr(object):
LOG.debug("Sent server notification with parameters: %s to %s",
xargs, url)
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def add_subscriber(self, protocol, host, port, uuid, command_id):
"""
Adds a new Plex Companion subscriber to PKC.
@@ -434,7 +425,7 @@ class SubscriptionMgr(object):
self.subscribers[subscriber.uuid] = subscriber
return subscriber
- @LOCKER.lockthis
+ @state.LOCKER_SUBSCRIBER.lockthis
def remove_subscriber(self, uuid):
"""
Removes a connected Plex Companion subscriber with machine identifier
diff --git a/resources/lib/plexdb_functions.py b/resources/lib/plexdb_functions.py
index 782da994..9a0e3866 100644
--- a/resources/lib/plexdb_functions.py
+++ b/resources/lib/plexdb_functions.py
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
-
###############################################################################
-
-from utils import kodi_sql
-import variables as v
+from . import utils
+from . import variables as v
###############################################################################
@@ -17,7 +15,7 @@ class Get_Plex_DB():
and the db gets closed
"""
def __enter__(self):
- self.plexconn = kodi_sql('plex')
+ self.plexconn = utils.kodi_sql('plex')
return Plex_DB_Functions(self.plexconn.cursor())
def __exit__(self, type, value, traceback):
diff --git a/resources/lib/service_entry.py b/resources/lib/service_entry.py
new file mode 100644
index 00000000..8d38df2d
--- /dev/null
+++ b/resources/lib/service_entry.py
@@ -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()
diff --git a/resources/lib/state.py b/resources/lib/state.py
index ce27325f..0687825a 100644
--- a/resources/lib/state.py
+++ b/resources/lib/state.py
@@ -1,5 +1,48 @@
# -*- coding: utf-8 -*-
# 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
STOP_PKC = False
diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py
index cefa31b7..d52450d9 100644
--- a/resources/lib/userclient.py
+++ b/resources/lib/userclient.py
@@ -7,20 +7,20 @@ from xbmc import sleep, executebuiltin, translatePath
import xbmcaddon
from xbmcvfs import exists
-from utils import window, settings, language as lang, thread_methods, dialog
-from downloadutils import DownloadUtils as DU
-import plex_tv
-import PlexFunctions as PF
-import state
+from .downloadutils import DownloadUtils as DU
+from . import utils
+from . import plex_tv
+from . import plex_functions as PF
+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):
"""
Manage Plex users
@@ -54,11 +54,11 @@ class UserClient(Thread):
Get the current PMS' URL
"""
# Original host
- self.server_name = settings('plex_servername')
- https = settings('https') == "true"
- host = settings('ipaddress')
- port = settings('port')
- self.machine_identifier = settings('plex_machineIdentifier')
+ self.server_name = utils.settings('plex_servername')
+ https = utils.settings('https') == "true"
+ host = utils.settings('ipaddress')
+ port = utils.settings('port')
+ self.machine_identifier = utils.settings('plex_machineIdentifier')
if not host:
LOG.debug("No server information saved.")
return False
@@ -74,7 +74,8 @@ class UserClient(Thread):
self.machine_identifier = PF.GetMachineIdentifier(server)
if not 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)
return server
@@ -84,15 +85,15 @@ class UserClient(Thread):
Do we need to verify the SSL certificate? Return None if that is the
case, else False
"""
- return None if settings('sslverify') == 'true' else False
+ return None if utils.settings('sslverify') == 'true' else False
@staticmethod
def get_ssl_certificate():
"""
Client side certificate
"""
- return None if settings('sslcert') == 'None' \
- else settings('sslcert')
+ return None if utils.settings('sslcert') == 'None' \
+ else utils.settings('sslcert')
def set_user_prefs(self):
"""
@@ -103,7 +104,7 @@ class UserClient(Thread):
if self.token:
url = PF.GetUserArtworkURL(self.user)
if url:
- window('PlexUserImage', value=url)
+ utils.window('PlexUserImage', value=url)
@staticmethod
def check_access():
@@ -141,29 +142,32 @@ class UserClient(Thread):
state.PLEX_USER_ID = user_id or None
state.PLEX_USERNAME = username
# 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
# This is the token for plex.tv for the current user
# Is only '' if user is not signed in to plex.tv
- window('plex_token', value=settings('plexToken'))
- state.PLEX_TOKEN = settings('plexToken') or None
- window('plex_restricteduser', value=settings('plex_restricteduser'))
+ utils.window('plex_token', value=utils.settings('plexToken'))
+ state.PLEX_TOKEN = utils.settings('plexToken') or None
+ utils.window('plex_restricteduser',
+ value=utils.settings('plex_restricteduser'))
state.RESTRICTED_USER = True \
- if settings('plex_restricteduser') == 'true' else False
- window('pms_server', value=self.server)
- window('plex_machineIdentifier', value=self.machine_identifier)
- window('plex_servername', value=self.server_name)
- window('plex_authenticated', value='true')
+ if utils.settings('plex_restricteduser') == 'true' else False
+ utils.window('pms_server', value=self.server)
+ utils.window('plex_machineIdentifier', value=self.machine_identifier)
+ utils.window('plex_servername', value=self.server_name)
+ utils.window('plex_authenticated', value='true')
state.AUTHENTICATED = True
- window('useDirectPaths', value='true'
- if settings('useDirectPaths') == "1" else 'false')
- state.DIRECT_PATHS = True if settings('useDirectPaths') == "1" \
+ utils.window('useDirectPaths',
+ value='true' if utils.settings('useDirectPaths') == "1"
+ else 'false')
+ state.DIRECT_PATHS = True if utils.settings('useDirectPaths') == "1" \
else False
state.INDICATE_MEDIA_VERSIONS = True \
- if settings('indicate_media_versions') == "true" else False
- window('plex_force_transcode_pix', value='true'
- if settings('force_transcode_pix') == "1" else 'false')
+ if utils.settings('indicate_media_versions') == "true" else False
+ utils.window('plex_force_transcode_pix',
+ value='true' if utils.settings('force_transcode_pix') == "1"
+ else 'false')
# Start DownloadUtils session
self.do_utils = DU()
@@ -173,9 +177,9 @@ class UserClient(Thread):
self.set_user_prefs()
# Writing values to settings file
- settings('username', value=username)
- settings('userid', value=user_id)
- settings('accessToken', value=usertoken)
+ utils.settings('username', value=username)
+ utils.settings('userid', value=user_id)
+ utils.settings('accessToken', value=usertoken)
return True
def authenticate(self):
@@ -188,9 +192,9 @@ class UserClient(Thread):
if self.retry >= 2:
LOG.error("Too many retries to login.")
state.PMS_STATUS = 'Stop'
- dialog('ok', lang(33001), lang(39023))
+ utils.dialog('ok', utils.lang(33001), utils.lang(39023))
executebuiltin(
- 'Addon.OpenSettings(plugin.video.plexkodiconnect)')
+ 'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
return False
# Get /profile/addon_data
@@ -209,10 +213,10 @@ class UserClient(Thread):
return False
# If there is a username in the settings, try authenticating
- username = settings('username')
- userId = settings('userid')
- usertoken = settings('accessToken')
- enforceLogin = settings('enforceUserLogin')
+ username = utils.settings('username')
+ userId = utils.settings('userid')
+ usertoken = utils.settings('accessToken')
+ enforceLogin = utils.settings('enforceUserLogin')
# Found a user in the settings, try to authenticate
if username and enforceLogin == 'false':
LOG.debug('Trying to authenticate with old settings')
@@ -225,15 +229,15 @@ class UserClient(Thread):
return True
elif answ == 401:
LOG.error("User token no longer valid. Sign user out")
- settings('username', value='')
- settings('userid', value='')
- settings('accessToken', value='')
+ utils.settings('username', value='')
+ utils.settings('userid', value='')
+ utils.settings('accessToken', value='')
else:
LOG.debug("Could not yet authenticate user")
return False
# Could not use settings - try to get Plex user list from plex.tv
- plextoken = settings('plexToken')
+ plextoken = utils.settings('plexToken')
if plextoken:
LOG.info("Trying to connect to plex.tv to get a user list")
userInfo = plex_tv.choose_home_user(plextoken)
@@ -268,24 +272,24 @@ class UserClient(Thread):
self.do_utils.stopSession()
except AttributeError:
pass
- window('plex_authenticated', clear=True)
+ utils.window('plex_authenticated', clear=True)
state.AUTHENTICATED = False
- window('pms_token', clear=True)
+ utils.window('pms_token', clear=True)
state.PLEX_TOKEN = None
state.PLEX_TRANSIENT_TOKEN = None
state.PMS_TOKEN = None
- window('plex_token', clear=True)
- window('pms_server', clear=True)
- window('plex_machineIdentifier', clear=True)
- window('plex_servername', clear=True)
+ utils.window('plex_token', clear=True)
+ utils.window('pms_server', clear=True)
+ utils.window('plex_machineIdentifier', clear=True)
+ utils.window('plex_servername', clear=True)
state.PLEX_USER_ID = None
state.PLEX_USERNAME = None
- window('plex_restricteduser', clear=True)
+ utils.window('plex_restricteduser', clear=True)
state.RESTRICTED_USER = False
- settings('username', value='')
- settings('userid', value='')
- settings('accessToken', value='')
+ utils.settings('username', value='')
+ utils.settings('userid', value='')
+ utils.settings('accessToken', value='')
self.token = None
self.auth = True
@@ -313,7 +317,7 @@ class UserClient(Thread):
elif state.PMS_STATUS == "401":
# Unauthorized access, revoke token
state.PMS_STATUS = 'Auth'
- window('plex_serverStatus', value='Auth')
+ utils.window('plex_serverStatus', value='Auth')
self.reset_client()
sleep(3000)
@@ -330,7 +334,7 @@ class UserClient(Thread):
LOG.info("Current userId: %s", state.PLEX_USER_ID)
self.retry = 0
state.SUSPEND_LIBRARY_THREAD = False
- window('plex_serverStatus', clear=True)
+ utils.window('plex_serverStatus', clear=True)
state.PMS_STATUS = False
if not self.auth and (self.user is None):
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 62f92556..a2f96dc9 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -4,6 +4,10 @@ Various functions and decorators for PKC
"""
###############################################################################
from logging import getLogger
+import xbmc
+import xbmcaddon
+import xbmcgui
+from xbmcvfs import exists, delete
import os
from cProfile import Profile
from pstats import Stats
@@ -20,17 +24,12 @@ import hashlib
import re
import unicodedata
-import xbmc
-import xbmcaddon
-import xbmcgui
-from xbmcvfs import exists, delete
-
-import variables as v
-import state
+from . import variables as v
+from . import state
###############################################################################
-LOG = getLogger("PLEX." + __name__)
+LOG = getLogger('PLEX.utils')
WINDOW = xbmcgui.Window(10000)
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
@@ -51,7 +50,7 @@ def reboot_kodi(message=None):
Set optional custom message
"""
- message = message or language(33033)
+ message = message or lang(33033)
dialog('ok', heading='{plex}', line1=message)
xbmc.executebuiltin('RestartApp')
@@ -130,7 +129,7 @@ def exists_dir(path):
return answ
-def language(stringid):
+def lang(stringid):
"""
Central string retrieval from strings.po
"""
@@ -194,7 +193,7 @@ def dialog(typus, *args, **kwargs):
kwargs['option'] = types[kwargs['option']]
if 'heading' in kwargs:
kwargs['heading'] = kwargs['heading'].replace("{plex}",
- language(29999))
+ lang(29999))
dia = xbmcgui.Dialog()
types = {
'yesno': dia.yesno,
@@ -487,8 +486,8 @@ def wipe_database():
connection.commit()
cursor.close()
# Reset the artwork sync status in the PKC settings
- settings('caching_artwork_count', value=language(39310))
- settings('fanarttv_lookups', value=language(39310))
+ settings('caching_artwork_count', value=lang(39310))
+ settings('fanarttv_lookups', value=lang(39310))
# reset the install run flag
settings('SyncInstallRunDone', value="false")
@@ -500,8 +499,8 @@ def reset(ask_user=True):
"""
# Are you sure you want to reset your local Kodi database?
if ask_user and not dialog('yesno',
- heading='{plex} %s ' % language(30132),
- line1=language(39600)):
+ heading='{plex} %s ' % lang(30132),
+ line1=lang(39600)):
return
# first stop any db sync
@@ -513,8 +512,8 @@ def reset(ask_user=True):
if count == 0:
# Could not stop the database from running. Please try again later.
dialog('ok',
- heading='{plex} %s' % language(30132),
- line1=language(39601))
+ heading='{plex} %s' % lang(30132),
+ line1=lang(39601))
return
xbmc.sleep(1000)
@@ -524,8 +523,8 @@ def reset(ask_user=True):
# Reset all PlexKodiConnect Addon settings? (this is usually NOT
# recommended and unnecessary!)
if ask_user and dialog('yesno',
- heading='{plex} %s ' % language(30132),
- line1=language(39603)):
+ heading='{plex} %s ' % lang(30132),
+ line1=lang(39603)):
# Delete the settings
addon = xbmcaddon.Addon()
addondir = try_decode(xbmc.translatePath(addon.getAddonInfo('profile')))
@@ -708,7 +707,7 @@ class XmlKodiSetting(object):
LOG.error('Error parsing %s', self.path)
# "Kodi cannot parse {0}. PKC will not function correctly. Please
# visit {1} and correct your file!"
- dialog('ok', language(29999), language(39716).format(
+ dialog('ok', lang(29999), lang(39716).format(
self.filename,
'http://kodi.wiki'))
self.__exit__(etree.ParseError, None, None)
@@ -861,7 +860,7 @@ def passwords_xml():
LOG.error('Error parsing %s', xmlpath)
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit
# {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/'))
return
else:
@@ -1192,33 +1191,3 @@ def thread_methods(cls=None, add_stops=None, add_suspends=None):
# Return class to render this a decorator
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
diff --git a/resources/lib/variables.py b/resources/lib/variables.py
index 3ebb36d1..b1ef2d98 100644
--- a/resources/lib/variables.py
+++ b/resources/lib/variables.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
import os
-
import xbmc
from xbmcaddon import Addon
diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py
index 85b624b2..a960880d 100644
--- a/resources/lib/videonodes.py
+++ b/resources/lib/videonodes.py
@@ -4,18 +4,16 @@ from logging import getLogger
from distutils import dir_util
import xml.etree.ElementTree as etree
from os import makedirs
-
import xbmc
from xbmcvfs import exists
-from utils import window, settings, language as lang, try_encode, indent, \
- normalize_nodes, exists_dir, try_decode
-import variables as v
-import state
+from . import utils
+from . import variables as v
+from . import state
###############################################################################
-log = getLogger("PLEX."+__name__)
+LOG = getLogger('PLEX.videonodes')
###############################################################################
# Paths are strings, NOT unicode!
@@ -30,21 +28,26 @@ class VideoNodes(object):
root = etree.Element('node', attrib={'order': "%s" % order})
elif roottype == 1:
# 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"
# 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
else:
# 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, 'icon').text = "special://home/addons/plugin.video.plexkodiconnect/icon.png"
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
# How many items do we get at most?
limit = state.FETCH_PMS_ITEM_NUMBER
@@ -63,32 +66,32 @@ class VideoNodes(object):
dirname = viewid
# Returns strings
- path = try_decode(xbmc.translatePath(
+ path = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/"))
- nodepath = try_decode(xbmc.translatePath(
+ nodepath = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/Plex-%s/" % dirname))
if delete:
- if exists_dir(nodepath):
+ if utils.exists_dir(nodepath):
from shutil import rmtree
rmtree(nodepath)
- log.info("Sucessfully removed videonode: %s." % tagname)
+ LOG.info("Sucessfully removed videonode: %s." % tagname)
return
# Verify the video directory
- if not exists_dir(path):
+ if not utils.exists_dir(path):
dir_util.copy_tree(
- src=try_decode(
+ src=utils.try_decode(
xbmc.translatePath("special://xbmc/system/library/video")),
- dst=try_decode(
+ dst=utils.try_decode(
xbmc.translatePath("special://profile/library/video")),
preserve_mode=0) # do not copy permission bits!
# Create the node directory
if mediatype != "photos":
- if not exists_dir(nodepath):
+ if not utils.exists_dir(nodepath):
# folder does not exist yet
- log.debug('Creating folder %s' % nodepath)
+ LOG.debug('Creating folder %s' % nodepath)
makedirs(nodepath)
# Create index entry
@@ -97,13 +100,13 @@ class VideoNodes(object):
path = "library://video/Plex-%s/" % dirname
for i in range(1, indexnumber):
# 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
if mediatype == "photos":
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
if not mediatype == "photos":
@@ -119,7 +122,7 @@ class VideoNodes(object):
tagname=tagname,
roottype=0)
try:
- indent(root)
+ utils.indent(root)
except:
pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
@@ -222,14 +225,15 @@ class VideoNodes(object):
# Get label
stringid = nodes[node]
if node != "1":
- label = lang(stringid)
+ label = utils.lang(stringid)
if not label:
label = xbmc.getLocalizedString(stringid)
else:
label = stringid
# Set window properties
- if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
+ if ((mediatype == "homevideos" or mediatype == "photos") and
+ nodetype == "all"):
# Custom query
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s"
% (viewid, mediatype))
@@ -278,34 +282,39 @@ class VideoNodes(object):
templabel = label
embynode = "Plex.nodes.%s" % indexnumber
- window('%s.title' % embynode, value=templabel)
- window('%s.path' % embynode, value=windowpath)
- window('%s.content' % embynode, value=path)
- window('%s.type' % embynode, value=mediatype)
+ utils.window('%s.title' % embynode, value=templabel)
+ utils.window('%s.path' % embynode, value=windowpath)
+ utils.window('%s.content' % embynode, value=path)
+ utils.window('%s.type' % embynode, value=mediatype)
else:
embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype)
- window('%s.title' % embynode, value=label)
- window('%s.path' % embynode, value=windowpath)
- window('%s.content' % embynode, value=path)
+ utils.window('%s.title' % embynode, value=label)
+ utils.window('%s.path' % embynode, value=windowpath)
+ utils.window('%s.content' % embynode, value=path)
if mediatype == "photos":
- # For photos, we do not create a node in videos but we do want the window props
- # to be created.
- # To do: add our photos nodes to kodi picture sources somehow
+ # For photos, we do not create a node in videos but we do want
+ # the window props to be created. To do: add our photos nodes to
+ # kodi picture sources somehow
continue
- if exists(try_encode(nodeXML)):
+ if exists(utils.try_encode(nodeXML)):
# Don't recreate xml if already exists
continue
# Create the root
if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"):
# 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, 'content').text = "episodes"
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'):
etree.SubElement(root, 'content').text = "episodes"
else:
@@ -313,20 +322,24 @@ class VideoNodes(object):
# Elements per nodetype
if nodetype == "all":
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
-
+ etree.SubElement(root,
+ 'order',
+ {'direction': "ascending"}).text = "sorttitle"
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
- if settings('MovieShowWatched') == 'false':
+ if utils.settings('MovieShowWatched') == 'false':
rule = etree.SubElement(root,
'rule',
{'field': "playcount",
'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
-
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,
@@ -335,54 +348,67 @@ class VideoNodes(object):
).text = 'lastplayed'
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"
-
elif nodetype == "unwatched":
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
- rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
+ etree.SubElement(root,
+ 'order',
+ {'direction': "ascending"}).text = "sorttitle"
+ rule = etree.SubElement(root,
+ "rule",
+ {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
-
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"
-
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
-
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
- rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+ rule = etree.SubElement(root,
+ 'rule',
+ {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
- rule2 = etree.SubElement(root, 'rule',
- attrib={'field': "rating", 'operator': "greaterthan"})
+ rule2 = etree.SubElement(root,
+ 'rule',
+ attrib={'field': "rating", 'operator': "greaterthan"})
etree.SubElement(rule2, 'value').text = "7"
-
elif nodetype == "recentepisodes":
# 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
- rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+ rule = etree.SubElement(root,
+ 'rule',
+ {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
-
elif nodetype == "inprogressepisodes":
# Kodi Isengard, Jarvis
etree.SubElement(root, 'limit').text = limit
- rule = etree.SubElement(root, 'rule',
- attrib={'field': "inprogress", 'operator':"true"})
-
+ rule = etree.SubElement(root,
+ 'rule',
+ attrib={'field': "inprogress", 'operator':"true"})
try:
- indent(root)
+ utils.indent(root)
except:
pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
- tagname = try_encode(tagname)
- cleantagname = try_decode(normalize_nodes(tagname))
- nodepath = try_decode(xbmc.translatePath(
+ tagname = utils.try_encode(tagname)
+ cleantagname = utils.try_decode(utils.normalize_nodes(tagname))
+ nodepath = utils.try_decode(xbmc.translatePath(
"special://profile/library/video/"))
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
path = "library://video/plex_%s.xml" % cleantagname
@@ -393,12 +419,12 @@ class VideoNodes(object):
windowpath = "ActivateWindow(Video,%s,return)" % path
# Create the video node directory
- if not exists_dir(nodepath):
+ if not utils.exists_dir(nodepath):
# We need to copy over the default items
dir_util.copy_tree(
- src=try_decode(
+ src=utils.try_decode(
xbmc.translatePath("special://xbmc/system/library/video")),
- dst=try_decode(
+ dst=utils.try_decode(
xbmc.translatePath("special://profile/library/video")),
preserve_mode=0) # do not copy permission bits!
@@ -407,14 +433,14 @@ class VideoNodes(object):
'Favorite tvshows': 30181,
'channels': 30173
}
- label = lang(labels[tagname])
+ label = utils.lang(labels[tagname])
embynode = "Plex.nodes.%s" % indexnumber
- window('%s.title' % embynode, value=label)
- window('%s.path' % embynode, value=windowpath)
- window('%s.content' % embynode, value=path)
- window('%s.type' % embynode, value=itemtype)
+ utils.window('%s.title' % embynode, value=label)
+ utils.window('%s.path' % embynode, value=windowpath)
+ utils.window('%s.content' % embynode, value=path)
+ 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
return
@@ -423,23 +449,26 @@ class VideoNodes(object):
label=label,
tagname=tagname,
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:
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
try:
- indent(root)
+ utils.indent(root)
except:
pass
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
def clearProperties(self):
- log.info("Clearing nodes properties.")
- plexprops = window('Plex.nodes.total')
+ LOG.info("Clearing nodes properties.")
+ plexprops = utils.window('Plex.nodes.total')
propnames = [
"index","path","title","content",
"inprogress.content","inprogress.title",
@@ -457,4 +486,5 @@ class VideoNodes(object):
totalnodes = int(plexprops)
for i in range(totalnodes):
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)
diff --git a/resources/lib/watchdog/events.py b/resources/lib/watchdog/events.py
index 7c1f0750..24cfec62 100644
--- a/resources/lib/watchdog/events.py
+++ b/resources/lib/watchdog/events.py
@@ -88,9 +88,9 @@ Event Handler Classes
import os.path
import logging
import re
-from pathtools.patterns import match_any_paths
-from watchdog.utils import has_attribute
-from watchdog.utils import unicode_paths
+from ..pathtools.patterns import match_any_paths
+from .utils import has_attribute
+from .utils import unicode_paths
EVENT_TYPE_MOVED = 'moved'
diff --git a/resources/lib/watchdog/observers/__init__.py b/resources/lib/watchdog/observers/__init__.py
index d84fac90..32148a17 100644
--- a/resources/lib/watchdog/observers/__init__.py
+++ b/resources/lib/watchdog/observers/__init__.py
@@ -55,8 +55,8 @@ Class Platforms Note
"""
import warnings
-from watchdog.utils import platform
-from watchdog.utils import UnsupportedLibc
+from ..utils import platform
+from ..utils import UnsupportedLibc
if platform.is_linux():
try:
diff --git a/resources/lib/watchdog/observers/api.py b/resources/lib/watchdog/observers/api.py
index 30f2e9a3..35a7748d 100644
--- a/resources/lib/watchdog/observers/api.py
+++ b/resources/lib/watchdog/observers/api.py
@@ -18,9 +18,9 @@
from __future__ import with_statement
import threading
-from watchdog.utils import BaseThread
-from watchdog.utils.compat import queue
-from watchdog.utils.bricks import SkipRepeatsQueue
+from ..utils import BaseThread
+from ..utils.compat import queue
+from ..utils.bricks import SkipRepeatsQueue
DEFAULT_EMITTER_TIMEOUT = 1 # in seconds.
DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds.
diff --git a/resources/lib/watchdog/observers/fsevents.py b/resources/lib/watchdog/observers/fsevents.py
index 67481480..d2519e28 100644
--- a/resources/lib/watchdog/observers/fsevents.py
+++ b/resources/lib/watchdog/observers/fsevents.py
@@ -30,7 +30,7 @@ import threading
import unicodedata
import _watchdog_fsevents as _fsevents
-from watchdog.events import (
+from ..events import (
FileDeletedEvent,
FileModifiedEvent,
FileCreatedEvent,
@@ -41,8 +41,8 @@ from watchdog.events import (
DirMovedEvent
)
-from watchdog.utils.dirsnapshot import DirectorySnapshot
-from watchdog.observers.api import (
+from ..utils.dirsnapshot import DirectorySnapshot
+from ..observers.api import (
BaseObserver,
EventEmitter,
DEFAULT_EMITTER_TIMEOUT,
diff --git a/resources/lib/watchdog/observers/fsevents2.py b/resources/lib/watchdog/observers/fsevents2.py
index 9b6ebcba..e2d7c667 100644
--- a/resources/lib/watchdog/observers/fsevents2.py
+++ b/resources/lib/watchdog/observers/fsevents2.py
@@ -24,9 +24,9 @@ import os
import logging
import unicodedata
from threading import Thread
-from watchdog.utils.compat import queue
+from ..utils.compat import queue
-from watchdog.events import (
+from ..events import (
FileDeletedEvent,
FileModifiedEvent,
FileCreatedEvent,
@@ -36,7 +36,7 @@ from watchdog.events import (
DirCreatedEvent,
DirMovedEvent
)
-from watchdog.observers.api import (
+from ..observers.api import (
BaseObserver,
EventEmitter,
DEFAULT_EMITTER_TIMEOUT,
diff --git a/resources/lib/watchdog/observers/inotify.py b/resources/lib/watchdog/observers/inotify.py
index 9f3a4dfc..f8e87235 100644
--- a/resources/lib/watchdog/observers/inotify.py
+++ b/resources/lib/watchdog/observers/inotify.py
@@ -73,14 +73,14 @@ import os
import threading
from .inotify_buffer import InotifyBuffer
-from watchdog.observers.api import (
+from ..observers.api import (
EventEmitter,
BaseObserver,
DEFAULT_EMITTER_TIMEOUT,
DEFAULT_OBSERVER_TIMEOUT
)
-from watchdog.events import (
+from ..events import (
DirDeletedEvent,
DirModifiedEvent,
DirMovedEvent,
@@ -92,7 +92,7 @@ from watchdog.events import (
generate_sub_moved_events,
generate_sub_created_events,
)
-from watchdog.utils import unicode_paths
+from ..utils import unicode_paths
class InotifyEmitter(EventEmitter):
diff --git a/resources/lib/watchdog/observers/inotify_buffer.py b/resources/lib/watchdog/observers/inotify_buffer.py
index dce2ae12..274c847a 100644
--- a/resources/lib/watchdog/observers/inotify_buffer.py
+++ b/resources/lib/watchdog/observers/inotify_buffer.py
@@ -15,9 +15,9 @@
# limitations under the License.
import logging
-from watchdog.utils import BaseThread
-from watchdog.utils.delayed_queue import DelayedQueue
-from watchdog.observers.inotify_c import Inotify
+from ..utils import BaseThread
+from ..utils.delayed_queue import DelayedQueue
+from ..observers.inotify_c import Inotify
logger = logging.getLogger(__name__)
diff --git a/resources/lib/watchdog/observers/inotify_c.py b/resources/lib/watchdog/observers/inotify_c.py
index 5f208b6a..6c7f1ffc 100644
--- a/resources/lib/watchdog/observers/inotify_c.py
+++ b/resources/lib/watchdog/observers/inotify_c.py
@@ -24,8 +24,8 @@ import ctypes
import ctypes.util
from functools import reduce
from ctypes import c_int, c_char_p, c_uint32
-from watchdog.utils import has_attribute
-from watchdog.utils import UnsupportedLibc
+from ..utils import has_attribute
+from ..utils import UnsupportedLibc
def _load_libc():
diff --git a/resources/lib/watchdog/observers/kqueue.py b/resources/lib/watchdog/observers/kqueue.py
index 9ace9232..a5838d18 100644
--- a/resources/lib/watchdog/observers/kqueue.py
+++ b/resources/lib/watchdog/observers/kqueue.py
@@ -78,7 +78,7 @@ Collections and Utility Classes
"""
from __future__ import with_statement
-from watchdog.utils import platform
+from ..utils import platform
import threading
import errno
@@ -94,18 +94,18 @@ if sys.version_info < (2, 7, 0):
else:
import select
-from pathtools.path import absolute_path
+from ...pathtools.path import absolute_path
-from watchdog.observers.api import (
+from ..observers.api import (
BaseObserver,
EventEmitter,
DEFAULT_OBSERVER_TIMEOUT,
DEFAULT_EMITTER_TIMEOUT
)
-from watchdog.utils.dirsnapshot import DirectorySnapshot
+from ..utils.dirsnapshot import DirectorySnapshot
-from watchdog.events import (
+from ..events import (
DirMovedEvent,
DirDeletedEvent,
DirCreatedEvent,
diff --git a/resources/lib/watchdog/observers/polling.py b/resources/lib/watchdog/observers/polling.py
index 3b1d995b..96e5f42b 100644
--- a/resources/lib/watchdog/observers/polling.py
+++ b/resources/lib/watchdog/observers/polling.py
@@ -38,16 +38,17 @@ from __future__ import with_statement
import os
import threading
from functools import partial
-from watchdog.utils import stat as default_stat
-from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff
-from watchdog.observers.api import (
+from ..utils import stat as default_stat
+from ..utils.dirsnapshot import DirectorySnapshot, \
+ DirectorySnapshotDiff
+from ..observers.api import (
EventEmitter,
BaseObserver,
DEFAULT_OBSERVER_TIMEOUT,
DEFAULT_EMITTER_TIMEOUT
)
-from watchdog.events import (
+from ..events import (
DirMovedEvent,
DirDeletedEvent,
DirCreatedEvent,
diff --git a/resources/lib/watchdog/observers/read_directory_changes.py b/resources/lib/watchdog/observers/read_directory_changes.py
index 623a2356..d68cae57 100644
--- a/resources/lib/watchdog/observers/read_directory_changes.py
+++ b/resources/lib/watchdog/observers/read_directory_changes.py
@@ -24,7 +24,7 @@ import threading
import os.path
import time
-from watchdog.events import (
+from ..events import (
DirCreatedEvent,
DirDeletedEvent,
DirMovedEvent,
@@ -37,14 +37,14 @@ from watchdog.events import (
generate_sub_created_events,
)
-from watchdog.observers.api import (
+from ..observers.api import (
EventEmitter,
BaseObserver,
DEFAULT_OBSERVER_TIMEOUT,
DEFAULT_EMITTER_TIMEOUT
)
-from watchdog.observers.winapi import (
+from ..observers.winapi import (
read_events,
get_directory_handle,
close_directory_handle,
diff --git a/resources/lib/watchdog/tricks/__init__.py b/resources/lib/watchdog/tricks/__init__.py
index 7e1c9fe2..dcffaf57 100644
--- a/resources/lib/watchdog/tricks/__init__.py
+++ b/resources/lib/watchdog/tricks/__init__.py
@@ -22,8 +22,8 @@ import signal
import subprocess
import time
-from watchdog.utils import echo, has_attribute
-from watchdog.events import PatternMatchingEventHandler
+from ..utils import echo, has_attribute
+from ..events import PatternMatchingEventHandler
class Trick(PatternMatchingEventHandler):
diff --git a/resources/lib/watchdog/utils/__init__.py b/resources/lib/watchdog/utils/__init__.py
index e0f7ad1b..e912033c 100644
--- a/resources/lib/watchdog/utils/__init__.py
+++ b/resources/lib/watchdog/utils/__init__.py
@@ -33,9 +33,8 @@ Classes
import os
import sys
import threading
-import watchdog.utils.platform
-from watchdog.utils.compat import Event
-from collections import namedtuple
+from . import platform
+from .compat import Event
if sys.version_info[0] == 2 and platform.is_windows():
diff --git a/resources/lib/watchdog/utils/compat.py b/resources/lib/watchdog/utils/compat.py
index 0f6e7947..b1ae6674 100644
--- a/resources/lib/watchdog/utils/compat.py
+++ b/resources/lib/watchdog/utils/compat.py
@@ -24,6 +24,6 @@ except ImportError:
if sys.version_info < (2, 7):
- from watchdog.utils.event_backport import Event
+ from .event_backport import Event
else:
from threading import Event
\ No newline at end of file
diff --git a/resources/lib/watchdog/utils/dirsnapshot.py b/resources/lib/watchdog/utils/dirsnapshot.py
index 20abe221..7d56fe0a 100644
--- a/resources/lib/watchdog/utils/dirsnapshot.py
+++ b/resources/lib/watchdog/utils/dirsnapshot.py
@@ -47,8 +47,7 @@ Classes
import errno
import os
from stat import S_ISDIR
-from watchdog.utils import platform
-from watchdog.utils import stat as default_stat
+from . import stat as default_stat
class DirectorySnapshotDiff(object):
diff --git a/resources/lib/watchdog/utils/unicode_paths.py b/resources/lib/watchdog/utils/unicode_paths.py
index 501a2f15..c1e3ceed 100644
--- a/resources/lib/watchdog/utils/unicode_paths.py
+++ b/resources/lib/watchdog/utils/unicode_paths.py
@@ -24,7 +24,7 @@
import sys
-from watchdog.utils import platform
+from . import platform
try:
# Python 2
diff --git a/resources/lib/watchdog/watchmedo.py b/resources/lib/watchdog/watchmedo.py
index ce891f83..29c63e46 100644
--- a/resources/lib/watchdog/watchmedo.py
+++ b/resources/lib/watchdog/watchmedo.py
@@ -37,8 +37,8 @@ except ImportError:
from io import StringIO
from argh import arg, aliases, ArghParser, expects_obj
-from watchdog.version import VERSION_STRING
-from watchdog.utils import load_class
+from .version import VERSION_STRING
+from .utils import load_class
logging.basicConfig(level=logging.INFO)
diff --git a/resources/lib/websocket.py b/resources/lib/websocket.py
index 8c551623..ac8aff70 100644
--- a/resources/lib/websocket.py
+++ b/resources/lib/websocket.py
@@ -48,11 +48,11 @@ import logging
import traceback
import sys
-import utils
+from . import utils
###############################################################################
-LOG = logging.getLogger("PLEX." + __name__)
+LOG = logging.getLogger('PLEX.websocket')
###############################################################################
diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py
index ce15f30e..4018ff9c 100644
--- a/resources/lib/websocket_client.py
+++ b/resources/lib/websocket_client.py
@@ -7,17 +7,16 @@ from json import loads
import xml.etree.ElementTree as etree
from threading import Thread
from ssl import CERT_NONE
-
from xbmc import sleep
-from utils import window, settings, thread_methods
-from companion import process_command
-import state
-import variables as v
+from . import utils
+from . import companion
+from . import state
+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__)
-@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
- 'BACKGROUND_SYNC_DISABLED'])
+@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
+ 'BACKGROUND_SYNC_DISABLED'])
class PMS_Websocket(WebSocket):
"""
Websocket connection with the PMS for Plex Companion
"""
def getUri(self):
- server = window('pms_server')
+ server = utils.window('pms_server')
# Get the appropriate prefix for the websocket
if server.startswith('https'):
server = "wss%s" % server[5:]
@@ -158,7 +157,7 @@ class PMS_Websocket(WebSocket):
if state.PLEX_TOKEN:
uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN
sslopt = {}
- if settings('sslverify') == "false":
+ if utils.settings('sslverify') == "false":
sslopt["cert_reqs"] = CERT_NONE
LOG.debug("%s: Uri: %s, sslopt: %s",
self.__class__.__name__, uri, sslopt)
@@ -206,7 +205,7 @@ class Alexa_Websocket(WebSocket):
"""
Websocket connection to talk to Amazon Alexa.
- Can't use thread_methods!
+ Can't use utils.thread_methods!
"""
thread_stopped = False
thread_suspended = False
@@ -244,9 +243,9 @@ class Alexa_Websocket(WebSocket):
LOG.error('%s: Could not parse Alexa message',
self.__class__.__name__)
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):
self.thread_stopped = True
diff --git a/service.py b/service.py
index 5b62d549..39657f3b 100644
--- a/service.py
+++ b/service.py
@@ -1,298 +1,8 @@
# -*- coding: utf-8 -*-
###############################################################################
-from __future__ import unicode_literals # nice to fix os.walk unicode
-from logging import getLogger
-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")
-###############################################################################
+from __future__ import absolute_import, division, unicode_literals
+from resources.lib import 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", 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()
+if __name__ == "__main__":
+ service_entry.start()