Rewire llibrary sync, part 5

This commit is contained in:
croneter 2018-10-24 07:08:32 +02:00
parent 23dada9fe5
commit 2f96749fc7
10 changed files with 258 additions and 1151 deletions

View file

@ -123,10 +123,6 @@ class Main():
elif mode == 'chooseServer': elif mode == 'chooseServer':
entrypoint.choose_pms_server() entrypoint.choose_pms_server()
elif mode == 'refreshplaylist':
log.info('Requesting playlist/nodes refresh')
utils.plex_command('RUN_LIB_SCAN', 'views')
elif mode == 'deviceid': elif mode == 'deviceid':
self.deviceid() self.deviceid()

View file

@ -1036,10 +1036,6 @@ msgctxt "#39201"
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgctxt "#39203"
msgid "Refresh Plex playlists/nodes"
msgstr ""
msgctxt "#39204" msgctxt "#39204"
msgid "Perform manual library sync" msgid "Perform manual library sync"
msgstr "" msgstr ""

View file

@ -170,8 +170,6 @@ def show_main_menu(content_type=None):
# some extra entries for settings and stuff # some extra entries for settings and stuff
directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID) directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID)
directory_item(utils.lang(39203),
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
directory_item(utils.lang(39204), directory_item(utils.lang(39204),
"plugin://%s?mode=manualsync" % v.ADDON_ID) "plugin://%s?mode=manualsync" % v.ADDON_ID)
xbmcplugin.endOfDirectory(int(argv[1])) xbmcplugin.endOfDirectory(int(argv[1]))

View file

@ -2,3 +2,4 @@
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from .full_sync import start, PLAYLIST_SYNC_ENABLED from .full_sync import start, PLAYLIST_SYNC_ENABLED
from .time import sync_pms_time

View file

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
import time
from .get_metadata import GetMetadataTask from .get_metadata import GetMetadataTask
from . import common, process_metadata, sections from . import common, process_metadata, sections
@ -24,12 +23,13 @@ LOG = getLogger('PLEX.library_sync.full_sync')
class FullSync(backgroundthread.KillableThread, common.libsync_mixin): class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
def __init__(self, repair, callback): def __init__(self, repair, callback, show_dialog):
""" """
repair=True: force sync EVERY item repair=True: force sync EVERY item
""" """
self.repair = repair self.repair = repair
self.callback = callback self.callback = callback
self.show_dialog = show_dialog
self.queue = None self.queue = None
self.process_thread = None self.process_thread = None
self.last_sync = None self.last_sync = None
@ -141,14 +141,18 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
if self.isCanceled(): if self.isCanceled():
return return
successful = False successful = False
self.last_sync = time.time() self.last_sync = utils.unix_timestamp()
# Delete playlist and video node files from Kodi
utils.delete_playlists()
utils.delete_nodes()
# Get latest Plex libraries and build playlist and video node files
if not sections.sync_from_pms(): if not sections.sync_from_pms():
return return
try: try:
# Fire up our single processing thread # Fire up our single processing thread
self.queue = backgroundthread.Queue.Queue(maxsize=200) self.queue = backgroundthread.Queue.Queue(maxsize=200)
self.processing_thread = process_metadata.ProcessMetadata( self.processing_thread = process_metadata.ProcessMetadata(
self.queue, self.last_sync) self.queue, self.last_sync, self.show_dialog)
self.processing_thread.start() self.processing_thread.start()
# Actual syncing - do only new items first # Actual syncing - do only new items first
@ -180,12 +184,13 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
# This will block until the processing thread exits # This will block until the processing thread exits
LOG.debug('Waiting for processing thread to exit') LOG.debug('Waiting for processing thread to exit')
self.processing_thread.join() self.processing_thread.join()
self.callback(successful) if self.callback:
self.callback(successful)
LOG.info('Done full_sync') LOG.info('Done full_sync')
def start(repair, callback): def start(show_dialog, repair=False, callback=None):
""" """
""" """
# backgroundthread.BGThreader.addTask(FullSync().setup(repair, callback)) # backgroundthread.BGThreader.addTask(FullSync().setup(repair, callback))
FullSync(repair, callback).start() FullSync(repair, callback, show_dialog).start()

View file

@ -36,34 +36,38 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
item_class: as used to call functions in itemtypes.py e.g. 'Movies' => item_class: as used to call functions in itemtypes.py e.g. 'Movies' =>
itemtypes.Movies() itemtypes.Movies()
""" """
def __init__(self, queue, last_sync): def __init__(self, queue, last_sync, show_dialog):
self.queue = queue self.queue = queue
self.last_sync = last_sync self.last_sync = last_sync
self.show_dialog = show_dialog
self.total = 0 self.total = 0
self.current = 0 self.current = 0
self.title = None self.title = None
self.section_name = None self.section_name = None
self.dialog = None
super(ProcessMetadata, self).__init__() super(ProcessMetadata, self).__init__()
def update_dialog(self): def update(self):
""" """
""" """
try: if self.show_dialog:
progress = int(float(self.current) / float(self.total) * 100.0) try:
except ZeroDivisionError: progress = int(float(self.current) / float(self.total) * 100.0)
progress = 0 except ZeroDivisionError:
self.dialog.update(progress, progress = 0
self.section_name, self.dialog.update(progress,
'%s/%s: %s' self.section_name,
% (self.current, self.total, self.title)) '%s/%s: %s'
% (self.current, self.total, self.title))
def run(self): def run(self):
""" """
Do the work Do the work
""" """
LOG.debug('Processing thread started') LOG.debug('Processing thread started')
self.dialog = xbmcgui.DialogProgressBG() if self.show_dialog:
self.dialog.create(utils.lang(39714)) self.dialog = xbmcgui.DialogProgressBG()
self.dialog.create(utils.lang(39714))
try: try:
# Init with the very first library section. This will block! # Init with the very first library section. This will block!
section = self.queue.get() section = self.queue.get()
@ -91,13 +95,15 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
children=xml.children) children=xml.children)
except: except:
utils.ERROR(txt='process_metadata crashed', utils.ERROR(txt='process_metadata crashed',
notify=True) notify=True,
cancel_sync=True)
if self.current % 20 == 0: if self.current % 20 == 0:
self.title = utils.cast(unicode, self.title = utils.cast(unicode,
xml[0].get('title')) xml[0].get('title'))
self.update_dialog() self.update()
self.current += 1 self.current += 1
self.queue.task_done() self.queue.task_done()
finally: finally:
self.dialog.close() if self.dialog:
self.dialog.close()
LOG.debug('Processing thread terminated') LOG.debug('Processing thread terminated')

