Merge branch 'develop'
This commit is contained in:
commit
a360663134
10 changed files with 495 additions and 86 deletions
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect"
|
||||
name="PlexKodiConnect"
|
||||
version="1.0.7"
|
||||
version="1.0.8"
|
||||
provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
version 1.0.8
|
||||
- Redesigned fast incremential sync
|
||||
- Workaround to sync Kodi time with PMS time
|
||||
- Fix library sync key error
|
||||
|
||||
version 1.0.7
|
||||
- Fix userclient
|
||||
- Fix KeyError in library sync
|
||||
|
|
|
@ -1476,36 +1476,6 @@ class API():
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
return self.DateToKodi(self.item.attrib.get('addedAt', None))
|
||||
return utils.DateToKodi(self.item.attrib.get('addedAt', None))
|
||||
|
||||
def getUserData(self):
|
||||
"""
|
||||
|
@ -1576,7 +1546,7 @@ class API():
|
|||
played = True
|
||||
|
||||
try:
|
||||
lastPlayedDate = self.DateToKodi(int(item['lastViewedAt']))
|
||||
lastPlayedDate = utils.DateToKodi(int(item['lastViewedAt']))
|
||||
except:
|
||||
lastPlayedDate = None
|
||||
|
||||
|
@ -1851,10 +1821,10 @@ class API():
|
|||
]
|
||||
"""
|
||||
item = self.item.attrib
|
||||
key = item['grandparentRatingKey']
|
||||
title = item['grandparentTitle']
|
||||
season = item['parentIndex']
|
||||
episode = item['index']
|
||||
key = item.get('grandparentRatingKey')
|
||||
title = item.get('grandparentTitle')
|
||||
season = item.get('parentIndex')
|
||||
episode = item.get('index')
|
||||
return key, title, season, episode
|
||||
|
||||
def addPlexHeadersToUrl(self, url, arguments={}):
|
||||
|
|
|
@ -3,6 +3,7 @@ from urllib import urlencode
|
|||
from ast import literal_eval
|
||||
from urlparse import urlparse, parse_qs
|
||||
import re
|
||||
import time
|
||||
|
||||
from xbmcaddon import Addon
|
||||
|
||||
|
@ -371,3 +372,22 @@ def PMSHttpsEnabled(url):
|
|||
# couldn't get an xml - switch to http traffic
|
||||
logMsg('PMSHttpsEnabled', 'PMS on %s talks HTTPS' % url, 1)
|
||||
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,))
|
||||
|
||||
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):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
@ -183,6 +199,23 @@ class Embydb_Functions():
|
|||
|
||||
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):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
|
|
@ -231,10 +231,12 @@ class Items(object):
|
|||
time=time,
|
||||
sound=False)
|
||||
|
||||
def updateUserdata(self, xml):
|
||||
def updateUserdata(self, xml, viewtag=None, viewid=None):
|
||||
"""
|
||||
Updates the Kodi watched state of the item from PMS. Also retrieves
|
||||
Plex resume points for movies in progress.
|
||||
|
||||
viewtag and viewid only serve as dummies
|
||||
"""
|
||||
for mediaitem in xml:
|
||||
API = PlexAPI.API(mediaitem)
|
||||
|
@ -1256,12 +1258,13 @@ class TVShows(Items):
|
|||
seriesId, seriesName, season, episode = API.getEpisodeDetails()
|
||||
|
||||
if season is None:
|
||||
if item.get('AbsoluteEpisodeNumber'):
|
||||
# Anime scenario
|
||||
season = 1
|
||||
episode = item['AbsoluteEpisodeNumber']
|
||||
else:
|
||||
season = -1
|
||||
season = -1
|
||||
# if item.get('AbsoluteEpisodeNumber'):
|
||||
# # Anime scenario
|
||||
# season = 1
|
||||
# episode = item['AbsoluteEpisodeNumber']
|
||||
# else:
|
||||
# season = -1
|
||||
|
||||
# Specials ordering within season
|
||||
if item.get('AirsAfterSeasonNumber'):
|
||||
|
|
|
@ -17,7 +17,7 @@ class GetKodiDB():
|
|||
do stuff with kodi_db
|
||||
|
||||
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
|
||||
and the db gets closed
|
||||
|
@ -701,6 +701,68 @@ class Kodidb_Functions():
|
|||
)
|
||||
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):
|
||||
|
||||
cursor = self.cursor
|
||||
|
|
|
@ -11,8 +11,7 @@ import downloadutils
|
|||
import embydb_functions as embydb
|
||||
import playbackutils as pbutils
|
||||
import utils
|
||||
|
||||
from urllib import urlencode
|
||||
from PlexFunctions import scrobble
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -144,16 +143,11 @@ class KodiMonitor(xbmc.Monitor):
|
|||
utils.window('emby_skipWatched%s' % itemid, clear=True)
|
||||
else:
|
||||
# notify the server
|
||||
args = {'key': itemid,
|
||||
'identifier': 'com.plexapp.plugins.library'}
|
||||
if playcount != 0:
|
||||
url = "{server}/:/scrobble?" + urlencode(args)
|
||||
doUtils.downloadUrl(url, type="GET")
|
||||
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
|
||||
scrobble(itemid, 'watched')
|
||||
else:
|
||||
url = "{server}/:/unscrobble?" + urlencode(args)
|
||||
doUtils.downloadUrl(url, type="GET")
|
||||
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
|
||||
scrobble(itemid, 'unwatched')
|
||||
|
||||
finally:
|
||||
embycursor.close()
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
from threading import Thread, Lock
|
||||
import Queue
|
||||
try:
|
||||
import xml.etree.cElementTree as etree
|
||||
except ImportError:
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
@ -234,6 +238,12 @@ class LibrarySync(Thread):
|
|||
self.enableBackgroundSync = True if utils.settings(
|
||||
'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)
|
||||
|
||||
def showKodiNote(self, message, forced=False, icon="plex"):
|
||||
|
@ -261,6 +271,180 @@ class LibrarySync(Thread):
|
|||
time=7000,
|
||||
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):
|
||||
"""
|
||||
Resets the list of PMS items that we have already processed
|
||||
"""
|
||||
self.processed = {
|
||||
'movie': {},
|
||||
'show': {},
|
||||
'season': {},
|
||||
'episode': {},
|
||||
'artist': {},
|
||||
'album': {},
|
||||
'track': {}
|
||||
}
|
||||
|
||||
def getFastUpdateList(self, xml, plexType, viewName, viewId):
|
||||
"""
|
||||
THIS METHOD NEEDS TO BE FAST! => e.g. no API calls
|
||||
|
||||
Adds items to self.updatelist as well as self.allPlexElementsId dict
|
||||
|
||||
Input:
|
||||
xml: PMS answer for section items
|
||||
plexType: 'movie', 'show', 'episode', ...
|
||||
viewName: Name of the Plex view (e.g. 'My TV shows')
|
||||
viewId: Id/Key of Plex library (e.g. '1')
|
||||
|
||||
Output: self.updatelist, self.allPlexElementsId
|
||||
self.updatelist APPENDED(!!) list itemids (Plex Keys as
|
||||
as received from API.getRatingKey())
|
||||
One item in this list is of the form:
|
||||
'itemId': xxx,
|
||||
'itemType': 'Movies','TVShows', ...
|
||||
'method': 'add_update', 'add_updateSeason', ...
|
||||
'viewName': xxx,
|
||||
'viewId': xxx,
|
||||
'title': xxx
|
||||
|
||||
self.allPlexElementsId APPENDED(!!) dict
|
||||
= {itemid: checksum}
|
||||
"""
|
||||
# Needs to call other methods than if we're only updating userdata
|
||||
for item in xml:
|
||||
itemId = item.attrib.get('ratingKey')
|
||||
# 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:
|
||||
# Only look at the updatedAt flag!
|
||||
# tuple: (lastViewedAt, updatedAt)
|
||||
if res[1] == 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': PlexFunctions.GetMethodFromPlexType(plexType),
|
||||
'viewName': viewName,
|
||||
'viewId': viewId,
|
||||
'title': title
|
||||
})
|
||||
# And safe to self.processed:
|
||||
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
||||
# Quickly log
|
||||
if self.updatelist:
|
||||
self.logMsg('fastSync updatelist: %s' % self.updatelist, 1)
|
||||
self.logMsg('fastSync processed list: %s' % self.processed, 1)
|
||||
|
||||
def fastSync(self):
|
||||
"""
|
||||
Fast incremential lib sync
|
||||
|
@ -271,24 +455,24 @@ class LibrarySync(Thread):
|
|||
This will NOT remove items from Kodi db that were removed from the PMS
|
||||
(happens only during fullsync)
|
||||
|
||||
Currently, ALL items returned by the PMS (because they've just been
|
||||
edited by the PMS or have been watched) will be processed. This will
|
||||
probably happen several times.
|
||||
Items that are processed are appended to the dict self.processed:
|
||||
{
|
||||
'<Plex itemtype>': e.g. 'movie'
|
||||
{
|
||||
'<ratingKey>': ( unique plex id 'ratingKey' as str
|
||||
lastViewedAt,
|
||||
updatedAt
|
||||
)
|
||||
}
|
||||
}
|
||||
"""
|
||||
self.compare = True
|
||||
# Get last sync time
|
||||
# Get last sync time and look a bit in the past (safety margin)
|
||||
lastSync = self.lastSync - self.syncPast
|
||||
if not lastSync:
|
||||
# Original Emby format:
|
||||
# lastSync = "2016-01-01T00:00:00Z"
|
||||
# January 1, 2015 at midnight:
|
||||
lastSync = 1420070400
|
||||
# Set new timestamp NOW because sync might take a while
|
||||
self.saveLastSync()
|
||||
|
||||
# Original idea: Get all PMS items already saved in Kodi
|
||||
# Also get checksums of every Plex items already saved in Kodi
|
||||
# NEW idea: process every item returned by the PMS
|
||||
self.allKodiElementsId = {}
|
||||
|
||||
# Run through views and get latest changed elements using time diff
|
||||
|
@ -296,39 +480,94 @@ class LibrarySync(Thread):
|
|||
self.updateKodiMusicLib = False
|
||||
for view in self.views:
|
||||
self.updatelist = []
|
||||
if self.threadStopped():
|
||||
return True
|
||||
# Get items per view
|
||||
items = PlexFunctions.GetAllPlexLeaves(view['id'],
|
||||
updatedAt=lastSync)
|
||||
# Just skip item if something went wrong
|
||||
items = PlexFunctions.GetAllPlexLeaves(
|
||||
view['id'], updatedAt=self.getPMSfromKodiTime(lastSync))
|
||||
# Just skip if something went wrong
|
||||
if not items:
|
||||
continue
|
||||
# Get one itemtype, because they're the same in the PMS section
|
||||
plexType = items[0].attrib['type']
|
||||
try:
|
||||
plexType = items[0].attrib['type']
|
||||
except:
|
||||
# There was no child - PMS response is empty
|
||||
continue
|
||||
# Populate self.updatelist
|
||||
self.GetUpdatelist(items,
|
||||
PlexFunctions.GetItemClassFromType(plexType),
|
||||
PlexFunctions.GetMethodFromPlexType(plexType),
|
||||
view['name'],
|
||||
view['id'])
|
||||
self.getFastUpdateList(
|
||||
items, plexType, view['name'], view['id'])
|
||||
# Process self.updatelist
|
||||
if self.updatelist:
|
||||
if self.updatelist[0]['itemType'] in ['Movies', 'TVShows']:
|
||||
self.updateKodiVideoLib = True
|
||||
elif self.updatelist[0]['itemType'] == 'Music':
|
||||
self.updateKodiMusicLib = True
|
||||
# Do the work
|
||||
self.GetAndProcessXMLs(
|
||||
PlexFunctions.GetItemClassFromType(plexType),
|
||||
showProgress=False)
|
||||
self.updatelist = []
|
||||
|
||||
# Update userdata
|
||||
self.updatelist = []
|
||||
|
||||
# Update userdata DIRECTLY
|
||||
# We don't need to refresh the Kodi library for deltas!!
|
||||
# Start with an empty ElementTree and attach items to update
|
||||
movieupdate = False
|
||||
episodeupdate = False
|
||||
songupdate = False
|
||||
for view in self.views:
|
||||
self.PlexUpdateWatched(
|
||||
view['id'],
|
||||
PlexFunctions.GetItemClassFromType(view['itemtype']),
|
||||
lastViewedAt=lastSync)
|
||||
items = PlexFunctions.GetAllPlexLeaves(
|
||||
view['id'], lastViewedAt=self.getPMSfromKodiTime(lastSync))
|
||||
for item in items:
|
||||
itemId = item.attrib.get('ratingKey')
|
||||
# Skipping items 'title=All episodes' without a 'ratingKey'
|
||||
if not itemId:
|
||||
continue
|
||||
plexType = item.attrib['type']
|
||||
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:
|
||||
# Only look at lastViewedAt
|
||||
if res[0] == lastViewedAt:
|
||||
# Nothing to update, we have already processed this
|
||||
# item
|
||||
continue
|
||||
if plexType == 'movie':
|
||||
movieupdate = True
|
||||
try:
|
||||
movieXML.append(item)
|
||||
except:
|
||||
movieXML = etree.Element('root')
|
||||
movieXML.append(item)
|
||||
elif plexType == 'episode':
|
||||
episodeupdate = True
|
||||
try:
|
||||
episodeXML.append(item)
|
||||
except:
|
||||
episodeXML = etree.Element('root')
|
||||
episodeXML.append(item)
|
||||
elif plexType == 'track':
|
||||
songupdate = True
|
||||
try:
|
||||
musicXML.append(item)
|
||||
except:
|
||||
musicXML = etree.Element('root')
|
||||
musicXML.append(item)
|
||||
# And safe to self.processed:
|
||||
self.processed[plexType][itemId] = (lastViewedAt, updatedAt)
|
||||
|
||||
if movieupdate:
|
||||
with itemtypes.Movies() as movies:
|
||||
movies.updateUserdata(movieXML)
|
||||
if episodeupdate:
|
||||
with itemtypes.TVShows() as tvshows:
|
||||
tvshows.updateUserdata(episodeXML)
|
||||
if songupdate:
|
||||
with itemtypes.Music() as music:
|
||||
music.updateUserdata(musicXML)
|
||||
|
||||
# Let Kodi update the library now (artwork and userdata)
|
||||
if self.updateKodiVideoLib:
|
||||
|
@ -338,8 +577,6 @@ class LibrarySync(Thread):
|
|||
self.logMsg("Doing Kodi Music Lib update", 1)
|
||||
xbmc.executebuiltin('UpdateLibrary(music)')
|
||||
|
||||
# Reset and return
|
||||
self.allPlexElementsId = {}
|
||||
# Show warning if itemtypes.py crashed at some point
|
||||
if utils.window('plex_scancrashed') == 'true':
|
||||
xbmcgui.Dialog().ok(self.addonName, self.__language__(39408))
|
||||
|
@ -1168,6 +1405,9 @@ class LibrarySync(Thread):
|
|||
count = 0
|
||||
errorcount = 0
|
||||
|
||||
# Initialize self.processed
|
||||
self.resetProcessedItems()
|
||||
|
||||
log("---===### Starting LibrarySync ###===---", 0)
|
||||
while not threadStopped():
|
||||
|
||||
|
@ -1223,6 +1463,8 @@ class LibrarySync(Thread):
|
|||
log("Db version: %s" % settings('dbCreatedWithVersion'), 0)
|
||||
log("Initial start-up full sync starting", 0)
|
||||
librarySync = fullSync(manualrun=True)
|
||||
# Initialize time offset Kodi - PMS
|
||||
self.syncPMStime()
|
||||
window('emby_dbScan', clear=True)
|
||||
if librarySync:
|
||||
log("Initial start-up full sync successful", 0)
|
||||
|
@ -1282,6 +1524,10 @@ class LibrarySync(Thread):
|
|||
# Run full lib scan approx every 30min
|
||||
if count >= 1800:
|
||||
count = 0
|
||||
# Also reset self.processed, just in case
|
||||
self.resetProcessedItems()
|
||||
# Recalculate time offset Kodi - PMS
|
||||
self.syncPMStime()
|
||||
window('emby_dbScan', value="true")
|
||||
log('Running background full lib scan', 0)
|
||||
fullSync(manualrun=True)
|
||||
|
|
|
@ -19,12 +19,88 @@ import xbmcaddon
|
|||
import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
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):
|
||||
"""
|
||||
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
|
||||
|
|
Loading…
Reference in a new issue