Rewire llibrary sync, part 4
This commit is contained in:
parent
35a25a7f15
commit
23dada9fe5
12 changed files with 613 additions and 263 deletions
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
from .movies import Movie
|
||||||
from .tvshows import Show, Season, Episode
|
from .tvshows import Show, Season, Episode
|
||||||
|
|
||||||
# Note: always use same order of URL arguments, NOT urlencode:
|
# Note: always use same order of URL arguments, NOT urlencode:
|
||||||
|
|
249
resources/lib/itemtypes/movies.py
Normal file
249
resources/lib/itemtypes/movies.py
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
from .common import ItemBase
|
||||||
|
from ..plex_api import API
|
||||||
|
from .. import state, variables as v, plex_functions as PF
|
||||||
|
|
||||||
|
LOG = getLogger('PLEX.movies')
|
||||||
|
|
||||||
|
|
||||||
|
class Movie(ItemBase):
|
||||||
|
"""
|
||||||
|
Used for plex library-type movies
|
||||||
|
"""
|
||||||
|
def add_update(self, xml, section_name=None, section_id=None,
|
||||||
|
children=None):
|
||||||
|
"""
|
||||||
|
Process single movie
|
||||||
|
"""
|
||||||
|
api = API(xml)
|
||||||
|
update_item = True
|
||||||
|
plex_id = api.plex_id()
|
||||||
|
LOG.debug('Adding movie with plex_id %s', plex_id)
|
||||||
|
# Cannot parse XML, abort
|
||||||
|
if not plex_id:
|
||||||
|
LOG.error('Cannot parse XML data for movie: %s', xml.attrib)
|
||||||
|
return
|
||||||
|
movie = self.plex_db.getItem_byId(plex_id)
|
||||||
|
try:
|
||||||
|
kodi_id = movie[0]
|
||||||
|
old_kodi_fileid = movie[1]
|
||||||
|
kodi_pathid = movie[2]
|
||||||
|
except TypeError:
|
||||||
|
update_item = False
|
||||||
|
self.kodicursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie')
|
||||||
|
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||||
|
else:
|
||||||
|
# Verification the item is still in Kodi
|
||||||
|
self.kodicursor.execute('SELECT idMovie FROM movie WHERE idMovie = ? LIMIT 1',
|
||||||
|
(kodi_id, ))
|
||||||
|
try:
|
||||||
|
self.kodicursor.fetchone()[0]
|
||||||
|
except TypeError:
|
||||||
|
# item is not found, let's recreate it.
|
||||||
|
update_item = False
|
||||||
|
LOG.info("kodi_id: %s missing from Kodi, repairing the entry.",
|
||||||
|
kodi_id)
|
||||||
|
|
||||||
|
userdata = api.userdata()
|
||||||
|
playcount = userdata['PlayCount']
|
||||||
|
dateplayed = userdata['LastPlayedDate']
|
||||||
|
resume = userdata['Resume']
|
||||||
|
runtime = userdata['Runtime']
|
||||||
|
rating = userdata['Rating']
|
||||||
|
|
||||||
|
title = api.title()
|
||||||
|
people = api.people()
|
||||||
|
genres = api.genre_list()
|
||||||
|
collections = api.collection_list()
|
||||||
|
countries = api.country_list()
|
||||||
|
studios = api.music_studio_list()
|
||||||
|
|
||||||
|
# GET THE FILE AND PATH #####
|
||||||
|
do_indirect = not state.DIRECT_PATHS
|
||||||
|
if state.DIRECT_PATHS:
|
||||||
|
# Direct paths is set the Kodi way
|
||||||
|
playurl = api.file_path(force_first_media=True)
|
||||||
|
if playurl is None:
|
||||||
|
# Something went wrong, trying to use non-direct paths
|
||||||
|
do_indirect = True
|
||||||
|
else:
|
||||||
|
playurl = api.validate_playurl(playurl, api.plex_type())
|
||||||
|
if playurl is None:
|
||||||
|
return False
|
||||||
|
if '\\' in playurl:
|
||||||
|
# Local path
|
||||||
|
filename = playurl.rsplit("\\", 1)[1]
|
||||||
|
else:
|
||||||
|
# Network share
|
||||||
|
filename = playurl.rsplit("/", 1)[1]
|
||||||
|
path = playurl.replace(filename, "")
|
||||||
|
kodi_pathid = self.kodi_db.add_video_path(path,
|
||||||
|
content='movies',
|
||||||
|
scraper='metadata.local')
|
||||||
|
if do_indirect:
|
||||||
|
# Set plugin path and media flags using real filename
|
||||||
|
filename = api.file_name(force_first_media=True)
|
||||||
|
path = 'plugin://%s.movies/' % v.ADDON_ID
|
||||||
|
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||||
|
% (path, plex_id, v.PLEX_TYPE_MOVIE, filename))
|
||||||
|
playurl = filename
|
||||||
|
kodi_pathid = self.kodi_db.get_path(path)
|
||||||
|
|
||||||
|
file_id = self.kodi_db.add_file(filename,
|
||||||
|
kodi_pathid,
|
||||||
|
api.date_created())
|
||||||
|
|
||||||
|
if update_item:
|
||||||
|
LOG.info('UPDATE movie plex_id: %s - Title: %s',
|
||||||
|
plex_id, api.title())
|
||||||
|
if file_id != old_kodi_fileid:
|
||||||
|
self.kodi_db.remove_file(old_kodi_fileid)
|
||||||
|
rating_id = self.kodi_db.get_ratingid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE)
|
||||||
|
self.kodi_db.update_ratings(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
"default",
|
||||||
|
rating,
|
||||||
|
api.votecount(),
|
||||||
|
rating_id)
|
||||||
|
# update new uniqueid Kodi 17
|
||||||
|
if api.provider('imdb') is not None:
|
||||||
|
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE)
|
||||||
|
self.kodi_db.update_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
api.provider('imdb'),
|
||||||
|
"imdb",
|
||||||
|
uniqueid)
|
||||||
|
else:
|
||||||
|
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE)
|
||||||
|
uniqueid = -1
|
||||||
|
else:
|
||||||
|
LOG.info("ADD movie plex_id: %s - Title: %s", plex_id, title)
|
||||||
|
rating_id = self.kodi_db.get_ratingid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE)
|
||||||
|
self.kodi_db.add_ratings(rating_id,
|
||||||
|
kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
"default",
|
||||||
|
rating,
|
||||||
|
api.votecount())
|
||||||
|
if api.provider('imdb') is not None:
|
||||||
|
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE)
|
||||||
|
self.kodi_db.add_uniqueid(uniqueid,
|
||||||
|
kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
api.provider('imdb'),
|
||||||
|
"imdb")
|
||||||
|
else:
|
||||||
|
uniqueid = -1
|
||||||
|
|
||||||
|
# Update Kodi's main entry
|
||||||
|
query = '''
|
||||||
|
INSERT OR REPLACE INTO movie(idMovie, idFile, c00, c01, c02, c03,
|
||||||
|
c04, c05, c06, c07, c09, c10, c11, c12, c14, c15, c16,
|
||||||
|
c18, c19, c21, c22, c23, premiered, userrating)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.kodicursor.execute(
|
||||||
|
query,
|
||||||
|
(kodi_id, file_id, title, api.plot(), api.shortplot(),
|
||||||
|
api.tagline(), api.votecount(), rating_id,
|
||||||
|
api.list_to_string(people['Writer']), api.year(),
|
||||||
|
uniqueid, api.sorttitle(), runtime, api.content_rating(),
|
||||||
|
api.list_to_string(genres), api.list_to_string(people['Director']),
|
||||||
|
title, api.list_to_string(studios), api.trailer(),
|
||||||
|
api.list_to_string(countries), playurl, kodi_pathid,
|
||||||
|
api.premiere_date(), userdata['UserRating']))
|
||||||
|
|
||||||
|
self.kodi_db.modify_countries(kodi_id, v.KODI_TYPE_MOVIE, countries)
|
||||||
|
self.kodi_db.modify_people(kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
api.people_list())
|
||||||
|
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, genres)
|
||||||
|
self.artwork.modify_artwork(api.artwork(),
|
||||||
|
kodi_id,
|
||||||
|
v.KODI_TYPE_MOVIE,
|
||||||
|
self.kodicursor)
|
||||||
|
self.kodi_db.modify_streams(file_id, api.mediastreams(), runtime)
|
||||||
|
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios)
|
||||||
|
tags = [section_name]
|
||||||
|
if collections:
|
||||||
|
collections_match = api.collections_match()
|
||||||
|
for plex_set_id, set_name in collections:
|
||||||
|
tags.append(set_name)
|
||||||
|
# Add any sets from Plex collection tags
|
||||||
|
kodi_set_id = self.kodi_db.create_collection(set_name)
|
||||||
|
self.kodi_db.assign_collection(kodi_set_id, kodi_id)
|
||||||
|
for index, plex_id in collections_match:
|
||||||
|
# Get Plex artwork for collections - a pain
|
||||||
|
if index == plex_set_id:
|
||||||
|
set_xml = PF.GetPlexMetadata(plex_id)
|
||||||
|
try:
|
||||||
|
set_xml.attrib
|
||||||
|
except AttributeError:
|
||||||
|
LOG.error('Could not get set metadata %s', plex_id)
|
||||||
|
continue
|
||||||
|
set_api = API(set_xml[0])
|
||||||
|
self.artwork.modify_artwork(set_api.artwork(),
|
||||||
|
kodi_set_id,
|
||||||
|
v.KODI_TYPE_SET,
|
||||||
|
self.kodicursor)
|
||||||
|
break
|
||||||
|
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags)
|
||||||
|
# Process playstate
|
||||||
|
self.kodi_db.set_resume(file_id,
|
||||||
|
resume,
|
||||||
|
runtime,
|
||||||
|
playcount,
|
||||||
|
dateplayed,
|
||||||
|
v.PLEX_TYPE_MOVIE)
|
||||||
|
self.plex_db.add_movie(plex_id=plex_id,
|
||||||
|
checksum=api.checksum(),
|
||||||
|
section_id=section_id,
|
||||||
|
kodi_id=kodi_id,
|
||||||
|
kodi_fileid=file_id,
|
||||||
|
kodi_pathid=kodi_pathid,
|
||||||
|
last_sync=self.last_sync)
|
||||||
|
|
||||||
|
def remove(self, plex_id):
|
||||||
|
"""
|
||||||
|
Remove a movie with all references and all orphaned associated entries
|
||||||
|
from the Kodi DB
|
||||||
|
"""
|
||||||
|
movie = self.plex_db.movie(plex_id)
|
||||||
|
try:
|
||||||
|
kodi_id = movie[3]
|
||||||
|
file_id = movie[4]
|
||||||
|
kodi_type = v.KODI_TYPE_MOVIE
|
||||||
|
LOG.debug('Removing movie with plex_id %s, kodi_id: %s',
|
||||||
|
plex_id, kodi_id)
|
||||||
|
except TypeError:
|
||||||
|
LOG.error('Movie with plex_id %s not found - cannot delete',
|
||||||
|
plex_id)
|
||||||
|
return
|
||||||
|
# Remove the plex reference
|
||||||
|
self.plex_db.remove(plex_id, v.PLEX_TYPE_MOVIE)
|
||||||
|
# Remove artwork
|
||||||
|
self.artwork.delete_artwork(kodi_id, kodi_type, self.self.kodicursor)
|
||||||
|
set_id = self.kodi_db.get_set_id(kodi_id)
|
||||||
|
self.kodi_db.modify_countries(kodi_id, kodi_type)
|
||||||
|
self.kodi_db.modify_people(kodi_id, kodi_type)
|
||||||
|
self.kodi_db.modify_genres(kodi_id, kodi_type)
|
||||||
|
self.kodi_db.modify_studios(kodi_id, kodi_type)
|
||||||
|
self.kodi_db.modify_tags(kodi_id, kodi_type)
|
||||||
|
# Delete kodi movie and file
|
||||||
|
self.kodi_db.remove_file(file_id)
|
||||||
|
self.self.kodicursor.execute('DELETE FROM movie WHERE idMovie = ?',
|
||||||
|
(kodi_id,))
|
||||||
|
if set_id:
|
||||||
|
self.kodi_db.delete_possibly_empty_set(set_id)
|
||||||
|
self.kodi_db.remove_uniqueid(kodi_id, kodi_type)
|
||||||
|
self.kodi_db.remove_ratings(kodi_id, kodi_type)
|
||||||
|
LOG.debug('Deleted movie %s from kodi database', plex_id)
|
|
@ -203,8 +203,8 @@ class Show(ItemBase, TvShowMixin):
|
||||||
toppathid = None
|
toppathid = None
|
||||||
|
|
||||||
kodi_pathid = self.kodi_db.add_video_path(path,
|
kodi_pathid = self.kodi_db.add_video_path(path,
|
||||||
date_added=api.date_created(),
|
date_added=api.date_created(),
|
||||||
id_parent_path=toppathid)
|
id_parent_path=toppathid)
|
||||||
# UPDATE THE TVSHOW #####
|
# UPDATE THE TVSHOW #####
|
||||||
if update_item:
|
if update_item:
|
||||||
LOG.info("UPDATE tvshow plex_id: %s - Title: %s",
|
LOG.info("UPDATE tvshow plex_id: %s - Title: %s",
|
||||||
|
@ -248,8 +248,6 @@ class Show(ItemBase, TvShowMixin):
|
||||||
# Link the path
|
# Link the path
|
||||||
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
|
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
|
||||||
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
|
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
|
||||||
# Create the reference in plex table
|
|
||||||
|
|
||||||
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
self.kodi_db.add_ratings(rating_id,
|
self.kodi_db.add_ratings(rating_id,
|
||||||
kodi_id,
|
kodi_id,
|
||||||
|
@ -293,13 +291,12 @@ class Show(ItemBase, TvShowMixin):
|
||||||
tags = [section_name]
|
tags = [section_name]
|
||||||
tags.extend([i for _, i in api.collection_list()])
|
tags.extend([i for _, i in api.collection_list()])
|
||||||
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
|
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
|
||||||
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_SHOW,
|
self.plex_db.add_show(plex_id=plex_id,
|
||||||
plex_id=plex_id,
|
checksum=api.checksum(),
|
||||||
checksum=api.checksum(),
|
section_id=section_id,
|
||||||
section_id=section_id,
|
kodi_id=kodi_id,
|
||||||
kodi_id=kodi_id,
|
kodi_pathid=kodi_pathid,
|
||||||
kodi_pathid=kodi_pathid,
|
last_sync=self.last_sync)
|
||||||
last_sync=self.last_sync)
|
|
||||||
|
|
||||||
|
|
||||||
class Season(ItemBase, TvShowMixin):
|
class Season(ItemBase, TvShowMixin):
|
||||||
|
@ -328,14 +325,13 @@ class Season(ItemBase, TvShowMixin):
|
||||||
kodi_id,
|
kodi_id,
|
||||||
v.KODI_TYPE_SEASON,
|
v.KODI_TYPE_SEASON,
|
||||||
self.kodicursor)
|
self.kodicursor)
|
||||||
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_SEASON,
|
self.plex_db.add_season(plex_id=plex_id,
|
||||||
plex_id=plex_id,
|
checksum=api.checksum(),
|
||||||
checksum=api.checksum(),
|
section_id=section_id,
|
||||||
section_id=section_id,
|
show_id=show_id,
|
||||||
show_id=show_id,
|
parent_id=parent_id,
|
||||||
parent_id=parent_id,
|
kodi_id=kodi_id,
|
||||||
kodi_id=kodi_id,
|
last_sync=self.last_sync)
|
||||||
last_sync=self.last_sync)
|
|
||||||
|
|
||||||
|
|
||||||
class Episode(ItemBase, TvShowMixin):
|
class Episode(ItemBase, TvShowMixin):
|
||||||
|
@ -535,15 +531,14 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
userdata['PlayCount'],
|
userdata['PlayCount'],
|
||||||
userdata['LastPlayedDate'],
|
userdata['LastPlayedDate'],
|
||||||
None) # Do send None - 2nd entry
|
None) # Do send None - 2nd entry
|
||||||
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_EPISODE,
|
self.plex_db.add_episode(plex_id=plex_id,
|
||||||
plex_id=plex_id,
|
checksum=api.checksum(),
|
||||||
checksum=api.checksum(),
|
section_id=section_id,
|
||||||
section_id=section_id,
|
show_id=show_id,
|
||||||
show_id=show_id,
|
grandparent_id=grandparent_id,
|
||||||
grandparent_id=grandparent_id,
|
season_id=season_id,
|
||||||
season_id=season_id,
|
parent_id=parent_id,
|
||||||
parent_id=parent_id,
|
kodi_id=kodi_id,
|
||||||
kodi_id=kodi_id,
|
kodi_fileid=kodi_fileid,
|
||||||
kodi_fileid=kodi_fileid,
|
kodi_pathid=kodi_pathid,
|
||||||
kodi_pathid=kodi_pathid,
|
last_sync=self.last_sync)
|
||||||
last_sync=self.last_sync)
|
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
# Dummy file to make this directory a package.
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
from .full_sync import start, PLAYLIST_SYNC_ENABLED
|
||||||
|
|
|
@ -4,21 +4,25 @@ from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from . import common, process_metadata, sections
|
|
||||||
from .get_metadata import GetMetadataTask
|
from .get_metadata import GetMetadataTask
|
||||||
from .. import utils, backgroundthread, playlists, variables as v, state
|
from . import common, process_metadata, sections
|
||||||
|
from .. import utils, backgroundthread, variables as v, state
|
||||||
from .. import plex_functions as PF, itemtypes
|
from .. import plex_functions as PF, itemtypes
|
||||||
|
from ..plex_db import PlexDB
|
||||||
|
|
||||||
|
|
||||||
|
if (v.PLATFORM != 'Microsoft UWP' and
|
||||||
|
utils.settings('enablePlaylistSync') == 'true'):
|
||||||
|
# Xbox cannot use watchdog, a dependency for PKC playlist features
|
||||||
|
from .. import playlists
|
||||||
|
PLAYLIST_SYNC_ENABLED = True
|
||||||
|
else:
|
||||||
|
PLAYLIST_SYNC_ENABLED = False
|
||||||
|
|
||||||
|
|
||||||
LOG = getLogger('PLEX.library_sync.full_sync')
|
LOG = getLogger('PLEX.library_sync.full_sync')
|
||||||
|
|
||||||
|
|
||||||
def start(repair, callback):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
# backgroundthread.BGThreader.addTask(FullSync().setup(repair, callback))
|
|
||||||
FullSync(repair, callback).start()
|
|
||||||
|
|
||||||
|
|
||||||
class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
def __init__(self, repair, callback):
|
def __init__(self, repair, callback):
|
||||||
"""
|
"""
|
||||||
|
@ -99,6 +103,7 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
LOG.error('Could not entirely process section %s', section)
|
LOG.error('Could not entirely process section %s', section)
|
||||||
continue
|
continue
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
LOG.debug('Finished processing %ss', self.plex_type)
|
LOG.debug('Finished processing %ss', self.plex_type)
|
||||||
return True
|
return True
|
||||||
|
@ -110,50 +115,55 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
(v.PLEX_TYPE_MOVIE, itemtypes.Movie, False),
|
(v.PLEX_TYPE_MOVIE, itemtypes.Movie, False),
|
||||||
(v.PLEX_TYPE_SHOW, itemtypes.Show, False),
|
(v.PLEX_TYPE_SHOW, itemtypes.Show, False),
|
||||||
(v.PLEX_TYPE_SEASON, itemtypes.Season, False),
|
(v.PLEX_TYPE_SEASON, itemtypes.Season, False),
|
||||||
(v.PLEX_TYPE_EPISODE, itemtypes.Episode, False),
|
(v.PLEX_TYPE_EPISODE, itemtypes.Episode, False)
|
||||||
(v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
|
|
||||||
(v.PLEX_TYPE_ALBUM, itemtypes.Album, True),
|
|
||||||
(v.PLEX_TYPE_SONG, itemtypes.Song, False),
|
|
||||||
]
|
]
|
||||||
|
if state.ENABLE_MUSIC:
|
||||||
|
kinds.extend(
|
||||||
|
(v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
|
||||||
|
(v.PLEX_TYPE_ALBUM, itemtypes.Album, True),
|
||||||
|
(v.PLEX_TYPE_SONG, itemtypes.Song, False))
|
||||||
for kind in kinds:
|
for kind in kinds:
|
||||||
# Setup our variables
|
# Setup our variables
|
||||||
self.plex_type = kind[0]
|
self.plex_type = kind[0]
|
||||||
self.context = kind[1]
|
self.context = kind[1]
|
||||||
self.get_children = kind[2]
|
self.get_children = kind[2]
|
||||||
# Now do the heavy lifting
|
# Now do the heavy lifting
|
||||||
if self.isCanceled() or not self.process_kind():
|
with PlexDB() as self.plex_db:
|
||||||
return False
|
if self.isCanceled() or not self.process_kind():
|
||||||
if self.new_items_only:
|
return False
|
||||||
# Delete movies that are not on Plex anymore - do this only once
|
if self.new_items_only:
|
||||||
self.process_delete()
|
# Delete movies that are not on Plex anymore - do this only once
|
||||||
|
self.process_delete()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@utils.log_time
|
@utils.log_time
|
||||||
def run(self):
|
def run(self):
|
||||||
|
if self.isCanceled():
|
||||||
|
return
|
||||||
successful = False
|
successful = False
|
||||||
self.last_sync = time.time()
|
self.last_sync = time.time()
|
||||||
if self.isCanceled():
|
|
||||||
return
|
|
||||||
LOG.info('Running fullsync for NEW PMS items with repair=%s',
|
|
||||||
self.repair)
|
|
||||||
if not sections.sync_from_pms():
|
if not sections.sync_from_pms():
|
||||||
return
|
return
|
||||||
if self.isCanceled():
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
# Fire up our single processing thread
|
# Fire up our single processing thread
|
||||||
self.queue = backgroundthread.Queue.Queue(maxsize=200)
|
self.queue = backgroundthread.Queue.Queue(maxsize=200)
|
||||||
self.processing_thread = process_metadata.ProcessMetadata(
|
self.processing_thread = process_metadata.ProcessMetadata(
|
||||||
self.queue, self.last_sync)
|
self.queue, self.last_sync)
|
||||||
self.processing_thread.start()
|
self.processing_thread.start()
|
||||||
|
|
||||||
|
# Actual syncing - do only new items first
|
||||||
|
LOG.info('Running fullsync for **NEW** items with repair=%s',
|
||||||
|
self.repair)
|
||||||
|
self.new_items_only = True
|
||||||
# This will also update playstates and userratings!
|
# This will also update playstates and userratings!
|
||||||
if self.full_library_sync(new_items_only=True) is False:
|
if not self.full_library_sync():
|
||||||
return
|
return
|
||||||
if self.isCanceled():
|
if self.isCanceled():
|
||||||
return
|
return
|
||||||
# This will NOT update playstates and userratings!
|
# This will NOT update playstates and userratings!
|
||||||
LOG.info('Running fullsync for CHANGED PMS items with repair=%s',
|
LOG.info('Running fullsync for **CHANGED** items with repair=%s',
|
||||||
self.repair)
|
self.repair)
|
||||||
|
self.new_items_only = False
|
||||||
if not self.full_library_sync():
|
if not self.full_library_sync():
|
||||||
return
|
return
|
||||||
if self.isCanceled():
|
if self.isCanceled():
|
||||||
|
@ -174,58 +184,8 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
LOG.info('Done full_sync')
|
LOG.info('Done full_sync')
|
||||||
|
|
||||||
|
|
||||||
def process_updatelist(item_class, show_sync_info=True):
|
def start(repair, callback):
|
||||||
"""
|
"""
|
||||||
Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes
|
|
||||||
them by then calling item_classs.<item_class>()
|
|
||||||
|
|
||||||
Input:
|
|
||||||
item_class: 'Movies', 'TVShows' (itemtypes.py classes)
|
|
||||||
"""
|
"""
|
||||||
search_fanart = (item_class in ('Movies', 'TVShows') and
|
# backgroundthread.BGThreader.addTask(FullSync().setup(repair, callback))
|
||||||
utils.settings('FanartTV') == 'true')
|
FullSync(repair, callback).start()
|
||||||
LOG.debug("Starting sync threads")
|
|
||||||
# Spawn GetMetadata threads for downloading
|
|
||||||
for _ in range(state.SYNC_THREAD_NUMBER):
|
|
||||||
thread = get_metadata.ThreadedGetMetadata(DOWNLOAD_QUEUE,
|
|
||||||
PROCESS_QUEUE)
|
|
||||||
thread.start()
|
|
||||||
THREADS.append(thread)
|
|
||||||
LOG.debug("%s download threads spawned", state.SYNC_THREAD_NUMBER)
|
|
||||||
# Spawn one more thread to process Metadata, once downloaded
|
|
||||||
thread = process_metadata.ThreadedProcessMetadata(PROCESS_QUEUE,
|
|
||||||
item_class)
|
|
||||||
thread.start()
|
|
||||||
THREADS.append(thread)
|
|
||||||
# Start one thread to show sync progress ONLY for new PMS items
|
|
||||||
if show_sync_info:
|
|
||||||
sync_info.GET_METADATA_COUNT = 0
|
|
||||||
sync_info.PROCESS_METADATA_COUNT = 0
|
|
||||||
sync_info.PROCESSING_VIEW_NAME = ''
|
|
||||||
thread = sync_info.ThreadedShowSyncInfo(item_number, item_class)
|
|
||||||
thread.start()
|
|
||||||
THREADS.append(thread)
|
|
||||||
# Process items we need to download
|
|
||||||
for _ in generator:
|
|
||||||
DOWNLOAD_QUEUE.put(self.updatelist.pop(0))
|
|
||||||
if search_fanart:
|
|
||||||
pass
|
|
||||||
# Wait until finished
|
|
||||||
DOWNLOAD_QUEUE.join()
|
|
||||||
PROCESS_QUEUE.join()
|
|
||||||
# Kill threads
|
|
||||||
LOG.debug("Waiting to kill threads")
|
|
||||||
for thread in THREADS:
|
|
||||||
# Threads might already have quit by themselves (e.g. Kodi exit)
|
|
||||||
try:
|
|
||||||
thread.stop()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
LOG.debug("Stop sent to all threads")
|
|
||||||
# Wait till threads are indeed dead
|
|
||||||
for thread in threads:
|
|
||||||
try:
|
|
||||||
thread.join(1.0)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
LOG.debug("Sync threads finished")
|
|
||||||
|
|
|
@ -80,9 +80,9 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
while self.isCanceled() is False:
|
while self.isCanceled() is False:
|
||||||
# grabs item from queue. This will block!
|
# grabs item from queue. This will block!
|
||||||
xml = self.queue.get()
|
xml = self.queue.get()
|
||||||
self.queue.task_done()
|
|
||||||
if xml is InitNewSection or xml is None:
|
if xml is InitNewSection or xml is None:
|
||||||
section = xml
|
section = xml
|
||||||
|
self.queue.task_done()
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
context.add_update(xml[0],
|
context.add_update(xml[0],
|
||||||
|
@ -97,6 +97,7 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
xml[0].get('title'))
|
xml[0].get('title'))
|
||||||
self.update_dialog()
|
self.update_dialog()
|
||||||
self.current += 1
|
self.current += 1
|
||||||
|
self.queue.task_done()
|
||||||
finally:
|
finally:
|
||||||
self.dialog.close()
|
self.dialog.close()
|
||||||
LOG.debug('Processing thread terminated')
|
LOG.debug('Processing thread terminated')
|
||||||
|
|
|
@ -95,7 +95,7 @@ def _process_section(section_xml, kodi_db, plex_db, sorted_sections,
|
||||||
# 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 = plex_db.section_by_id(section_id)
|
section = plex_db.section(section_id)
|
||||||
try:
|
try:
|
||||||
current_sectionname = section[1]
|
current_sectionname = section[1]
|
||||||
current_sectiontype = section[2]
|
current_sectiontype = section[2]
|
||||||
|
@ -137,7 +137,10 @@ def _process_section(section_xml, kodi_db, plex_db, sorted_sections,
|
||||||
tagid = kodi_db.create_tag(section_name)
|
tagid = kodi_db.create_tag(section_name)
|
||||||
|
|
||||||
# Update view with new info
|
# Update view with new info
|
||||||
plex_db.update_section(section_name, tagid, section_id)
|
plex_db.add_section(section_id,
|
||||||
|
section_name,
|
||||||
|
plex_type,
|
||||||
|
tagid)
|
||||||
|
|
||||||
if plex_db.section_id_by_name(current_sectionname) is None:
|
if plex_db.section_id_by_name(current_sectionname) is None:
|
||||||
# The tag could be a combined view. Ensure there's
|
# The tag could be a combined view. Ensure there's
|
||||||
|
@ -210,7 +213,7 @@ def delete_sections(old_sections):
|
||||||
video_library_update = False
|
video_library_update = False
|
||||||
music_library_update = False
|
music_library_update = False
|
||||||
with plexdb.PlexDB() as plex_db:
|
with plexdb.PlexDB() as plex_db:
|
||||||
old_sections = [plex_db.section_by_id(x) for x in old_sections]
|
old_sections = [plex_db.section(x) for x in old_sections]
|
||||||
LOG.info("Removing entire Plex library sections: %s", old_sections)
|
LOG.info("Removing entire Plex library sections: %s", old_sections)
|
||||||
with kodidb.GetKodiDB() as kodi_db:
|
with kodidb.GetKodiDB() as kodi_db:
|
||||||
for section in old_sections:
|
for section in old_sections:
|
||||||
|
|
|
@ -476,33 +476,39 @@ class API(object):
|
||||||
"""
|
"""
|
||||||
Returns the title of the element as unicode or 'Missing Title Name'
|
Returns the title of the element as unicode or 'Missing Title Name'
|
||||||
"""
|
"""
|
||||||
return utils.try_decode(self.item.get('title', 'Missing Title Name'))
|
return cast(unicode, self.item.get('title', 'Missing Title Name'))
|
||||||
|
|
||||||
def title(self):
|
|
||||||
"""
|
|
||||||
Returns an item's name/title or "Missing Title".
|
|
||||||
"""
|
|
||||||
return self.item.get('title', 'Missing Title')
|
|
||||||
|
|
||||||
def sorttitle(self):
|
def sorttitle(self):
|
||||||
"""
|
"""
|
||||||
Returns an item's sorting name/title or the title itself if not found
|
Returns an item's sorting name/title or the title itself if not found
|
||||||
"Missing Title" if both are not present
|
"Missing Title" if both are not present
|
||||||
"""
|
"""
|
||||||
return self.item.get('titleSort',
|
return cast(unicode, self.item.get('titleSort',
|
||||||
self.item.get('title','Missing Title'))
|
self.item.get('title','Missing Title')))
|
||||||
|
|
||||||
def plot(self):
|
def plot(self):
|
||||||
"""
|
"""
|
||||||
Returns the plot or None.
|
Returns the plot or None.
|
||||||
"""
|
"""
|
||||||
return self.item.get('summary')
|
return cast(unicode, self.item.get('summary'))
|
||||||
|
|
||||||
|
def shortplot(self):
|
||||||
|
"""
|
||||||
|
Not yet implemented
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def votecount(self):
|
||||||
|
"""
|
||||||
|
Not yet implemented
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def tagline(self):
|
def tagline(self):
|
||||||
"""
|
"""
|
||||||
Returns a shorter tagline or None
|
Returns a shorter tagline or None
|
||||||
"""
|
"""
|
||||||
return self.item.get('tagline')
|
return cast(unicode, self.item.get('tagline'))
|
||||||
|
|
||||||
def audience_rating(self):
|
def audience_rating(self):
|
||||||
"""
|
"""
|
||||||
|
@ -755,7 +761,7 @@ class API(object):
|
||||||
answ.append(extra)
|
answ.append(extra)
|
||||||
return answ
|
return answ
|
||||||
|
|
||||||
def trailers(self):
|
def trailer(self):
|
||||||
"""
|
"""
|
||||||
Returns the URL for a single trailer (local trailer preferred; first
|
Returns the URL for a single trailer (local trailer preferred; first
|
||||||
trailer found returned) or an add-on path to list all Plex extras
|
trailer found returned) or an add-on path to list all Plex extras
|
||||||
|
|
|
@ -2,42 +2,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
from .common import PlexDBBase
|
from .common import PlexDBBase, initialize, wipe
|
||||||
from .tvshows import
|
from .tvshows import TVShows
|
||||||
from .. import utils, variables as v
|
from .movies import Movies
|
||||||
|
|
||||||
|
|
||||||
class PlexDB(object):
|
class PlexDB(PlexDBBase, TVShows, Movies):
|
||||||
"""
|
pass
|
||||||
Usage: with PlexDB() as plex_db:
|
|
||||||
plex_db.do_something()
|
|
||||||
|
|
||||||
On exiting "with" (no matter what), commits get automatically committed
|
|
||||||
and the db gets closed
|
|
||||||
"""
|
|
||||||
def __init__(self, kind=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.plexconn = utils.kodi_sql('plex')
|
|
||||||
if kind is None:
|
|
||||||
func = PlexDBBase
|
|
||||||
return func(self.plexconn.cursor())
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
self.plexconn.commit()
|
|
||||||
self.plexconn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def wipe_dbs():
|
|
||||||
"""
|
|
||||||
Completely resets the Plex database
|
|
||||||
"""
|
|
||||||
query = "SELECT name FROM sqlite_master WHERE type = 'table'"
|
|
||||||
with PlexDB() as plex_db:
|
|
||||||
plex_db.plexcursor.execute(query)
|
|
||||||
tables = plex_db.plexcursor.fetchall()
|
|
||||||
tables = [i[0] for i in tables]
|
|
||||||
for table in tables:
|
|
||||||
delete_query = 'DELETE FROM %s' % table
|
|
||||||
plex_db.plexcursor.execute(delete_query)
|
|
||||||
|
|
|
@ -2,14 +2,26 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
class PlexDB(object):
|
|
||||||
|
class PlexDBBase(object):
|
||||||
"""
|
"""
|
||||||
Methods used for all types of items
|
Methods used for all types of items
|
||||||
"""
|
"""
|
||||||
def __init__(self, cursor):
|
def __init__(self, cursor=None):
|
||||||
|
# Allows us to use this class with a cursor instead of context mgr
|
||||||
self.cursor = cursor
|
self.cursor = cursor
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.plexconn = utils.kodi_sql('plex')
|
||||||
|
self.cursor = self.plexconn.cursor()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, e_typ, e_val, trcbak):
|
||||||
|
self.plexconn.commit()
|
||||||
|
self.plexconn.close()
|
||||||
|
|
||||||
def section_ids(self):
|
def section_ids(self):
|
||||||
"""
|
"""
|
||||||
Returns an iterator for section Plex ids for all sections
|
Returns an iterator for section Plex ids for all sections
|
||||||
|
@ -35,14 +47,14 @@ class PlexDB(object):
|
||||||
'kodi_tagid': x[3],
|
'kodi_tagid': x[3],
|
||||||
'sync_to_kodi': x[4]} for x in self.cursor)
|
'sync_to_kodi': x[4]} for x in self.cursor)
|
||||||
|
|
||||||
def section_by_id(self, section_id):
|
def section(self, section_id):
|
||||||
"""
|
"""
|
||||||
For section_id, returns tuple (or None)
|
For section_id, returns the tuple (or None)
|
||||||
(section_id,
|
section_id INTEGER PRIMARY KEY,
|
||||||
section_name,
|
section_name TEXT,
|
||||||
plex_type,
|
plex_type TEXT,
|
||||||
kodi_tagid,
|
kodi_tagid INTEGER,
|
||||||
sync_to_kodi)
|
sync_to_kodi 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, ))
|
||||||
|
@ -66,7 +78,7 @@ class PlexDB(object):
|
||||||
sync=False: Plex library won't be synced to Kodi
|
sync=False: Plex library won't be synced to Kodi
|
||||||
"""
|
"""
|
||||||
query = '''
|
query = '''
|
||||||
INSERT INTO sections(
|
INSERT OR REPLACE INTO sections(
|
||||||
section_id, section_name, plex_type, kodi_tagid, sync_to_kodi)
|
section_id, section_name, plex_type, kodi_tagid, sync_to_kodi)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
'''
|
'''
|
||||||
|
@ -77,17 +89,6 @@ class PlexDB(object):
|
||||||
kodi_tagid,
|
kodi_tagid,
|
||||||
sync_to_kodi))
|
sync_to_kodi))
|
||||||
|
|
||||||
def update_section(self, section_name, kodi_tagid, section_id):
|
|
||||||
"""
|
|
||||||
Updates the section_id with section_name and kodi_tagid
|
|
||||||
"""
|
|
||||||
query = '''
|
|
||||||
UPDATE sections
|
|
||||||
SET section_name = ?, kodi_tagid = ?
|
|
||||||
WHERE section_id = ?
|
|
||||||
'''
|
|
||||||
self.cursor.execute(query, (section_name, kodi_tagid, section_id))
|
|
||||||
|
|
||||||
def remove_section(self, section_id):
|
def remove_section(self, section_id):
|
||||||
"""
|
"""
|
||||||
Removes the Plex db entry for the section with section_id
|
Removes the Plex db entry for the section with section_id
|
||||||
|
@ -95,18 +96,147 @@ class PlexDB(object):
|
||||||
self.cursor.execute('DELETE FROM sections WHERE section_id = ?',
|
self.cursor.execute('DELETE FROM sections WHERE section_id = ?',
|
||||||
(section_id, ))
|
(section_id, ))
|
||||||
|
|
||||||
def item_by_id(self, plex_id):
|
def plex_id_by_last_sync(self, plex_type, last_sync):
|
||||||
"""
|
"""
|
||||||
For plex_id, returns the tuple
|
Returns an iterator for all items where the last_sync is NOT identical
|
||||||
(kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type, plex_type)
|
"""
|
||||||
|
query = 'SELECT plex_id FROM %s WHERE last_sync <> ?' % plex_type
|
||||||
|
self.cursor.execute(query, (last_sync, ))
|
||||||
|
return (x[0] for x in self.cursor)
|
||||||
|
|
||||||
None if not found
|
def update_last_sync(self, plex_type, plex_id, last_sync):
|
||||||
"""
|
"""
|
||||||
query = '''
|
Sets a new timestamp for plex_id
|
||||||
SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type,
|
"""
|
||||||
plex_type
|
query = 'UPDATE %s SET last_sync = ? WHERE plex_id = ?' % plex_type
|
||||||
FROM plex WHERE plex_id = ?
|
self.cursor.execute(query, (last_sync, plex_id))
|
||||||
LIMIT 1
|
|
||||||
'''
|
def remove(self, plex_id, plex_type):
|
||||||
self.cursor.execute(query, (plex_id,))
|
"""
|
||||||
return self.cursor.fetchone()
|
Removes the item from our Plex db
|
||||||
|
"""
|
||||||
|
query = 'DELETE FROM ? WHERE plex_id = ?' % plex_type
|
||||||
|
self.cursor.execute(query, (plex_id, ))
|
||||||
|
|
||||||
|
|
||||||
|
def initialize():
|
||||||
|
"""
|
||||||
|
Run once during startup to verify that plex db exists.
|
||||||
|
"""
|
||||||
|
with PlexDBBase() as plex_db:
|
||||||
|
# Create the tables for the plex database
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS sections(
|
||||||
|
section_id INTEGER PRIMARY KEY,
|
||||||
|
section_name TEXT,
|
||||||
|
plex_type TEXT,
|
||||||
|
kodi_tagid INTEGER,
|
||||||
|
sync_to_kodi INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS movie(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS show(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS season(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
show_id INTEGER, # plex_id of the parent show
|
||||||
|
parent_id INTEGER, # kodi_id of the parent show
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS episode(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
show_id INTEGER, # plex_id of the parent show
|
||||||
|
grandparent_id INTEGER, # kodi_id of the parent show
|
||||||
|
season_id INTEGER, # plex_id of the parent season
|
||||||
|
parent_id INTEGER, # kodi_id of the parent season
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS artist(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS album(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
artist_id INTEGER, # plex_id of the parent artist
|
||||||
|
parent_id INTEGER, # kodi_id of the parent artist
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS track(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
artist_id INTEGER, # plex_id of the parent artist
|
||||||
|
grandparent_id INTEGER, # kodi_id of the parent artist
|
||||||
|
album_id INTEGER, # plex_id of the parent album
|
||||||
|
parent_id INTEGER, # kodi_id of the parent album
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS playlists(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
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():
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
tables = [i[0] for i in tables]
|
||||||
|
for table in tables:
|
||||||
|
delete_query = 'DELETE FROM %s' % table
|
||||||
|
plex_db.cursor.execute(delete_query)
|
||||||
|
|
50
resources/lib/plex_db/movies.py
Normal file
50
resources/lib/plex_db/movies.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
class Movies(object):
|
||||||
|
def add_movie(self, plex_id=None, checksum=None, section_id=None,
|
||||||
|
kodi_id=None, kodi_fileid=None, kodi_pathid=None,
|
||||||
|
last_sync=None):
|
||||||
|
"""
|
||||||
|
Appends or replaces an entry into the plex table for movies
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
INSERT OR REPLACE INTO movie(
|
||||||
|
plex_id,
|
||||||
|
checksum,
|
||||||
|
section_id,
|
||||||
|
kodi_id,
|
||||||
|
kodi_fileid,
|
||||||
|
kodi_pathid,
|
||||||
|
fanart_synced,
|
||||||
|
last_sync)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.plexcursor.execute(
|
||||||
|
query,
|
||||||
|
(plex_id,
|
||||||
|
checksum,
|
||||||
|
section_id,
|
||||||
|
kodi_id,
|
||||||
|
kodi_fileid,
|
||||||
|
kodi_pathid,
|
||||||
|
0,
|
||||||
|
last_sync))
|
||||||
|
|
||||||
|
def movie(self, plex_id):
|
||||||
|
"""
|
||||||
|
Returns the show info as a tuple for the TV show with plex_id:
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT * FROM movie WHERE plex_id = ?',
|
||||||
|
(plex_id, ))
|
||||||
|
return self.cursor.fetchone()
|
|
@ -2,55 +2,59 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
from . import common
|
|
||||||
from .. import variables as v
|
|
||||||
|
|
||||||
###############################################################################
|
class TVShows(object):
|
||||||
|
def add_show(self, plex_id=None, checksum=None, section_id=None,
|
||||||
|
kodi_id=None, kodi_pathid=None, last_sync=None):
|
||||||
|
"""
|
||||||
|
Appends or replaces tv show entry into the plex table
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
INSERT OR REPLACE INTO show(
|
||||||
|
plex_id, checksum, section_id, kodi_id, kodi_pathid,
|
||||||
|
fanart_synced, last_sync)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.plexcursor.execute(
|
||||||
|
query,
|
||||||
|
(plex_id, checksum, section_id, kodi_id, kodi_pathid, 0,
|
||||||
|
last_sync))
|
||||||
|
|
||||||
|
def add_season(self, plex_id=None, checksum=None, section_id=None,
|
||||||
class PlexDB(common.PlexDB):
|
show_id=None, parent_id=None, kodi_id=None, last_sync=None):
|
||||||
def add_reference(self, plex_type=None, plex_id=None, checksum=None,
|
|
||||||
section_id=None, show_id=None, grandparent_id=None,
|
|
||||||
season_id=None, parent_id=None, kodi_id=None,
|
|
||||||
kodi_fileid=None, kodi_pathid=None, last_sync=None):
|
|
||||||
"""
|
"""
|
||||||
Appends or replaces an entry into the plex table
|
Appends or replaces an entry into the plex table
|
||||||
"""
|
"""
|
||||||
if plex_type == v.PLEX_TYPE_EPISODE:
|
query = '''
|
||||||
query = '''
|
INSERT OR REPLACE INTO season(
|
||||||
INSERT OR REPLACE INTO episode(
|
plex_id, checksum, section_id, show_id, parent_id,
|
||||||
plex_id, checksum, section_id, show_id, grandparent_id,
|
kodi_id, fanart_synced, last_sync)
|
||||||
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
fanart_synced, last_sync)
|
'''
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
self.plexcursor.execute(
|
||||||
'''
|
query,
|
||||||
self.plexcursor.execute(
|
(plex_id, checksum, section_id, show_id, parent_id,
|
||||||
query,
|
kodi_id, 0, last_sync))
|
||||||
(plex_id, checksum, section_id, show_id, grandparent_id,
|
|
||||||
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
def add_episode(self, plex_id=None, checksum=None, section_id=None,
|
||||||
0, last_sync))
|
show_id=None, grandparent_id=None, season_id=None,
|
||||||
elif plex_type == v.PLEX_TYPE_SEASON:
|
parent_id=None, kodi_id=None, kodi_fileid=None,
|
||||||
query = '''
|
kodi_pathid=None, last_sync=None):
|
||||||
INSERT OR REPLACE INTO season(
|
"""
|
||||||
plex_id, checksum, section_id, show_id, parent_id,
|
Appends or replaces an entry into the plex table
|
||||||
kodi_id, fanart_synced, last_sync)
|
"""
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
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)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
'''
|
'''
|
||||||
self.plexcursor.execute(
|
self.plexcursor.execute(
|
||||||
query,
|
query,
|
||||||
(plex_id, checksum, section_id, show_id, parent_id,
|
(plex_id, checksum, section_id, show_id, grandparent_id,
|
||||||
kodi_id, 0, last_sync))
|
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
||||||
elif plex_type == v.PLEX_TYPE_SHOW:
|
0, last_sync))
|
||||||
query = '''
|
|
||||||
INSERT OR REPLACE INTO show(
|
|
||||||
plex_id, checksum, section_id, kodi_id, kodi_pathid,
|
|
||||||
fanart_synced, last_sync)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
'''
|
|
||||||
self.plexcursor.execute(
|
|
||||||
query,
|
|
||||||
(plex_id, checksum, section_id, kodi_id, kodi_pathid, 0,
|
|
||||||
last_sync))
|
|
||||||
|
|
||||||
def show(self, plex_id):
|
def show(self, plex_id):
|
||||||
"""
|
"""
|
||||||
|
@ -63,7 +67,7 @@ class PlexDB(common.PlexDB):
|
||||||
fanart_synced INTEGER,
|
fanart_synced INTEGER,
|
||||||
last_sync INTEGER
|
last_sync INTEGER
|
||||||
"""
|
"""
|
||||||
self.cursor.execute('SELECT * FROM show WHERE plex_id = ?',
|
self.cursor.execute('SELECT * FROM show WHERE plex_id = ? LIMIT 1',
|
||||||
(plex_id, ))
|
(plex_id, ))
|
||||||
return self.cursor.fetchone()
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
@ -79,7 +83,7 @@ class PlexDB(common.PlexDB):
|
||||||
fanart_synced INTEGER,
|
fanart_synced INTEGER,
|
||||||
last_sync INTEGER
|
last_sync INTEGER
|
||||||
"""
|
"""
|
||||||
self.cursor.execute('SELECT * FROM season WHERE plex_id = ?',
|
self.cursor.execute('SELECT * FROM season WHERE plex_id = ? LIMIT 1',
|
||||||
(plex_id, ))
|
(plex_id, ))
|
||||||
return self.cursor.fetchone()
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
@ -99,26 +103,6 @@ class PlexDB(common.PlexDB):
|
||||||
fanart_synced INTEGER,
|
fanart_synced INTEGER,
|
||||||
last_sync INTEGER
|
last_sync INTEGER
|
||||||
"""
|
"""
|
||||||
self.cursor.execute('SELECT * FROM episode WHERE plex_id = ?',
|
self.cursor.execute('SELECT * FROM episode WHERE plex_id = ? LIMIT 1',
|
||||||
(plex_id, ))
|
(plex_id, ))
|
||||||
return self.cursor.fetchone()
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
def plex_id_by_last_sync(self, plex_type, last_sync):
|
|
||||||
"""
|
|
||||||
Returns an iterator for all items where the last_sync is NOT identical
|
|
||||||
"""
|
|
||||||
self.cursor.execute('SELECT plex_id FROM ? WHERE last_sync <> ?',
|
|
||||||
(plex_type, last_sync, ))
|
|
||||||
return (x[0] for x in self.cursor)
|
|
||||||
|
|
||||||
def shows_plex_id_section_id(self):
|
|
||||||
"""
|
|
||||||
Iterator for tuples (plex_id, section_id) of all our TV shows
|
|
||||||
"""
|
|
||||||
self.cursor.execute('SELECT plex_id, section_id FROM show')
|
|
||||||
return self.cursor
|
|
||||||
|
|
||||||
def update_last_sync(self, plex_type, plex_id, last_sync):
|
|
||||||
"""
|
|
||||||
Sets a new timestamp for plex_id
|
|
||||||
"""
|
|
||||||
|
|
Loading…
Reference in a new issue