diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index 26b9132a..ddff3abf 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -11,7 +11,6 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \ httppersist, plexsettings from PlexFunctions import ParseContainerKey, GetPlayQueue, \ ConvertPlexToKodiTime -import playlist import player ############################################################################### @@ -25,18 +24,18 @@ log = logging.getLogger("PLEX."+__name__) @ThreadMethods class PlexCompanion(Thread): """ - Initialize with a Queue for callbacks """ - def __init__(self): + def __init__(self, callback=None): log.info("----===## Starting PlexCompanion ##===----") + if callback is not None: + self.mgr = callback + self.playqueue = self.mgr.playqueue self.settings = plexsettings.getSettings() # Start GDM for server/client discovery self.client = plexgdm.plexgdm() self.client.clientDetails(self.settings) log.debug("Registration string is: %s " % self.client.getClientDetails()) - # Initialize playlist/queue stuff - self.playlist = playlist.Playlist('video') # kodi player instance self.player = player.Player() @@ -72,49 +71,44 @@ class PlexCompanion(Thread): data = task['data'] if task['action'] == 'playlist': + # Get the playqueue ID try: - _, queueId, query = ParseContainerKey(data['containerKey']) + _, ID, query = ParseContainerKey(data['containerKey']) except Exception as e: log.error('Exception while processing: %s' % e) import traceback log.error("Traceback:\n%s" % traceback.format_exc()) return - if self.playlist is not None: - if self.playlist.Typus() != data.get('type'): - log.debug('Switching to Kodi playlist of type %s' - % data.get('type')) - self.playlist = None - if self.playlist is None: - if data.get('type') == 'music': - self.playlist = playlist.Playlist('music') - else: - self.playlist = playlist.Playlist('video') - if queueId != self.playlist.QueueId(): + self.mgr.playqueue.update_playqueue_with_companion(data) + + self.playqueue = self.mgr.playqueue.get_playqueue_from_plextype( + data.get('type')) + if queueId != self.playqueue.ID: log.info('New playlist received, updating!') xml = GetPlayQueue(queueId) if xml in (None, 401): log.error('Could not download Plex playlist.') return # Clear existing playlist on the Kodi side - self.playlist.clear() + self.playqueue.clear() # Set new values - self.playlist.QueueId(queueId) - self.playlist.PlayQueueVersion(int( + self.playqueue.QueueId(queueId) + self.playqueue.PlayQueueVersion(int( xml.attrib.get('playQueueVersion'))) - self.playlist.Guid(xml.attrib.get('guid')) + self.playqueue.Guid(xml.attrib.get('guid')) items = [] for item in xml: items.append({ 'playQueueItemID': item.get('playQueueItemID'), 'plexId': item.get('ratingKey'), 'kodiId': None}) - self.playlist.playAll( + self.playqueue.playAll( items, startitem=self._getStartItem(data.get('key', '')), offset=ConvertPlexToKodiTime(data.get('offset', 0))) log.info('Initiated playlist no %s with version %s' - % (self.playlist.QueueId(), - self.playlist.PlayQueueVersion())) + % (self.playqueue.QueueId(), + self.playqueue.PlayQueueVersion())) else: log.error('This has never happened before!') @@ -129,7 +123,7 @@ class PlexCompanion(Thread): requestMgr = httppersist.RequestMgr() jsonClass = functions.jsonClass(requestMgr, self.settings) subscriptionManager = subscribers.SubscriptionManager( - jsonClass, requestMgr, self.player, self.playlist) + jsonClass, requestMgr, self.player, self.playqueue) queue = Queue.Queue(maxsize=100) diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index f93ffb47..2705e0a6 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -60,11 +60,12 @@ KODITYPE_FROM_PLEXTYPE = { 'XXXXXXX': 'genre' } -KODIAUDIOVIDEO_FROM_MEDIA_TYPE = { +KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = { 'movie': 'video', 'episode': 'video', 'season': 'video', 'tvshow': 'video', + 'clip': 'video', 'artist': 'audio', 'album': 'audio', 'track': 'audio', diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 75fcd2d5..de68e73f 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -19,7 +19,6 @@ import clientinfo import downloadutils import embydb_functions as embydb import playbackutils as pbutils -import playlist import PlexFunctions import PlexAPI diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index a895ec8c..4724be97 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -9,7 +9,7 @@ import xbmcgui from utils import settings, window, language as lang import clientinfo import downloadutils -import userclient +from userclient import UserClient import PlexAPI from PlexFunctions import GetMachineIdentifier, get_PMS_settings @@ -30,11 +30,10 @@ class InitialSetup(): self.clientInfo = clientinfo.ClientInfo() self.addonId = self.clientInfo.getAddonId() self.doUtils = downloadutils.DownloadUtils().downloadUrl - self.userClient = userclient.UserClient() self.plx = PlexAPI.PlexAPI() self.dialog = xbmcgui.Dialog() - self.server = self.userClient.getServer() + self.server = UserClient().getServer() self.serverid = settings('plex_machineIdentifier') # Get Plex credentials from settings file, if they exist plexdict = self.plx.GetPlexLoginFromSettings() diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 5606198f..09c42445 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -14,7 +14,6 @@ import kodidb_functions as kodidb import playbackutils as pbutils from utils import window, settings, CatchExceptions, tryDecode, tryEncode from PlexFunctions import scrobble, REMAP_TYPE_FROM_PLEXTYPE -from playlist import Playlist ############################################################################### @@ -25,11 +24,11 @@ log = logging.getLogger("PLEX."+__name__) class KodiMonitor(xbmc.Monitor): - def __init__(self): - + def __init__(self, callback): + self.mgr = callback self.doUtils = downloadutils.DownloadUtils().downloadUrl self.xbmcplayer = xbmc.Player() - self.playlist = Playlist('video') + self.playqueue = self.mgr.playqueue xbmc.Monitor.__init__(self) log.info("Kodi monitor started.") @@ -173,7 +172,6 @@ class KodiMonitor(xbmc.Monitor): # Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1, # u'position': 0} self.playlist.kodi_onadd(data) - Playlist() def PlayBackStart(self, data): """ diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 7b37ce4c..9b6f8cc4 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -356,19 +356,10 @@ class ProcessFanartThread(Thread): @ThreadMethods class LibrarySync(Thread): """ - librarysync.LibrarySync(queue) - - where (communication with websockets) - queue: Queue object for background sync """ - # Borg, even though it's planned to only have 1 instance up and running! - _shared_state = {} + def __init__(self, callback=None): + self.mgr = callback - def __init__(self, queue): - self.__dict__ = self._shared_state - - # Communication with websockets - self.queue = queue self.itemsToProcess = [] self.sessionKeys = [] self.fanartqueue = Queue.Queue() @@ -1720,7 +1711,8 @@ class LibrarySync(Thread): xbmcplayer = xbmc.Player() - queue = self.queue + # Link to Websocket queue + queue = self.mgr.ws.queue startupComplete = False self.views = [] diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 8205ff3e..40a984f9 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -11,7 +11,7 @@ import xbmcgui import xbmcplugin import playutils as putils -import playlist +from playqueue import Playqueue from utils import window, settings, tryEncode, tryDecode import downloadutils @@ -37,10 +37,7 @@ class PlaybackUtils(): self.userid = window('currUserId') self.server = window('pms_server') - if self.API.getType() == 'track': - self.pl = playlist.Playlist(typus='music') - else: - self.pl = playlist.Playlist(typus='video') + self.pl = Playqueue().get_playqueue_from_plextype(self.API.getType()) def play(self, itemid, dbid=None): @@ -89,7 +86,7 @@ class PlaybackUtils(): contextmenu_play = window('plex_contextplay') == 'true' window('plex_contextplay', clear=True) homeScreen = xbmc.getCondVisibility('Window.IsActive(home)') - kodiPl = self.pl.playlist + kodiPl = self.pl.kodi_pl sizePlaylist = kodiPl.size() if contextmenu_play: # Need to start with the items we're inserting here diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py deleted file mode 100644 index 3e055156..00000000 --- a/resources/lib/playlist.py +++ /dev/null @@ -1,565 +0,0 @@ -# -*- coding: utf-8 -*- - -############################################################################### - -import logging -import json -from urllib import urlencode -from threading import Lock -from functools import wraps -from urllib import quote, urlencode - -import xbmc - -import embydb_functions as embydb -import kodidb_functions as kodidb -from utils import window, tryEncode, JSONRPC -import playbackutils -import PlexFunctions as PF -import PlexAPI -from downloadutils import DownloadUtils - -############################################################################### - -log = logging.getLogger("PLEX."+__name__) - -############################################################################### - -PLEX_PLAYQUEUE_ARGS = ( - 'playQueueID', - 'playQueueVersion', - 'playQueueSelectedItemID', - 'playQueueSelectedItemOffset' -) - - -class lockMethod: - """ - Decorator for class methods to lock hem completely. Same lock is used for - every single decorator and instance used! - - Here only used for Playlist() - """ - lock = Lock() - - @classmethod - def decorate(cls, func): - @wraps(func) - def wrapper(*args, **kwargs): - with cls.lock: - result = func(*args, **kwargs) - return result - return wrapper - - -class Playlist(): - """ - Initiate with Playlist(typus='video' or 'music') - - ATTRIBUTES: - id: integer - position: integer, default -1 - type: string, default "unknown" - "unknown", - "video", - "audio", - "picture", - "mixed" - size: integer - """ - # Borg - multiple instances, shared state - _shared_state = {} - - player = xbmc.Player() - - playlists = None - - @lockMethod.decorate - def __init__(self, typus=None): - # Borg - self.__dict__ = self._shared_state - - # If already initiated, return - if self.playlists is not None: - return - - self.doUtils = DownloadUtils().downloadUrl - # Get all playlists from Kodi - self.playlists = JSONRPC('Playlist.GetPlaylists').execute() - try: - self.playlists = self.playlists['result'] - except KeyError: - log.error('Could not get Kodi playlists. JSON Result was: %s' - % self.playlists) - self.playlists = None - return - # Example return: [{u'playlistid': 0, u'type': u'audio'}, - # {u'playlistid': 1, u'type': u'video'}, - # {u'playlistid': 2, u'type': u'picture'}] - # Initiate the Kodi playlists - for playlist in self.playlists: - # Initialize each Kodi playlist - if playlist['type'] == 'audio': - playlist['kodi_pl'] = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) - elif playlist['type'] == 'video': - playlist['kodi_pl'] = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - else: - # Currently, only video or audio playlists available - playlist['kodi_pl'] = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - - # Initialize Plex info on the playQueue - for arg in PLEX_PLAYQUEUE_ARGS: - playlist[arg] = None - - # Build a list of all items within each playlist - playlist['items'] = [] - for item in self._get_kodi_items(playlist['playlistid']): - playlist['items'].append({ - 'kodi_id': item.get('id'), - 'type': item['type'], - 'file': item['file'], - 'playQueueItemID': None, - 'plex_id': self._get_plexid(item) - }) - log.debug('self.playlist: %s' % playlist) - - def _init_pl_item(self): - return { - 'plex_id': None, - 'kodi_id': None, - 'file': None, - 'type': None, # 'audio' or 'video' - 'playQueueItemID': None, - 'uri': None, - # To be able to drag Kodi JSON data along: - 'playlistid': None, - 'position': None, - 'item': None, - } - - def _get_plexid(self, item): - """ - Supply with data['item'] as returned from Kodi JSON-RPC interface - """ - with embydb.GetEmbyDB() as emby_db: - emby_dbitem = emby_db.getItem_byKodiId(item.get('id'), - item.get('type')) - try: - plex_id = emby_dbitem[0] - except TypeError: - plex_id = None - return plex_id - - def _get_kodi_items(self, playlistid): - params = { - 'playlistid': playlistid, - 'properties': ["title", "file"] - } - answ = JSONRPC('Playlist.GetItems').execute(params) - # returns e.g. [{u'title': u'3 Idiots', u'type': u'movie', u'id': 3, - # u'file': u'smb://nas/PlexMovies/3 Idiots 2009 pt1.mkv', u'label': - # u'3 Idiots'}] - try: - answ = answ['result']['items'] - except KeyError: - answ = [] - return answ - - @lockMethod.decorate - def getQueueIdFromPosition(self, playlistPosition): - return self.items[playlistPosition]['playQueueItemID'] - - @lockMethod.decorate - def Typus(self, value=None): - if value: - self.typus = value - else: - return self.typus - - @lockMethod.decorate - def PlayQueueVersion(self, value=None): - if value: - self.playQueueVersion = value - else: - return self.playQueueVersion - - @lockMethod.decorate - def QueueId(self, value=None): - if value: - self.queueId = value - else: - return self.queueId - - @lockMethod.decorate - def Guid(self, value=None): - if value: - self.guid = value - else: - return self.guid - - @lockMethod.decorate - def clear(self): - """ - Empties current Kodi playlist and associated variables - """ - log.info('Clearing playlist') - self.playlist.clear() - self.items = [] - self.queueId = None - self.playQueueVersion = None - self.guid = None - - def _initiatePlaylist(self): - log.info('Initiating playlist') - playlist = None - with embydb.GetEmbyDB() as emby_db: - for item in self.items: - itemid = item['plexId'] - embydb_item = emby_db.getItem_byId(itemid) - try: - mediatype = embydb_item[4] - except TypeError: - log.info('Couldnt find item %s in Kodi db' % itemid) - item = PF.GetPlexMetadata(itemid) - if item in (None, 401): - log.info('Couldnt find item %s on PMS, trying next' - % itemid) - continue - if PlexAPI.API(item[0]).getType() == 'track': - playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) - log.info('Music playlist initiated') - self.typus = 'music' - else: - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - log.info('Video playlist initiated') - self.typus = 'video' - else: - if mediatype == 'song': - playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) - log.info('Music playlist initiated') - self.typus = 'music' - else: - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - log.info('Video playlist initiated') - self.typus = 'video' - break - self.playlist = playlist - if self.playlist is not None: - self.playlistId = self.playlist.getPlayListId() - - def _processItems(self, startitem, startPlayer=False): - startpos = None - with embydb.GetEmbyDB() as emby_db: - for pos, item in enumerate(self.items): - kodiId = None - plexId = item['plexId'] - embydb_item = emby_db.getItem_byId(plexId) - try: - kodiId = embydb_item[0] - mediatype = embydb_item[4] - except TypeError: - log.info('Couldnt find item %s in Kodi db' % plexId) - xml = PF.GetPlexMetadata(plexId) - if xml in (None, 401): - log.error('Could not download plexId %s' % plexId) - else: - log.debug('Downloaded xml metadata, adding now') - self._addtoPlaylist_xbmc(xml[0]) - else: - # Add to playlist - log.debug("Adding %s PlexId %s, KodiId %s to playlist." - % (mediatype, plexId, kodiId)) - self._addtoPlaylist(kodiId, mediatype) - # Add the kodiId - if kodiId is not None: - item['kodiId'] = str(kodiId) - if (startpos is None and startitem[1] == item[startitem[0]]): - startpos = pos - - if startPlayer is True and len(self.playlist) > 0: - if startpos is not None: - self.player.play(self.playlist, startpos=startpos) - else: - log.info('Never received a starting item for playlist, ' - 'starting with the first entry') - self.player.play(self.playlist) - - @lockMethod.decorate - def playAll(self, items, startitem, offset): - """ - items: list of dicts of the form - { - 'playQueueItemID': Plex playQueueItemID, e.g. '29175' - 'plexId': Plex ratingKey, e.g. '125' - 'kodiId': Kodi's db id of the same item - } - - startitem: tuple (typus, id), where typus is either - 'playQueueItemID' or 'plexId' and id is the corresponding - id as a string - offset: First item's time offset to play in Kodi time (an int) - """ - log.info("---*** PLAY ALL ***---") - log.debug('Startitem: %s, offset: %s, items: %s' - % (startitem, offset, items)) - self.items = items - if self.playlist is None: - self._initiatePlaylist() - if self.playlist is None: - log.error('Could not create playlist, abort') - return - - window('plex_customplaylist', value="true") - if offset != 0: - # Seek to the starting position - window('plex_customplaylist.seektime', str(offset)) - self._processItems(startitem, startPlayer=True) - # Log playlist - self._verifyPlaylist() - log.debug('Internal playlist: %s' % self.items) - - @lockMethod.decorate - def modifyPlaylist(self, itemids): - log.info("---*** MODIFY PLAYLIST ***---") - log.debug("Items: %s" % itemids) - - self._initiatePlaylist(itemids) - self._processItems(itemids, startPlayer=True) - - self._verifyPlaylist() - - @lockMethod.decorate - def addtoPlaylist(self, dbid=None, mediatype=None, url=None): - """ - mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist', - 'album', 'song', 'genre' - """ - self._addtoPlaylist(dbid, mediatype, url) - - def _addtoPlaylist(self, dbid=None, mediatype=None, url=None): - pl = { - 'jsonrpc': "2.0", - 'id': 1, - 'method': "Playlist.Add", - 'params': { - 'playlistid': self.playlistId - } - } - if dbid is not None: - pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)} - else: - pl['params']['item'] = {'file': url} - log.debug(xbmc.executeJSONRPC(json.dumps(pl))) - - def _addtoPlaylist_xbmc(self, item): - API = PlexAPI.API(item) - params = { - 'mode': "play", - 'dbid': 'plextrailer', - 'id': API.getRatingKey(), - 'filename': API.getKey() - } - playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \ - % urlencode(params) - - listitem = API.CreateListItemFromPlexItem() - playbackutils.PlaybackUtils(item).setArtwork(listitem) - - self.playlist.add(playurl, listitem) - - @lockMethod.decorate - def insertintoPlaylist(self, - position, - dbid=None, - mediatype=None, - url=None): - pl = { - 'jsonrpc': "2.0", - 'id': 1, - 'method': "Playlist.Insert", - 'params': { - 'playlistid': self.playlistId, - 'position': position - } - } - if dbid is not None: - pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)} - else: - pl['params']['item'] = {'file': url} - - log.debug(xbmc.executeJSONRPC(json.dumps(pl))) - - @lockMethod.decorate - def verifyPlaylist(self): - self._verifyPlaylist() - - def _verifyPlaylist(self): - pl = { - 'jsonrpc': "2.0", - 'id': 1, - 'method': "Playlist.GetItems", - 'params': { - 'playlistid': self.playlistId, - 'properties': ['title', 'file'] - } - } - log.debug(xbmc.executeJSONRPC(json.dumps(pl))) - - @lockMethod.decorate - def removefromPlaylist(self, position): - pl = { - 'jsonrpc': "2.0", - 'id': 1, - 'method': "Playlist.Remove", - 'params': { - 'playlistid': self.playlistId, - 'position': position - } - } - log.debug(xbmc.executeJSONRPC(json.dumps(pl))) - - def _get_uri(self, plex_id=None, item=None): - """ - Supply with either plex_id or data['item'] as received from Kodi JSON- - RPC - """ - uri = None - if plex_id is None: - plex_id = self._get_plexid(item) - self._cur_item['plex_id'] = plex_id - if plex_id is not None: - xml = PF.GetPlexMetadata(plex_id) - try: - uri = ('library://%s/item/%s%s' % - (xml.attrib.get('librarySectionUUID'), - quote('library/metadata/', safe=''), plex_id)) - except: - pass - if uri is None: - try: - uri = 'library://whatever/item/%s' % quote(item['file'], - safe='') - except: - raise KeyError('Could not get file/url with item: %s' % item) - self._cur_item['uri'] = uri - return uri - - def _init_plex_playQueue(self, plex_id=None, data=None): - """ - Supply either plex_id or the data supplied by Kodi JSON-RPC - """ - if plex_id is None: - plex_id = self._get_plexid(data['item']) - self._cur_item['plex_id'] = plex_id - - if data is not None: - playlistid = data['playlistid'] - plex_type = self.playlists[playlistid]['type'] - else: - with embydb.GetEmbyDB() as emby_db: - plex_type = emby_db.getItem_byId(plex_id) - try: - plex_type = PF.KODIAUDIOVIDEO_FROM_MEDIA_TYPE[plex_type[4]] - except TypeError: - raise KeyError('Unknown plex_type %s' % plex_type) - for playlist in self.playlists: - if playlist['type'] == plex_type: - playlistid = playlist['playlistid'] - self._cur_item['playlistid'] = playlistid - self._cur_item['type'] = plex_type - - params = { - 'next': 0, - 'type': plex_type, - 'uri': self._get_uri(plex_id=plex_id, item=data['item']) - } - log.debug('params: %s' % urlencode(params)) - xml = self.doUtils(url="{server}/playQueues", - action_type="POST", - parameters=params) - try: - xml.attrib - except (TypeError, AttributeError): - raise KeyError('Could not post to PMS, received: %s' % xml) - self._Plex_item_updated(xml) - - def _Plex_item_updated(self, xml): - """ - Called if a new item has just been added/updated @ Plex playQueue - - Call with the PMS' xml reply - """ - # Update the ITEM - log.debug('xml.attrib: %s' % xml.attrib) - args = { - 'playQueueItemID': 'playQueueLastAddedItemID', # for playlist PUT - 'playQueueItemID': 'playQueueSelectedItemID' # for playlist INIT - } - for old, new in args.items(): - if new in xml.attrib: - self._cur_item[old] = xml.attrib[new] - # Update the PLAYLIST - for arg in PLEX_PLAYQUEUE_ARGS: - if arg in xml.attrib: - self.playlists[self._cur_item['playlistid']][arg] = xml.attrib[arg] - - def _init_Kodi_item(self, item): - """ - Call with Kodi's JSON-RPC data['item'] - """ - self._cur_item['kodi_id'] = item.get('id') - try: - self._cur_item['type'] = PF.KODIAUDIOVIDEO_FROM_MEDIA_TYPE[ - item.get('type')] - except KeyError: - log.error('Could not get media_type for %s' % item) - - def _add_curr_item(self): - self.playlists[self._cur_item['playlistid']]['items'].insert( - self._cur_item['position'], - self._cur_item) - - @lockMethod.decorate - def kodi_onadd(self, data): - """ - Called if Kodi playlist is modified. Data is Kodi JSON-RPC output, e.g. - { - u'item': {u'type': u'movie', u'id': 3}, - u'playlistid': 1, - u'position': 0 - } - """ - self._cur_item = self._init_pl_item() - self._cur_item.update(data) - self._init_Kodi_item(data['item']) - - pl = self.playlists[data['playlistid']] - if pl['playQueueID'] is None: - # Playlist needs to be initialized! - try: - self._init_plex_playQueue(data=data) - except KeyError as e: - log.error('Error encountered while init playQueue: %s' % e) - return - else: - next_item = data['position'] - if next_item != 0: - next_item = pl['items'][data['position']-1]['playQueueItemID'] - params = { - 'next': next_item, - 'type': pl['type'], - 'uri': self._get_uri(item=data['item']) - } - xml = self.doUtils(url="{server}/playQueues/%s" - % pl['playQueueID'], - action_type="PUT", - parameters=params) - try: - xml.attrib - except AttributeError: - log.error('Could not add item %s to playQueue' % data) - return - self._Plex_item_updated(xml) - # Add the new item to our playlist - self._add_curr_item() - log.debug('self.playlists are now: %s' % self.playlists) diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py new file mode 100644 index 00000000..6dc6a02c --- /dev/null +++ b/resources/lib/playlist_func.py @@ -0,0 +1,394 @@ +import logging +from urllib import quote + +import embydb_functions as embydb +from downloadutils import DownloadUtils as DU +from utils import JSONRPC, tryEncode + +############################################################################### + +log = logging.getLogger("PLEX."+__name__) + +############################################################################### + + +class Playlist_Object_Baseclase(object): + playlistid = None # Kodi playlist ID, [int] + type = None # Kodi type: 'audio', 'video', 'picture' + kodi_pl = None # Kodi xbmc.PlayList object + items = [] # list of PLAYLIST_ITEMS + old_kodi_pl = [] # to store old Kodi JSON result with all pl items + ID = None # Plex id, e.g. playQueueID + version = None # Plex version, [int] + selectedItemID = None + selectedItemOffset = None + shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ??? + repeat = 0 # [int], 0: not repeated, 1: ??? 2: ??? + + def __repr__(self): + answ = "<%s object: " % (self.__class__.__name__) + for key in self.__dict__: + answ += '%s: %s, ' % (key, getattr(self, key)) + return answ[:-2] + ">" + + +class Playlist_Object(Playlist_Object_Baseclase): + kind = 'playList' + + +class Playqueue_Object(Playlist_Object_Baseclase): + kind = 'playQueue' + + +class Playlist_Item(object): + ID = None # Plex playlist/playqueue id, e.g. playQueueItemID + plex_id = None # Plex unique item id, "ratingKey" + plex_UUID = None # Plex librarySectionUUID + kodi_id = None # Kodi unique kodi id (unique only within type!) + kodi_type = None # Kodi type: 'movie' + file = None # Path to the item's file + uri = None # Weird Plex uri path involving plex_UUID + + +def playlist_item_from_kodi_item(kodi_item): + """ + Turns the JSON answer from Kodi into a playlist element + + Supply with data['item'] as returned from Kodi JSON-RPC interface. + kodi_item dict contains keys 'id', 'type', 'file' (if applicable) + """ + item = Playlist_Item() + if kodi_item.get('id'): + item.kodi_id = kodi_item['id'] + with embydb.GetEmbyDB() as emby_db: + emby_dbitem = emby_db.getItem_byKodiId(kodi_item['id'], + kodi_item['type']) + try: + item.plex_id = emby_dbitem[0] + item.plex_UUID = emby_dbitem[0] + except TypeError: + pass + item.file = kodi_item.get('file') if kodi_item.get('file') else None + item.kodi_type = kodi_item.get('type') if kodi_item.get('type') else None + if item.plex_id is None: + item.uri = 'library://whatever/item/%s' % quote(item.file, safe='') + else: + item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % + (item.plex_UUID, item.plex_id)) + return item + + +def playlist_item_from_plex(plex_id): + """ + Returns a playlist element providing the plex_id ("ratingKey") + """ + item = Playlist_Item() + item.plex_id = plex_id + with embydb.GetEmbyDB() as emby_db: + emby_dbitem = emby_db.getItem_byId(plex_id) + try: + item.kodi_id = emby_dbitem[0] + item.kodi_type = emby_dbitem[4] + except: + raise KeyError('Could not find plex_id %s in database' % plex_id) + return item + + +def _log_xml(xml): + try: + xml.attrib + except AttributeError: + log.error('Did not receive an XML. Answer was: %s' % xml) + else: + from xml.etree.ElementTree import dump + log.error('XML received from the PMS: %s' % dump(xml)) + + +def _get_playListVersion_from_xml(playlist, xml): + """ + Takes a PMS xml as input to overwrite the playlist version (e.g. Plex + playQueueVersion). Returns True if successful, False otherwise + """ + try: + playlist.version = int(xml.attrib['%sVersion' % playlist.kind]) + except (TypeError, AttributeError, KeyError): + log.error('Could not get new playlist Version for playlist %s' + % playlist) + _log_xml(xml) + return False + return True + + +def _get_playlist_details_from_xml(playlist, xml): + """ + Takes a PMS xml as input and overwrites all the playlist's details, e.g. + playlist.ID with the XML's playQueueID + """ + try: + playlist.ID = xml.attrib['%sID' % playlist.kind] + playlist.version = xml.attrib['%sVersion' % playlist.kind] + playlist.selectedItemID = xml.attrib['%sSelectedItemID' % playlist.kind] + playlist.selectedItemOffset = xml.attrib['%sSelectedItemOffset' % playlist.kind] + playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind] + except: + log.error('Could not parse xml answer from PMS for playlist %s' + % playlist) + import traceback + log.error(traceback.format_exc()) + _log_xml(xml) + raise KeyError + + +def init_Plex_playlist(playlist, plex_id=None, kodi_item=None): + """ + Supply either plex_id or the data supplied by Kodi JSON-RPC + """ + if plex_id is not None: + item = playlist_item_from_plex(plex_id) + else: + item = playlist_item_from_kodi_item(kodi_item) + params = { + 'next': 0, + 'type': playlist.type, + 'uri': item.uri + } + xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind, + action_type="POST", + parameters=params) + _get_playlist_details_from_xml(xml) + playlist.items.append(item) + log.debug('Initialized the playlist: %s' % playlist) + + +def add_playlist_item(playlist, kodi_item, after_pos): + """ + Adds the new kodi_item to playlist after item at position after_pos + [int] + """ + item = playlist_item_from_kodi_item(kodi_item) + url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri) + # Will always put the new item at the end of the playlist + xml = DU().downloadUrl(url, action_type="PUT") + try: + item.ID = xml.attrib['%sLastAddedItemID' % playlist.kind] + except (TypeError, AttributeError, KeyError): + log.error('Could not add item %s to playlist %s' + % (kodi_item, playlist)) + _log_xml(xml) + return + playlist.items.append(item) + if after_pos == len(playlist.items) - 1: + # Item was added at the end + _get_playListVersion_from_xml(playlist, xml) + else: + # Move the new item to the correct position + move_playlist_item(playlist, + len(playlist.items) - 1, + after_pos) + + +def move_playlist_item(playlist, before_pos, after_pos): + """ + Moves playlist item from before_pos [int] to after_pos [int] + """ + if after_pos == 0: + url = "{server}/%ss/%s/items/%s/move?after=0" % \ + (playlist.kind, + playlist.ID, + playlist.items[before_pos].ID) + else: + url = "{server}/%ss/%s/items/%s/move?after=%s" % \ + (playlist.kind, + playlist.ID, + playlist.items[before_pos].ID, + playlist.items[after_pos - 1].ID) + xml = DU().downloadUrl(url, action_type="PUT") + # We need to increment the playlistVersion + _get_playListVersion_from_xml(playlist, xml) + # Move our item's position in our internal playlist + playlist.items.insert(after_pos, playlist.items.pop(before_pos)) + + +def delete_playlist_item(playlist, pos): + """ + Delete the item at position pos [int] + """ + xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" % + (playlist.kind, + playlist.ID, + playlist.items[pos].ID, + playlist.repeat), + action_type="DELETE") + _get_playListVersion_from_xml(playlist, xml) + del playlist.items[pos] + + +def get_kodi_playlist_items(playlist): + """ + Returns a list of the current Kodi playlist items using JSON + + E.g.: + [{u'title': u'3 Idiots', u'type': u'movie', u'id': 3, u'file': + u'smb://nas/PlexMovies/3 Idiots 2009 pt1.mkv', u'label': u'3 Idiots'}] + """ + answ = JSONRPC('Playlist.GetItems').execute({ + 'playlistid': playlist.playlistid, + 'properties': ["title", "file"] + }) + try: + answ = answ['result']['items'] + except KeyError: + answ = [] + return answ + + +def get_kodi_playqueues(): + """ + Example return: [{u'playlistid': 0, u'type': u'audio'}, + {u'playlistid': 1, u'type': u'video'}, + {u'playlistid': 2, u'type': u'picture'}] + """ + queues = JSONRPC('Playlist.GetPlaylists').execute() + try: + queues = queues['result'] + except KeyError: + raise KeyError('Could not get Kodi playqueues. JSON Result was: %s' + % queues) + return queues + + +# Functions operating on the Kodi playlist objects ########## + +def insertintoPlaylist(self, + position, + dbid=None, + mediatype=None, + url=None): + params = { + 'playlistid': self.playlistId, + 'position': position + } + if dbid is not None: + params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)} + else: + params['item'] = {'file': url} + JSONRPC('Playlist.Insert').execute(params) + + +def addtoPlaylist(self, dbid=None, mediatype=None, url=None): + params = { + 'playlistid': self.playlistId + } + if dbid is not None: + params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)} + else: + params['item'] = {'file': url} + JSONRPC('Playlist.Add').execute(params) + + +def removefromPlaylist(self, position): + params = { + 'playlistid': self.playlistId, + 'position': position + } + JSONRPC('Playlist.Remove').execute(params) + + +def playAll(self, items, startitem, offset): + """ + items: list of dicts of the form + { + 'playQueueItemID': Plex playQueueItemID, e.g. '29175' + 'plexId': Plex ratingKey, e.g. '125' + 'kodiId': Kodi's db id of the same item + } + + startitem: tuple (typus, id), where typus is either + 'playQueueItemID' or 'plexId' and id is the corresponding + id as a string + offset: First item's time offset to play in Kodi time (an int) + """ + log.info("---*** PLAY ALL ***---") + log.debug('Startitem: %s, offset: %s, items: %s' + % (startitem, offset, items)) + self.items = items + if self.playlist is None: + self._initiatePlaylist() + if self.playlist is None: + log.error('Could not create playlist, abort') + return + + window('plex_customplaylist', value="true") + if offset != 0: + # Seek to the starting position + window('plex_customplaylist.seektime', str(offset)) + self._processItems(startitem, startPlayer=True) + # Log playlist + self._verifyPlaylist() + log.debug('Internal playlist: %s' % self.items) + + +def _processItems(self, startitem, startPlayer=False): + startpos = None + with embydb.GetEmbyDB() as emby_db: + for pos, item in enumerate(self.items): + kodiId = None + plexId = item['plexId'] + embydb_item = emby_db.getItem_byId(plexId) + try: + kodiId = embydb_item[0] + mediatype = embydb_item[4] + except TypeError: + log.info('Couldnt find item %s in Kodi db' % plexId) + xml = PF.GetPlexMetadata(plexId) + if xml in (None, 401): + log.error('Could not download plexId %s' % plexId) + else: + log.debug('Downloaded xml metadata, adding now') + self._addtoPlaylist_xbmc(xml[0]) + else: + # Add to playlist + log.debug("Adding %s PlexId %s, KodiId %s to playlist." + % (mediatype, plexId, kodiId)) + self._addtoPlaylist(kodiId, mediatype) + # Add the kodiId + if kodiId is not None: + item['kodiId'] = str(kodiId) + if (startpos is None and startitem[1] == item[startitem[0]]): + startpos = pos + + if startPlayer is True and len(self.playlist) > 0: + if startpos is not None: + self.player.play(self.playlist, startpos=startpos) + else: + log.info('Never received a starting item for playlist, ' + 'starting with the first entry') + self.player.play(self.playlist) + +def _addtoPlaylist_xbmc(self, item): + API = PlexAPI.API(item) + params = { + 'mode': "play", + 'dbid': 'plextrailer', + 'id': API.getRatingKey(), + 'filename': API.getKey() + } + playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \ + % urlencode(params) + + listitem = API.CreateListItemFromPlexItem() + playbackutils.PlaybackUtils(item).setArtwork(listitem) + + self.playlist.add(playurl, listitem) + + +def clear(self): + """ + Empties current Kodi playlist and associated variables + """ + self.playlist.clear() + self.items = [] + self.queueId = None + self.playQueueVersion = None + self.guid = None + log.info('Playlist cleared') \ No newline at end of file diff --git a/resources/lib/playqueue.py b/resources/lib/playqueue.py new file mode 100644 index 00000000..997c6bef --- /dev/null +++ b/resources/lib/playqueue.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +############################################################################### +import logging +from threading import Lock, Thread + +import xbmc + +from utils import ThreadMethods, ThreadMethodsAdditionalSuspend, Lock_Function +import playlist_func as PL +from PlexFunctions import KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, GetPlayQueue, \ + ParseContainerKey + +############################################################################### +log = logging.getLogger("PLEX."+__name__) + +# Lock used to lock methods +lock = Lock() +lockmethod = Lock_Function(lock) +############################################################################### + + +@ThreadMethodsAdditionalSuspend('plex_serverStatus') +@ThreadMethods +class Playqueue(Thread): + """ + Monitors Kodi's playqueues for changes on the Kodi side + """ + # Borg - multiple instances, shared state + __shared_state = {} + playqueues = None + + @lockmethod.lockthis + def __init__(self, callback=None): + self.__dict__ = self.__shared_state + Thread.__init__(self) + if self.playqueues is not None: + return + self.mgr = callback + + # Initialize Kodi playqueues + self.playqueues = [] + for queue in PL.get_kodi_playqueues(): + playqueue = PL.Playqueue_Object() + playqueue.playlistid = queue['playlistid'] + playqueue.type = queue['type'] + # Initialize each Kodi playlist + if playqueue.type == 'audio': + playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) + elif playqueue.type == 'video': + playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + else: + # Currently, only video or audio playqueues available + playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + self.playqueues.append(playqueue) + log.debug('Initialized the Kodi play queues: %s' % self.playqueues) + + @lockmethod.lockthis + def update_playqueue_with_companion(self, data): + """ + Feed with Plex companion data + """ + + # Get the correct queue + for playqueue in self.playqueues: + if playqueue.type == KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[ + data['type']]: + break + + @lockmethod.lockthis + def kodi_onadd(self, data): + """ + Called if an item is added to a Kodi playqueue. Data is Kodi JSON-RPC + output, e.g. + { + u'item': {u'type': u'movie', u'id': 3}, + u'playlistid': 1, + u'position': 0 + } + """ + for playqueue in self.playqueues: + if playqueue.playlistid == data['playlistid']: + break + if playqueue.ID is None: + # Need to initialize the queue for the first time + PL.init_Plex_playlist(playqueue, kodi_item=data['item']) + else: + PL.add_playlist_item(playqueue, data['item'], data['position']) + + @lockmethod.lockthis + def _compare_playqueues(self, playqueue, new): + """ + Used to poll the Kodi playqueue and update the Plex playqueue if needed + """ + old = playqueue.old_kodi_pl + log.debug('Comparing new Kodi playqueue %s with our play queue %s' + % (new, playqueue)) + index = list(range(0, len(old))) + for i, new_item in enumerate(new): + for j, old_item in enumerate(old): + if old_item.get('id') is None: + identical = old_item['file'] == new_item['file'] + else: + identical = (old_item['id'] == new_item['id'] and + old_item['type'] == new_item['type']) + if j == 0 and identical: + del old[j], index[j] + break + elif identical: + # item now at pos i has been moved from original pos i+j + PL.move_playlist_item(playqueue, i + j, i) + # Delete the item we just found + del old[i + j], index[i + j] + break + else: + # Did not find element i in the old list - Kodi monitor should + # pick this up! + # PL.add_playlist_item(playqueue, new_item, i-1) + pass + for i in index: + # Still got some old items left that need deleting + PL.delete_playlist_item(playqueue, i) + log.debug('New playqueue: %s' % playqueue) + + def run(self): + threadStopped = self.threadStopped + threadSuspended = self.threadSuspended + log.info("----===## Starting PlayQueue client ##===----") + # Initialize the playqueues, if Kodi already got items in them + for playqueue in self.playqueues: + for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)): + if i == 0: + PL.init_Plex_playlist(playqueue, kodi_item=item) + else: + PL.add_playlist_item(playqueue, item, i) + while not threadStopped(): + while threadSuspended(): + if threadStopped(): + break + xbmc.sleep(1000) + for playqueue in self.playqueues: + if not playqueue.items: + # Skip empty playqueues as items can't be modified + continue + kodi_playqueue = PL.get_kodi_playlist_items(playqueue) + if playqueue.old_kodi_pl != kodi_playqueue: + # compare old and new playqueue + self._compare_playqueues(playqueue, kodi_playqueue) + playqueue.old_kodi_pl = list(kodi_playqueue) + xbmc.sleep(1000) + log.info("----===## PlayQueue client stopped ##===----") diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 659586b2..75ff98bb 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -32,8 +32,10 @@ class UserClient(threading.Thread): # Borg - multiple instances, shared state __shared_state = {} - def __init__(self): + def __init__(self, callback=None): self.__dict__ = self.__shared_state + if callback is not None: + self.mgr = callback self.auth = True self.retry = 0 diff --git a/resources/lib/utils.py b/resources/lib/utils.py index a0a5179a..be6d96d3 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -133,21 +133,21 @@ def tryDecode(string, encoding='utf-8'): def DateToKodi(stamp): - """ - converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a - propper, human-readable time stamp used by Kodi + """ + converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a + propper, human-readable time stamp used by Kodi - Output: Y-m-d h:m:s = 2009-04-05 23:16:04 + Output: Y-m-d h:m:s = 2009-04-05 23:16:04 - None if an error was encountered - """ - try: - stamp = float(stamp) + float(window('kodiplextimeoffset')) - date_time = time.localtime(stamp) - localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time) - except: - localdate = None - return localdate + None if an error was encountered + """ + try: + stamp = float(stamp) + float(window('kodiplextimeoffset')) + date_time = time.localtime(stamp) + localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time) + except: + localdate = None + return localdate def IfExists(path): @@ -938,9 +938,33 @@ def ThreadMethods(cls): return cls +class Lock_Function: + """ + Decorator for class methods and functions to lock them with lock. + + Initialize this class first + lockfunction = Lock_Function(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): + @wraps(func) + def wrapper(*args, **kwargs): + with self.lock: + result = func(*args, **kwargs) + return result + return wrapper + ############################################################################### # UNUSED METHODS + def changePlayState(itemType, kodiId, playCount, lastplayed): """ YET UNUSED diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index d83b9ce0..312080b5 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -5,6 +5,7 @@ import logging import websocket from json import loads from threading import Thread +from Queue import Queue from ssl import CERT_NONE from xbmc import sleep @@ -24,10 +25,12 @@ log = logging.getLogger("PLEX."+__name__) class WebSocket(Thread): opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) - def __init__(self, queue): + def __init__(self, callback=None): + if callback is not None: + self.mgr = callback self.ws = None # Communication with librarysync - self.queue = queue + self.queue = Queue() Thread.__init__(self) def process(self, opcode, message): diff --git a/service.py b/service.py index 35499474..c5bf737d 100644 --- a/service.py +++ b/service.py @@ -5,7 +5,6 @@ import logging import os import sys -import Queue import xbmc import xbmcaddon @@ -33,17 +32,18 @@ sys.path.append(_base_resource) ############################################################################### from utils import settings, window, language as lang -import userclient +from userclient import UserClient import clientinfo import initialsetup -import kodimonitor -import librarysync +from kodimonitor import KodiMonitor +from librarysync import LibrarySync import videonodes -import websocket_client as wsc +from websocket_client import WebSocket import downloadutils +from playqueue import Playqueue import PlexAPI -import PlexCompanion +from PlexCompanion import PlexCompanion ############################################################################### @@ -61,11 +61,18 @@ class Service(): server_online = True warn_auth = True - userclient_running = False - websocket_running = False + user = None + ws = None + library = None + plexCompanion = None + playqueue = None + + user_running = False + ws_running = False library_running = False - kodimonitor_running = False plexCompanion_running = False + playqueue_running = False + kodimonitor_running = False def __init__(self): @@ -96,7 +103,7 @@ class Service(): "plex_online", "plex_serverStatus", "plex_onWake", "plex_dbCheck", "plex_kodiScan", "plex_shouldStop", "currUserId", "plex_dbScan", - "plex_initialScan", "plex_customplaylist", "plex_playbackProps", + "plex_initialScan", "plex_customplayqueue", "plex_playbackProps", "plex_runLibScan", "plex_username", "pms_token", "plex_token", "pms_server", "plex_machineIdentifier", "plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths", @@ -129,13 +136,13 @@ class Service(): # Server auto-detect initialsetup.InitialSetup().setup() - # Queue for background sync - queue = Queue.Queue() + # Initialize important threads, handing over self for callback purposes + self.user = UserClient(self) + self.ws = WebSocket(self) + self.library = LibrarySync(self) + self.plexCompanion = PlexCompanion(self) + self.playqueue = Playqueue(self) - # Initialize important threads - user = userclient.UserClient() - ws = wsc.WebSocket(queue) - library = librarysync.LibrarySync(queue) plx = PlexAPI.PlexAPI() welcome_msg = True @@ -157,7 +164,7 @@ class Service(): if window('plex_online') == "true": # Plex server is online # Verify if user is set and has access to the server - if (user.currUser is not None) and user.HasAccess: + if (self.user.currUser is not None) and self.user.HasAccess: if not self.kodimonitor_running: # Start up events self.warn_auth = True @@ -166,38 +173,43 @@ class Service(): welcome_msg = False xbmcgui.Dialog().notification( heading=addonName, - message="%s %s" % (lang(33000), user.currUser), - icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", + message="%s %s" % (lang(33000), + self.user.currUser), + icon="special://home/addons/plugin." + "video.plexkodiconnect/icon.png", time=2000, sound=False) # Start monitoring kodi events - self.kodimonitor_running = kodimonitor.KodiMonitor() - + self.kodimonitor_running = KodiMonitor(self) + # Start playqueue client + if not self.playqueue_running: + self.playqueue_running = True + self.playqueue.start() # Start the Websocket Client - if not self.websocket_running: - self.websocket_running = True - ws.start() + if not self.ws_running: + self.ws_running = True + self.ws.start() # Start the syncing thread if not self.library_running: self.library_running = True - library.start() + self.library.start() # Start the Plex Companion thread if not self.plexCompanion_running: self.plexCompanion_running = True - plexCompanion = PlexCompanion.PlexCompanion() - plexCompanion.start() + self.plexCompanion.start() else: - if (user.currUser is None) and self.warn_auth: - # Alert user is not authenticated and suppress future warning + if (self.user.currUser 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 user.HasAccess == False: + while self.user.HasAccess is False: # Verify access with an API call - user.hasAccess() + self.user.hasAccess() if window('plex_online') != "true": # Server went offline @@ -211,7 +223,7 @@ class Service(): # Wait until Plex server is online # or Kodi is shut down. while not monitor.abortRequested(): - server = user.getServer() + server = self.user.getServer() if server is False: # No server info set in add-on settings pass @@ -268,9 +280,9 @@ class Service(): window('suspend_LibraryThread', clear=True) # Start the userclient thread - if not self.userclient_running: - self.userclient_running = True - user.start() + if not self.user_running: + self.user_running = True + self.user.start() break @@ -286,27 +298,22 @@ class Service(): # Tell all threads to terminate (e.g. several lib sync threads) window('plex_terminateNow', value='true') - try: - plexCompanion.stopThread() + self.plexCompanion.stopThread() except: log.warn('plexCompanion already shut down') - try: - library.stopThread() + self.library.stopThread() except: log.warn('Library sync already shut down') - try: - ws.stopThread() + self.ws.stopThread() except: log.warn('Websocket client already shut down') - try: - user.stopThread() + self.user.stopThread() except: log.warn('User client already shut down') - try: downloadutils.DownloadUtils().stopSession() except: