Choose which Plex libraries PKC should sync

This commit is contained in:
croneter 2019-02-07 20:15:49 +01:00
parent f24266fb54
commit 50d770718d
10 changed files with 242 additions and 131 deletions

View file

@ -141,6 +141,10 @@ class Main():
elif mode == 'hub': elif mode == 'hub':
entrypoint.hub(params.get('type')) entrypoint.hub(params.get('type'))
elif mode == 'select-libraries':
LOG.info('User requested to select Plex libraries')
transfer.plex_command('select-libraries')
else: else:
entrypoint.show_main_menu(content_type=params.get('content_type')) entrypoint.show_main_menu(content_type=params.get('content_type'))

View file

@ -558,6 +558,12 @@ msgctxt "#30523"
msgid "Also show sync progress for playstate and user data" msgid "Also show sync progress for playstate and user data"
msgstr "" msgstr ""
# PKC Settings - Sync Options
msgctxt "#30524"
msgid "Select Plex libraries to sync"
msgstr ""
# PKC Settings - Playback # PKC Settings - Playback
msgctxt "#30527" msgctxt "#30527"
msgid "Ignore specials in next episodes" msgid "Ignore specials in next episodes"

View file

@ -7,3 +7,5 @@ from .websocket import store_websocket_message, process_websocket_messages, \
WEBSOCKET_MESSAGES, PLAYSTATE_SESSIONS WEBSOCKET_MESSAGES, PLAYSTATE_SESSIONS
from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED
from .fanart import FanartThread, FanartTask from .fanart import FanartThread, FanartTask
from .videonodes import VideoNodes
from .sections import force_full_sync

View file

@ -68,6 +68,7 @@ class FullSync(common.fullsync_mixin):
self.context = None self.context = None
self.get_children = None self.get_children = None
self.successful = None self.successful = None
self.section_success = None
self.install_sync_done = utils.settings('SyncInstallRunDone') == 'true' self.install_sync_done = utils.settings('SyncInstallRunDone') == 'true'
self.threader = backgroundthread.ThreaderManager( self.threader = backgroundthread.ThreaderManager(
worker=backgroundthread.NonstoppingBackgroundWorker, worker=backgroundthread.NonstoppingBackgroundWorker,
@ -232,8 +233,8 @@ class FullSync(common.fullsync_mixin):
if not itemtype.update_userdata(xml_item, section['plex_type']): if not itemtype.update_userdata(xml_item, section['plex_type']):
# Somehow did not sync this item yet # Somehow did not sync this item yet
itemtype.add_update(xml_item, itemtype.add_update(xml_item,
section['section_name'], section_name=section['section_name'],
section['section_id']) section_id=section['section_id'])
itemtype.plexdb.update_last_sync(int(xml_item.attrib['ratingKey']), itemtype.plexdb.update_last_sync(int(xml_item.attrib['ratingKey']),
section['plex_type'], section['plex_type'],
self.current_sync) self.current_sync)
@ -248,38 +249,39 @@ class FullSync(common.fullsync_mixin):
LOG.error('Could not entirely process section %s', section) LOG.error('Could not entirely process section %s', section)
return False return False
def threaded_get_iterators(self, kinds, queue, updated_at=None, def threaded_get_iterators(self, kinds, queue, all_items=False):
last_viewed_at=None):
""" """
PF.SectionItems is costly, so let's do it asynchronous PF.SectionItems is costly, so let's do it asynchronous
""" """
if self.repair:
updated_at = None
last_viewed_at = None
else:
updated_at = updated_at - UPDATED_AT_SAFETY if updated_at else None
last_viewed_at = last_viewed_at - LAST_VIEWED_AT_SAFETY \
if last_viewed_at else None
try: try:
for kind in kinds: for kind in kinds:
for section in (x for x in sections.SECTIONS for section in (x for x in sections.SECTIONS
if x['plex_type'] == kind[1]): if x['plex_type'] == kind[1]):
if self.isCanceled(): if self.isCanceled():
return return
if not section['sync_to_kodi']:
LOG.info('User chose to not sync section %s', section)
continue
element = copy.deepcopy(section) element = copy.deepcopy(section)
element['section_type'] = element['plex_type'] element['section_type'] = element['plex_type']
element['plex_type'] = kind[0] element['plex_type'] = kind[0]
element['element_type'] = kind[1] element['element_type'] = kind[1]
element['context'] = kind[2] element['context'] = kind[2]
element['get_children'] = kind[3] element['get_children'] = kind[3]
if self.repair or all_items:
updated_at = None
else:
updated_at = section['last_sync'] - UPDATED_AT_SAFETY \
if section['last_sync'] else None
try: try:
element['iterator'] = PF.SectionItems(section['section_id'], element['iterator'] = PF.SectionItems(section['section_id'],
plex_type=kind[0], plex_type=kind[0],
updated_at=updated_at, updated_at=updated_at,
last_viewed_at=last_viewed_at) last_viewed_at=None)
except RuntimeError: except RuntimeError:
LOG.warn('Sync at least partially unsuccessful') LOG.warn('Sync at least partially unsuccessful')
self.successful = False self.successful = False
self.section_success = False
else: else:
queue.put(element) queue.put(element)
finally: finally:
@ -303,14 +305,13 @@ class FullSync(common.fullsync_mixin):
# Already start setting up the iterators. We need to enforce # Already start setting up the iterators. We need to enforce
# syncing e.g. show before season before episode # syncing e.g. show before season before episode
iterator_queue = Queue.Queue() iterator_queue = Queue.Queue()
updated_at = int(utils.settings('lastfullsync')) or None
task = backgroundthread.FunctionAsTask(self.threaded_get_iterators, task = backgroundthread.FunctionAsTask(self.threaded_get_iterators,
None, None,
kinds, kinds,
iterator_queue, iterator_queue)
updated_at=updated_at)
backgroundthread.BGThreader.addTask(task) backgroundthread.BGThreader.addTask(task)
while True: while True:
self.section_success = True
section = iterator_queue.get() section = iterator_queue.get()
iterator_queue.task_done() iterator_queue.task_done()
if section is None: if section is None:
@ -323,10 +324,14 @@ class FullSync(common.fullsync_mixin):
# Now do the heavy lifting # Now do the heavy lifting
if self.isCanceled() or not self.addupdate_section(section): if self.isCanceled() or not self.addupdate_section(section):
return False return False
if self.section_success:
# Need to check because a thread might have missed to get
# some items from the PMS
with PlexDB() as plexdb:
# Set the new time mark for the next delta sync
plexdb.update_section_last_sync(section['section_id'],
self.current_sync)
common.update_kodi_library(video=True, music=True) common.update_kodi_library(video=True, music=True)
if self.successful:
# Set timestamp for next sync - neglecting playstates!
utils.settings('lastfullsync', value=str(int(self.current_sync)))
# In order to not delete all your songs again # In order to not delete all your songs again
if app.SYNC.enable_music: if app.SYNC.enable_music:
kinds.extend([ kinds.extend([
@ -347,7 +352,8 @@ class FullSync(common.fullsync_mixin):
task = backgroundthread.FunctionAsTask(self.threaded_get_iterators, task = backgroundthread.FunctionAsTask(self.threaded_get_iterators,
None, None,
kinds, kinds,
iterator_queue) iterator_queue,
all_items=True)
backgroundthread.BGThreader.addTask(task) backgroundthread.BGThreader.addTask(task)
while True: while True:
section = iterator_queue.get() section = iterator_queue.get()
@ -364,7 +370,7 @@ class FullSync(common.fullsync_mixin):
return False return False
# Delete movies that are not on Plex anymore # Delete movies that are not on Plex anymore
LOG.info('Looking for items to delete') LOG.debug('Looking for items to delete')
kinds = [ kinds = [
(v.PLEX_TYPE_MOVIE, itemtypes.Movie), (v.PLEX_TYPE_MOVIE, itemtypes.Movie),
(v.PLEX_TYPE_SHOW, itemtypes.Show), (v.PLEX_TYPE_SHOW, itemtypes.Show),

View file

@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
import copy import copy
from . import common, videonodes from . import videonodes
from ..utils import cast from ..utils import cast
from ..plex_db import PlexDB from ..plex_db import PlexDB
from .. import kodi_db from .. import kodi_db
@ -22,6 +22,16 @@ SECTIONS = []
IS_CANCELED = None IS_CANCELED = None
def force_full_sync():
"""
Resets the sync timestamp for all sections to 0, thus forcing a subsequent
full sync (not delta)
"""
LOG.info('Telling PKC to do a full sync instead of a delta sync')
with PlexDB() as plexdb:
plexdb.force_full_sync()
def sync_from_pms(parent_self): def sync_from_pms(parent_self):
""" """
Sync the Plex library sections Sync the Plex library sections
@ -35,6 +45,7 @@ def sync_from_pms(parent_self):
def _sync_from_pms(): def _sync_from_pms():
global PLAYLISTS, NODES, SECTIONS
sections = PF.get_plex_sections() sections = PF.get_plex_sections()
try: try:
sections.attrib sections.attrib
@ -45,7 +56,7 @@ def _sync_from_pms():
# Will reboot Kodi is new library detected # Will reboot Kodi is new library detected
music.excludefromscan_music_folders(xml=sections) music.excludefromscan_music_folders(xml=sections)
global PLAYLISTS, NODES, SECTIONS VNODES.clearProperties()
SECTIONS = [] SECTIONS = []
NODES = { NODES = {
v.PLEX_TYPE_MOVIE: [], v.PLEX_TYPE_MOVIE: [],
@ -54,64 +65,46 @@ def _sync_from_pms():
v.PLEX_TYPE_PHOTO: [] v.PLEX_TYPE_PHOTO: []
} }
PLAYLISTS = copy.deepcopy(NODES) PLAYLISTS = copy.deepcopy(NODES)
sorted_sections = []
for section in sections:
if (section.attrib['type'] in
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO,
v.PLEX_TYPE_ARTIST)):
sorted_sections.append(section.attrib['title'])
LOG.debug('Sorted sections: %s', sorted_sections)
totalnodes = len(sorted_sections)
VNODES.clearProperties()
with PlexDB() as plexdb: with PlexDB() as plexdb:
# Backup old sections to delete them later, if needed (at the end # Backup old sections to delete them later, if needed (at the end
# of this method, only unused sections will be left in old_sections) # of this method, only unused sections will be left in old_sections)
old_sections = list(plexdb.section_ids()) old_sections = list(plexdb.all_sections())
with kodi_db.KodiVideoDB() as kodidb: with kodi_db.KodiVideoDB() as kodidb:
for section in sections: for index, section in enumerate(sections):
_process_section(section, _process_section(section,
kodidb, kodidb,
plexdb, plexdb,
sorted_sections, index,
old_sections, old_sections)
totalnodes)
if old_sections: if old_sections:
# Section has been deleted on the PMS # Section has been deleted on the PMS
delete_sections(old_sections) delete_sections(old_sections)
# update sections for all: # update sections for all:
with PlexDB() as plexdb: with PlexDB() as plexdb:
SECTIONS = list(plexdb.section_infos()) SECTIONS = list(plexdb.all_sections())
utils.window('Plex.nodes.total', str(totalnodes)) utils.window('Plex.nodes.total', str(len(sections)))
LOG.info("Finished processing library sections: %s", SECTIONS) LOG.info("Finished processing %s library sections: %s", len(sections), SECTIONS)
return True return True
def _process_section(section_xml, kodidb, plexdb, sorted_sections, def _process_section(section_xml, kodidb, plexdb, index, old_sections):
old_sections, totalnodes): global PLAYLISTS, NODES
folder = section_xml.attrib folder = section_xml.attrib
plex_type = folder['type'] plex_type = folder['type']
# Only process supported formats # Only process supported formats
if plex_type not in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, if plex_type not in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW,
v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_PHOTO): v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_PHOTO):
LOG.error('Unsupported Plex section type: %s', folder) LOG.error('Unsupported Plex section type: %s', folder)
return totalnodes return
section_id = cast(int, folder['key']) section_id = cast(int, folder['key'])
section_name = folder['title'] section_name = folder['title']
global PLAYLISTS, NODES
# Prevent duplicate for nodes of the same type # Prevent duplicate for nodes of the same type
nodes = NODES[plex_type] nodes = NODES[plex_type]
# Prevent duplicate for playlists of the same type # Prevent duplicate for playlists of the same type
playlists = PLAYLISTS[plex_type] playlists = PLAYLISTS[plex_type]
# Get current media folders from plex database # Get current media folders from plex database
section = plexdb.section(section_id) section = plexdb.section(section_id)
try: if not section:
current_sectionname = section[1]
current_sectiontype = section[2]
current_tagid = section[3]
except TypeError:
LOG.info('Creating section id: %s in Plex database.', section_id) LOG.info('Creating section id: %s in Plex database.', section_id)
tagid = kodidb.create_tag(section_name) tagid = kodidb.create_tag(section_name)
# Create playlist for the video library # Create playlist for the video library
@ -121,28 +114,30 @@ def _process_section(section_xml, kodidb, plexdb, sorted_sections,
playlists.append(section_name) playlists.append(section_name)
# Create the video node # Create the video node
if section_name not in nodes: if section_name not in nodes:
VNODES.viewNode(sorted_sections.index(section_name), VNODES.viewNode(index,
section_name, section_name,
plex_type, plex_type,
None, None,
section_id) section_id)
nodes.append(section_name) nodes.append(section_name)
totalnodes += 1
# Add view to plex database # Add view to plex database
plexdb.add_section(section_id, section_name, plex_type, tagid) plexdb.add_section(section_id,
section_name,
plex_type,
tagid,
True, # Sync this new section for now
None)
else: else:
LOG.info('Found library section id %s, name %s, type %s, tagid %s', LOG.info('Found library section id %s, name %s, type %s, tagid %s',
section_id, current_sectionname, current_sectiontype, section_id, section['section_name'], section['plex_type'],
current_tagid) section['kodi_tagid'])
# Remove views that are still valid to delete rest later # Remove views that are still valid to delete rest later
try: for section in old_sections:
old_sections.remove(section_id) if section['section_id'] == section_id:
except ValueError: old_sections.remove(section)
# View was just created, nothing to remove break
pass
# View was modified, update with latest info # View was modified, update with latest info
if current_sectionname != section_name: if section['section_name'] != section_name:
LOG.info('section id: %s new sectionname: %s', LOG.info('section id: %s new sectionname: %s',
section_id, section_name) section_id, section_name)
tagid = kodidb.create_tag(section_name) tagid = kodidb.create_tag(section_name)
@ -151,22 +146,24 @@ def _process_section(section_xml, kodidb, plexdb, sorted_sections,
plexdb.add_section(section_id, plexdb.add_section(section_id,
section_name, section_name,
plex_type, plex_type,
tagid) tagid,
section['sync_to_kodi'], # Use "old" setting
section['last_sync'])
if plexdb.section_id_by_name(current_sectionname) is None: if plexdb.section_id_by_name(section['section_name']) is None:
# The tag could be a combined view. Ensure there's # The tag could be a combined view. Ensure there's
# no other tags with the same name before deleting # no other tags with the same name before deleting
# playlist. # playlist.
utils.playlist_xsp(plex_type, utils.playlist_xsp(plex_type,
current_sectionname, section['section_name'],
section_id, section_id,
current_sectiontype, section['plex_type'],
True) True)
# Delete video node # Delete video node
if plex_type != "musicvideos": if plex_type != "musicvideos":
VNODES.viewNode( VNODES.viewNode(
indexnumber=sorted_sections.index(section_name), indexnumber=index,
tagname=current_sectionname, tagname=section['section_name'],
mediatype=plex_type, mediatype=plex_type,
viewtype=None, viewtype=None,
viewid=section_id, viewid=section_id,
@ -179,17 +176,16 @@ def _process_section(section_xml, kodidb, plexdb, sorted_sections,
playlists.append(section_name) playlists.append(section_name)
# Add new video node # Add new video node
if section_name not in nodes and plex_type != "musicvideos": if section_name not in nodes and plex_type != "musicvideos":
VNODES.viewNode(sorted_sections.index(section_name), VNODES.viewNode(index,
section_name, section_name,
plex_type, plex_type,
None, None,
section_id) section_id)
nodes.append(section_name) nodes.append(section_name)
totalnodes += 1
# Update items with new tag # Update items with new tag
for kodi_id in plexdb.kodiid_by_sectionid(section_id, plex_type): for kodi_id in plexdb.kodiid_by_sectionid(section_id, plex_type):
kodidb.update_tag( kodidb.update_tag(
current_tagid, tagid, kodi_id, current_sectiontype) section['kodi_tagid'], tagid, kodi_id, section['plex_type'])
else: else:
# Validate the playlist exists or recreate it # Validate the playlist exists or recreate it
if (section_name not in playlists and plex_type in if (section_name not in playlists and plex_type in
@ -200,14 +196,12 @@ def _process_section(section_xml, kodidb, plexdb, sorted_sections,
playlists.append(section_name) playlists.append(section_name)
# Create the video node if not already exists # Create the video node if not already exists
if section_name not in nodes and plex_type != "musicvideos": if section_name not in nodes and plex_type != "musicvideos":
VNODES.viewNode(sorted_sections.index(section_name), VNODES.viewNode(index,
section_name, section_name,
plex_type, plex_type,
None, None,
section_id) section_id)
nodes.append(section_name) nodes.append(section_name)
totalnodes += 1
return totalnodes
def _delete_kodi_db_items(section_id, section_type): def _delete_kodi_db_items(section_id, section_type):
@ -246,25 +240,55 @@ def delete_sections(old_sections):
Deletes all elements for a Plex section that has been deleted. (e.g. all Deletes all elements for a Plex section that has been deleted. (e.g. all
TV shows, Seasons and Episodes of a Show section) TV shows, Seasons and Episodes of a Show section)
""" """
try: LOG.info("Removing entire Plex library sections: %s", old_sections)
for section in old_sections:
# "Deleting <section_name>"
utils.dialog('notification',
heading='{plex}',
message='%s %s' % (utils.lang(30052), section['section_name']),
icon='{plex}',
sound=False)
if section['plex_type'] == v.PLEX_TYPE_PHOTO:
# not synced - just remove the link in our Plex sections table
pass
else:
if not _delete_kodi_db_items(section['section_id'], section['plex_type']):
return
# Only remove Plex entry if we've removed all items first
with PlexDB() as plexdb: with PlexDB() as plexdb:
old_sections = [plexdb.section(x) for x in old_sections] plexdb.remove_section(section['section_id'])
LOG.info("Removing entire Plex library sections: %s", old_sections)
for section in old_sections:
# "Deleting <section_name>" def choose_libraries():
utils.dialog('notification', """
heading='{plex}', Displays a dialog for the user to select the libraries he wants synched
message='%s %s' % (utils.lang(30052), section[1]),
icon='{plex}', Returns True if this was successful, False if not
sound=False) """
if section[2] == v.PLEX_TYPE_PHOTO: # xbmcgui.Dialog().multiselect(heading, options[, autoclose, preselect, useDetails])
# not synced - just remove the link in our Plex sections table # "Select Plex libraries to sync"
pass import xbmcgui
else: sections = []
if not _delete_kodi_db_items(section[0], section[2]): preselect = []
return for i, section in enumerate(SECTIONS):
# Only remove Plex entry if we've removed all items first sections.append(section['section_name'])
with PlexDB() as plexdb: if section['plex_type'] == v.PLEX_TYPE_ARTIST:
plexdb.remove_section(section[0]) if section['sync_to_kodi'] and app.SYNC.enable_music:
finally: preselect.append(i)
common.update_kodi_library() else:
if section['sync_to_kodi']:
preselect.append(i)
selected = xbmcgui.Dialog().multiselect(utils.lang(30524),
sections,
preselect=preselect,
useDetails=False)
if selected is None:
# User canceled
return False
with PlexDB() as plexdb:
for i, section in enumerate(SECTIONS):
sync = True if i in selected else False
plexdb.update_section_sync(section['section_id'], sync)
sections = list(plexdb.all_sections())
LOG.info('Plex libraries to sync: %s', sections)
return True

View file

@ -194,7 +194,8 @@ def initialize():
section_name TEXT, section_name TEXT,
plex_type TEXT, plex_type TEXT,
kodi_tagid INTEGER, kodi_tagid INTEGER,
sync_to_kodi INTEGER) sync_to_kodi INTEGER,
last_sync INTEGER)
''') ''')
plexdb.cursor.execute(''' plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS movie( CREATE TABLE IF NOT EXISTS movie(

View file

@ -4,43 +4,39 @@ from __future__ import absolute_import, division, unicode_literals
class Sections(object): class Sections(object):
def section_ids(self): def all_sections(self):
""" """
Returns an iterator for section Plex ids for all sections Returns an iterator for all sections
"""
self.cursor.execute('SELECT section_id FROM sections')
return (x[0] for x in self.cursor)
def section_infos(self):
"""
Returns an iterator for dicts for all Plex libraries:
{
'section_id'
'section_name'
'plex_type'
'kodi_tagid'
'sync_to_kodi'
}
""" """
self.cursor.execute('SELECT * FROM sections') self.cursor.execute('SELECT * FROM sections')
return ({'section_id': x[0], return (self.entry_to_section(x) for x in self.cursor)
'section_name': x[1],
'plex_type': x[2],
'kodi_tagid': x[3],
'sync_to_kodi': x[4]} for x in self.cursor)
def section(self, section_id): def section(self, section_id):
""" """
For section_id, returns the tuple (or None) For section_id, returns the dict
section_id INTEGER PRIMARY KEY, section_id INTEGER PRIMARY KEY,
section_name TEXT, section_name TEXT,
plex_type TEXT, plex_type TEXT,
kodi_tagid INTEGER, kodi_tagid INTEGER,
sync_to_kodi INTEGER sync_to_kodi BOOL,
last_sync INTEGER
""" """
self.cursor.execute('SELECT * FROM sections WHERE section_id = ? LIMIT 1', self.cursor.execute('SELECT * FROM sections WHERE section_id = ? LIMIT 1',
(section_id, )) (section_id, ))
return self.cursor.fetchone() return self.entry_to_section(self.cursor.fetchone())
@staticmethod
def entry_to_section(entry):
if not entry:
return
return {
'section_id': entry[0],
'section_name': entry[1],
'plex_type': entry[2],
'kodi_tagid': entry[3],
'sync_to_kodi': entry[4] == 1,
'last_sync': entry[5]
}
def section_id_by_name(self, section_name): def section_id_by_name(self, section_name):
""" """
@ -54,22 +50,35 @@ class Sections(object):
pass pass
def add_section(self, section_id, section_name, plex_type, kodi_tagid, def add_section(self, section_id, section_name, plex_type, kodi_tagid,
sync_to_kodi=True): sync_to_kodi, last_sync):
""" """
Appends a Plex section to the Plex sections table Appends a Plex section to the Plex sections table
sync=False: Plex library won't be synced to Kodi sync=False: Plex library won't be synced to Kodi
""" """
query = ''' query = '''
INSERT OR REPLACE INTO sections( INSERT OR REPLACE INTO sections(
section_id, section_name, plex_type, kodi_tagid, sync_to_kodi) section_id,
VALUES (?, ?, ?, ?, ?) section_name,
plex_type,
kodi_tagid,
sync_to_kodi,
last_sync)
VALUES (?, ?, ?, ?, ?, ?)
''' '''
self.cursor.execute(query, self.cursor.execute(query,
(section_id, (section_id,
section_name, section_name,
plex_type, plex_type,
kodi_tagid, kodi_tagid,
sync_to_kodi)) sync_to_kodi,
last_sync))
def update_section(self, section_id, section_name):
"""
Updates the section with section_id
"""
query = 'UPDATE sections SET section_name = ? WHERE section_id = ?'
self.cursor.execute(query, (section_name, section_id))
def remove_section(self, section_id): def remove_section(self, section_id):
""" """
@ -77,3 +86,35 @@ class Sections(object):
""" """
self.cursor.execute('DELETE FROM sections WHERE section_id = ?', self.cursor.execute('DELETE FROM sections WHERE section_id = ?',
(section_id, )) (section_id, ))
def update_section_sync(self, section_id, sync_to_kodi):
"""
Updates whether we should sync sections_id (sync=True) or not
"""
if sync_to_kodi:
query = '''
UPDATE sections
SET sync_to_kodi = ?
WHERE section_id = ?
'''
else:
# Set last_sync = 0 in order to force a full sync if reactivated
query = '''
UPDATE sections
SET sync_to_kodi = ?, last_sync = 0
WHERE section_id = ?
'''
self.cursor.execute(query, (sync_to_kodi, section_id))
def update_section_last_sync(self, section_id, last_sync):
"""
Updates the timestamp for the section
"""
self.cursor.execute('UPDATE sections SET last_sync = ? WHERE section_id = ?',
(last_sync, section_id))
def force_full_sync(self):
"""
Sets the last_sync flag to 0 for every section
"""
self.cursor.execute('UPDATE sections SET last_sync = 0')

View file

@ -9,7 +9,7 @@ import xbmcgui
from . import utils, clientinfo, timing from . import utils, clientinfo, timing
from . import initialsetup from . import initialsetup
from . import kodimonitor from . import kodimonitor
from . import sync from . import sync, library_sync
from . import websocket_client from . import websocket_client
from . import plex_companion from . import plex_companion
from . import plex_functions as PF, playqueue as PQ from . import plex_functions as PF, playqueue as PQ
@ -97,8 +97,7 @@ class Service():
# Load/Reset PKC entirely - important for user/Kodi profile switch # Load/Reset PKC entirely - important for user/Kodi profile switch
# Clear video nodes properties # Clear video nodes properties
from .library_sync import videonodes library_sync.VideoNodes().clearProperties()
videonodes.VideoNodes().clearProperties()
clientinfo.getDeviceId() clientinfo.getDeviceId()
# Init time-offset between Kodi and Plex # Init time-offset between Kodi and Plex
timing.KODI_PLEX_TIME_OFFSET = float(utils.settings('kodiplextimeoffset') or 0.0) timing.KODI_PLEX_TIME_OFFSET = float(utils.settings('kodiplextimeoffset') or 0.0)
@ -222,7 +221,7 @@ class Service():
utils.delete_nodes() utils.delete_nodes()
app.ACCOUNT.set_unauthenticated() app.ACCOUNT.set_unauthenticated()
# Force full sync after login # Force full sync after login
utils.settings('lastfullsync', value='0') library_sync.force_full_sync()
app.SYNC.run_lib_scan = 'full' app.SYNC.run_lib_scan = 'full'
# Enable the main loop to display user selection dialog # Enable the main loop to display user selection dialog
app.APP.suspend = False app.APP.suspend = False
@ -286,6 +285,33 @@ class Service():
LOG.info("Entering PMS address complete") LOG.info("Entering PMS address complete")
return True return True
def choose_plex_libraries(self):
if not app.CONN.online:
LOG.error('PMS not online to choose libraries')
# "{0} offline"
utils.dialog('notification',
utils.lang(29999),
utils.lang(39213).format(app.CONN.server_name or ''),
icon='{plex}')
return
if not app.ACCOUNT.authenticated:
LOG.error('Not yet authenticated for PMS to choose libraries')
# "Unauthorized for PMS"
utils.dialog('notification', utils.lang(29999), utils.lang(30017))
return
app.APP.suspend_threads()
from .library_sync import sections
try:
# Get newest sections from the PMS
if not sections.sync_from_pms(self):
return
if not sections.choose_libraries():
return
# Force a full sync
app.SYNC.run_lib_scan = 'full'
finally:
app.APP.resume_threads()
def _do_auth(self): def _do_auth(self):
LOG.info('Authenticating user') LOG.info('Authenticating user')
if app.ACCOUNT.plex_username and not app.ACCOUNT.force_login: # Found a user in the settings, try to authenticate if app.ACCOUNT.plex_username and not app.ACCOUNT.force_login: # Found a user in the settings, try to authenticate
@ -435,6 +461,8 @@ class Service():
app.SYNC.run_lib_scan = 'fanart' app.SYNC.run_lib_scan = 'fanart'
elif plex_command == 'textures-scan': elif plex_command == 'textures-scan':
app.SYNC.run_lib_scan = 'textures' app.SYNC.run_lib_scan = 'textures'
elif plex_command == 'select-libraries':
self.choose_plex_libraries()
elif plex_command == 'RESET-PKC': elif plex_command == 'RESET-PKC':
utils.reset() utils.reset()
if task: if task:

View file

@ -466,7 +466,6 @@ def wipe_database():
kodi_db.reset_cached_images() kodi_db.reset_cached_images()
# reset the install run flag # reset the install run flag
settings('SyncInstallRunDone', value="false") settings('SyncInstallRunDone', value="false")
settings('lastfullsync', value="0")
init_dbs() init_dbs()
LOG.info('Wiping done') LOG.info('Wiping done')
if settings('kodi_db_has_been_wiped_clean') != 'true': if settings('kodi_db_has_been_wiped_clean') != 'true':

View file

@ -52,6 +52,7 @@
</category> </category>
<category label="30506"><!-- Sync Options --> <category label="30506"><!-- Sync Options -->
<setting label="[COLOR yellow]$ADDON[plugin.video.plexkodiconnect 30524][/COLOR]" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=select-libraries)" option="close" help="Choose which Plex library you want to sync"/><!-- Select Plex libraries to sync -->
<setting type="lsep" label="30537" /><!-- Restart if you make changes --> <setting type="lsep" label="30537" /><!-- Restart if you make changes -->
<setting type="sep" /> <setting type="sep" />
<setting id="fullSyncInterval" type="number" label="39053" default="60" option="int" /> <setting id="fullSyncInterval" type="number" label="39053" default="60" option="int" />
@ -84,7 +85,6 @@
<setting id="themoviedbAPIKey" type="text" default="19c90103adb9e98f2172c6a6a3d85dc4" visible="false"/> <setting id="themoviedbAPIKey" type="text" default="19c90103adb9e98f2172c6a6a3d85dc4" visible="false"/>
<setting id="FanArtTVAPIKey" type="text" default="639191cb0774661597f28a47e7e2bad5" visible="false"/> <setting id="FanArtTVAPIKey" type="text" default="639191cb0774661597f28a47e7e2bad5" visible="false"/>
<setting id="syncEmptyShows" type="bool" label="30508" default="false" visible="false"/> <setting id="syncEmptyShows" type="bool" label="30508" default="false" visible="false"/>
<setting id="lastfullsync" type="number" label="Time stamp when last successful full sync was conducted" default="0" visible="false" />
<setting id="kodi_db_has_been_wiped_clean" type="bool" label="PKC needs to completely clean the Kodi DB at least once, then reboot, to avoid Kodi error messages, e.g. OperationalError" default="false" visible="false" /> <setting id="kodi_db_has_been_wiped_clean" type="bool" label="PKC needs to completely clean the Kodi DB at least once, then reboot, to avoid Kodi error messages, e.g. OperationalError" default="false" visible="false" />
</category> </category>