Playqueues major haul-over

This commit is contained in:
tomkat83 2016-12-27 17:33:52 +01:00
parent 95c87065ed
commit 0c2d4984ab
14 changed files with 673 additions and 678 deletions

View file

@ -11,7 +11,6 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \
httppersist, plexsettings httppersist, plexsettings
from PlexFunctions import ParseContainerKey, GetPlayQueue, \ from PlexFunctions import ParseContainerKey, GetPlayQueue, \
ConvertPlexToKodiTime ConvertPlexToKodiTime
import playlist
import player import player
############################################################################### ###############################################################################
@ -25,18 +24,18 @@ log = logging.getLogger("PLEX."+__name__)
@ThreadMethods @ThreadMethods
class PlexCompanion(Thread): class PlexCompanion(Thread):
""" """
Initialize with a Queue for callbacks
""" """
def __init__(self): def __init__(self, callback=None):
log.info("----===## Starting PlexCompanion ##===----") log.info("----===## Starting PlexCompanion ##===----")
if callback is not None:
self.mgr = callback
self.playqueue = self.mgr.playqueue
self.settings = plexsettings.getSettings() self.settings = plexsettings.getSettings()
# Start GDM for server/client discovery # Start GDM for server/client discovery
self.client = plexgdm.plexgdm() self.client = plexgdm.plexgdm()
self.client.clientDetails(self.settings) self.client.clientDetails(self.settings)
log.debug("Registration string is: %s " log.debug("Registration string is: %s "
% self.client.getClientDetails()) % self.client.getClientDetails())
# Initialize playlist/queue stuff
self.playlist = playlist.Playlist('video')
# kodi player instance # kodi player instance
self.player = player.Player() self.player = player.Player()
@ -72,49 +71,44 @@ class PlexCompanion(Thread):
data = task['data'] data = task['data']
if task['action'] == 'playlist': if task['action'] == 'playlist':
# Get the playqueue ID
try: try:
_, queueId, query = ParseContainerKey(data['containerKey']) _, ID, query = ParseContainerKey(data['containerKey'])
except Exception as e: except Exception as e:
log.error('Exception while processing: %s' % e) log.error('Exception while processing: %s' % e)
import traceback import traceback
log.error("Traceback:\n%s" % traceback.format_exc()) log.error("Traceback:\n%s" % traceback.format_exc())
return return
if self.playlist is not None: self.mgr.playqueue.update_playqueue_with_companion(data)
if self.playlist.Typus() != data.get('type'):
log.debug('Switching to Kodi playlist of type %s' self.playqueue = self.mgr.playqueue.get_playqueue_from_plextype(
% data.get('type')) data.get('type'))
self.playlist = None if queueId != self.playqueue.ID:
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():
log.info('New playlist received, updating!') log.info('New playlist received, updating!')
xml = GetPlayQueue(queueId) xml = GetPlayQueue(queueId)
if xml in (None, 401): if xml in (None, 401):
log.error('Could not download Plex playlist.') log.error('Could not download Plex playlist.')
return return
# Clear existing playlist on the Kodi side # Clear existing playlist on the Kodi side
self.playlist.clear() self.playqueue.clear()
# Set new values # Set new values
self.playlist.QueueId(queueId) self.playqueue.QueueId(queueId)
self.playlist.PlayQueueVersion(int( self.playqueue.PlayQueueVersion(int(
xml.attrib.get('playQueueVersion'))) xml.attrib.get('playQueueVersion')))
self.playlist.Guid(xml.attrib.get('guid')) self.playqueue.Guid(xml.attrib.get('guid'))
items = [] items = []
for item in xml: for item in xml:
items.append({ items.append({
'playQueueItemID': item.get('playQueueItemID'), 'playQueueItemID': item.get('playQueueItemID'),
'plexId': item.get('ratingKey'), 'plexId': item.get('ratingKey'),
'kodiId': None}) 'kodiId': None})
self.playlist.playAll( self.playqueue.playAll(
items, items,
startitem=self._getStartItem(data.get('key', '')), startitem=self._getStartItem(data.get('key', '')),
offset=ConvertPlexToKodiTime(data.get('offset', 0))) offset=ConvertPlexToKodiTime(data.get('offset', 0)))
log.info('Initiated playlist no %s with version %s' log.info('Initiated playlist no %s with version %s'
% (self.playlist.QueueId(), % (self.playqueue.QueueId(),
self.playlist.PlayQueueVersion())) self.playqueue.PlayQueueVersion()))
else: else:
log.error('This has never happened before!') log.error('This has never happened before!')
@ -129,7 +123,7 @@ class PlexCompanion(Thread):
requestMgr = httppersist.RequestMgr() requestMgr = httppersist.RequestMgr()
jsonClass = functions.jsonClass(requestMgr, self.settings) jsonClass = functions.jsonClass(requestMgr, self.settings)
subscriptionManager = subscribers.SubscriptionManager( subscriptionManager = subscribers.SubscriptionManager(
jsonClass, requestMgr, self.player, self.playlist) jsonClass, requestMgr, self.player, self.playqueue)
queue = Queue.Queue(maxsize=100) queue = Queue.Queue(maxsize=100)

