PlexKodiConnect/resources/lib/library_sync/sections.py

314 lines
12 KiB
Python
Raw Normal View History

2018-10-20 23:49:04 +11:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import copy
from . import videonodes
2018-10-20 23:49:04 +11:00
from ..utils import cast
2018-10-25 02:17:02 +11:00
from ..plex_db import PlexDB
2018-11-09 07:22:16 +11:00
from .. import kodi_db
2018-10-20 23:49:04 +11:00
from .. import itemtypes
2018-11-19 00:59:17 +11:00
from .. import plex_functions as PF, music, utils, variables as v, app
2018-10-20 23:49:04 +11:00
2018-11-02 01:43:43 +11:00
LOG = getLogger('PLEX.sync.sections')
2018-10-20 23:49:04 +11:00
BATCH_SIZE = 500
2018-10-20 23:49:04 +11:00
VNODES = videonodes.VideoNodes()
PLAYLISTS = {}
NODES = {}
SECTIONS = []
# Need a way to interrupt
IS_CANCELED = None
2018-10-20 23:49:04 +11:00
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):
2018-10-20 23:49:04 +11:00
"""
Sync the Plex library sections
"""
global IS_CANCELED
IS_CANCELED = parent_self.isCanceled
try:
return _sync_from_pms()
finally:
IS_CANCELED = None
def _sync_from_pms():
global PLAYLISTS, NODES, SECTIONS
2018-10-20 23:49:04 +11:00
sections = PF.get_plex_sections()
try:
sections.attrib
except AttributeError:
LOG.error("Error download PMS sections, abort")
return False
2018-11-19 00:59:17 +11:00
if app.SYNC.direct_paths is True and app.SYNC.enable_music is True:
2018-10-20 23:49:04 +11:00
# Will reboot Kodi is new library detected
music.excludefromscan_music_folders(xml=sections)
VNODES.clearProperties()
2018-10-20 23:49:04 +11:00
SECTIONS = []
NODES = {
v.PLEX_TYPE_MOVIE: [],
v.PLEX_TYPE_SHOW: [],
v.PLEX_TYPE_ARTIST: [],
v.PLEX_TYPE_PHOTO: []
}
PLAYLISTS = copy.deepcopy(NODES)
2018-10-25 02:17:02 +11:00
with PlexDB() as plexdb:
2018-10-20 23:49:04 +11:00
# Backup old sections to delete them later, if needed (at the end
# of this method, only unused sections will be left in old_sections)
old_sections = list(plexdb.all_sections())
2018-11-09 07:22:16 +11:00
with kodi_db.KodiVideoDB() as kodidb:
for index, section in enumerate(sections):
2018-10-20 23:49:04 +11:00
_process_section(section,
2018-11-09 07:22:16 +11:00
kodidb,
2018-10-25 02:17:02 +11:00
plexdb,
index,
old_sections)
2018-10-20 23:49:04 +11:00
if old_sections:
# Section has been deleted on the PMS
delete_sections(old_sections)
# update sections for all:
2018-10-25 22:20:46 +11:00
with PlexDB() as plexdb:
SECTIONS = list(plexdb.all_sections())
utils.window('Plex.nodes.total', str(len(sections)))
LOG.info("Finished processing %s library sections: %s", len(sections), SECTIONS)
if app.CONN.machine_identifier != utils.settings('sections_asked_for_machine_identifier'):
LOG.info('First time connecting to this PMS, choosing libraries')
if choose_libraries():
with PlexDB() as plexdb:
SECTIONS = list(plexdb.all_sections())
2018-10-20 23:49:04 +11:00
return True
def _process_section(section_xml, kodidb, plexdb, index, old_sections):
global PLAYLISTS, NODES
2018-10-20 23:49:04 +11:00
folder = section_xml.attrib
2018-10-26 03:28:41 +11:00
plex_type = folder['type']
2018-10-20 23:49:04 +11:00
# Only process supported formats
if plex_type not in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW,
v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_PHOTO):
LOG.error('Unsupported Plex section type: %s', folder)
return
2018-10-20 23:49:04 +11:00
section_id = cast(int, folder['key'])
2018-10-26 03:28:41 +11:00
section_name = folder['title']
2018-10-20 23:49:04 +11:00
# Prevent duplicate for nodes of the same type
nodes = NODES[plex_type]
# Prevent duplicate for playlists of the same type
playlists = PLAYLISTS[plex_type]
# Get current media folders from plex database
2018-10-25 02:17:02 +11:00
section = plexdb.section(section_id)
if not section:
2018-10-20 23:49:04 +11:00
LOG.info('Creating section id: %s in Plex database.', section_id)
2018-11-09 07:22:16 +11:00
tagid = kodidb.create_tag(section_name)
2018-10-20 23:49:04 +11:00
# Create playlist for the video library
if (section_name not in playlists and
plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
utils.playlist_xsp(plex_type, section_name, section_id)
playlists.append(section_name)
# Create the video node
if section_name not in nodes:
VNODES.viewNode(index,
2018-10-20 23:49:04 +11:00
section_name,
plex_type,
None,
section_id)
nodes.append(section_name)
# Add view to plex database
plexdb.add_section(section_id,
section_name,
plex_type,
tagid,
True, # Sync this new section for now
None)
2018-10-20 23:49:04 +11:00
else:
LOG.info('Found library section id %s, name %s, type %s, tagid %s',
section_id, section['section_name'], section['plex_type'],
section['kodi_tagid'])
2018-10-20 23:49:04 +11:00
# Remove views that are still valid to delete rest later
for section in old_sections:
if section['section_id'] == section_id:
old_sections.remove(section)
break
2018-10-20 23:49:04 +11:00
# View was modified, update with latest info
if section['section_name'] != section_name:
2018-10-20 23:49:04 +11:00
LOG.info('section id: %s new sectionname: %s',
section_id, section_name)
2018-11-09 07:22:16 +11:00
tagid = kodidb.create_tag(section_name)
2018-10-20 23:49:04 +11:00
# Update view with new info
2018-10-25 02:17:02 +11:00
plexdb.add_section(section_id,
2018-11-09 07:22:16 +11:00
section_name,
plex_type,
tagid,
section['sync_to_kodi'], # Use "old" setting
section['last_sync'])
2018-10-20 23:49:04 +11:00
if plexdb.section_id_by_name(section['section_name']) is None:
2018-10-20 23:49:04 +11:00
# The tag could be a combined view. Ensure there's
# no other tags with the same name before deleting
# playlist.
utils.playlist_xsp(plex_type,
section['section_name'],
2018-10-20 23:49:04 +11:00
section_id,
section['plex_type'],
2018-10-20 23:49:04 +11:00
True)
# Delete video node
if plex_type != "musicvideos":
VNODES.viewNode(
indexnumber=index,
tagname=section['section_name'],
2018-10-20 23:49:04 +11:00
mediatype=plex_type,
viewtype=None,
viewid=section_id,
delete=True)
# Added new playlist
if section_name not in playlists and plex_type in v.KODI_VIDEOTYPES:
utils.playlist_xsp(plex_type,
section_name,
section_id)
playlists.append(section_name)
# Add new video node
if section_name not in nodes and plex_type != "musicvideos":
VNODES.viewNode(index,
2018-10-20 23:49:04 +11:00
section_name,
plex_type,
None,
section_id)
nodes.append(section_name)
# Update items with new tag
2018-11-27 03:32:21 +11:00
for kodi_id in plexdb.kodiid_by_sectionid(section_id, plex_type):
2018-11-09 07:22:16 +11:00
kodidb.update_tag(
section['kodi_tagid'], tagid, kodi_id, section['plex_type'])
2018-10-20 23:49:04 +11:00
else:
# Validate the playlist exists or recreate it
if (section_name not in playlists and plex_type in
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
utils.playlist_xsp(plex_type,
section_name,
section_id)
playlists.append(section_name)
# Create the video node if not already exists
if section_name not in nodes and plex_type != "musicvideos":
VNODES.viewNode(index,
2018-10-20 23:49:04 +11:00
section_name,
plex_type,
None,
section_id)
nodes.append(section_name)
def _delete_kodi_db_items(section_id, section_type):
if section_type == v.PLEX_TYPE_MOVIE:
kodi_context = kodi_db.KodiVideoDB
types = ((v.PLEX_TYPE_MOVIE, itemtypes.Movie), )
elif section_type == v.PLEX_TYPE_SHOW:
kodi_context = kodi_db.KodiVideoDB
types = ((v.PLEX_TYPE_SHOW, itemtypes.Show),
(v.PLEX_TYPE_SEASON, itemtypes.Season),
(v.PLEX_TYPE_EPISODE, itemtypes.Episode))
elif section_type == v.PLEX_TYPE_ARTIST:
kodi_context = kodi_db.KodiMusicDB
types = ((v.PLEX_TYPE_ARTIST, itemtypes.Artist),
(v.PLEX_TYPE_ALBUM, itemtypes.Album),
(v.PLEX_TYPE_SONG, itemtypes.Song))
for plex_type, context in types:
while True:
with PlexDB() as plexdb:
plex_ids = list(plexdb.plexid_by_sectionid(section_id,
plex_type,
BATCH_SIZE))
with kodi_context(texture_db=True) as kodidb:
typus = context(None, plexdb=plexdb, kodidb=kodidb)
for plex_id in plex_ids:
if IS_CANCELED():
return False
typus.remove(plex_id)
if len(plex_ids) < BATCH_SIZE:
break
return True
2018-10-20 23:49:04 +11:00
def delete_sections(old_sections):
"""
Deletes all elements for a Plex section that has been deleted. (e.g. all
TV shows, Seasons and Episodes of a Show section)
"""
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:
plexdb.remove_section(section['section_id'])
def choose_libraries():
"""
Displays a dialog for the user to select the libraries he wants synched
Returns True if this was successful, False if not
"""
# Re-set value in order to make sure we got the lastest user input
app.SYNC.enable_music = utils.settings('enableMusic') == 'true'
import xbmcgui
sections = []
preselect = []
index = 0
for section in SECTIONS:
if not app.SYNC.enable_music and section['plex_type'] == v.PLEX_TYPE_ARTIST:
LOG.info('Ignoring music section: %s', section)
continue
elif section['plex_type'] == v.PLEX_TYPE_PHOTO:
continue
else:
sections.append(section['section_name'])
if section['sync_to_kodi']:
preselect.append(index)
index += 1
# "Select Plex libraries to sync"
selected = xbmcgui.Dialog().multiselect(utils.lang(30524),
sections,
preselect=preselect,
useDetails=False)
if selected is None:
# User canceled
return False
index = 0
with PlexDB() as plexdb:
for section in SECTIONS:
if not app.SYNC.enable_music and section['plex_type'] == v.PLEX_TYPE_ARTIST:
continue
elif section['plex_type'] == v.PLEX_TYPE_PHOTO:
continue
else:
sync = True if index in selected else False
plexdb.update_section_sync(section['section_id'], sync)
index += 1
sections = list(plexdb.all_sections())
LOG.info('Plex libraries to sync: %s', sections)
utils.settings('sections_asked_for_machine_identifier',
value=app.CONN.machine_identifier)
return True