Redesign fast sync
This commit is contained in:
parent
7a86909357
commit
ae9d4924c2
7 changed files with 365 additions and 125 deletions
|
@ -1476,36 +1476,6 @@ class API():
|
||||||
"""
|
"""
|
||||||
return self.part
|
return self.part
|
||||||
|
|
||||||
def DateToKodi(self, stamp):
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
# DATEFORMAT = xbmc.getRegion('dateshort')
|
|
||||||
# TIMEFORMAT = xbmc.getRegion('meridiem')
|
|
||||||
# date_time = time.localtime(stamp)
|
|
||||||
# if DATEFORMAT[1] == 'd':
|
|
||||||
# localdate = time.strftime('%d-%m-%Y', date_time)
|
|
||||||
# elif DATEFORMAT[1] == 'm':
|
|
||||||
# localdate = time.strftime('%m-%d-%Y', date_time)
|
|
||||||
# else:
|
|
||||||
# localdate = time.strftime('%Y-%m-%d', date_time)
|
|
||||||
# if TIMEFORMAT != '/':
|
|
||||||
# localtime = time.strftime('%I:%M%p', date_time)
|
|
||||||
# else:
|
|
||||||
# localtime = time.strftime('%H:%M', date_time)
|
|
||||||
# return localtime + ' ' + localdate
|
|
||||||
try:
|
|
||||||
# DATEFORMAT = xbmc.getRegion('dateshort')
|
|
||||||
# TIMEFORMAT = xbmc.getRegion('meridiem')
|
|
||||||
date_time = time.localtime(float(stamp))
|
|
||||||
localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time)
|
|
||||||
except:
|
|
||||||
localdate = None
|
|
||||||
return localdate
|
|
||||||
|
|
||||||
def getType(self):
|
def getType(self):
|
||||||
"""
|
"""
|
||||||
Returns the type of media, e.g. 'movie' or 'clip' for trailers
|
Returns the type of media, e.g. 'movie' or 'clip' for trailers
|
||||||
|
@ -1543,7 +1513,7 @@ class API():
|
||||||
"""
|
"""
|
||||||
Returns the date when this library item was created
|
Returns the date when this library item was created
|
||||||
"""
|
"""
|
||||||
return self.DateToKodi(self.item.attrib.get('addedAt', None))
|
return utils.DateToKodi(self.item.attrib.get('addedAt', None))
|
||||||
|
|
||||||
def getUserData(self):
|
def getUserData(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1576,7 +1546,7 @@ class API():
|
||||||
played = True
|
played = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lastPlayedDate = self.DateToKodi(int(item['lastViewedAt']))
|
lastPlayedDate = utils.DateToKodi(int(item['lastViewedAt']))
|
||||||
except:
|
except:
|
||||||
lastPlayedDate = None
|
lastPlayedDate = None
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from urllib import urlencode
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
from urlparse import urlparse, parse_qs
|
from urlparse import urlparse, parse_qs
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
from xbmcaddon import Addon
|
from xbmcaddon import Addon
|
||||||
|
|
||||||
|
@ -371,3 +372,22 @@ def PMSHttpsEnabled(url):
|
||||||
# couldn't get an xml - switch to http traffic
|
# couldn't get an xml - switch to http traffic
|
||||||
logMsg('PMSHttpsEnabled', 'PMS on %s talks HTTPS' % url, 1)
|
logMsg('PMSHttpsEnabled', 'PMS on %s talks HTTPS' % url, 1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def scrobble(ratingKey, state):
|
||||||
|
"""
|
||||||
|
Tells the PMS to set an item's watched state to state="watched" or
|
||||||
|
state="unwatched"
|
||||||
|
"""
|
||||||
|
args = {
|
||||||
|
'key': ratingKey,
|
||||||
|
'identifier': 'com.plexapp.plugins.library'
|
||||||
|
}
|
||||||
|
if state == "watched":
|
||||||
|
url = "{server}/:/scrobble?" + urlencode(args)
|
||||||
|
elif state == "unwatched":
|
||||||
|
url = "{server}/:/unscrobble?" + urlencode(args)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
downloadutils.DownloadUtils().downloadUrl(url, type="GET")
|
||||||
|
logMsg("Toggled watched state for Plex item %s" % ratingKey, 1)
|
||||||
|
|
|
@ -152,6 +152,22 @@ class Embydb_Functions():
|
||||||
))
|
))
|
||||||
self.embycursor.execute(query, (viewid,))
|
self.embycursor.execute(query, (viewid,))
|
||||||
|
|
||||||
|
def getItem_byFileId(self, fileId):
|
||||||
|
"""
|
||||||
|
Returns the Plex itemId by using the Kodi fileId
|
||||||
|
"""
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT emby_id",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE kodi_fileid = ?"
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
self.embycursor.execute(query, (fileId,))
|
||||||
|
item = self.embycursor.fetchone()[0]
|
||||||
|
return item
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
def getItem_byId(self, embyid):
|
def getItem_byId(self, embyid):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
embycursor = self.embycursor
|
||||||
|
@ -183,6 +199,23 @@ class Embydb_Functions():
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def getPlexId(self, kodiid):
|
||||||
|
"""
|
||||||
|
Returns the Plex ID usind the Kodiid. Result:
|
||||||
|
(Plex Id, Parent's Plex Id)
|
||||||
|
"""
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT emby_id, parent_id",
|
||||||
|
"FROM emby",
|
||||||
|
"WHERE kodi_id = ?"
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
self.embycursor.execute(query, (kodiid))
|
||||||
|
item = self.embycursor.fetchone()
|
||||||
|
return item
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
def getItem_byKodiId(self, kodiid, mediatype):
|
def getItem_byKodiId(self, kodiid, mediatype):
|
||||||
|
|
||||||
embycursor = self.embycursor
|
embycursor = self.embycursor
|
||||||
|
|
|
@ -17,7 +17,7 @@ class GetKodiDB():
|
||||||
do stuff with kodi_db
|
do stuff with kodi_db
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
itemType: itemtype for Kodi DB, e.g. 'video'
|
itemType: itemtype for Kodi DB, e.g. 'video', 'music'
|
||||||
|
|
||||||
On exiting "with" (no matter what), commits get automatically committed
|
On exiting "with" (no matter what), commits get automatically committed
|
||||||
and the db gets closed
|
and the db gets closed
|
||||||
|
@ -701,6 +701,68 @@ class Kodidb_Functions():
|
||||||
)
|
)
|
||||||
cursor.execute(query, (fileid, 2, subtitletrack))
|
cursor.execute(query, (fileid, 2, subtitletrack))
|
||||||
|
|
||||||
|
def getResumes(self):
|
||||||
|
"""
|
||||||
|
VIDEOS
|
||||||
|
|
||||||
|
Returns all Kodi idFile that have a resume point set (not unwatched
|
||||||
|
ones or items that have already been completely watched)
|
||||||
|
"""
|
||||||
|
cursor = self.cursor
|
||||||
|
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT idFile",
|
||||||
|
"FROM bookmark"
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
rows = cursor.execute(query)
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
ids = []
|
||||||
|
for row in rows:
|
||||||
|
ids.append(row[0])
|
||||||
|
return ids
|
||||||
|
|
||||||
|
def getUnplayedMusicItems(self):
|
||||||
|
"""
|
||||||
|
MUSIC
|
||||||
|
|
||||||
|
Returns all Kodi Item idFile that have not yet been completely played
|
||||||
|
"""
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT idPath",
|
||||||
|
"FROM song",
|
||||||
|
"WHERE iTimesPlayed IS NULL OR iTimesPlayed = ''"
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
rows = self.cursor.execute(query)
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
ids = []
|
||||||
|
for row in rows:
|
||||||
|
ids.append(row[0])
|
||||||
|
return ids
|
||||||
|
|
||||||
|
def getUnplayedItems(self):
|
||||||
|
"""
|
||||||
|
VIDEOS
|
||||||
|
|
||||||
|
Returns all Kodi Item idFile that have not yet been completely played
|
||||||
|
"""
|
||||||
|
query = ' '.join((
|
||||||
|
"SELECT idFile",
|
||||||
|
"FROM files",
|
||||||
|
"WHERE playCount IS NULL OR playCount = ''"
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
rows = self.cursor.execute(query)
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
ids = []
|
||||||
|
for row in rows:
|
||||||
|
ids.append(row[0])
|
||||||
|
return ids
|
||||||
|
|
||||||
def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
|
def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
|
||||||
|
|
||||||
cursor = self.cursor
|
cursor = self.cursor
|
||||||
|
|
|
@ -11,8 +11,7 @@ import downloadutils
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
import playbackutils as pbutils
|
import playbackutils as pbutils
|
||||||
import utils
|
import utils
|
||||||
|
from PlexFunctions import scrobble
|
||||||
from urllib import urlencode
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -144,16 +143,11 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
utils.window('emby_skipWatched%s' % itemid, clear=True)
|
utils.window('emby_skipWatched%s' % itemid, clear=True)
|
||||||
else:
|
else:
|
||||||
# notify the server
|
# notify the server
|
||||||
args = {'key': itemid,
|
|
||||||
'identifier': 'com.plexapp.plugins.library'}
|
|
||||||
if playcount != 0:
|
if playcount != 0:
|
||||||
url = "{server}/:/scrobble?" + urlencode(args)
|
scrobble(itemid, 'watched')
|
||||||
doUtils.downloadUrl(url, type="GET")
|
|
||||||
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
|
|
||||||
else:
|
else:
|
||||||
url = "{server}/:/unscrobble?" + urlencode(args)
|
scrobble(itemid, 'unwatched')
|
||||||
doUtils.downloadUrl(url, type="GET")
|
|
||||||
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
|
|
||||||
finally:
|
finally:
|
||||||
embycursor.close()
|
embycursor.close()
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,12 @@ class LibrarySync(Thread):
|
||||||
self.enableBackgroundSync = True if utils.settings(
|
self.enableBackgroundSync = True if utils.settings(
|
||||||
'enableBackgroundSync') == "true" else False
|
'enableBackgroundSync') == "true" else False
|
||||||
|
|
||||||
|
# Time offset between Kodi and PMS in seconds (=Koditime - PMStime)
|
||||||
|
self.timeoffset = 0
|
||||||
|
# Time in seconds to look into the past when looking for PMS changes
|
||||||
|
# (safety margin - the larger, the more items we need to process)
|
||||||
|
self.syncPast = 30
|
||||||
|
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
||||||
def showKodiNote(self, message, forced=False, icon="plex"):
|
def showKodiNote(self, message, forced=False, icon="plex"):
|
||||||
|
@ -265,6 +271,102 @@ class LibrarySync(Thread):
|
||||||
time=7000,
|
time=7000,
|
||||||
sound=True)
|
sound=True)
|
||||||
|
|
||||||
|
def syncPMStime(self):
|
||||||
|
"""
|
||||||
|
PMS does not provide a means to get a server timestamp. This is a work-
|
||||||
|
around.
|
||||||
|
"""
|
||||||
|
self.logMsg('Synching time with PMS server', 0)
|
||||||
|
# Find a PMS item where we can toggle the view state to enforce a
|
||||||
|
# change in lastViewedAt
|
||||||
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
|
unplayedIds = kodi_db.getUnplayedItems()
|
||||||
|
resumeIds = kodi_db.getResumes()
|
||||||
|
self.logMsg('resumeIds: %s' % resumeIds, 1)
|
||||||
|
|
||||||
|
plexId = False
|
||||||
|
for unplayedId in unplayedIds:
|
||||||
|
if unplayedId not in resumeIds:
|
||||||
|
# Found an item we can work with!
|
||||||
|
kodiId = unplayedId
|
||||||
|
self.logMsg('Found kodiId: %s' % kodiId, 1)
|
||||||
|
# Get Plex ID using the Kodi ID
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
plexId = emby_db.getItem_byFileId(kodiId)
|
||||||
|
if plexId:
|
||||||
|
self.logMsg('Found plexId: %s' % plexId, 1)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Try getting a music item if we did not find a video item
|
||||||
|
if not plexId:
|
||||||
|
self.logMsg("Could not find a video item to sync time with", 0)
|
||||||
|
with kodidb.GetKodiDB('music') as kodi_db:
|
||||||
|
unplayedIds = kodi_db.getUnplayedMusicItems()
|
||||||
|
# We don't care about resuming songs in the middle
|
||||||
|
for unplayedId in unplayedIds:
|
||||||
|
# Found an item we can work with!
|
||||||
|
kodiId = unplayedId
|
||||||
|
self.logMsg('Found kodiId: %s' % kodiId, 1)
|
||||||
|
# Get Plex ID using the Kodi ID
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
plexId = emby_db.getItem_byFileId(kodiId)
|
||||||
|
if plexId:
|
||||||
|
self.logMsg('Found plexId: %s' % plexId, 1)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.logMsg("Could not find an item to sync time with", -1)
|
||||||
|
self.logMsg("Aborting PMS-Kodi time sync", -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the Plex item's metadata
|
||||||
|
xml = PlexFunctions.GetPlexMetadata(plexId)
|
||||||
|
if not xml:
|
||||||
|
self.logMsg("Could not download metadata, aborting time sync", -1)
|
||||||
|
return
|
||||||
|
libraryId = xml[0].attrib['librarySectionID']
|
||||||
|
# Get a PMS timestamp to start our question with
|
||||||
|
timestamp = xml[0].attrib.get('lastViewedAt')
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = xml[0].attrib.get('updatedAt')
|
||||||
|
self.logMsg('Using items updatedAt=%s' % timestamp, 1)
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = xml[0].attrib.get('addedAt')
|
||||||
|
self.logMsg('Using items addedAt=%s' % timestamp, 1)
|
||||||
|
# Set the timer
|
||||||
|
koditime = utils.getUnixTimestamp()
|
||||||
|
# Toggle watched state
|
||||||
|
PlexFunctions.scrobble(plexId, 'watched')
|
||||||
|
# Let the PMS process this first!
|
||||||
|
xbmc.sleep(2000)
|
||||||
|
# Get all PMS items to find the item we changed
|
||||||
|
items = PlexFunctions.GetAllPlexLeaves(libraryId,
|
||||||
|
lastViewedAt=timestamp)
|
||||||
|
# Toggle watched state back
|
||||||
|
PlexFunctions.scrobble(plexId, 'unwatched')
|
||||||
|
# Get server timestamp for this change
|
||||||
|
plextime = None
|
||||||
|
for item in items:
|
||||||
|
if item.attrib['ratingKey'] == plexId:
|
||||||
|
plextime = item.attrib.get('lastViewedAt')
|
||||||
|
break
|
||||||
|
if not plextime:
|
||||||
|
self.logMsg("Could not set the items watched state, abort", -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate time offset Kodi-PMS
|
||||||
|
self.timeoffset = int(koditime) - int(plextime)
|
||||||
|
self.logMsg("Time offset Koditime - PMStime in seconds: %s"
|
||||||
|
% str(self.timeoffset), 0)
|
||||||
|
|
||||||
|
def getPMSfromKodiTime(self, koditime):
|
||||||
|
"""
|
||||||
|
Uses self.timeoffset to return the PMS time for a given Kodi timestamp
|
||||||
|
(in unix time)
|
||||||
|
|
||||||
|
Feed with integers
|
||||||
|
"""
|
||||||
|
return koditime - self.timeoffset
|
||||||
|
|
||||||
def resetProcessedItems(self):
|
def resetProcessedItems(self):
|
||||||
"""
|
"""
|
||||||
Resets the list of PMS items that we have already processed
|
Resets the list of PMS items that we have already processed
|
||||||
|
@ -279,7 +381,7 @@ class LibrarySync(Thread):
|
||||||
'track': {}
|
'track': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getFastUpdateList(self, xml, plexType, viewName, viewId, update=True):
|
def getFastUpdateList(self, xml, plexType, viewName, viewId):
|
||||||
"""
|
"""
|
||||||
THIS METHOD NEEDS TO BE FAST! => e.g. no API calls
|
THIS METHOD NEEDS TO BE FAST! => e.g. no API calls
|
||||||
|
|
||||||
|
@ -305,8 +407,6 @@ class LibrarySync(Thread):
|
||||||
self.allPlexElementsId APPENDED(!!) dict
|
self.allPlexElementsId APPENDED(!!) dict
|
||||||
= {itemid: checksum}
|
= {itemid: checksum}
|
||||||
"""
|
"""
|
||||||
# Updated items are prefered over userdata updates!
|
|
||||||
if update:
|
|
||||||
# Needs to call other methods than if we're only updating userdata
|
# Needs to call other methods than if we're only updating userdata
|
||||||
for item in xml:
|
for item in xml:
|
||||||
itemId = item.attrib.get('ratingKey')
|
itemId = item.attrib.get('ratingKey')
|
||||||
|
@ -321,7 +421,9 @@ class LibrarySync(Thread):
|
||||||
# specific item
|
# specific item
|
||||||
res = self.processed[plexType].get(itemId)
|
res = self.processed[plexType].get(itemId)
|
||||||
if res:
|
if res:
|
||||||
if res == (lastViewedAt, updatedAt):
|
# Only look at the updatedAt flag!
|
||||||
|
# tuple: (lastViewedAt, updatedAt)
|
||||||
|
if res[1] == updatedAt:
|
||||||
# Nothing to update, we have already processed this
|
# Nothing to update, we have already processed this
|
||||||
# item
|
# item
|
||||||
continue
|
continue
|
||||||
|
@ -338,38 +440,10 @@ class LibrarySync(Thread):
|
||||||
})
|
})
|
||||||
# And safe to self.processed:
|
# And safe to self.processed:
|
||||||
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
||||||
else:
|
# Quickly log
|
||||||
# Needs to call other methods than if we're only updating userdata
|
if self.updatelist:
|
||||||
for item in xml:
|
self.logMsg('fastSync updatelist: %s' % self.updatelist, 1)
|
||||||
itemId = item.attrib.get('ratingKey')
|
self.logMsg('fastSync processed list: %s' % self.processed, 1)
|
||||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
|
||||||
if not itemId:
|
|
||||||
continue
|
|
||||||
|
|
||||||
lastViewedAt = item.attrib.get('lastViewedAt')
|
|
||||||
updatedAt = item.attrib.get('updatedAt')
|
|
||||||
|
|
||||||
# returns the tuple (lastViewedAt, updatedAt) for the
|
|
||||||
# specific item
|
|
||||||
res = self.processed[plexType].get(itemId)
|
|
||||||
if res:
|
|
||||||
if res == (lastViewedAt, updatedAt):
|
|
||||||
# Nothing to update, we have already processed this
|
|
||||||
# item
|
|
||||||
continue
|
|
||||||
title = item.attrib.get('title', 'Missing Title Name')
|
|
||||||
# We need to process this:
|
|
||||||
self.updatelist.append({
|
|
||||||
'itemId': itemId,
|
|
||||||
'itemType': PlexFunctions.GetItemClassFromType(
|
|
||||||
plexType),
|
|
||||||
'method': 'updateUserdata',
|
|
||||||
'viewName': viewName,
|
|
||||||
'viewId': viewId,
|
|
||||||
'title': title
|
|
||||||
})
|
|
||||||
# And safe to self.processed:
|
|
||||||
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
|
||||||
|
|
||||||
def fastSync(self):
|
def fastSync(self):
|
||||||
"""
|
"""
|
||||||
|
@ -392,8 +466,7 @@ class LibrarySync(Thread):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
self.compare = True
|
# Get last sync time and look a bit in the past (safety margin)
|
||||||
# Get last sync time
|
|
||||||
lastSync = self.lastSync - self.syncPast
|
lastSync = self.lastSync - self.syncPast
|
||||||
# Set new timestamp NOW because sync might take a while
|
# Set new timestamp NOW because sync might take a while
|
||||||
self.saveLastSync()
|
self.saveLastSync()
|
||||||
|
@ -408,8 +481,8 @@ 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(view['id'],
|
items = PlexFunctions.GetAllPlexLeaves(
|
||||||
updatedAt=lastSync)
|
view['id'], updatedAt=self.getPMSfromKodiTime(lastSync))
|
||||||
# Just skip if something went wrong
|
# Just skip if something went wrong
|
||||||
if not items:
|
if not items:
|
||||||
continue
|
continue
|
||||||
|
@ -439,14 +512,11 @@ class LibrarySync(Thread):
|
||||||
# We don't need to refresh the Kodi library for deltas!!
|
# We don't need to refresh the Kodi library for deltas!!
|
||||||
# Start with an empty ElementTree and attach items to update
|
# Start with an empty ElementTree and attach items to update
|
||||||
movieupdate = False
|
movieupdate = False
|
||||||
movieXML = etree.Element('root')
|
|
||||||
episodeupdate = False
|
episodeupdate = False
|
||||||
episodeXML = etree.Element('root')
|
|
||||||
songupdate = False
|
songupdate = False
|
||||||
musicXML = etree.Element('root')
|
|
||||||
for view in self.views:
|
for view in self.views:
|
||||||
items = PlexFunctions.GetAllPlexLeaves(view['id'],
|
items = PlexFunctions.GetAllPlexLeaves(
|
||||||
lastViewedAt=lastSync)
|
view['id'], lastViewedAt=self.getPMSfromKodiTime(lastSync))
|
||||||
for item in items:
|
for item in items:
|
||||||
itemId = item.attrib.get('ratingKey')
|
itemId = item.attrib.get('ratingKey')
|
||||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||||
|
@ -460,21 +530,32 @@ class LibrarySync(Thread):
|
||||||
# specific item
|
# specific item
|
||||||
res = self.processed[plexType].get(itemId)
|
res = self.processed[plexType].get(itemId)
|
||||||
if res:
|
if res:
|
||||||
if res == (lastViewedAt, updatedAt):
|
# Only look at lastViewedAt
|
||||||
|
if res[0] == lastViewedAt:
|
||||||
# Nothing to update, we have already processed this
|
# Nothing to update, we have already processed this
|
||||||
# item
|
# item
|
||||||
continue
|
continue
|
||||||
if plexType == 'movie':
|
if plexType == 'movie':
|
||||||
movieXML.append(item)
|
|
||||||
movieupdate = True
|
movieupdate = True
|
||||||
|
try:
|
||||||
|
movieXML.append(item)
|
||||||
|
except:
|
||||||
|
movieXML = etree.Element('root')
|
||||||
|
movieXML.append(item)
|
||||||
elif plexType == 'episode':
|
elif plexType == 'episode':
|
||||||
episodeXML.append(item)
|
|
||||||
episodeupdate = True
|
episodeupdate = True
|
||||||
|
try:
|
||||||
|
episodeXML.append(item)
|
||||||
|
except:
|
||||||
|
episodeXML = etree.Element('root')
|
||||||
|
episodeXML.append(item)
|
||||||
elif plexType == 'track':
|
elif plexType == 'track':
|
||||||
musicXML.append(item)
|
|
||||||
songupdate = True
|
songupdate = True
|
||||||
else:
|
try:
|
||||||
self.logMsg('Unknown plex type %s' % plexType, -1)
|
musicXML.append(item)
|
||||||
|
except:
|
||||||
|
musicXML = etree.Element('root')
|
||||||
|
musicXML.append(item)
|
||||||
# And safe to self.processed:
|
# And safe to self.processed:
|
||||||
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
||||||
|
|
||||||
|
@ -1382,6 +1463,8 @@ class LibrarySync(Thread):
|
||||||
log("Db version: %s" % settings('dbCreatedWithVersion'), 0)
|
log("Db version: %s" % settings('dbCreatedWithVersion'), 0)
|
||||||
log("Initial start-up full sync starting", 0)
|
log("Initial start-up full sync starting", 0)
|
||||||
librarySync = fullSync(manualrun=True)
|
librarySync = fullSync(manualrun=True)
|
||||||
|
# Initialize time offset Kodi - PMS
|
||||||
|
self.syncPMStime()
|
||||||
window('emby_dbScan', clear=True)
|
window('emby_dbScan', clear=True)
|
||||||
if librarySync:
|
if librarySync:
|
||||||
log("Initial start-up full sync successful", 0)
|
log("Initial start-up full sync successful", 0)
|
||||||
|
@ -1440,9 +1523,11 @@ class LibrarySync(Thread):
|
||||||
elif enableBackgroundSync:
|
elif enableBackgroundSync:
|
||||||
# Run full lib scan approx every 30min
|
# Run full lib scan approx every 30min
|
||||||
if count >= 1800:
|
if count >= 1800:
|
||||||
|
count = 0
|
||||||
# Also reset self.processed, just in case
|
# Also reset self.processed, just in case
|
||||||
self.resetProcessedItems()
|
self.resetProcessedItems()
|
||||||
count = 0
|
# Recalculate time offset Kodi - PMS
|
||||||
|
self.syncPMStime()
|
||||||
window('emby_dbScan', value="true")
|
window('emby_dbScan', value="true")
|
||||||
log('Running background full lib scan', 0)
|
log('Running background full lib scan', 0)
|
||||||
fullSync(manualrun=True)
|
fullSync(manualrun=True)
|
||||||
|
|
|
@ -19,12 +19,88 @@ import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
addonName = xbmcaddon.Addon().getAddonInfo('name')
|
addonName = xbmcaddon.Addon().getAddonInfo('name')
|
||||||
|
|
||||||
|
|
||||||
|
def DateToKodi(stamp):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
# DATEFORMAT = xbmc.getRegion('dateshort')
|
||||||
|
# TIMEFORMAT = xbmc.getRegion('meridiem')
|
||||||
|
# date_time = time.localtime(stamp)
|
||||||
|
# if DATEFORMAT[1] == 'd':
|
||||||
|
# localdate = time.strftime('%d-%m-%Y', date_time)
|
||||||
|
# elif DATEFORMAT[1] == 'm':
|
||||||
|
# localdate = time.strftime('%m-%d-%Y', date_time)
|
||||||
|
# else:
|
||||||
|
# localdate = time.strftime('%Y-%m-%d', date_time)
|
||||||
|
# if TIMEFORMAT != '/':
|
||||||
|
# localtime = time.strftime('%I:%M%p', date_time)
|
||||||
|
# else:
|
||||||
|
# localtime = time.strftime('%H:%M', date_time)
|
||||||
|
# return localtime + ' ' + localdate
|
||||||
|
try:
|
||||||
|
# DATEFORMAT = xbmc.getRegion('dateshort')
|
||||||
|
# TIMEFORMAT = xbmc.getRegion('meridiem')
|
||||||
|
date_time = time.localtime(float(stamp))
|
||||||
|
localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time)
|
||||||
|
except:
|
||||||
|
localdate = None
|
||||||
|
return localdate
|
||||||
|
|
||||||
|
|
||||||
|
def changePlayState(itemType, kodiId, playCount, lastplayed):
|
||||||
|
"""
|
||||||
|
YET UNUSED
|
||||||
|
|
||||||
|
kodiId: int or str
|
||||||
|
playCount: int or str
|
||||||
|
lastplayed: str or int unix timestamp
|
||||||
|
"""
|
||||||
|
logMsg("changePlayState", "start", 1)
|
||||||
|
lastplayed = DateToKodi(lastplayed)
|
||||||
|
|
||||||
|
kodiId = int(kodiId)
|
||||||
|
playCount = int(playCount)
|
||||||
|
method = {
|
||||||
|
'movie': ' VideoLibrary.SetMovieDetails',
|
||||||
|
'episode': 'VideoLibrary.SetEpisodeDetails',
|
||||||
|
'musicvideo': ' VideoLibrary.SetMusicVideoDetails', # TODO
|
||||||
|
'show': 'VideoLibrary.SetTVShowDetails', # TODO
|
||||||
|
'': 'AudioLibrary.SetAlbumDetails', # TODO
|
||||||
|
'': 'AudioLibrary.SetArtistDetails', # TODO
|
||||||
|
'track': 'AudioLibrary.SetSongDetails'
|
||||||
|
}
|
||||||
|
params = {
|
||||||
|
'movie': {
|
||||||
|
'movieid': kodiId,
|
||||||
|
'playcount': playCount,
|
||||||
|
'lastplayed': lastplayed
|
||||||
|
},
|
||||||
|
'episode': {
|
||||||
|
'episodeid': kodiId,
|
||||||
|
'playcount': playCount,
|
||||||
|
'lastplayed': lastplayed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
}
|
||||||
|
query['method'] = method[itemType]
|
||||||
|
query['params'] = params[itemType]
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(query))
|
||||||
|
result = json.loads(result)
|
||||||
|
result = result.get('result')
|
||||||
|
logMsg("changePlayState", "JSON result was: %s" % result, 1)
|
||||||
|
|
||||||
|
|
||||||
def IfExists(path):
|
def IfExists(path):
|
||||||
"""
|
"""
|
||||||
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
|
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
|
||||||
|
|
Loading…
Reference in a new issue