View file

@ -60,11 +60,12 @@ KODITYPE_FROM_PLEXTYPE = {
'XXXXXXX': 'genre' 'XXXXXXX': 'genre'
} }
KODIAUDIOVIDEO_FROM_MEDIA_TYPE = { KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = {
'movie': 'video', 'movie': 'video',
'episode': 'video', 'episode': 'video',
'season': 'video', 'season': 'video',
'tvshow': 'video', 'tvshow': 'video',
'clip': 'video',
'artist': 'audio', 'artist': 'audio',
'album': 'audio', 'album': 'audio',
'track': 'audio', 'track': 'audio',

View file

@ -19,7 +19,6 @@ import clientinfo
import downloadutils import downloadutils
import embydb_functions as embydb import embydb_functions as embydb
import playbackutils as pbutils import playbackutils as pbutils
import playlist
import PlexFunctions import PlexFunctions
import PlexAPI import PlexAPI

View file

@ -9,7 +9,7 @@ import xbmcgui
from utils import settings, window, language as lang from utils import settings, window, language as lang
import clientinfo import clientinfo
import downloadutils import downloadutils
import userclient from userclient import UserClient
import PlexAPI import PlexAPI
from PlexFunctions import GetMachineIdentifier, get_PMS_settings from PlexFunctions import GetMachineIdentifier, get_PMS_settings
@ -30,11 +30,10 @@ class InitialSetup():
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.addonId = self.clientInfo.getAddonId() self.addonId = self.clientInfo.getAddonId()
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.userClient = userclient.UserClient()
self.plx = PlexAPI.PlexAPI() self.plx = PlexAPI.PlexAPI()
self.dialog = xbmcgui.Dialog() self.dialog = xbmcgui.Dialog()
self.server = self.userClient.getServer() self.server = UserClient().getServer()
self.serverid = settings('plex_machineIdentifier') self.serverid = settings('plex_machineIdentifier')
# Get Plex credentials from settings file, if they exist # Get Plex credentials from settings file, if they exist
plexdict = self.plx.GetPlexLoginFromSettings() plexdict = self.plx.GetPlexLoginFromSettings()

View file

@ -14,7 +14,6 @@ import kodidb_functions as kodidb
import playbackutils as pbutils import playbackutils as pbutils
from utils import window, settings, CatchExceptions, tryDecode, tryEncode from utils import window, settings, CatchExceptions, tryDecode, tryEncode
from PlexFunctions import scrobble, REMAP_TYPE_FROM_PLEXTYPE 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): class KodiMonitor(xbmc.Monitor):
def __init__(self): def __init__(self, callback):
self.mgr = callback
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = xbmc.Player() self.xbmcplayer = xbmc.Player()
self.playlist = Playlist('video') self.playqueue = self.mgr.playqueue
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
log.info("Kodi monitor started.") 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, # Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
# u'position': 0} # u'position': 0}
self.playlist.kodi_onadd(data) self.playlist.kodi_onadd(data)
Playlist()
def PlayBackStart(self, data): def PlayBackStart(self, data):
""" """

View file

@ -356,19 +356,10 @@ class ProcessFanartThread(Thread):
@ThreadMethods @ThreadMethods
class LibrarySync(Thread): 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! def __init__(self, callback=None):
_shared_state = {} self.mgr = callback
def __init__(self, queue):
self.__dict__ = self._shared_state
# Communication with websockets
self.queue = queue
self.itemsToProcess = [] self.itemsToProcess = []
self.sessionKeys = [] self.sessionKeys = []
self.fanartqueue = Queue.Queue() self.fanartqueue = Queue.Queue()
@ -1720,7 +1711,8 @@ class LibrarySync(Thread):
xbmcplayer = xbmc.Player() xbmcplayer = xbmc.Player()
queue = self.queue # Link to Websocket queue
queue = self.mgr.ws.queue
startupComplete = False startupComplete = False
self.views = [] self.views = []

View file

@ -11,7 +11,7 @@ import xbmcgui
import xbmcplugin import xbmcplugin
import playutils as putils import playutils as putils
import playlist from playqueue import Playqueue
from utils import window, settings, tryEncode, tryDecode from utils import window, settings, tryEncode, tryDecode
import downloadutils import downloadutils
@ -37,10 +37,7 @@ class PlaybackUtils():
self.userid = window('currUserId') self.userid = window('currUserId')
self.server = window('pms_server') self.server = window('pms_server')
if self.API.getType() == 'track': self.pl = Playqueue().get_playqueue_from_plextype(self.API.getType())
self.pl = playlist.Playlist(typus='music')
else:
self.pl = playlist.Playlist(typus='video')
def play(self, itemid, dbid=None): def play(self, itemid, dbid=None):
@ -89,7 +86,7 @@ class PlaybackUtils():
contextmenu_play = window('plex_contextplay') == 'true' contextmenu_play = window('plex_contextplay') == 'true'
window('plex_contextplay', clear=True) window('plex_contextplay', clear=True)
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)') homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
kodiPl = self.pl.playlist kodiPl = self.pl.kodi_pl
sizePlaylist = kodiPl.size() sizePlaylist = kodiPl.size()
if contextmenu_play: if contextmenu_play:
# Need to start with the items we're inserting here # Need to start with the items we're inserting here

