Plex websockets - groundworks 2
This commit is contained in:
parent
ed5483e2ae
commit
5949988b68
8 changed files with 312 additions and 69 deletions
|
@ -31,6 +31,8 @@ def ConvertPlexToKodiTime(plexTime):
|
||||||
"""
|
"""
|
||||||
Converts Plextime to Koditime. Returns an int (in seconds).
|
Converts Plextime to Koditime. Returns an int (in seconds).
|
||||||
"""
|
"""
|
||||||
|
if plexTime is None:
|
||||||
|
return None
|
||||||
return int(float(plexTime) * PlexToKodiTimefactor())
|
return int(float(plexTime) * PlexToKodiTimefactor())
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,6 +49,14 @@ def GetItemClassFromType(itemType):
|
||||||
return classes[itemType]
|
return classes[itemType]
|
||||||
|
|
||||||
|
|
||||||
|
def GetItemClassFromNumber(itemType):
|
||||||
|
classes = {
|
||||||
|
1: 'Movies',
|
||||||
|
4: 'TVShows',
|
||||||
|
}
|
||||||
|
return classes[itemType]
|
||||||
|
|
||||||
|
|
||||||
def GetKodiTypeFromPlex(plexItemType):
|
def GetKodiTypeFromPlex(plexItemType):
|
||||||
"""
|
"""
|
||||||
As used in playlist.item here: http://kodi.wiki/view/JSON-RPC_API
|
As used in playlist.item here: http://kodi.wiki/view/JSON-RPC_API
|
||||||
|
|
|
@ -257,6 +257,17 @@ class Items(object):
|
||||||
userdata['PlayCount'],
|
userdata['PlayCount'],
|
||||||
userdata['LastPlayedDate'])
|
userdata['LastPlayedDate'])
|
||||||
|
|
||||||
|
def updatePlaystate(self, item):
|
||||||
|
if item['duration'] is None:
|
||||||
|
item['duration'] = self.kodi_db.getVideoRuntime(item['kodi_id'],
|
||||||
|
item['kodi_type'])
|
||||||
|
self.logMsg('Updating item with: %s' % item, 0)
|
||||||
|
self.kodi_db.addPlaystate(item['file_id'],
|
||||||
|
item['viewOffset'],
|
||||||
|
item['duration'],
|
||||||
|
item['viewCount'],
|
||||||
|
item['lastViewedAt'])
|
||||||
|
|
||||||
|
|
||||||
class Movies(Items):
|
class Movies(Items):
|
||||||
|
|
||||||
|
|
|
@ -857,10 +857,28 @@ class Kodidb_Functions():
|
||||||
ids.append(row[0])
|
ids.append(row[0])
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
|
def getVideoRuntime(self, kodiid, mediatype):
|
||||||
|
if mediatype == 'movie':
|
||||||
cursor = self.cursor
|
query = ' '.join((
|
||||||
|
"SELECT c11",
|
||||||
|
"FROM movie",
|
||||||
|
"WHERE idMovie = ?",
|
||||||
|
))
|
||||||
|
elif mediatype == 'episode':
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT c09",
|
||||||
|
"FROM episode",
|
||||||
|
"WHERE idEpisode = ?",
|
||||||
|
))
|
||||||
|
self.cursor.execute(query, (kodiid,))
|
||||||
|
try:
|
||||||
|
runtime = self.cursor.fetchone()[0]
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
return int(runtime)
|
||||||
|
|
||||||
|
def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
|
||||||
|
cursor = self.cursor
|
||||||
# Delete existing resume point
|
# Delete existing resume point
|
||||||
query = ' '.join((
|
query = ' '.join((
|
||||||
|
|
||||||
|
@ -870,13 +888,20 @@ class Kodidb_Functions():
|
||||||
cursor.execute(query, (fileid,))
|
cursor.execute(query, (fileid,))
|
||||||
|
|
||||||
# Set watched count
|
# Set watched count
|
||||||
query = ' '.join((
|
if playcount is None:
|
||||||
|
query = ' '.join((
|
||||||
"UPDATE files",
|
"UPDATE files",
|
||||||
"SET playCount = ?, lastPlayed = ?",
|
"SET lastPlayed = ?",
|
||||||
"WHERE idFile = ?"
|
"WHERE idFile = ?"
|
||||||
))
|
))
|
||||||
cursor.execute(query, (playcount, dateplayed, fileid))
|
cursor.execute(query, (dateplayed, fileid))
|
||||||
|
else:
|
||||||
|
query = ' '.join((
|
||||||
|
"UPDATE files",
|
||||||
|
"SET playCount = ?, lastPlayed = ?",
|
||||||
|
"WHERE idFile = ?"
|
||||||
|
))
|
||||||
|
cursor.execute(query, (playcount, dateplayed, fileid))
|
||||||
|
|
||||||
# Set the resume bookmark
|
# Set the resume bookmark
|
||||||
if resume_seconds:
|
if resume_seconds:
|
||||||
|
|
|
@ -21,7 +21,7 @@ import read_embyserver as embyserver
|
||||||
import userclient
|
import userclient
|
||||||
import videonodes
|
import videonodes
|
||||||
|
|
||||||
import PlexFunctions
|
import PlexFunctions as PF
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ class ThreadedGetMetadata(Thread):
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(100)
|
||||||
continue
|
continue
|
||||||
# Download Metadata
|
# Download Metadata
|
||||||
plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId'])
|
plexXML = PF.GetPlexMetadata(updateItem['itemId'])
|
||||||
if plexXML is None:
|
if plexXML is None:
|
||||||
# Did not receive a valid XML - skip that item for now
|
# Did not receive a valid XML - skip that item for now
|
||||||
self.logMsg("Could not get metadata for %s. "
|
self.logMsg("Could not get metadata for %s. "
|
||||||
|
@ -209,17 +209,27 @@ class ThreadedShowSyncInfo(Thread):
|
||||||
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
||||||
@utils.ThreadMethods
|
@utils.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!
|
# Borg, even though it's planned to only have 1 instance up and running!
|
||||||
_shared_state = {}
|
_shared_state = {}
|
||||||
# How long should we look into the past for fast syncing items (in s)
|
# How long should we look into the past for fast syncing items (in s)
|
||||||
syncPast = 30
|
syncPast = 30
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, queue):
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
self.__dict__ = self._shared_state
|
||||||
|
|
||||||
self.__language__ = xbmcaddon.Addon().getLocalizedString
|
self.__language__ = xbmcaddon.Addon().getLocalizedString
|
||||||
|
|
||||||
|
# Communication with websockets
|
||||||
|
self.queue = queue
|
||||||
|
self.itemsToProcess = []
|
||||||
|
self.safteyMargin = 30
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.user = userclient.UserClient()
|
self.user = userclient.UserClient()
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
|
@ -325,7 +335,7 @@ class LibrarySync(Thread):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the Plex item's metadata
|
# Get the Plex item's metadata
|
||||||
xml = PlexFunctions.GetPlexMetadata(plexId)
|
xml = PF.GetPlexMetadata(plexId)
|
||||||
if xml is None:
|
if xml is None:
|
||||||
self.logMsg("Could not download metadata, aborting time sync", -1)
|
self.logMsg("Could not download metadata, aborting time sync", -1)
|
||||||
return
|
return
|
||||||
|
@ -341,15 +351,15 @@ class LibrarySync(Thread):
|
||||||
# Set the timer
|
# Set the timer
|
||||||
koditime = utils.getUnixTimestamp()
|
koditime = utils.getUnixTimestamp()
|
||||||
# Toggle watched state
|
# Toggle watched state
|
||||||
PlexFunctions.scrobble(plexId, 'watched')
|
PF.scrobble(plexId, 'watched')
|
||||||
# Let the PMS process this first!
|
# Let the PMS process this first!
|
||||||
xbmc.sleep(2000)
|
xbmc.sleep(2000)
|
||||||
# Get all PMS items to find the item we changed
|
# Get all PMS items to find the item we changed
|
||||||
items = PlexFunctions.GetAllPlexLeaves(libraryId,
|
items = PF.GetAllPlexLeaves(libraryId,
|
||||||
lastViewedAt=timestamp,
|
lastViewedAt=timestamp,
|
||||||
containerSize=self.limitindex)
|
containerSize=self.limitindex)
|
||||||
# Toggle watched state back
|
# Toggle watched state back
|
||||||
PlexFunctions.scrobble(plexId, 'unwatched')
|
PF.scrobble(plexId, 'unwatched')
|
||||||
# Get server timestamp for this change
|
# Get server timestamp for this change
|
||||||
plextime = None
|
plextime = None
|
||||||
for item in items:
|
for item in items:
|
||||||
|
@ -438,9 +448,9 @@ class LibrarySync(Thread):
|
||||||
# We need to process this:
|
# We need to process this:
|
||||||
self.updatelist.append({
|
self.updatelist.append({
|
||||||
'itemId': itemId,
|
'itemId': itemId,
|
||||||
'itemType': PlexFunctions.GetItemClassFromType(
|
'itemType': PF.GetItemClassFromType(
|
||||||
plexType),
|
plexType),
|
||||||
'method': PlexFunctions.GetMethodFromPlexType(plexType),
|
'method': PF.GetMethodFromPlexType(plexType),
|
||||||
'viewName': viewName,
|
'viewName': viewName,
|
||||||
'viewId': viewId,
|
'viewId': viewId,
|
||||||
'title': title
|
'title': title
|
||||||
|
@ -488,7 +498,7 @@ class LibrarySync(Thread):
|
||||||
for view in self.views:
|
for view in self.views:
|
||||||
self.updatelist = []
|
self.updatelist = []
|
||||||
# Get items per view
|
# Get items per view
|
||||||
items = PlexFunctions.GetAllPlexLeaves(
|
items = PF.GetAllPlexLeaves(
|
||||||
view['id'],
|
view['id'],
|
||||||
updatedAt=self.getPMSfromKodiTime(lastSync),
|
updatedAt=self.getPMSfromKodiTime(lastSync),
|
||||||
containerSize=self.limitindex)
|
containerSize=self.limitindex)
|
||||||
|
@ -512,7 +522,7 @@ class LibrarySync(Thread):
|
||||||
self.updateKodiMusicLib = True
|
self.updateKodiMusicLib = True
|
||||||
# Do the work
|
# Do the work
|
||||||
self.GetAndProcessXMLs(
|
self.GetAndProcessXMLs(
|
||||||
PlexFunctions.GetItemClassFromType(plexType),
|
PF.GetItemClassFromType(plexType),
|
||||||
showProgress=False)
|
showProgress=False)
|
||||||
|
|
||||||
self.updatelist = []
|
self.updatelist = []
|
||||||
|
@ -524,7 +534,7 @@ class LibrarySync(Thread):
|
||||||
episodeupdate = False
|
episodeupdate = False
|
||||||
songupdate = False
|
songupdate = False
|
||||||
for view in self.views:
|
for view in self.views:
|
||||||
items = PlexFunctions.GetAllPlexLeaves(
|
items = PF.GetAllPlexLeaves(
|
||||||
view['id'],
|
view['id'],
|
||||||
lastViewedAt=self.getPMSfromKodiTime(lastSync),
|
lastViewedAt=self.getPMSfromKodiTime(lastSync),
|
||||||
containerSize=self.limitindex)
|
containerSize=self.limitindex)
|
||||||
|
@ -1079,7 +1089,7 @@ class LibrarySync(Thread):
|
||||||
# Get items per view
|
# Get items per view
|
||||||
viewId = view['id']
|
viewId = view['id']
|
||||||
viewName = view['name']
|
viewName = view['name']
|
||||||
all_plexmovies = PlexFunctions.GetPlexSectionResults(
|
all_plexmovies = PF.GetPlexSectionResults(
|
||||||
viewId, args=None, containerSize=self.limitindex)
|
viewId, args=None, containerSize=self.limitindex)
|
||||||
if all_plexmovies is None:
|
if all_plexmovies is None:
|
||||||
self.logMsg("Couldnt get section items, aborting for view.", 1)
|
self.logMsg("Couldnt get section items, aborting for view.", 1)
|
||||||
|
@ -1115,10 +1125,10 @@ class LibrarySync(Thread):
|
||||||
also updates resume times.
|
also updates resume times.
|
||||||
This is done by downloading one XML for ALL elements with viewId
|
This is done by downloading one XML for ALL elements with viewId
|
||||||
"""
|
"""
|
||||||
xml = PlexFunctions.GetAllPlexLeaves(viewId,
|
xml = PF.GetAllPlexLeaves(viewId,
|
||||||
lastViewedAt=lastViewedAt,
|
lastViewedAt=lastViewedAt,
|
||||||
updatedAt=updatedAt,
|
updatedAt=updatedAt,
|
||||||
containerSize=self.limitindex)
|
containerSize=self.limitindex)
|
||||||
# Return if there are no items in PMS reply - it's faster
|
# Return if there are no items in PMS reply - it's faster
|
||||||
try:
|
try:
|
||||||
xml[0].attrib
|
xml[0].attrib
|
||||||
|
@ -1213,7 +1223,7 @@ class LibrarySync(Thread):
|
||||||
# Get items per view
|
# Get items per view
|
||||||
viewId = view['id']
|
viewId = view['id']
|
||||||
viewName = view['name']
|
viewName = view['name']
|
||||||
allPlexTvShows = PlexFunctions.GetPlexSectionResults(
|
allPlexTvShows = PF.GetPlexSectionResults(
|
||||||
viewId, containerSize=self.limitindex)
|
viewId, containerSize=self.limitindex)
|
||||||
if allPlexTvShows is None:
|
if allPlexTvShows is None:
|
||||||
self.logMsg(
|
self.logMsg(
|
||||||
|
@ -1240,7 +1250,7 @@ class LibrarySync(Thread):
|
||||||
if self.threadStopped():
|
if self.threadStopped():
|
||||||
return False
|
return False
|
||||||
# Grab all seasons to tvshow from PMS
|
# Grab all seasons to tvshow from PMS
|
||||||
seasons = PlexFunctions.GetAllPlexChildren(
|
seasons = PF.GetAllPlexChildren(
|
||||||
tvShowId, containerSize=self.limitindex)
|
tvShowId, containerSize=self.limitindex)
|
||||||
if seasons is None:
|
if seasons is None:
|
||||||
self.logMsg(
|
self.logMsg(
|
||||||
|
@ -1265,7 +1275,7 @@ class LibrarySync(Thread):
|
||||||
if self.threadStopped():
|
if self.threadStopped():
|
||||||
return False
|
return False
|
||||||
# Grab all episodes to tvshow from PMS
|
# Grab all episodes to tvshow from PMS
|
||||||
episodes = PlexFunctions.GetAllPlexLeaves(
|
episodes = PF.GetAllPlexLeaves(
|
||||||
view['id'], containerSize=self.limitindex)
|
view['id'], containerSize=self.limitindex)
|
||||||
if episodes is None:
|
if episodes is None:
|
||||||
self.logMsg(
|
self.logMsg(
|
||||||
|
@ -1288,7 +1298,7 @@ class LibrarySync(Thread):
|
||||||
# Cycle through tv shows
|
# Cycle through tv shows
|
||||||
with itemtypes.TVShows() as TVshow:
|
with itemtypes.TVShows() as TVshow:
|
||||||
for tvShowId in allPlexTvShowsId:
|
for tvShowId in allPlexTvShowsId:
|
||||||
XMLtvshow = PlexFunctions.GetPlexMetadata(tvShowId)
|
XMLtvshow = PF.GetPlexMetadata(tvShowId)
|
||||||
TVshow.refreshSeasonEntry(XMLtvshow, tvShowId)
|
TVshow.refreshSeasonEntry(XMLtvshow, tvShowId)
|
||||||
self.logMsg("Season info refreshed", 1)
|
self.logMsg("Season info refreshed", 1)
|
||||||
|
|
||||||
|
@ -1363,7 +1373,7 @@ class LibrarySync(Thread):
|
||||||
# Get items per view
|
# Get items per view
|
||||||
viewId = view['id']
|
viewId = view['id']
|
||||||
viewName = view['name']
|
viewName = view['name']
|
||||||
itemsXML = PlexFunctions.GetPlexSectionResults(
|
itemsXML = PF.GetPlexSectionResults(
|
||||||
viewId, args=urlArgs, containerSize=self.limitindex)
|
viewId, args=urlArgs, containerSize=self.limitindex)
|
||||||
if itemsXML is None:
|
if itemsXML is None:
|
||||||
self.logMsg("Error downloading xml for view %s"
|
self.logMsg("Error downloading xml for view %s"
|
||||||
|
@ -1401,6 +1411,159 @@ class LibrarySync(Thread):
|
||||||
# Database out of date.
|
# Database out of date.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def processMessage(self, message):
|
||||||
|
"""
|
||||||
|
processes json.loads() messages from websocket. Triage what we need to
|
||||||
|
do with "process_" methods
|
||||||
|
"""
|
||||||
|
typus = message.get('type')
|
||||||
|
if typus is None:
|
||||||
|
self.logMsg('No type, dropping message: %s' % message, -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
if typus == 'playing':
|
||||||
|
self.process_playing(message['_children'])
|
||||||
|
elif typus == 'timeline':
|
||||||
|
self.process_timeline(message['_children'])
|
||||||
|
else:
|
||||||
|
self.logMsg('Dropping message: %s' % message, -1)
|
||||||
|
|
||||||
|
def multi_delete(self, liste, deleteListe):
|
||||||
|
"""
|
||||||
|
Deletes the list items of liste at the positions in deleteListe
|
||||||
|
"""
|
||||||
|
indexes = sorted(deleteListe, reverse=True)
|
||||||
|
for index in indexes:
|
||||||
|
del liste[index]
|
||||||
|
return liste
|
||||||
|
|
||||||
|
def process_newitems(self):
|
||||||
|
"""
|
||||||
|
Periodically called to process new/updated PMS items
|
||||||
|
|
||||||
|
PMS needs a while to download info from internet AFTER it
|
||||||
|
showed up under 'timeline' websocket messages
|
||||||
|
"""
|
||||||
|
videoLibUpdate = False
|
||||||
|
now = utils.getUnixTimestamp()
|
||||||
|
deleteListe = []
|
||||||
|
for i, item in enumerate(self.itemsToProcess):
|
||||||
|
ratingKey = item['ratingKey']
|
||||||
|
timestamp = item['timestamp']
|
||||||
|
if now - timestamp < self.safteyMargin:
|
||||||
|
# We haven't waited long enough for the PMS to finish
|
||||||
|
# processing the item
|
||||||
|
continue
|
||||||
|
xml = PF.GetPlexMetadata(ratingKey)
|
||||||
|
if xml is None:
|
||||||
|
self.logMsg('Could not download metadata for %s'
|
||||||
|
% ratingKey, -1)
|
||||||
|
continue
|
||||||
|
deleteListe.append(i)
|
||||||
|
self.logMsg("Adding new PMS item: %s" % ratingKey, 1)
|
||||||
|
viewtag = xml.attrib.get('librarySectionTitle')
|
||||||
|
viewid = xml.attrib.get('librarySectionID')
|
||||||
|
mediatype = xml[0].attrib.get('type')
|
||||||
|
if mediatype == 'movie':
|
||||||
|
# Movie
|
||||||
|
videoLibUpdate = True
|
||||||
|
with itemtypes.Movies() as movie:
|
||||||
|
movie.add_update(xml[0],
|
||||||
|
viewtag=viewtag,
|
||||||
|
viewid=viewid)
|
||||||
|
elif mediatype == 'episode':
|
||||||
|
# Episode
|
||||||
|
videoLibUpdate = True
|
||||||
|
with itemtypes.TVShows() as show:
|
||||||
|
show.add_updateEpisode(xml[0],
|
||||||
|
viewtag=viewtag,
|
||||||
|
viewid=viewid)
|
||||||
|
|
||||||
|
# Get rid of the items we just processed
|
||||||
|
if len(deleteListe) > 0:
|
||||||
|
self.itemsToProcess = self.multi_delete(
|
||||||
|
self.itemsToProcess, deleteListe)
|
||||||
|
# Let Kodi know of the change
|
||||||
|
if videoLibUpdate is True:
|
||||||
|
self.logMsg("Doing Kodi Video Lib update", 1)
|
||||||
|
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||||
|
|
||||||
|
def process_timeline(self, data):
|
||||||
|
"""
|
||||||
|
PMS is messing with the library items
|
||||||
|
|
||||||
|
data['type']:
|
||||||
|
1: movie
|
||||||
|
2: tv show??
|
||||||
|
3: season??
|
||||||
|
4: episode
|
||||||
|
12: trailer, extras?
|
||||||
|
"""
|
||||||
|
videoLibUpdate = False
|
||||||
|
for item in data:
|
||||||
|
if item.get('state') == 9:
|
||||||
|
# Item was deleted.
|
||||||
|
# Only care for playable type
|
||||||
|
# For some reason itemID and not ratingKey
|
||||||
|
if item.get('type') == 1:
|
||||||
|
# Movie
|
||||||
|
self.logMsg("Removing movie %s" % item.get('itemID'), 1)
|
||||||
|
videoLibUpdate = True
|
||||||
|
with itemtypes.Movies() as movie:
|
||||||
|
movie.remove(item.get('itemID'))
|
||||||
|
elif item.get('type') == 4:
|
||||||
|
# Episode
|
||||||
|
self.logMsg("Removing episode %s" % item.get('itemID'), 1)
|
||||||
|
videoLibUpdate = True
|
||||||
|
with itemtypes.TVShows() as show:
|
||||||
|
show.remove(item.get('itemID'))
|
||||||
|
|
||||||
|
elif item.get('state') == 5 and item.get('type') in (1, 4):
|
||||||
|
# Item added or changed
|
||||||
|
# Need to process later because PMS needs to be done first
|
||||||
|
self.logMsg('New/changed PMS item: %s' % item.get('itemID'), 1)
|
||||||
|
self.itemsToProcess.append({
|
||||||
|
'ratingKey': item.get('itemID'),
|
||||||
|
'timestamp': utils.getUnixTimestamp()
|
||||||
|
})
|
||||||
|
|
||||||
|
# Let Kodi know of the change
|
||||||
|
if videoLibUpdate is True:
|
||||||
|
self.logMsg("Doing Kodi Video Lib update", 1)
|
||||||
|
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||||
|
|
||||||
|
def process_playing(self, data):
|
||||||
|
items = []
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
for item in data:
|
||||||
|
# Drop buffering messages
|
||||||
|
state = item.get('state')
|
||||||
|
if state == 'buffering':
|
||||||
|
continue
|
||||||
|
ratingKey = item.get('ratingKey')
|
||||||
|
kodiInfo = emby_db.getItem_byId(ratingKey)
|
||||||
|
if kodiInfo is None:
|
||||||
|
# Item not (yet) in Kodi library
|
||||||
|
continue
|
||||||
|
items.append({
|
||||||
|
'ratingKey': ratingKey,
|
||||||
|
'kodi_id': kodiInfo[0],
|
||||||
|
'file_id': kodiInfo[1],
|
||||||
|
'kodi_type': kodiInfo[4],
|
||||||
|
'viewOffset': PF.ConvertPlexToKodiTime(
|
||||||
|
item.get('viewOffset')),
|
||||||
|
'state': state,
|
||||||
|
'duration': PF.ConvertPlexToKodiTime(
|
||||||
|
item.get('duration')),
|
||||||
|
'viewCount': item.get('viewCount'),
|
||||||
|
'lastViewedAt': utils.DateToKodi(utils.getUnixTimestamp())
|
||||||
|
})
|
||||||
|
for item in items:
|
||||||
|
itemFkt = getattr(itemtypes,
|
||||||
|
PF.GetItemClassFromType(item['kodi_type']))
|
||||||
|
with itemFkt() as Fkt:
|
||||||
|
Fkt.updatePlaystate(item)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.run_internal()
|
self.run_internal()
|
||||||
|
@ -1424,11 +1587,13 @@ class LibrarySync(Thread):
|
||||||
installSyncDone = self.installSyncDone
|
installSyncDone = self.installSyncDone
|
||||||
enableBackgroundSync = self.enableBackgroundSync
|
enableBackgroundSync = self.enableBackgroundSync
|
||||||
fullSync = self.fullSync
|
fullSync = self.fullSync
|
||||||
fastSync = self.fastSync
|
processMessage = self.processMessage
|
||||||
string = self.__language__
|
string = self.__language__
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
|
queue = self.queue
|
||||||
|
|
||||||
startupComplete = False
|
startupComplete = False
|
||||||
self.views = []
|
self.views = []
|
||||||
count = 0
|
count = 0
|
||||||
|
@ -1563,16 +1728,27 @@ class LibrarySync(Thread):
|
||||||
window('emby_dbScan', clear=True)
|
window('emby_dbScan', clear=True)
|
||||||
# Full library sync finished
|
# Full library sync finished
|
||||||
self.showKodiNote(string(39407), forced=False)
|
self.showKodiNote(string(39407), forced=False)
|
||||||
# Run fast sync otherwise (ever second or so)
|
elif count % 300 == 0:
|
||||||
|
count += 1
|
||||||
|
self.process_newitems()
|
||||||
else:
|
else:
|
||||||
window('emby_dbScan', value="true")
|
count += 1
|
||||||
if not fastSync():
|
# See if there is a PMS message we need to handle
|
||||||
# Fast sync failed or server plugin is not found
|
try:
|
||||||
log("Something went wrong, starting full sync", -1)
|
message = queue.get(block=False)
|
||||||
fullSync(manualrun=True)
|
# Empty queue
|
||||||
window('emby_dbScan', clear=True)
|
except Queue.Empty:
|
||||||
|
xbmc.sleep(100)
|
||||||
|
continue
|
||||||
|
# Got a message from PMS; process it
|
||||||
|
else:
|
||||||
|
window('emby_dbScan', value="true")
|
||||||
|
processMessage(message)
|
||||||
|
window('emby_dbScan', clear=True)
|
||||||
|
# NO sleep!
|
||||||
|
continue
|
||||||
|
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(100)
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
log("###===--- LibrarySync Stopped ---===###", 0)
|
log("###===--- LibrarySync Stopped ---===###", 0)
|
||||||
|
|
|
@ -6,9 +6,11 @@ import utils
|
||||||
|
|
||||||
settings = {}
|
settings = {}
|
||||||
try:
|
try:
|
||||||
guidoc = parse(xbmc.translatePath('special://userdata/guisettings.xml'))
|
path = xbmc.translatePath(
|
||||||
|
'special://userdata/guisettings.xml').decode('utf-8')
|
||||||
|
guidoc = parse(path)
|
||||||
except:
|
except:
|
||||||
print "Unable to read XBMC's guisettings.xml"
|
print "PlexKodiConnect - Unable to read XBMC's guisettings.xml"
|
||||||
|
|
||||||
def getGUI(name):
|
def getGUI(name):
|
||||||
global guidoc
|
global guidoc
|
||||||
|
@ -36,6 +38,7 @@ for entry in kodiSettingsList:
|
||||||
settings['client_name'] = plexbmc.getSetting('deviceName')
|
settings['client_name'] = plexbmc.getSetting('deviceName')
|
||||||
|
|
||||||
# XBMC web server settings
|
# XBMC web server settings
|
||||||
|
xbmc.sleep(5000)
|
||||||
settings['webserver_enabled'] = (getGUI('webserver') == "true")
|
settings['webserver_enabled'] = (getGUI('webserver') == "true")
|
||||||
settings['port'] = int(getGUI('webserverport'))
|
settings['port'] = int(getGUI('webserverport'))
|
||||||
settings['user'] = getGUI('webserverusername')
|
settings['user'] = getGUI('webserverusername')
|
||||||
|
|
|
@ -46,6 +46,8 @@ import logging
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
"""
|
"""
|
||||||
websocket python client.
|
websocket python client.
|
||||||
=========================
|
=========================
|
||||||
|
@ -728,9 +730,12 @@ class WebSocket(object):
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
raise WebSocketTimeoutException(e.args[0])
|
raise WebSocketTimeoutException(e.args[0])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "timed out" in e.args[0]:
|
try:
|
||||||
raise WebSocketTimeoutException(e.args[0])
|
if "timed out" in e.args[0]:
|
||||||
else:
|
raise WebSocketTimeoutException(e.args[0])
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
except:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def _recv(self, bufsize):
|
def _recv(self, bufsize):
|
||||||
|
@ -879,6 +884,7 @@ class WebSocketApp(object):
|
||||||
#print str(e.args[0])
|
#print str(e.args[0])
|
||||||
if "timed out" not in e.args[0]:
|
if "timed out" not in e.args[0]:
|
||||||
raise e
|
raise e
|
||||||
|
xbmc.sleep(100)
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self._callback(self.on_error, e)
|
self._callback(self.on_error, e)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
|
import Queue
|
||||||
import websocket
|
import websocket
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
|
@ -26,20 +27,27 @@ logging.basicConfig()
|
||||||
@utils.logging
|
@utils.logging
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class WebSocket_Client(threading.Thread):
|
class WebSocket_Client(threading.Thread):
|
||||||
|
"""
|
||||||
|
websocket_client.WebSocket_Client(queue)
|
||||||
|
|
||||||
|
where (communication with librarysync)
|
||||||
|
queue: Queue object for background sync
|
||||||
|
"""
|
||||||
_shared_state = {}
|
_shared_state = {}
|
||||||
|
|
||||||
client = None
|
client = None
|
||||||
stopWebsocket = False
|
stopWebsocket = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, queue):
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
self.__dict__ = self._shared_state
|
||||||
|
|
||||||
|
# Communication with librarysync
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
self.doUtils = downloadutils.DownloadUtils()
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.deviceId = self.clientInfo.getDeviceId()
|
self.deviceId = self.clientInfo.getDeviceId()
|
||||||
self.librarySync = librarysync.LibrarySync()
|
|
||||||
|
|
||||||
# 'state' that can be returned by PMS
|
# 'state' that can be returned by PMS
|
||||||
self.timeStates = {
|
self.timeStates = {
|
||||||
|
@ -77,23 +85,18 @@ class WebSocket_Client(threading.Thread):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
message = json.loads(message)
|
message = json.loads(message)
|
||||||
except Exception as ex:
|
except Exception as e:
|
||||||
self.logMsg('Error decoding message from websocket: %s' % ex, -1)
|
self.logMsg('Error decoding message from websocket: %s' % e, -1)
|
||||||
self.logMsg(message, -1)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
typus = message.get('type')
|
# Put PMS message on queue and let libsync take care of it
|
||||||
if not typus:
|
try:
|
||||||
return False
|
self.queue.put(message)
|
||||||
|
|
||||||
process_func = getattr(self, 'processing_%s' % typus, None)
|
|
||||||
if process_func and process_func(message):
|
|
||||||
return True
|
return True
|
||||||
|
except Queue.Full:
|
||||||
# Something went wrong; log
|
# Queue only takes 100 messages. No worries if we miss one or two
|
||||||
self.logMsg("Error processing PMS websocket message.", -1)
|
self.logMsg('Queue is full, dropping PMS message', 0)
|
||||||
self.logMsg("Received websocket message from PMS: %s" % message, -1)
|
return False
|
||||||
return True
|
|
||||||
|
|
||||||
def processing_playing(self, message):
|
def processing_playing(self, message):
|
||||||
"""
|
"""
|
||||||
|
|
17
service.py
17
service.py
|
@ -5,6 +5,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import Queue
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
|
@ -109,10 +110,13 @@ class Service():
|
||||||
# Server auto-detect
|
# Server auto-detect
|
||||||
initialsetup.InitialSetup().setup()
|
initialsetup.InitialSetup().setup()
|
||||||
|
|
||||||
|
# Queue and lock for background sync
|
||||||
|
queue = Queue.LifoQueue(maxsize=100)
|
||||||
|
|
||||||
# Initialize important threads
|
# Initialize important threads
|
||||||
user = userclient.UserClient()
|
user = userclient.UserClient()
|
||||||
ws = wsc.WebSocket_Client()
|
ws = wsc.WebSocket_Client(queue)
|
||||||
library = librarysync.LibrarySync()
|
library = librarysync.LibrarySync(queue)
|
||||||
kplayer = player.Player()
|
kplayer = player.Player()
|
||||||
xplayer = xbmc.Player()
|
xplayer = xbmc.Player()
|
||||||
plx = PlexAPI.PlexAPI()
|
plx = PlexAPI.PlexAPI()
|
||||||
|
@ -307,14 +311,19 @@ class Service():
|
||||||
except:
|
except:
|
||||||
xbmc.log('User client already shut down')
|
xbmc.log('User client already shut down')
|
||||||
|
|
||||||
|
try:
|
||||||
|
downloadutils.DownloadUtils().stopSession()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
log("======== STOP %s ========" % self.addonName, 0)
|
log("======== STOP %s ========" % self.addonName, 0)
|
||||||
|
|
||||||
# Delay option
|
# Delay option
|
||||||
delay = int(utils.settings('startupDelay'))
|
delay = int(utils.settings('startupDelay'))
|
||||||
|
|
||||||
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
xbmc.log("Delaying Plex startup by: %s sec..." % delay)
|
||||||
# Plex: add 5 seconds just for good measure
|
# Plex: add 10 seconds just for good measure
|
||||||
if delay and xbmc.Monitor().waitForAbort(delay+5):
|
if delay and xbmc.Monitor().waitForAbort(delay+10):
|
||||||
# Start the service
|
# Start the service
|
||||||
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
xbmc.log("Abort requested while waiting. Emby for kodi not started.")
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue