Rewire llibrary sync, part 6
This commit is contained in:
parent
2f96749fc7
commit
f520cebf66
11 changed files with 928 additions and 244 deletions
|
@ -3,6 +3,18 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
from .movies import Movie
|
||||
from .tvshows import Show, Season, Episode
|
||||
from .music import Artist, Album, Song
|
||||
from .. import variables as v
|
||||
|
||||
# Note: always use same order of URL arguments, NOT urlencode:
|
||||
# plex_id=<plex_id>&plex_type=<plex_type>&mode=play
|
||||
|
||||
ITEMTYPE_FROM_PLEXTYPE = {
|
||||
v.PLEX_TYPE_MOVIE: Movie,
|
||||
v.PLEX_TYPE_SHOW: Show,
|
||||
v.PLEX_TYPE_SEASON: Season,
|
||||
v.PLEX_TYPE_EPISODE: Episode,
|
||||
v.PLEX_TYPE_ARTIST: Artist,
|
||||
v.PLEX_TYPE_ALBUM: Album,
|
||||
v.PLEX_TYPE_SONG: Song
|
||||
}
|
||||
|
|
544
resources/lib/itemtypes/music.py
Normal file
544
resources/lib/itemtypes/music.py
Normal file
|
@ -0,0 +1,544 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
|
||||
from .common import ItemBase, process_path
|
||||
from ..plex_api import API
|
||||
from .. import state, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.music')
|
||||
|
||||
|
||||
class MusicMixin(object):
|
||||
def remove(self, plex_id, plex_type=None):
|
||||
"""
|
||||
Remove the entire TV shows object (show, season or episode) including
|
||||
all associated entries from the Kodi DB.
|
||||
"""
|
||||
if plex_type is None:
|
||||
entry = self.plex_db.episode(plex_id)
|
||||
kodi_type = v.KODI_TYPE_EPISODE
|
||||
if not entry:
|
||||
entry = self.plex_db.season(plex_id)
|
||||
kodi_type = v.KODI_TYPE_SEASON
|
||||
if not entry:
|
||||
entry = self.plex_db.show(plex_id)
|
||||
kodi_type = v.KODI_TYPE_SHOW
|
||||
else:
|
||||
pass
|
||||
if not entry:
|
||||
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
|
||||
return
|
||||
kodi_id = entry[0]
|
||||
file_id = entry[1]
|
||||
parent_id = entry[3]
|
||||
kodi_type = entry[4]
|
||||
LOG.debug("Removing %s with kodi_id: %s file_id: %s parent_id: %s",
|
||||
kodi_type, kodi_id, file_id, parent_id)
|
||||
|
||||
# Remove the plex reference
|
||||
self.plex_db.removeItem(plex_id)
|
||||
|
||||
# EPISODE #####
|
||||
if kodi_type == v.KODI_TYPE_EPISODE:
|
||||
# Delete episode, verify season and tvshow
|
||||
self.remove_episode(kodi_id, file_id)
|
||||
# Season verification
|
||||
season = self.plex_db.getItem_byKodiId(parent_id,
|
||||
v.KODI_TYPE_SEASON)
|
||||
if season is not None:
|
||||
if not self.plex_db.getItem_byParentId(parent_id,
|
||||
v.KODI_TYPE_EPISODE):
|
||||
# No episode left for season - so delete the season
|
||||
self.remove_season(parent_id)
|
||||
self.plex_db.removeItem(season[0])
|
||||
show = self.plex_db.getItem_byKodiId(season[1],
|
||||
v.KODI_TYPE_SHOW)
|
||||
if show is not None:
|
||||
if not self.plex_db.getItem_byParentId(season[1],
|
||||
v.KODI_TYPE_SEASON):
|
||||
# No seasons for show left - so delete entire show
|
||||
self.remove_show(season[1])
|
||||
self.plex_db.removeItem(show[0])
|
||||
else:
|
||||
LOG.error('No show found in Plex DB for season %s', season)
|
||||
else:
|
||||
LOG.error('No season found in Plex DB!')
|
||||
# SEASON #####
|
||||
elif kodi_type == v.KODI_TYPE_SEASON:
|
||||
# Remove episodes, season, verify tvshow
|
||||
for episode in self.plex_db.getItem_byParentId(
|
||||
kodi_id, v.KODI_TYPE_EPISODE):
|
||||
self.remove_episode(episode[1], episode[2])
|
||||
self.plex_db.removeItem(episode[0])
|
||||
# Remove season
|
||||
self.remove_season(kodi_id)
|
||||
# Show verification
|
||||
if not self.plex_db.getItem_byParentId(parent_id,
|
||||
v.KODI_TYPE_SEASON):
|
||||
# There's no other season left, delete the show
|
||||
self.remove_show(parent_id)
|
||||
self.plex_db.removeItem_byKodiId(parent_id, v.KODI_TYPE_SHOW)
|
||||
# TVSHOW #####
|
||||
elif kodi_type == v.KODI_TYPE_SHOW:
|
||||
# Remove episodes, seasons and the tvshow itself
|
||||
for season in self.plex_db.getItem_byParentId(kodi_id,
|
||||
v.KODI_TYPE_SEASON):
|
||||
for episode in self.plex_db.getItem_byParentId(
|
||||
season[1], v.KODI_TYPE_EPISODE):
|
||||
self.remove_episode(episode[1], episode[2])
|
||||
self.plex_db.removeItem(episode[0])
|
||||
self.remove_season(season[1])
|
||||
self.plex_db.removeItem(season[0])
|
||||
self.remove_show(kodi_id)
|
||||
|
||||
LOG.debug("Deleted %s %s from Kodi database", kodi_type, plex_id)
|
||||
|
||||
def remove_show(self, kodi_id):
|
||||
"""
|
||||
Remove a TV show, and only the show, no seasons or episodes
|
||||
"""
|
||||
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
|
||||
self.artwork.delete_artwork(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
self.kodicursor)
|
||||
self.kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?",
|
||||
(kodi_id,))
|
||||
if v.KODIVERSION >= 17:
|
||||
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
|
||||
LOG.debug("Removed tvshow: %s", kodi_id)
|
||||
|
||||
def remove_season(self, kodi_id):
|
||||
"""
|
||||
Remove a season, and only a season, not the show or episodes
|
||||
"""
|
||||
self.artwork.delete_artwork(kodi_id,
|
||||
v.KODI_TYPE_SEASON,
|
||||
self.kodicursor)
|
||||
self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?",
|
||||
(kodi_id,))
|
||||
LOG.debug("Removed season: %s", kodi_id)
|
||||
|
||||
def remove_episode(self, kodi_id, file_id):
|
||||
"""
|
||||
Remove an episode, and episode only from the Kodi DB (not Plex DB)
|
||||
"""
|
||||
self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE)
|
||||
self.artwork.delete_artwork(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
self.kodicursor)
|
||||
self.kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?",
|
||||
(kodi_id,))
|
||||
if v.KODIVERSION >= 17:
|
||||
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
|
||||
LOG.debug("Removed episode: %s", kodi_id)
|
||||
|
||||
|
||||
class Artist(ItemBase, MusicMixin):
|
||||
"""
|
||||
For Plex library-type artists
|
||||
"""
|
||||
def add_update(self, xml, section_name=None, section_id=None,
|
||||
children=None):
|
||||
"""
|
||||
Process a single artist
|
||||
"""
|
||||
api = API(xml)
|
||||
update_item = True
|
||||
plex_id = api.plex_id()
|
||||
LOG.debug('Adding show with plex_id %s', plex_id)
|
||||
if not plex_id:
|
||||
LOG.error("Cannot parse XML data for TV show: %s", xml.attrib)
|
||||
return
|
||||
show = self.plex_db.show(plex_id)
|
||||
try:
|
||||
kodi_id = show[3]
|
||||
kodi_pathid = show[4]
|
||||
except TypeError:
|
||||
update_item = False
|
||||
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
|
||||
self.kodicursor.execute(query)
|
||||
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||
else:
|
||||
# Verification the item is still in Kodi
|
||||
self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?',
|
||||
(kodi_id,))
|
||||
try:
|
||||
self.kodicursor.fetchone()[0]
|
||||
except TypeError:
|
||||
# item is not found, let's recreate it.
|
||||
update_item = False
|
||||
LOG.info("idShow: %s missing from Kodi, repairing the entry.",
|
||||
kodi_id)
|
||||
|
||||
genres = api.genre_list()
|
||||
genre = api.list_to_string(genres)
|
||||
studios = api.music_studio_list()
|
||||
studio = api.list_to_string(studios)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
if state.DIRECT_PATHS:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = api.validate_playurl(api.tv_show_path(),
|
||||
api.plex_type(),
|
||||
folder=True)
|
||||
if playurl is None:
|
||||
return
|
||||
path, toplevelpath = process_path(playurl)
|
||||
toppathid = self.kodi_db.add_video_path(
|
||||
toplevelpath,
|
||||
content='tvshows',
|
||||
scraper='metadata.local')
|
||||
else:
|
||||
# Set plugin path
|
||||
toplevelpath = "plugin://%s.tvshows/" % v.ADDON_ID
|
||||
path = "%s%s/" % (toplevelpath, plex_id)
|
||||
# Do NOT set a parent id because addon-path cannot be "stacked"
|
||||
toppathid = None
|
||||
|
||||
kodi_pathid = self.kodi_db.add_video_path(path,
|
||||
date_added=api.date_created(),
|
||||
id_parent_path=toppathid)
|
||||
# UPDATE THE TVSHOW #####
|
||||
if update_item:
|
||||
LOG.info("UPDATE tvshow plex_id: %s - Title: %s",
|
||||
plex_id, api.title())
|
||||
# update new ratings Kodi 17
|
||||
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.update_ratings(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
"default",
|
||||
api.audience_rating(),
|
||||
api.votecount(),
|
||||
rating_id)
|
||||
# update new uniqueid Kodi 17
|
||||
if api.provider('tvdb') is not None:
|
||||
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.provider('tvdb'),
|
||||
"unknown",
|
||||
uniqueid)
|
||||
else:
|
||||
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
||||
uniqueid = -1
|
||||
# Update the tvshow entry
|
||||
query = '''
|
||||
UPDATE tvshow
|
||||
SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,
|
||||
c12 = ?, c13 = ?, c14 = ?, c15 = ?
|
||||
WHERE idShow = ?
|
||||
'''
|
||||
self.kodicursor.execute(
|
||||
query, (api.title(), api.plot(), rating_id,
|
||||
api.premiere_date(), genre, api.title(), uniqueid,
|
||||
api.content_rating(), studio, api.sorttitle(),
|
||||
kodi_id))
|
||||
# OR ADD THE TVSHOW #####
|
||||
else:
|
||||
LOG.info("ADD tvshow plex_id: %s - Title: %s",
|
||||
plex_id, api.title())
|
||||
# Link the path
|
||||
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
|
||||
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
|
||||
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.add_ratings(rating_id,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
"default",
|
||||
api.audience_rating(),
|
||||
api.votecount())
|
||||
if api.provider('tvdb') is not None:
|
||||
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW)
|
||||
self.kodi_db.add_uniqueid(uniqueid,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.provider('tvdb'),
|
||||
"unknown")
|
||||
else:
|
||||
uniqueid = -1
|
||||
# Create the tvshow entry
|
||||
query = '''
|
||||
INSERT INTO tvshow(
|
||||
idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14,
|
||||
c15)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.kodicursor.execute(
|
||||
query, (kodi_id, api.title(), api.plot(), rating_id,
|
||||
api.premiere_date(), genre, api.title(), uniqueid,
|
||||
api.content_rating(), studio, api.sorttitle()))
|
||||
|
||||
self.kodi_db.modify_people(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.people_list())
|
||||
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres)
|
||||
self.artwork.modify_artwork(api.artwork(),
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
self.kodicursor)
|
||||
# Process studios
|
||||
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios)
|
||||
# Process tags: view, PMS collection tags
|
||||
tags = [section_name]
|
||||
tags.extend([i for _, i in api.collection_list()])
|
||||
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
|
||||
self.plex_db.add_show(plex_id=plex_id,
|
||||
checksum=api.checksum(),
|
||||
section_id=section_id,
|
||||
kodi_id=kodi_id,
|
||||
kodi_pathid=kodi_pathid,
|
||||
last_sync=self.last_sync)
|
||||
|
||||
|
||||
class Album(ItemBase, MusicMixin):
|
||||
def add_update(self, xml, section_name=None, section_id=None,
|
||||
children=None):
|
||||
"""
|
||||
Process a single album
|
||||
"""
|
||||
api = API(xml)
|
||||
plex_id = api.plex_id()
|
||||
LOG.debug('Adding season with plex_id %s', plex_id)
|
||||
if not plex_id:
|
||||
LOG.error('Error getting plex_id for season, skipping: %s',
|
||||
xml.attrib)
|
||||
return
|
||||
show_id = api.parent_plex_id()
|
||||
show = self.plex_db.show(show_id)
|
||||
try:
|
||||
parent_id = show[3]
|
||||
except TypeError:
|
||||
LOG.error('Could not find parent tv show for season %s. '
|
||||
'Skipping season for now.', plex_id)
|
||||
return
|
||||
kodi_id = self.kodi_db.add_season(parent_id, api.season_number())
|
||||
self.artwork.modify_artwork(api.artwork(),
|
||||
kodi_id,
|
||||
v.KODI_TYPE_SEASON,
|
||||
self.kodicursor)
|
||||
self.plex_db.add_season(plex_id=plex_id,
|
||||
checksum=api.checksum(),
|
||||
section_id=section_id,
|
||||
show_id=show_id,
|
||||
parent_id=parent_id,
|
||||
kodi_id=kodi_id,
|
||||
last_sync=self.last_sync)
|
||||
|
||||
|
||||
class Song(ItemBase, MusicMixin):
|
||||
def add_update(self, xml, section_name=None, section_id=None,
|
||||
children=None):
|
||||
"""
|
||||
Process single song/track
|
||||
"""
|
||||
api = API(xml)
|
||||
update_item = True
|
||||
plex_id = api.plex_id()
|
||||
LOG.debug('Adding episode with plex_id %s', plex_id)
|
||||
if not plex_id:
|
||||
LOG.error('Error getting plex_id for episode, skipping: %s',
|
||||
xml.attrib)
|
||||
return
|
||||
entry = self.plex_db.item_by_id(plex_id)
|
||||
try:
|
||||
kodi_id = entry[0]
|
||||
old_kodi_fileid = entry[1]
|
||||
kodi_pathid = entry[2]
|
||||
except TypeError:
|
||||
update_item = False
|
||||
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
|
||||
self.kodicursor.execute(query)
|
||||
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||
else:
|
||||
# Verification the item is still in Kodi
|
||||
query = 'SELECT * FROM episode WHERE idEpisode = ? LIMIT 1'
|
||||
self.kodicursor.execute(query, (kodi_id, ))
|
||||
try:
|
||||
self.kodicursor.fetchone()[0]
|
||||
except TypeError:
|
||||
# item is not found, let's recreate it.
|
||||
update_item = False
|
||||
LOG.info('idEpisode %s missing from Kodi, repairing entry.',
|
||||
kodi_id)
|
||||
|
||||
peoples = api.people()
|
||||
director = api.list_to_string(peoples['Director'])
|
||||
writer = api.list_to_string(peoples['Writer'])
|
||||
userdata = api.userdata()
|
||||
show_id, season_id, _, season_no, episode_no = api.episode_data()
|
||||
|
||||
if season_no is None:
|
||||
season_no = -1
|
||||
if episode_no is None:
|
||||
episode_no = -1
|
||||
airs_before_season = "-1"
|
||||
airs_before_episode = "-1"
|
||||
|
||||
show = self.plex_db.show(show_id)
|
||||
try:
|
||||
grandparent_id = show[3]
|
||||
except TypeError:
|
||||
LOG.error("Parent tvshow now found, skip item")
|
||||
return False
|
||||
parent_id = self.kodi_db.add_season(grandparent_id, season_no)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
do_indirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
playurl = api.file_path(force_first_media=True)
|
||||
if playurl is None:
|
||||
do_indirect = True
|
||||
else:
|
||||
playurl = api.validate_playurl(playurl, v.PLEX_TYPE_EPISODE)
|
||||
if "\\" in playurl:
|
||||
# Local path
|
||||
filename = playurl.rsplit("\\", 1)[1]
|
||||
else:
|
||||
# Network share
|
||||
filename = playurl.rsplit("/", 1)[1]
|
||||
path = playurl.replace(filename, "")
|
||||
parent_path_id = self.kodi_db.parent_path_id(path)
|
||||
kodi_pathid = self.kodi_db.add_video_path(
|
||||
path, id_parent_path=parent_path_id)
|
||||
if do_indirect:
|
||||
# Set plugin path - do NOT use "intermediate" paths for the show
|
||||
# as with direct paths!
|
||||
filename = api.file_name(force_first_media=True)
|
||||
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, show_id)
|
||||
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
|
||||
playurl = filename
|
||||
# Root path tvshows/ already saved in Kodi DB
|
||||
kodi_pathid = self.kodi_db.add_video_path(path)
|
||||
|
||||
# add/retrieve kodi_pathid and fileid
|
||||
# if the path or file already exists, the calls return current value
|
||||
kodi_fileid = self.kodi_db.add_file(filename,
|
||||
kodi_pathid,
|
||||
api.date_created())
|
||||
|
||||
# UPDATE THE EPISODE #####
|
||||
if update_item:
|
||||
LOG.info("UPDATE episode plex_id: %s, Title: %s",
|
||||
plex_id, api.title())
|
||||
if kodi_fileid != old_kodi_fileid:
|
||||
self.kodi_db.remove_file(old_kodi_fileid)
|
||||
ratingid = self.kodi_db.get_ratingid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.update_ratings(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
"default",
|
||||
userdata['Rating'],
|
||||
api.votecount(),
|
||||
ratingid)
|
||||
# update new uniqueid Kodi 17
|
||||
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.provider('tvdb'),
|
||||
"tvdb",
|
||||
uniqueid)
|
||||
query = '''
|
||||
UPDATE episode
|
||||
SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?,
|
||||
c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?,
|
||||
c18 = ?, c19 = ?, idFile=?, idSeason = ?,
|
||||
userrating = ?
|
||||
WHERE idEpisode = ?
|
||||
'''
|
||||
self.kodicursor.execute(
|
||||
query, (api.title(), api.plot(), ratingid, writer,
|
||||
api.premiere_date(), api.runtime(), director, season_no,
|
||||
episode_no, api.title(), airs_before_season,
|
||||
airs_before_episode, playurl, kodi_pathid, kodi_fileid,
|
||||
parent_id, userdata['UserRating'], kodi_id))
|
||||
|
||||
# OR ADD THE EPISODE #####
|
||||
else:
|
||||
LOG.info("ADD episode plex_id: %s - Title: %s",
|
||||
plex_id, api.title())
|
||||
# Create the episode entry
|
||||
rating_id = self.kodi_db.get_ratingid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.add_ratings(rating_id,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
"default",
|
||||
userdata['Rating'],
|
||||
api.votecount())
|
||||
# add new uniqueid Kodi 17
|
||||
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE)
|
||||
self.kodi_db.add_uniqueid(uniqueid,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.provider('tvdb'),
|
||||
"tvdb")
|
||||
query = '''
|
||||
INSERT INTO episode( idEpisode, idFile, c00, c01, c03, c04,
|
||||
c05, c09, c10, c12, c13, c14, idShow, c15, c16, c18,
|
||||
c19, idSeason, userrating)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?)
|
||||
'''
|
||||
self.kodicursor.execute(
|
||||
query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id,
|
||||
writer, api.premiere_date(), api.runtime(), director,
|
||||
season_no, episode_no, api.title(), grandparent_id,
|
||||
airs_before_season, airs_before_episode, playurl,
|
||||
kodi_pathid, parent_id, userdata['UserRating']))
|
||||
|
||||
self.kodi_db.modify_people(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.people_list())
|
||||
self.artwork.modify_artwork(api.artwork(),
|
||||
kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
self.kodicursor)
|
||||
streams = api.mediastreams()
|
||||
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime())
|
||||
self.kodi_db.set_resume(kodi_fileid,
|
||||
api.resume_point(),
|
||||
api.runtime(),
|
||||
userdata['PlayCount'],
|
||||
userdata['LastPlayedDate'],
|
||||
None) # Do send None, we check here
|
||||
if not state.DIRECT_PATHS:
|
||||
# need to set a SECOND file entry for a path without plex show id
|
||||
filename = api.file_name(force_first_media=True)
|
||||
path = 'plugin://%s.tvshows/' % v.ADDON_ID
|
||||
# Filename is exactly the same, WITH plex show id!
|
||||
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
|
||||
filename))
|
||||
kodi_pathid = self.kodi_db.add_video_path(path)
|
||||
kodi_fileid = self.kodi_db.add_file(filename,
|
||||
kodi_pathid,
|
||||
api.date_created())
|
||||
self.kodi_db.set_resume(kodi_fileid,
|
||||
api.resume_point(),
|
||||
api.runtime(),
|
||||
userdata['PlayCount'],
|
||||
userdata['LastPlayedDate'],
|
||||
None) # Do send None - 2nd entry
|
||||
self.plex_db.add_episode(plex_id=plex_id,
|
||||
checksum=api.checksum(),
|
||||
section_id=section_id,
|
||||
show_id=show_id,
|
||||
grandparent_id=grandparent_id,
|
||||
season_id=season_id,
|
||||
parent_id=parent_id,
|
||||
kodi_id=kodi_id,
|
||||
kodi_fileid=kodi_fileid,
|
||||
kodi_pathid=kodi_pathid,
|
||||
last_sync=self.last_sync)
|
|
@ -16,84 +16,59 @@ class TvShowMixin(object):
|
|||
Remove the entire TV shows object (show, season or episode) including
|
||||
all associated entries from the Kodi DB.
|
||||
"""
|
||||
if plex_type is None:
|
||||
entry = self.plex_db.episode(plex_id)
|
||||
kodi_type = v.KODI_TYPE_EPISODE
|
||||
if not entry:
|
||||
entry = self.plex_db.season(plex_id)
|
||||
kodi_type = v.KODI_TYPE_SEASON
|
||||
if not entry:
|
||||
entry = self.plex_db.show(plex_id)
|
||||
kodi_type = v.KODI_TYPE_SHOW
|
||||
else:
|
||||
pass
|
||||
if not entry:
|
||||
db_item = self.plex_db.item_by_id(plex_id, plex_type)
|
||||
if not db_item:
|
||||
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
|
||||
return
|
||||
kodi_id = entry[0]
|
||||
file_id = entry[1]
|
||||
parent_id = entry[3]
|
||||
kodi_type = entry[4]
|
||||
LOG.debug("Removing %s with kodi_id: %s file_id: %s parent_id: %s",
|
||||
kodi_type, kodi_id, file_id, parent_id)
|
||||
LOG.debug('Removing %s %s with kodi_id: %s',
|
||||
db_item['plex_type'], plex_id, db_item['kodi_id'])
|
||||
|
||||
# Remove the plex reference
|
||||
self.plex_db.removeItem(plex_id)
|
||||
self.plex_db.remove(plex_id, db_item['plex_type'])
|
||||
|
||||
# EPISODE #####
|
||||
if kodi_type == v.KODI_TYPE_EPISODE:
|
||||
if db_item['plex_type'] == v.PLEX_TYPE_EPISODE:
|
||||
# Delete episode, verify season and tvshow
|
||||
self.remove_episode(kodi_id, file_id)
|
||||
self.remove_episode(db_item['kodi_id'], db_item['kodi_fileid'])
|
||||
# Season verification
|
||||
season = self.plex_db.getItem_byKodiId(parent_id,
|
||||
v.KODI_TYPE_SEASON)
|
||||
if season is not None:
|
||||
if not self.plex_db.getItem_byParentId(parent_id,
|
||||
v.KODI_TYPE_EPISODE):
|
||||
# No episode left for season - so delete the season
|
||||
self.remove_season(parent_id)
|
||||
self.plex_db.removeItem(season[0])
|
||||
show = self.plex_db.getItem_byKodiId(season[1],
|
||||
v.KODI_TYPE_SHOW)
|
||||
if show is not None:
|
||||
if not self.plex_db.getItem_byParentId(season[1],
|
||||
v.KODI_TYPE_SEASON):
|
||||
# No seasons for show left - so delete entire show
|
||||
self.remove_show(season[1])
|
||||
self.plex_db.removeItem(show[0])
|
||||
else:
|
||||
LOG.error('No show found in Plex DB for season %s', season)
|
||||
else:
|
||||
LOG.error('No season found in Plex DB!')
|
||||
# SEASON #####
|
||||
elif kodi_type == v.KODI_TYPE_SEASON:
|
||||
# Remove episodes, season, verify tvshow
|
||||
for episode in self.plex_db.getItem_byParentId(
|
||||
kodi_id, v.KODI_TYPE_EPISODE):
|
||||
self.remove_episode(episode[1], episode[2])
|
||||
self.plex_db.removeItem(episode[0])
|
||||
# Remove season
|
||||
self.remove_season(kodi_id)
|
||||
if not self.plex_db.season_has_episodes(db_item['season_id']):
|
||||
# No episode left for this season - so delete the season
|
||||
self.remove_season(db_item['parent_id'])
|
||||
self.plex_db.remove(db_item['season_id'], v.PLEX_TYPE_SEASON)
|
||||
# Show verification
|
||||
if not self.plex_db.getItem_byParentId(parent_id,
|
||||
v.KODI_TYPE_SEASON):
|
||||
# There's no other season left, delete the show
|
||||
self.remove_show(parent_id)
|
||||
self.plex_db.removeItem_byKodiId(parent_id, v.KODI_TYPE_SHOW)
|
||||
if (not self.plex_db.show_has_seasons(db_item['show_id']) and
|
||||
not self.plex_db.show_has_episodes(db_item['show_id'])):
|
||||
# No seasons for show left - so delete entire show
|
||||
self.remove_show(db_item['grandparent_id'])
|
||||
self.plex_db.remove(db_item['show_id'], v.PLEX_TYPE_SHOW)
|
||||
# SEASON #####
|
||||
elif db_item['plex_type'] == v.PLEX_TYPE_SEASON:
|
||||
# Remove episodes, season, verify tvshow
|
||||
for episode in self.plex_db.episode_by_season(db_item['plex_id']):
|
||||
self.remove_episode(episode['kodi_id'], episode['kodi_fileid'])
|
||||
self.plex_db.remove(episode['plex_id'], v.PLEX_TYPE_EPISODE)
|
||||
# Remove season
|
||||
self.remove_season(db_item['kodi_id'])
|
||||
# Show verification
|
||||
if (not self.plex_db.show_has_seasons(db_item['show_id']) and
|
||||
not self.plex_db.show_has_episodes(db_item['show_id'])):
|
||||
# There's no other season or episode left, delete the show
|
||||
self.remove_show(db_item['parent_id'])
|
||||
self.plex_db.remove(db_item['show_id'], v.KODI_TYPE_SHOW)
|
||||
# TVSHOW #####
|
||||
elif kodi_type == v.KODI_TYPE_SHOW:
|
||||
elif db_item['plex_type'] == v.PLEX_TYPE_SHOW:
|
||||
# Remove episodes, seasons and the tvshow itself
|
||||
for season in self.plex_db.getItem_byParentId(kodi_id,
|
||||
v.KODI_TYPE_SEASON):
|
||||
for episode in self.plex_db.getItem_byParentId(
|
||||
season[1], v.KODI_TYPE_EPISODE):
|
||||
self.remove_episode(episode[1], episode[2])
|
||||
self.plex_db.removeItem(episode[0])
|
||||
self.remove_season(season[1])
|
||||
self.plex_db.removeItem(season[0])
|
||||
self.remove_show(kodi_id)
|
||||
for episode in self.plex_db.episode_by_show(db_item['plex_id']):
|
||||
self.remove_episode(episode['kodi_id'],
|
||||
episode['kodi_fileid'])
|
||||
self.plex_db.remove(episode['plex_id'], v.PLEX_TYPE_EPISODE)
|
||||
for season in self.plex_db.season_by_show(db_item['plex_id']):
|
||||
self.remove_season(season['kodi_id'])
|
||||
self.plex_db.remove(season['plex_id'], v.PLEX_TYPE_SEASON)
|
||||
self.remove_show(db_item['kodi_id'])
|
||||
|
||||
LOG.debug("Deleted %s %s from Kodi database", kodi_type, plex_id)
|
||||
LOG.debug('Deleted %s %s from all databases',
|
||||
db_item['plex_type'], db_item['plex_id'])
|
||||
|
||||
def remove_show(self, kodi_id):
|
||||
"""
|
||||
|
|
|
@ -3,3 +3,4 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
|
||||
from .full_sync import start, PLAYLIST_SYNC_ENABLED
|
||||
from .time import sync_pms_time
|
||||
from .common import update_kodi_library
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from threading import Thread
|
||||
from Queue import Empty
|
||||
import xbmc
|
||||
|
||||
from ..plex_api import API
|
||||
from .. import utils, plexdb_functions as plexdb, kodidb_functions as kodidb
|
||||
from ..plex_db import PlexDB
|
||||
from .. import backgroundthread
|
||||
from ..backgroundthread.Queue import Empty
|
||||
from .. import utils, kodidb_functions as kodidb
|
||||
from .. import itemtypes, artwork, plex_functions as PF, variables as v, state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.library_sync.fanart')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'DB_SCAN',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
class ThreadedProcessFanart(Thread):
|
||||
class ThreadedProcessFanart(backgroundthread.KillableThread):
|
||||
"""
|
||||
Threaded download of additional fanart in the background
|
||||
|
||||
|
@ -36,27 +33,57 @@ class ThreadedProcessFanart(Thread):
|
|||
"""
|
||||
def __init__(self, queue):
|
||||
self.queue = queue
|
||||
Thread.__init__(self)
|
||||
super(ThreadedProcessFanart, self).__init__()
|
||||
|
||||
def isCanceled(self):
|
||||
return xbmc.abortRequested or state.STOP_PKC
|
||||
|
||||
def isSuspended(self):
|
||||
return (state.SUSPEND_LIBRARY_THREAD or
|
||||
state.DB_SCAN or
|
||||
state.STOP_SYNC or
|
||||
state.SUSPEND_SYNC)
|
||||
|
||||
def run(self):
|
||||
LOG.info('---===### Starting FanartSync ###===---')
|
||||
try:
|
||||
self._run()
|
||||
except:
|
||||
utils.ERROR(txt='FanartSync crashed', notify=True)
|
||||
raise
|
||||
LOG.info('---===### Stopping FanartSync ###===---')
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Do the work
|
||||
"""
|
||||
LOG.debug("---===### Starting FanartSync ###===---")
|
||||
while not self.stopped():
|
||||
# First run through our already synced items in the Plex DB
|
||||
for plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
|
||||
with PlexDB() as plexdb:
|
||||
for plex_id in plexdb.fanart(plex_type):
|
||||
if self.isCanceled():
|
||||
break
|
||||
while self.isSuspended():
|
||||
if self.isCanceled():
|
||||
break
|
||||
xbmc.sleep(1000)
|
||||
process_item(plexdb, {'plex_id': plex_id,
|
||||
'plex_type': plex_type,
|
||||
'refresh': False})
|
||||
|
||||
# Then keep checking the queue for new items
|
||||
while not self.isCanceled():
|
||||
# In the event the server goes offline
|
||||
while self.suspended():
|
||||
while self.isSuspended():
|
||||
# Set in service.py
|
||||
if self.stopped():
|
||||
# Abort was requested while waiting. We should exit
|
||||
LOG.debug("---===### Stopped FanartSync ###===---")
|
||||
if self.isCanceled():
|
||||
return
|
||||
xbmc.sleep(1000)
|
||||
# grabs Plex item from queue
|
||||
try:
|
||||
item = self.queue.get(block=False)
|
||||
except Empty:
|
||||
xbmc.sleep(200)
|
||||
xbmc.sleep(1000)
|
||||
continue
|
||||
self.queue.task_done()
|
||||
if isinstance(item, artwork.ArtworkSyncMessage):
|
||||
|
@ -68,26 +95,23 @@ class ThreadedProcessFanart(Thread):
|
|||
sound=False)
|
||||
continue
|
||||
LOG.debug('Get additional fanart for Plex id %s', item['plex_id'])
|
||||
_process(item)
|
||||
LOG.debug("---===### Stopped FanartSync ###===---")
|
||||
with PlexDB() as plexdb:
|
||||
process_item(plexdb, item)
|
||||
|
||||
|
||||
def _process(item):
|
||||
def process_item(plexdb, item):
|
||||
done = False
|
||||
try:
|
||||
artworks = None
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
db_item = plex_db.getItem_byId(item['plex_id'])
|
||||
try:
|
||||
kodi_id = db_item[0]
|
||||
kodi_type = db_item[4]
|
||||
except TypeError:
|
||||
db_item = plexdb.item_by_id(item['plex_id'], item['plex_type'])
|
||||
if not db_item:
|
||||
LOG.error('Could not get Kodi id for plex id %s, abort getfanart',
|
||||
item['plex_id'])
|
||||
return
|
||||
if item['refresh'] is False:
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
artworks = kodi_db.get_art(kodi_id, kodi_type)
|
||||
artworks = kodi_db.get_art(db_item['kodi_id'],
|
||||
db_item['kodi_type'])
|
||||
# Check if we even need to get additional art
|
||||
for key in v.ALL_KODI_ARTWORK:
|
||||
if key not in artworks:
|
||||
|
@ -111,9 +135,10 @@ def _process(item):
|
|||
artworks = api.artwork()
|
||||
# Get additional missing artwork from fanart artwork sites
|
||||
artworks = api.fanart_artwork(artworks)
|
||||
with getattr(itemtypes,
|
||||
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as itm:
|
||||
itm.set_fanart(artworks, kodi_id, kodi_type)
|
||||
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']] as context:
|
||||
context.set_fanart(artworks,
|
||||
db_item['kodi_id'],
|
||||
db_item['kodi_type'])
|
||||
# Additional fanart for sets/collections
|
||||
if api.plex_type() == v.PLEX_TYPE_MOVIE:
|
||||
for _, setname in api.collection_list():
|
||||
|
@ -128,14 +153,13 @@ def _process(item):
|
|||
for art in kodi_artwork:
|
||||
if art in external_set_artwork:
|
||||
del external_set_artwork[art]
|
||||
with itemtypes.Movies() as movie_db:
|
||||
movie_db.artwork.modify_artwork(external_set_artwork,
|
||||
setid,
|
||||
v.KODI_TYPE_SET,
|
||||
movie_db.kodicursor)
|
||||
with itemtypes.Movie() as movie:
|
||||
movie.artwork.modify_artwork(external_set_artwork,
|
||||
setid,
|
||||
v.KODI_TYPE_SET,
|
||||
movie.kodicursor)
|
||||
done = True
|
||||
finally:
|
||||
if done is True:
|
||||
LOG.debug('Done getting fanart for Plex id %s', item['plex_id'])
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
plex_db.set_fanart_synched(item['plex_id'])
|
||||
plexdb.set_fanart_synced(item['plex_id'], item['plex_type'])
|
||||
|
|
|
@ -38,6 +38,38 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
self.processing_thread = None
|
||||
super(FullSync, self).__init__()
|
||||
|
||||
def plex_update_watched(self, viewId, item_class, lastViewedAt=None,
|
||||
updatedAt=None):
|
||||
"""
|
||||
YET to implement
|
||||
|
||||
Updates plex elements' view status ('watched' or 'unwatched') and
|
||||
also updates resume times.
|
||||
This is done by downloading one XML for ALL elements with viewId
|
||||
"""
|
||||
if self.new_items_only is False:
|
||||
# Only do this once for fullsync: the first run where new items are
|
||||
# added to Kodi
|
||||
return
|
||||
xml = PF.GetAllPlexLeaves(viewId,
|
||||
lastViewedAt=lastViewedAt,
|
||||
updatedAt=updatedAt)
|
||||
# Return if there are no items in PMS reply - it's faster
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, AttributeError, IndexError):
|
||||
LOG.error('Error updating watch status. Could not get viewId: '
|
||||
'%s of item_class %s with lastViewedAt: %s, updatedAt: '
|
||||
'%s', viewId, item_class, lastViewedAt, updatedAt)
|
||||
return
|
||||
|
||||
if item_class in ('Movies', 'TVShows'):
|
||||
self.update_kodi_video_library = True
|
||||
elif item_class == 'Music':
|
||||
self.update_kodi_music_library = True
|
||||
with getattr(itemtypes, item_class)() as itemtype:
|
||||
itemtype.updateUserdata(xml)
|
||||
|
||||
def process_item(self, xml_item):
|
||||
"""
|
||||
Processes a single library item
|
||||
|
@ -94,7 +126,8 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
self.context,
|
||||
utils.cast(int, iterator.get('totalSize', 0)),
|
||||
utils.cast(unicode, iterator.get('librarySectionTitle')),
|
||||
section['id'])
|
||||
section['id'],
|
||||
utils.cast(unicode, section['viewGroup']))
|
||||
self.queue.put(queue_info)
|
||||
for xml_item in iterator:
|
||||
if self.isCanceled():
|
||||
|
|
|
@ -4,7 +4,7 @@ from logging import getLogger
|
|||
import xbmcgui
|
||||
|
||||
from . import common
|
||||
from .. import utils, backgroundthread
|
||||
from .. import backgroundthread, utils, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.library_sync.process_metadata')
|
||||
|
||||
|
@ -17,11 +17,12 @@ class InitNewSection(object):
|
|||
context: itemtypes.Movie, itemtypes.Episode, etc.
|
||||
"""
|
||||
def __init__(self, context, total_number_of_items, section_name,
|
||||
section_id):
|
||||
section_id, plex_type):
|
||||
self.context = context
|
||||
self.total = total_number_of_items
|
||||
self.name = section_name
|
||||
self.id = section_id
|
||||
self.plex_type = plex_type
|
||||
|
||||
|
||||
class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
|
||||
|
@ -59,6 +60,8 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
self.section_name,
|
||||
'%s/%s: %s'
|
||||
% (self.current, self.total, self.title))
|
||||
common.update_kodi_library(video=self.is_video,
|
||||
music=not self.is_video)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
|
@ -80,6 +83,7 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
self.current = 0
|
||||
self.total = section.total
|
||||
self.section_name = section.name
|
||||
self.is_video = section.plex_type in v.PLEX_VIDEO_TYPES
|
||||
with section.context(self.last_sync) as context:
|
||||
while self.isCanceled() is False:
|
||||
# grabs item from queue. This will block!
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from threading import Thread, Lock
|
||||
from xbmc import sleep
|
||||
from xbmcgui import DialogProgressBG
|
||||
|
||||
from .. import utils
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
|
||||
GET_METADATA_COUNT = 0
|
||||
PROCESS_METADATA_COUNT = 0
|
||||
PROCESSING_VIEW_NAME = ''
|
||||
LOCK = Lock()
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
class ThreadedShowSyncInfo(Thread):
|
||||
"""
|
||||
Threaded class to show the Kodi statusbar of the metadata download.
|
||||
|
||||
Input:
|
||||
total: Total number of items to get
|
||||
item_type:
|
||||
"""
|
||||
def __init__(self, total, item_type):
|
||||
self.total = total
|
||||
self.item_type = item_type
|
||||
Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Do the work
|
||||
"""
|
||||
LOG.debug('Show sync info thread started')
|
||||
# cache local variables because it's faster
|
||||
total = self.total
|
||||
dialog = DialogProgressBG('dialoglogProgressBG')
|
||||
dialog.create("%s %s: %s %s"
|
||||
% (utils.lang(39714),
|
||||
self.item_type,
|
||||
unicode(total),
|
||||
utils.lang(39715)))
|
||||
|
||||
total = 2 * total
|
||||
total_progress = 0
|
||||
while not self.stopped():
|
||||
with LOCK:
|
||||
get_progress = GET_METADATA_COUNT
|
||||
process_progress = PROCESS_METADATA_COUNT
|
||||
view_name = PROCESSING_VIEW_NAME
|
||||
total_progress = get_progress + process_progress
|
||||
try:
|
||||
percentage = int(float(total_progress) / float(total) * 100.0)
|
||||
except ZeroDivisionError:
|
||||
percentage = 0
|
||||
dialog.update(percentage,
|
||||
message="%s %s. %s %s: %s"
|
||||
% (get_progress,
|
||||
utils.lang(39712),
|
||||
process_progress,
|
||||
utils.lang(39713),
|
||||
view_name))
|
||||
# Sleep for x milliseconds
|
||||
sleep(200)
|
||||
dialog.close()
|
||||
LOG.debug('Show sync info thread terminated')
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from random import shuffle
|
||||
import Queue
|
||||
import xbmc
|
||||
|
||||
from . import library_sync
|
||||
|
@ -39,8 +40,12 @@ class LibrarySync(backgroundthread.KillableThread):
|
|||
self.session_keys = {}
|
||||
self.sync_successful = False
|
||||
self.last_full_sync = 0
|
||||
self.fanartqueue = Queue.Queue()
|
||||
self.fanartthread = fanart.ThreadedProcessFanart(self.fanartqueue)
|
||||
if utils.settings('FanartTV') == 'true':
|
||||
self.fanartqueue = Queue.Queue()
|
||||
self.fanartthread = library_sync.fanart.ThreadedProcessFanart(self.fanartqueue)
|
||||
else:
|
||||
self.fanartqueue = None
|
||||
self.fanartthread = None
|
||||
# How long should we wait at least to process new/changed PMS items?
|
||||
# Show sync dialog even if user deactivated?
|
||||
self.force_dialog = False
|
||||
|
@ -92,36 +97,6 @@ class LibrarySync(backgroundthread.KillableThread):
|
|||
message=message,
|
||||
icon='{error}')
|
||||
|
||||
def plex_update_watched(self, viewId, item_class, lastViewedAt=None,
|
||||
updatedAt=None):
|
||||
"""
|
||||
Updates plex elements' view status ('watched' or 'unwatched') and
|
||||
also updates resume times.
|
||||
This is done by downloading one XML for ALL elements with viewId
|
||||
"""
|
||||
if self.new_items_only is False:
|
||||
# Only do this once for fullsync: the first run where new items are
|
||||
# added to Kodi
|
||||
return
|
||||
xml = PF.GetAllPlexLeaves(viewId,
|
||||
lastViewedAt=lastViewedAt,
|
||||
updatedAt=updatedAt)
|
||||
# Return if there are no items in PMS reply - it's faster
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, AttributeError, IndexError):
|
||||
LOG.error('Error updating watch status. Could not get viewId: '
|
||||
'%s of item_class %s with lastViewedAt: %s, updatedAt: '
|
||||
'%s', viewId, item_class, lastViewedAt, updatedAt)
|
||||
return
|
||||
|
||||
if item_class in ('Movies', 'TVShows'):
|
||||
self.update_kodi_video_library = True
|
||||
elif item_class == 'Music':
|
||||
self.update_kodi_music_library = True
|
||||
with getattr(itemtypes, item_class)() as itemtype:
|
||||
itemtype.updateUserdata(xml)
|
||||
|
||||
def process_message(self, message):
|
||||
"""
|
||||
processes json.loads() messages from websocket. Triage what we need to
|
||||
|
@ -207,8 +182,8 @@ class LibrarySync(backgroundthread.KillableThread):
|
|||
delete_list)
|
||||
# Let Kodi know of the change
|
||||
if self.update_kodi_video_library or self.update_kodi_music_library:
|
||||
update_library(video=self.update_kodi_video_library,
|
||||
music=self.update_kodi_music_library)
|
||||
library_sync.update_library(video=self.update_kodi_video_library,
|
||||
music=self.update_kodi_music_library)
|
||||
self.update_kodi_video_library = False
|
||||
self.update_kodi_music_library = False
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
from . import utils
|
||||
from . import utils, variables as v
|
||||
|
||||
|
||||
class PlexDBBase(object):
|
||||
"""
|
||||
Methods used for all types of items
|
||||
Plex database methods used for all types of items
|
||||
"""
|
||||
def __init__(self, cursor=None):
|
||||
# Allows us to use this class with a cursor instead of context mgr
|
||||
|
@ -22,6 +22,111 @@ class PlexDBBase(object):
|
|||
self.plexconn.commit()
|
||||
self.plexconn.close()
|
||||
|
||||
def item_by_id(self, plex_id, plex_type=None):
|
||||
"""
|
||||
Returns the following dict or None if not found
|
||||
{
|
||||
plex_id
|
||||
plex_type
|
||||
kodi_id
|
||||
kodi_type
|
||||
}
|
||||
for plex_id. Supply with the correct plex_type to speed up lookup
|
||||
"""
|
||||
answ = None
|
||||
if plex_type == v.PLEX_TYPE_MOVIE:
|
||||
entry = self.movie(plex_id)
|
||||
if entry:
|
||||
answ = self.entry_to_movie(entry)
|
||||
elif plex_type == v.PLEX_TYPE_EPISODE:
|
||||
entry = self.episode(plex_id)
|
||||
if entry:
|
||||
answ = self.entry_to_episode(entry)
|
||||
elif plex_type == v.PLEX_TYPE_SHOW:
|
||||
entry = self.show(plex_id)
|
||||
if entry:
|
||||
answ = self.entry_to_show(entry)
|
||||
elif plex_type == v.PLEX_TYPE_SEASON:
|
||||
entry = self.season(plex_id)
|
||||
if entry:
|
||||
answ = self.entry_to_season(entry)
|
||||
else:
|
||||
# SLOW - lookup plex_id in all our tables
|
||||
for kind in (v.PLEX_TYPE_MOVIE,
|
||||
v.PLEX_TYPE_SHOW,
|
||||
v.PLEX_TYPE_EPISODE,
|
||||
v.PLEX_TYPE_SEASON):
|
||||
method = getattr(self, kind)
|
||||
entry = method(plex_id)
|
||||
if entry:
|
||||
method = getattr(self, 'entry_to_%s' % kind)
|
||||
answ = method(entry)
|
||||
break
|
||||
return answ
|
||||
|
||||
@staticmethod
|
||||
def entry_to_movie(entry):
|
||||
return {
|
||||
'plex_type': v.PLEX_TYPE_MOVIE,
|
||||
'kodi_type': v.KODI_TYPE_MOVIE,
|
||||
'plex_id': entry[0],
|
||||
'checksum': entry[1],
|
||||
'section_id': entry[2],
|
||||
'kodi_id': entry[3],
|
||||
'kodi_fileid': entry[4],
|
||||
'kodi_pathid': entry[5],
|
||||
'fanart_synced': entry[6],
|
||||
'last_sync': entry[7]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def entry_to_episode(entry):
|
||||
return {
|
||||
'plex_type': v.PLEX_TYPE_EPISODE,
|
||||
'kodi_type': v.KODI_TYPE_EPISODE,
|
||||
'plex_id': entry[0],
|
||||
'checksum': entry[1],
|
||||
'section_id': entry[2],
|
||||
'show_id': entry[3],
|
||||
'grandparent_id': entry[4],
|
||||
'season_id': entry[5],
|
||||
'parent_id': entry[6],
|
||||
'kodi_id': entry[7],
|
||||
'kodi_fileid': entry[8],
|
||||
'kodi_pathid': entry[9],
|
||||
'fanart_synced': entry[10],
|
||||
'last_sync': entry[11]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def entry_to_show(entry):
|
||||
return {
|
||||
'plex_type': v.PLEX_TYPE_SHOW,
|
||||
'kodi_type': v.KODI_TYPE_SHOW,
|
||||
'plex_id': entry[0],
|
||||
'checksum': entry[1],
|
||||
'section_id': entry[2],
|
||||
'kodi_id': entry[3],
|
||||
'kodi_pathid': entry[4],
|
||||
'fanart_synced': entry[5],
|
||||
'last_sync': entry[6]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def entry_to_season(entry):
|
||||
return {
|
||||
'plex_type': v.PLEX_TYPE_SEASON,
|
||||
'kodi_type': v.KODI_TYPE_SEASON,
|
||||
'plex_id': entry[0],
|
||||
'checksum': entry[1],
|
||||
'section_id': entry[2],
|
||||
'show_id': entry[3],
|
||||
'parent_id': entry[4],
|
||||
'kodi_id': entry[5],
|
||||
'fanart_synced': entry[6],
|
||||
'last_sync': entry[7]
|
||||
}
|
||||
|
||||
def section_ids(self):
|
||||
"""
|
||||
Returns an iterator for section Plex ids for all sections
|
||||
|
@ -118,14 +223,30 @@ class PlexDBBase(object):
|
|||
query = 'DELETE FROM ? WHERE plex_id = ?' % plex_type
|
||||
self.cursor.execute(query, (plex_id, ))
|
||||
|
||||
def fanart(self, plex_type):
|
||||
"""
|
||||
Returns an iterator for plex_type for all plex_id, where fanart_synced
|
||||
has not yet been set to 1
|
||||
"""
|
||||
query = 'SELECT plex_id from %s WHERE fanart_synced = 0' % plex_type
|
||||
self.cursor.execute(query)
|
||||
return (x[0] for x in self.cursor)
|
||||
|
||||
def set_fanart_synced(self, plex_id, plex_type):
|
||||
"""
|
||||
Toggles fanart_synced to 1 for plex_id
|
||||
"""
|
||||
query = 'UPDATE %s SET fanart_synced = 1 WHERE plex_id = ?' % plex_type
|
||||
self.cursor.execute(query, (plex_id, ))
|
||||
|
||||
|
||||
def initialize():
|
||||
"""
|
||||
Run once during startup to verify that plex db exists.
|
||||
Run once upon PKC startup to verify that plex db exists.
|
||||
"""
|
||||
with PlexDBBase() as plex_db:
|
||||
with PlexDBBase() as plexdb:
|
||||
# Create the tables for the plex database
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS sections(
|
||||
section_id INTEGER PRIMARY KEY,
|
||||
section_name TEXT,
|
||||
|
@ -133,9 +254,9 @@ def initialize():
|
|||
kodi_tagid INTEGER,
|
||||
sync_to_kodi INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS movie(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
|
@ -144,9 +265,9 @@ def initialize():
|
|||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS show(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
|
@ -154,7 +275,7 @@ def initialize():
|
|||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS season(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
|
@ -165,7 +286,7 @@ def initialize():
|
|||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS episode(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
|
@ -180,16 +301,16 @@ def initialize():
|
|||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS artist(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
section_id INTEGER,
|
||||
kodi_id INTEGER,
|
||||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS album(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
|
@ -200,7 +321,7 @@ def initialize():
|
|||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS track(
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
checksum INTEGER UNIQUE,
|
||||
|
@ -215,17 +336,15 @@ def initialize():
|
|||
fanart_synced INTEGER,
|
||||
last_sync INTEGER)
|
||||
''')
|
||||
plex_db.cursor.execute('''
|
||||
plexdb.cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS playlists(
|
||||
plex_id INTEGER PRIMARY KEY ASC,
|
||||
plex_id INTEGER PRIMARY KEY,
|
||||
plex_name TEXT,
|
||||
plex_updatedat INTEGER,
|
||||
kodi_path TEXT,
|
||||
kodi_type TEXT,
|
||||
kodi_hash TEXT)
|
||||
''')
|
||||
# Create an index for actors to speed up sync
|
||||
utils.create_actor_db_index()
|
||||
|
||||
|
||||
def wipe():
|
||||
|
@ -233,10 +352,10 @@ def wipe():
|
|||
Completely resets the Plex database
|
||||
"""
|
||||
query = "SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||
with PlexDBBase() as plex_db:
|
||||
plex_db.cursor.execute(query)
|
||||
tables = plex_db.cursor.fetchall()
|
||||
with PlexDBBase() as plexdb:
|
||||
plexdb.cursor.execute(query)
|
||||
tables = plexdb.cursor.fetchall()
|
||||
tables = [i[0] for i in tables]
|
||||
for table in tables:
|
||||
delete_query = 'DELETE FROM %s' % table
|
||||
plex_db.cursor.execute(delete_query)
|
||||
plexdb.cursor.execute(delete_query)
|
||||
|
|
|
@ -11,8 +11,13 @@ class TVShows(object):
|
|||
"""
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO show(
|
||||
plex_id, checksum, section_id, kodi_id, kodi_pathid,
|
||||
fanart_synced, last_sync)
|
||||
plex_id
|
||||
checksum
|
||||
section_id
|
||||
kodi_id
|
||||
kodi_pathid
|
||||
fanart_synced
|
||||
last_sync)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(
|
||||
|
@ -27,8 +32,14 @@ class TVShows(object):
|
|||
"""
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO season(
|
||||
plex_id, checksum, section_id, show_id, parent_id,
|
||||
kodi_id, fanart_synced, last_sync)
|
||||
plex_id
|
||||
checksum
|
||||
section_id
|
||||
show_id
|
||||
parent_id
|
||||
kodi_id
|
||||
fanart_synced
|
||||
last_sync)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(
|
||||
|
@ -45,9 +56,18 @@ class TVShows(object):
|
|||
"""
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO episode(
|
||||
plex_id, checksum, section_id, show_id, grandparent_id,
|
||||
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
||||
fanart_synced, last_sync)
|
||||
plex_id
|
||||
checksum
|
||||
section_id
|
||||
show_id
|
||||
grandparent_id
|
||||
season_id
|
||||
parent_id
|
||||
kodi_id
|
||||
kodi_fileid
|
||||
kodi_pathid
|
||||
fanart_synced
|
||||
last_sync)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
self.plexcursor.execute(
|
||||
|
@ -106,3 +126,54 @@ class TVShows(object):
|
|||
self.cursor.execute('SELECT * FROM episode WHERE plex_id = ? LIMIT 1',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def season_has_episodes(self, plex_id):
|
||||
"""
|
||||
Returns True if there are episodes left for the season with plex_id
|
||||
"""
|
||||
self.cursor.execute('SELECT plex_id FROM episode WHERE season_id = ? LIMIT 1',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone() is not None
|
||||
|
||||
def show_has_seasons(self, plex_id):
|
||||
"""
|
||||
Returns True if there are seasons left for the show with plex_id
|
||||
"""
|
||||
self.cursor.execute('SELECT plex_id FROM season WHERE show_id = ? LIMIT 1',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone() is not None
|
||||
|
||||
def show_has_episodes(self, plex_id):
|
||||
"""
|
||||
Returns True if there are episodes left for the show with plex_id
|
||||
"""
|
||||
self.cursor.execute('SELECT plex_id FROM episode WHERE show_id = ? LIMIT 1',
|
||||
(plex_id, ))
|
||||
return self.cursor.fetchone() is not None
|
||||
|
||||
def episode_by_season(self, plex_id):
|
||||
"""
|
||||
Returns an iterator for all episodes that have a parent season_id with
|
||||
a value of plex_id
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM episode WHERE season_id = ?',
|
||||
(plex_id, ))
|
||||
return (self.entry_to_episode(x) for x in self.cursor)
|
||||
|
||||
def episode_by_show(self, plex_id):
|
||||
"""
|
||||
Returns an iterator for all episodes that have a grandparent show_id
|
||||
with a value of plex_id
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM episode WHERE show_id = ?',
|
||||
(plex_id, ))
|
||||
return (self.entry_to_episode(x) for x in self.cursor)
|
||||
|
||||
def season_by_show(self, plex_id):
|
||||
"""
|
||||
Returns an iterator for all seasons that have a parent show_id
|
||||
with a value of plex_id
|
||||
"""
|
||||
self.cursor.execute('SELECT * FROM season WHERE show_id = ?',
|
||||
(plex_id, ))
|
||||
return (self.entry_to_season(x) for x in self.cursor)
|
||||
|
|
Loading…
Reference in a new issue