View file

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

View file

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

150
resources/lib/playqueue.py Normal file
View file

@ -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 ##===----")

View file

@ -32,8 +32,10 @@ class UserClient(threading.Thread):
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
__shared_state = {} __shared_state = {}
def __init__(self): def __init__(self, callback=None):
self.__dict__ = self.__shared_state self.__dict__ = self.__shared_state
if callback is not None:
self.mgr = callback
self.auth = True self.auth = True
self.retry = 0 self.retry = 0

View file

@ -133,21 +133,21 @@ def tryDecode(string, encoding='utf-8'):
def DateToKodi(stamp): def DateToKodi(stamp):
""" """
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
propper, human-readable time stamp used by Kodi 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 None if an error was encountered
""" """
try: try:
stamp = float(stamp) + float(window('kodiplextimeoffset')) stamp = float(stamp) + float(window('kodiplextimeoffset'))
date_time = time.localtime(stamp) date_time = time.localtime(stamp)
localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time) localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time)
except: except:
localdate = None localdate = None
return localdate return localdate
def IfExists(path): def IfExists(path):
@ -938,9 +938,33 @@ def ThreadMethods(cls):
return 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 # UNUSED METHODS
def changePlayState(itemType, kodiId, playCount, lastplayed): def changePlayState(itemType, kodiId, playCount, lastplayed):
""" """
YET UNUSED YET UNUSED

View file

@ -5,6 +5,7 @@ import logging
import websocket import websocket
from json import loads from json import loads
from threading import Thread from threading import Thread
from Queue import Queue
from ssl import CERT_NONE from ssl import CERT_NONE
from xbmc import sleep from xbmc import sleep
@ -24,10 +25,12 @@ log = logging.getLogger("PLEX."+__name__)
class WebSocket(Thread): class WebSocket(Thread):
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) 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 self.ws = None
# Communication with librarysync # Communication with librarysync
self.queue = queue self.queue = Queue()
Thread.__init__(self) Thread.__init__(self)
def process(self, opcode, message): def process(self, opcode, message):

View file

