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 common, videonodes
|
|
|
|
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
|
|
|
|
2019-02-05 23:21:08 +11:00
|
|
|
BATCH_SIZE = 500
|
2018-10-20 23:49:04 +11:00
|
|
|
VNODES = videonodes.VideoNodes()
|
|
|
|
PLAYLISTS = {}
|
|
|
|
NODES = {}
|
|
|
|
SECTIONS = []
|
2019-01-31 06:36:52 +11:00
|
|
|
# Need a way to interrupt
|
|
|
|
IS_CANCELED = None
|
2018-10-20 23:49:04 +11:00
|
|
|
|
|
|
|
|
2019-01-31 06:36:52 +11:00
|
|
|
def sync_from_pms(parent_self):
|
2018-10-20 23:49:04 +11:00
|
|
|
"""
|
|
|
|
Sync the Plex library sections
|
|
|
|
"""
|
2019-01-31 06:36:52 +11:00
|
|
|
global IS_CANCELED
|
|
|
|
IS_CANCELED = parent_self.isCanceled
|
|
|
|
try:
|
|
|
|
return _sync_from_pms()
|
|
|
|
finally:
|
|
|
|
IS_CANCELED = None
|
|
|
|
|
|
|
|
|
|
|
|
def _sync_from_pms():
|
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)
|
|
|
|
|
|
|
|
global PLAYLISTS, NODES, SECTIONS
|
|
|
|
SECTIONS = []
|
|
|
|
NODES = {
|
|
|
|
v.PLEX_TYPE_MOVIE: [],
|
|
|
|
v.PLEX_TYPE_SHOW: [],
|
|
|
|
v.PLEX_TYPE_ARTIST: [],
|
|
|
|
v.PLEX_TYPE_PHOTO: []
|
|
|
|
}
|
|
|
|
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)):
|
2018-10-26 03:28:41 +11:00
|
|
|
sorted_sections.append(section.attrib['title'])
|
2018-10-20 23:49:04 +11:00
|
|
|
LOG.debug('Sorted sections: %s', sorted_sections)
|
|
|
|
totalnodes = len(sorted_sections)
|
|
|
|
|
|
|
|
VNODES.clearProperties()
|
|
|
|
|
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)
|
2018-10-25 22:19:46 +11:00
|
|
|
old_sections = list(plexdb.section_ids())
|
2018-11-09 07:22:16 +11:00
|
|
|
with kodi_db.KodiVideoDB() as kodidb:
|
2018-10-20 23:49:04 +11:00
|
|
|
for section in sections:
|
|
|
|
_process_section(section,
|
2018-11-09 07:22:16 +11:00
|
|
|
kodidb,
|
2018-10-25 02:17:02 +11:00
|
|
|
plexdb,
|
2018-10-20 23:49:04 +11:00
|
|
|
sorted_sections,
|
|
|
|
old_sections,
|
|
|
|
totalnodes)
|
|
|
|
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:
|
2018-10-25 22:19:46 +11:00
|
|
|
SECTIONS = list(plexdb.section_infos())
|
2018-10-20 23:49:04 +11:00
|
|
|
utils.window('Plex.nodes.total', str(totalnodes))
|
|
|
|
LOG.info("Finished processing library sections: %s", SECTIONS)
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-11-09 07:22:16 +11:00
|
|
|
def _process_section(section_xml, kodidb, plexdb, sorted_sections,
|
2018-10-20 23:49:04 +11:00
|
|
|
old_sections, totalnodes):
|
|
|
|
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 totalnodes
|
|
|
|
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
|
|
|
global PLAYLISTS, NODES
|
|
|
|
# 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)
|
2018-10-20 23:49:04 +11:00
|
|
|
try:
|
|
|
|
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)
|
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(sorted_sections.index(section_name),
|
|
|
|
section_name,
|
|
|
|
plex_type,
|
|
|
|
None,
|
|
|
|
section_id)
|
|
|
|
nodes.append(section_name)
|
|
|
|
totalnodes += 1
|
|
|
|
# Add view to plex database
|
2018-10-25 02:17:02 +11:00
|
|
|
plexdb.add_section(section_id, section_name, plex_type, tagid)
|
2018-10-20 23:49:04 +11:00
|
|
|
else:
|
|
|
|
LOG.info('Found library section id %s, name %s, type %s, tagid %s',
|
|
|
|
section_id, current_sectionname, current_sectiontype,
|
|
|
|
current_tagid)
|
|
|
|
# Remove views that are still valid to delete rest later
|
|
|
|
try:
|
|
|
|
old_sections.remove(section_id)
|
|
|
|
except ValueError:
|
|
|
|
# View was just created, nothing to remove
|
|
|
|
pass
|
|
|
|
|
|
|
|
# View was modified, update with latest info
|
|
|
|
if current_sectionname != section_name:
|
|
|
|
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)
|
2018-10-20 23:49:04 +11:00
|
|
|
|
2018-10-25 02:17:02 +11:00
|
|
|
if plexdb.section_id_by_name(current_sectionname) 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,
|
|
|
|
current_sectionname,
|
|
|
|
section_id,
|
|
|
|
current_sectiontype,
|
|
|
|
True)
|
|
|
|
# Delete video node
|
|
|
|
if plex_type != "musicvideos":
|
|
|
|
VNODES.viewNode(
|
|
|
|
indexnumber=sorted_sections.index(section_name),
|
|
|
|
tagname=current_sectionname,
|
|
|
|
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(sorted_sections.index(section_name),
|
|
|
|
section_name,
|
|
|
|
plex_type,
|
|
|
|
None,
|
|
|
|
section_id)
|
|
|
|
nodes.append(section_name)
|
|
|
|
totalnodes += 1
|
|
|
|
# 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(
|
2018-11-27 03:32:21 +11:00
|
|
|
current_tagid, tagid, kodi_id, current_sectiontype)
|
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(sorted_sections.index(section_name),
|
|
|
|
section_name,
|
|
|
|
plex_type,
|
|
|
|
None,
|
|
|
|
section_id)
|
|
|
|
nodes.append(section_name)
|
|
|
|
totalnodes += 1
|
|
|
|
return totalnodes
|
|
|
|
|
|
|
|
|
2019-01-31 00:34:35 +11:00
|
|
|
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:
|
2019-01-31 06:36:52 +11:00
|
|
|
if IS_CANCELED():
|
2019-01-31 00:34:35 +11:00
|
|
|
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)
|
|
|
|
"""
|
2019-01-31 00:34:35 +11:00
|
|
|
try:
|
|
|
|
with PlexDB() as plexdb:
|
|
|
|
old_sections = [plexdb.section(x) for x in old_sections]
|
2018-10-20 23:49:04 +11:00
|
|
|
LOG.info("Removing entire Plex library sections: %s", old_sections)
|
2019-01-31 00:34:35 +11:00
|
|
|
for section in old_sections:
|
|
|
|
# "Deleting <section_name>"
|
|
|
|
utils.dialog('notification',
|
|
|
|
heading='{plex}',
|
|
|
|
message='%s %s' % (utils.lang(30052), section[1]),
|
|
|
|
icon='{plex}',
|
|
|
|
sound=False)
|
|
|
|
if section[2] == v.PLEX_TYPE_PHOTO:
|
|
|
|
# not synced - just remove the link in our Plex sections table
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if not _delete_kodi_db_items(section[0], section[2]):
|
|
|
|
return
|
|
|
|
# Only remove Plex entry if we've removed all items first
|
|
|
|
with PlexDB() as plexdb:
|
2018-10-25 02:17:02 +11:00
|
|
|
plexdb.remove_section(section[0])
|
2019-01-31 00:34:35 +11:00
|
|
|
finally:
|
|
|
|
common.update_kodi_library()
|