View file

@ -0,0 +1,108 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmc
from .. import plex_functions as PF, utils, variables as v, state
LOG = getLogger('PLEX.library_sync.time')
def sync_pms_time():
"""
PMS does not provide a means to get a server timestamp. This is a work-
around - because the PMS might be in another time zone
In general, everything saved to Kodi shall be in Kodi time.
Any info with a PMS timestamp is in Plex time, naturally
"""
LOG.info('Synching time with PMS server')
# Find a PMS item where we can toggle the view state to enforce a
# change in lastViewedAt
# Get all Plex libraries
sections = PF.get_plex_sections()
try:
sections.attrib
except AttributeError:
LOG.error("Error download PMS views, abort sync_pms_time")
return False
plex_id = None
typus = (
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_MOVIE,),
(v.PLEX_TYPE_SHOW, v.PLEX_TYPE_EPISODE),
(v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_SONG)
)
for section_type, plex_type in typus:
if plex_id:
break
for section in sections:
if plex_id:
break
if not section.attrib['type'] == section_type:
continue
library_id = section.attrib['key']
try:
iterator = PF.SectionItems(library_id, {'type': plex_type})
for item in iterator:
if item.get('viewCount'):
# Don't want to mess with items that have playcount>0
continue
if item.get('viewOffset'):
# Don't mess with items with a resume point
continue
plex_id = utils.cast(int, item.get('ratingKey'))
LOG.info('Found a %s item to sync with: %s',
plex_type, plex_id)
break
except RuntimeError:
pass
if plex_id is None:
LOG.error("Could not find an item to sync time with")
LOG.error("Aborting PMS-Kodi time sync")
return False
# Get the Plex item's metadata
xml = PF.GetPlexMetadata(plex_id)
if xml in (None, 401):
LOG.error("Could not download metadata, aborting time sync")
return False
timestamp = xml[0].get('lastViewedAt')
if timestamp is None:
timestamp = xml[0].get('updatedAt')
LOG.debug('Using items updatedAt=%s', timestamp)
if timestamp is None:
timestamp = xml[0].get('addedAt')
LOG.debug('Using items addedAt=%s', timestamp)
if timestamp is None:
timestamp = 0
LOG.debug('No timestamp; using 0')
timestamp = utils.cast(int, timestamp)
# Set the timer
koditime = utils.unix_timestamp()
# Toggle watched state
PF.scrobble(plex_id, 'watched')
# Let the PMS process this first!
xbmc.sleep(1000)
# Get updated metadata
xml = PF.GetPlexMetadata(plex_id)
# Toggle watched state back
PF.scrobble(plex_id, 'unwatched')
try:
plextime = xml[0].get('lastViewedAt')
except (IndexError, TypeError, AttributeError):
LOG.error('Could not get lastViewedAt - aborting')
return False
# Calculate time offset Kodi-PMS
state.KODI_PLEX_TIME_OFFSET = float(koditime) - float(plextime)
utils.settings('kodiplextimeoffset',
value=str(state.KODI_PLEX_TIME_OFFSET))
LOG.info("Time offset Koditime - Plextime in seconds: %s",
state.KODI_PLEX_TIME_OFFSET)
return True

File diff suppressed because it is too large Load diff

View file

@ -588,7 +588,7 @@ class SectionItems(DownloadGen):
""" """
Iterator object to get all items of a Plex library section Iterator object to get all items of a Plex library section
""" """
def __init__(self, section_id, args): def __init__(self, section_id, args=None):
super(SectionItems, self).__init__( super(SectionItems, self).__init__(
'{server}/library/sections/%s/all' % section_id, args) '{server}/library/sections/%s/all' % section_id, args)

View file

@ -233,10 +233,12 @@ def dialog(typus, *args, **kwargs):
return types[typus](*args, **kwargs) return types[typus](*args, **kwargs)
def ERROR(txt='', hide_tb=False, notify=False): def ERROR(txt='', hide_tb=False, notify=False, cancel_sync=False):
import sys import sys
short = str(sys.exc_info()[1]) short = str(sys.exc_info()[1])
LOG.error('Error encountered: %s - %s', txt, short) LOG.error('Error encountered: %s - %s', txt, short)
if cancel_sync:
state.STOP_SYNC = True
if hide_tb: if hide_tb:
return short return short