@ -5,7 +5,6 @@
import logging import logging
import os import os
import sys import sys
import Queue
import xbmc import xbmc
import xbmcaddon import xbmcaddon
@ -33,17 +32,18 @@ sys.path.append(_base_resource)
############################################################################### ###############################################################################
from utils import settings, window, language as lang from utils import settings, window, language as lang
import userclient from userclient import UserClient
import clientinfo import clientinfo
import initialsetup import initialsetup
import kodimonitor from kodimonitor import KodiMonitor
import librarysync from librarysync import LibrarySync
import videonodes import videonodes
import websocket_client as wsc from websocket_client import WebSocket
import downloadutils import downloadutils
from playqueue import Playqueue
import PlexAPI import PlexAPI
import PlexCompanion from PlexCompanion import PlexCompanion
############################################################################### ###############################################################################
@ -61,11 +61,18 @@ class Service():
server_online = True server_online = True
warn_auth = True warn_auth = True
userclient_running = False user = None
websocket_running = False ws = None
library = None
plexCompanion = None
playqueue = None
user_running = False
ws_running = False
library_running = False library_running = False
kodimonitor_running = False
plexCompanion_running = False plexCompanion_running = False
playqueue_running = False
kodimonitor_running = False
def __init__(self): def __init__(self):
@ -96,7 +103,7 @@ class Service():
"plex_online", "plex_serverStatus", "plex_onWake", "plex_online", "plex_serverStatus", "plex_onWake",
"plex_dbCheck", "plex_kodiScan", "plex_dbCheck", "plex_kodiScan",
"plex_shouldStop", "currUserId", "plex_dbScan", "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", "plex_runLibScan", "plex_username", "pms_token", "plex_token",
"pms_server", "plex_machineIdentifier", "plex_servername", "pms_server", "plex_machineIdentifier", "plex_servername",
"plex_authenticated", "PlexUserImage", "useDirectPaths", "plex_authenticated", "PlexUserImage", "useDirectPaths",
@ -129,13 +136,13 @@ class Service():
# Server auto-detect # Server auto-detect
initialsetup.InitialSetup().setup() initialsetup.InitialSetup().setup()
# Queue for background sync # Initialize important threads, handing over self for callback purposes
queue = Queue.Queue() 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() plx = PlexAPI.PlexAPI()
welcome_msg = True welcome_msg = True
@ -157,7 +164,7 @@ class Service():
if window('plex_online') == "true": if window('plex_online') == "true":
# Plex server is online # Plex server is online
# Verify if user is set and has access to the server # 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: if not self.kodimonitor_running:
# Start up events # Start up events
self.warn_auth = True self.warn_auth = True
@ -166,38 +173,43 @@ class Service():
welcome_msg = False welcome_msg = False
xbmcgui.Dialog().notification( xbmcgui.Dialog().notification(
heading=addonName, heading=addonName,
message="%s %s" % (lang(33000), user.currUser), message="%s %s" % (lang(33000),
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", self.user.currUser),
icon="special://home/addons/plugin."
"video.plexkodiconnect/icon.png",
time=2000, time=2000,
sound=False) sound=False)
# Start monitoring kodi events # 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 # Start the Websocket Client
if not self.websocket_running: if not self.ws_running:
self.websocket_running = True self.ws_running = True
ws.start() self.ws.start()
# Start the syncing thread # Start the syncing thread
if not self.library_running: if not self.library_running:
self.library_running = True self.library_running = True
library.start() self.library.start()
# Start the Plex Companion thread # Start the Plex Companion thread
if not self.plexCompanion_running: if not self.plexCompanion_running:
self.plexCompanion_running = True self.plexCompanion_running = True
plexCompanion = PlexCompanion.PlexCompanion() self.plexCompanion.start()
plexCompanion.start()
else: else:
if (user.currUser is None) and self.warn_auth: if (self.user.currUser is None) and self.warn_auth:
# Alert user is not authenticated and suppress future warning # Alert user is not authenticated and suppress future
# warning
self.warn_auth = False self.warn_auth = False
log.warn("Not authenticated yet.") log.warn("Not authenticated yet.")
# User access is restricted. # User access is restricted.
# Keep verifying until access is granted # Keep verifying until access is granted
# unless server goes offline or Kodi is shut down. # 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 # Verify access with an API call
user.hasAccess() self.user.hasAccess()
if window('plex_online') != "true": if window('plex_online') != "true":
# Server went offline # Server went offline
@ -211,7 +223,7 @@ class Service():
# Wait until Plex server is online # Wait until Plex server is online
# or Kodi is shut down. # or Kodi is shut down.
while not monitor.abortRequested(): while not monitor.abortRequested():
server = user.getServer() server = self.user.getServer()
if server is False: if server is False:
# No server info set in add-on settings # No server info set in add-on settings
pass pass
@ -268,9 +280,9 @@ class Service():
window('suspend_LibraryThread', clear=True) window('suspend_LibraryThread', clear=True)
# Start the userclient thread # Start the userclient thread
if not self.userclient_running: if not self.user_running:
self.userclient_running = True self.user_running = True
user.start() self.user.start()
break break
@ -286,27 +298,22 @@ class Service():
# Tell all threads to terminate (e.g. several lib sync threads) # Tell all threads to terminate (e.g. several lib sync threads)
window('plex_terminateNow', value='true') window('plex_terminateNow', value='true')
try: try:
plexCompanion.stopThread() self.plexCompanion.stopThread()
except: except:
log.warn('plexCompanion already shut down') log.warn('plexCompanion already shut down')
try: try:
library.stopThread() self.library.stopThread()
except: except:
log.warn('Library sync already shut down') log.warn('Library sync already shut down')
try: try:
ws.stopThread() self.ws.stopThread()
except: except:
log.warn('Websocket client already shut down') log.warn('Websocket client already shut down')
try: try:
user.stopThread() self.user.stopThread()
except: except:
log.warn('User client already shut down') log.warn('User client already shut down')
try: try:
downloadutils.DownloadUtils().stopSession() downloadutils.DownloadUtils().stopSession()
except: except: