Merge branch 'master' into translations

This commit is contained in:
croneter 2018-04-17 21:05:06 +02:00
commit 056285f7ae
20 changed files with 913 additions and 791 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-1.8.18-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.0.18-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.0.19-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.18" provider-name="croneter">
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.0.19" provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" />
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.1" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.2" />
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.2" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.3" />
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video audio image</provides>
@ -67,7 +67,15 @@
<summary lang="ru_RU">Нативная интеграция сервера Plex в Kodi</summary>
<description lang="ru_RU">Подключите Kodi к своему серверу Plex. Плагин предполагает что вы управляете своими видео с помощью Plex (а не в Kodi). Вы можете потерять текущие базы данных музыки и видео в Kodi (так как плагин напрямую их изменяет). Используйте на свой страх и риск</description>
<disclaimer lang="ru_RU">Используйте на свой страх и риск</disclaimer>
<news>version 2.0.18 (beta only):
<news>version 2.0.19 (beta only):
- Fix PKC playback startup getting caught in infinity loop
- Rewire library sync, suspend sync during playback
- Fix playback failing in certain cases
- Fix PKC not working anymore after using context menu on songs
- Fix deletion of Plex music items
- Code cleanup
version 2.0.18 (beta only):
- Fix some playqueue inconsistencies using Plex Companion
- Direct paths: fix replaying item where playback was started via PMS
- Fix Plex trailers screwing up playqueue

View file

@ -1,3 +1,11 @@
version 2.0.19 (beta only):
- Fix PKC playback startup getting caught in infinity loop
- Rewire library sync, suspend sync during playback
- Fix playback failing in certain cases
- Fix PKC not working anymore after using context menu on songs
- Fix deletion of Plex music items
- Code cleanup
version 2.0.18 (beta only):
- Fix some playqueue inconsistencies using Plex Companion
- Direct paths: fix replaying item where playback was started via PMS

View file

@ -171,8 +171,12 @@ class Main():
"""
Start up playback_starter in main Python thread
"""
request = '%s&handle=%s' % (argv[2], HANDLE)
# Put the request into the 'queue'
plex_command('PLAY', argv[2])
plex_command('PLAY', request)
if HANDLE == -1:
# Handle -1 received, not waiting for main thread
return
# Wait for the result
while not pickl_window('plex_result'):
sleep(50)

View file

@ -94,8 +94,7 @@ class PlexCompanion(Thread):
params = {
'mode': 'plex_node',
'key': '{server}%s' % data.get('key'),
'offset': data.get('offset'),
'play_directly': 'true'
'offset': data.get('offset')
}
executebuiltin('RunPlugin(plugin://%s?%s)'
% (v.ADDON_ID, urlencode(params)))

View file

@ -29,7 +29,7 @@ LOG = getLogger("PLEX." + __name__)
WINDOW_PROPERTIES = (
"plex_online", "plex_serverStatus", "plex_shouldStop", "plex_dbScan",
"plex_initialScan", "plex_customplayqueue", "plex_playbackProps",
"plex_customplayqueue", "plex_playbackProps",
"pms_token", "plex_token", "pms_server", "plex_machineIdentifier",
"plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths",
"countError", "countUnauthorized", "plex_restricteduser",

View file

@ -1719,6 +1719,7 @@ class Music(Items):
# Update album artwork
artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
@catch_exceptions(warnuser=True)
def remove(self, plex_id):
"""
Completely remove the item with plex_id from the Kodi and Plex DBs.
@ -1728,92 +1729,124 @@ class Music(Items):
try:
kodi_id = plex_dbitem[0]
file_id = plex_dbitem[1]
path_id = plex_dbitem[2]
parent_id = plex_dbitem[3]
kodi_type = plex_dbitem[4]
LOG.info("Removing %s with kodi_id: %s, parent_id: %s, file_id: %s",
kodi_type, kodi_id, parent_id, file_id)
LOG.debug('Removing plex_id %s with kodi_type %s, kodi_id %s, '
'parent_id %s, file_id %s, pathid %s',
plex_id, kodi_type, kodi_id, parent_id, file_id, path_id)
except TypeError:
LOG.debug('Cannot delete item with plex id %s from Kodi', plex_id)
return
# Remove the plex reference
self.plex_db.removeItem(plex_id)
##### SONG #####
if kodi_type == v.KODI_TYPE_SONG:
# Delete song
self.remove_song(kodi_id)
# Delete song and orphaned artists and albums
self._remove_song(kodi_id, path_id=path_id)
# Album verification
for item in self.plex_db.getItem_byWildId(plex_id):
item_kid = item[0]
item_kodi_type = item[1]
if item_kodi_type == v.KODI_TYPE_ALBUM:
childs = self.plex_db.getItem_byParentId(item_kid,
v.KODI_TYPE_SONG)
if not childs:
# Delete album
self.remove_album(item_kid)
album = self.plex_db.getItem_byKodiId(parent_id,
v.KODI_TYPE_ALBUM)
if not self.plex_db.getItem_byParentId(parent_id,
v.KODI_TYPE_SONG):
# No song left for album - so delete the album
self.plex_db.removeItem(album[0])
self._remove_album(parent_id)
##### ALBUM #####
elif kodi_type == v.KODI_TYPE_ALBUM:
# Delete songs, album
album_songs = self.plex_db.getItem_byParentId(kodi_id,
songs = self.plex_db.getItem_byParentId(kodi_id,
v.KODI_TYPE_SONG)
for song in album_songs:
self.remove_song(song[1])
# Remove plex songs
for song in songs:
self._remove_song(song[1], path_id=song[2])
# Remove songs from Plex table
self.plex_db.removeItems_byParentId(kodi_id,
v.KODI_TYPE_SONG)
# Remove the album
self.remove_album(kodi_id)
# Remove the album and associated orphaned entries
self._remove_album(kodi_id)
##### IF ARTIST #####
elif kodi_type == v.KODI_TYPE_ARTIST:
# Delete songs, album, artist
albums = self.plex_db.getItem_byParentId(kodi_id, v.KODI_TYPE_ALBUM)
for album in albums:
albumid = album[1]
album_songs = self.plex_db.getItem_byParentId(albumid,
songs = self.plex_db.getItem_byParentId(album[1],
v.KODI_TYPE_SONG)
for song in album_songs:
self.remove_song(song[1])
# Remove plex song
self.plex_db.removeItems_byParentId(albumid, v.KODI_TYPE_SONG)
# Remove plex artist
self.plex_db.removeItems_byParentId(albumid, v.KODI_TYPE_ARTIST)
for song in songs:
self._remove_song(song[1], path_id=song[2])
# Remove entries for the songs in the Plex db
self.plex_db.removeItems_byParentId(album[1], v.KODI_TYPE_SONG)
# Remove kodi album
self.remove_album(albumid)
# Remove plex albums
self._remove_album(album[1])
# Remove album entries in the Plex db
self.plex_db.removeItems_byParentId(kodi_id, v.KODI_TYPE_ALBUM)
# Remove artist
self.remove_artist(kodi_id)
self._remove_artist(kodi_id)
LOG.debug("Deleted plex_id %s from kodi database", plex_id)
def remove_song(self, kodi_id):
def _remove_song(self, kodi_id, path_id=None):
"""
Remove song, and only the song
Remove song, orphaned artists and orphaned paths
"""
if not path_id:
query = 'SELECT idPath FROM song WHERE idSong = ? LIMIT 1'
self.kodicursor.execute(query, (kodi_id, ))
try:
path_id = self.kodicursor.fetchone()[0]
except TypeError:
pass
artist_to_delete = self.kodi_db.delete_song_from_song_artist(kodi_id)
if artist_to_delete:
# Delete the artist reference in the Plex table
artist = self.plex_db.getItem_byKodiId(artist_to_delete,
v.KODI_TYPE_ARTIST)
try:
plex_id = artist[0]
except TypeError:
pass
else:
self.plex_db.removeItem(plex_id)
self._remove_artist(artist_to_delete)
self.kodicursor.execute('DELETE FROM song WHERE idSong = ?',
(kodi_id, ))
# Check whether we have orphaned path entries
query = 'SELECT idPath FROM song WHERE idPath = ? LIMIT 1'
self.kodicursor.execute(query, (path_id, ))
if not self.kodicursor.fetchone():
self.kodicursor.execute('DELETE FROM path WHERE idPath = ?',
(path_id, ))
if v.KODIVERSION < 18:
self.kodi_db.delete_song_from_song_genre(kodi_id)
query = 'DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?'
self.kodicursor.execute(query, (kodi_id, ))
self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_SONG, self.kodicursor)
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?",
(kodi_id,))
def remove_album(self, kodi_id):
"""
Remove an album, and only the album
"""
def _remove_album(self, kodi_id):
'''
Remove an album
'''
self.kodi_db.delete_album_from_discography(kodi_id)
if v.KODIVERSION < 18:
self.kodi_db.delete_album_from_album_genre(kodi_id)
query = 'DELETE FROM albuminfosong WHERE idAlbumInfo = ?'
self.kodicursor.execute(query, (kodi_id, ))
self.kodicursor.execute('DELETE FROM album_artist WHERE idAlbum = ?',
(kodi_id, ))
self.kodicursor.execute('DELETE FROM album WHERE idAlbum = ?',
(kodi_id, ))
self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_ALBUM, self.kodicursor)
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?",
(kodi_id,))
def remove_artist(self, kodi_id):
"""
Remove an artist, and only the artist
"""
def _remove_artist(self, kodi_id):
'''
Remove an artist and associated songs and albums
'''
self.kodicursor.execute('DELETE FROM album_artist WHERE idArtist = ?',
(kodi_id, ))
self.kodicursor.execute('DELETE FROM artist WHERE idArtist = ?',
(kodi_id, ))
self.kodicursor.execute('DELETE FROM song_artist WHERE idArtist = ?',
(kodi_id, ))
self.kodicursor.execute('DELETE FROM discography WHERE idArtist = ?',
(kodi_id, ))
self.artwork.delete_artwork(kodi_id,
v.KODI_TYPE_ARTIST,
self.kodicursor)
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?",
(kodi_id,))

View file

@ -907,6 +907,102 @@ class KodiDBMethods(object):
self.cursor.execute(query, (name, artistid,))
return artistid
def delete_song_from_song_artist(self, song_id):
"""
Deletes son from song_artist table and possibly orphaned roles
Will returned an orphaned idArtist or None if not orphaned
"""
query = '''
SELECT idArtist, idRole FROM song_artist WHERE idSong = ? LIMIT 1
'''
self.cursor.execute(query, (song_id, ))
artist = self.cursor.fetchone()
if artist is None:
# No entry to begin with
return
# Delete the entry
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
(song_id, ))
# Check whether we need to delete orphaned roles
query = 'SELECT idRole FROM song_artist WHERE idRole = ? LIMIT 1'
self.cursor.execute(query, (artist[1], ))
if not self.cursor.fetchone():
# Delete orphaned role
self.cursor.execute('DELETE FROM role WHERE idRole = ?',
(artist[1], ))
# Check whether we need to delete orphaned artists
query = 'SELECT idArtist FROM song_artist WHERE idArtist = ? LIMIT 1'
self.cursor.execute(query, (artist[0], ))
if self.cursor.fetchone():
return
else:
return artist[0]
def delete_album_from_discography(self, album_id):
"""
Removes the album with id album_id from the table discography
"""
# Need to get the album name as a string first!
query = 'SELECT strAlbum, iYear FROM album WHERE idAlbum = ? LIMIT 1'
self.cursor.execute(query, (album_id, ))
try:
name, year = self.cursor.fetchone()
except TypeError:
return
query = 'SELECT idArtist FROM album_artist WHERE idAlbum = ? LIMIT 1'
self.cursor.execute(query, (album_id, ))
artist = self.cursor.fetchone()
if not artist:
return
query = '''
DELETE FROM discography
WHERE idArtist = ? AND strAlbum = ? AND strYear = ?
'''
self.cursor.execute(query, (artist[0], name, year))
def delete_song_from_song_genre(self, song_id):
"""
Deletes the one entry with id song_id from the song_genre table.
Will also delete orphaned genres from genre table
"""
query = 'SELECT idGenre FROM song_genre WHERE idSong = ?'
self.cursor.execute(query, (song_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(song_id, ))
# Check for orphaned genres in both song_genre and album_genre tables
query = 'SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1'
query2 = 'SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1'
for genre in genres:
self.cursor.execute(query, (genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute(query2, (genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre[0], ))
def delete_album_from_album_genre(self, album_id):
"""
Deletes the one entry with id album_id from the album_genre table.
Will also delete orphaned genres from genre table
"""
query = 'SELECT idGenre FROM album_genre WHERE idAlbum = ?'
self.cursor.execute(query, (album_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(album_id, ))
# Check for orphaned genres in both album_genre and song_genre tables
query = 'SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1'
query2 = 'SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1'
for genre in genres:
self.cursor.execute(query, (genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute(query2, (genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre[0], ))
def addAlbum(self, name, musicbrainz):
query = 'SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?'
self.cursor.execute(query, (musicbrainz,))

View file

@ -136,6 +136,7 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug("Method: %s Data: %s", method, data)
if method == "Player.OnPlay":
state.SUSPEND_SYNC = True
self.PlayBackStart(data)
elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck
@ -143,12 +144,13 @@ class KodiMonitor(xbmc.Monitor):
if data.get('end'):
if state.PKC_CAUSED_STOP is True:
state.PKC_CAUSED_STOP = False
state.PKC_CAUSED_STOP_DONE = True
LOG.debug('PKC caused this playback stop - ignoring')
else:
_playback_cleanup(ended=True)
else:
_playback_cleanup()
state.PKC_CAUSED_STOP_DONE = True
state.SUSPEND_SYNC = False
elif method == 'Playlist.OnAdd':
self._playlist_onadd(data)
elif method == 'Playlist.OnRemove':
@ -253,8 +255,8 @@ class KodiMonitor(xbmc.Monitor):
"""
playqueue = PQ.PLAYQUEUES[data['playlistid']]
if not playqueue.is_pkc_clear():
playqueue.clear(kodi=False)
playqueue.pkc_edit = True
playqueue.clear(kodi=False)
else:
LOG.debug('Detected PKC clear - ignoring')

View file

@ -12,15 +12,16 @@ import variables as v
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
###############################################################################
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN',
'STOP_SYNC'])
class Process_Fanart_Thread(Thread):
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedProcessFanart(Thread):
"""
Threaded download of additional fanart in the background
@ -39,21 +40,10 @@ class Process_Fanart_Thread(Thread):
Thread.__init__(self)
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug("---===### Starting FanartSync ###===---")
LOG.debug("---===### Starting FanartSync ###===---")
stopped = self.stopped
suspended = self.suspended
queue = self.queue
@ -63,7 +53,7 @@ class Process_Fanart_Thread(Thread):
# Set in service.py
if stopped():
# Abort was requested while waiting. We should exit
log.info("---===### Stopped FanartSync ###===---")
LOG.info("---===### Stopped FanartSync ###===---")
return
sleep(1000)
# grabs Plex item from queue
@ -73,15 +63,14 @@ class Process_Fanart_Thread(Thread):
sleep(200)
continue
log.debug('Get additional fanart for Plex id %s' % item['plex_id'])
LOG.debug('Get additional fanart for Plex id %s', item['plex_id'])
with getattr(itemtypes,
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as cls:
result = cls.getfanart(item['plex_id'],
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as item_type:
result = item_type.getfanart(item['plex_id'],
refresh=item['refresh'])
if result is True:
log.debug('Done getting fanart for Plex id %s'
% item['plex_id'])
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'])
queue.task_done()
log.debug("---===### Stopped FanartSync ###===---")
LOG.debug("---===### Stopped FanartSync ###===---")

View file

@ -11,20 +11,22 @@ import sync_info
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC'])
class Threaded_Get_Metadata(Thread):
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedGetMetadata(Thread):
"""
Threaded download of Plex XML metadata for a certain library item.
Fills the out_queue with the downloaded etree XML objects
Input:
queue Queue.Queue() object that you'll need to fill up
with Plex itemIds
with plex_ids
out_queue Queue() object where this thread will store
the downloaded metadata XMLs as etree objects
"""
@ -60,21 +62,10 @@ class Threaded_Get_Metadata(Thread):
self.out_queue.task_done()
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug('Starting get metadata thread')
LOG.debug('Starting get metadata thread')
# cache local variables because it's faster
queue = self.queue
out_queue = self.out_queue
@ -88,11 +79,11 @@ class Threaded_Get_Metadata(Thread):
sleep(20)
continue
# Download Metadata
xml = GetPlexMetadata(item['itemId'])
xml = GetPlexMetadata(item['plex_id'])
if xml is None:
# Did not receive a valid XML - skip that item for now
log.error("Could not get metadata for %s. Skipping that item "
"for now" % item['itemId'])
LOG.error("Could not get metadata for %s. Skipping that item "
"for now", item['plex_id'])
# Increase BOTH counters - since metadata won't be processed
with sync_info.LOCK:
sync_info.GET_METADATA_COUNT += 1
@ -100,21 +91,21 @@ class Threaded_Get_Metadata(Thread):
queue.task_done()
continue
elif xml == 401:
log.error('HTTP 401 returned by PMS. Too much strain? '
LOG.error('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now')
window('plex_scancrashed', value='401')
# Kill remaining items in queue (for main thread to cont.)
queue.task_done()
break
item['XML'] = xml
item['xml'] = xml
if item.get('get_children') is True:
children_xml = GetAllPlexChildren(item['itemId'])
children_xml = GetAllPlexChildren(item['plex_id'])
try:
children_xml[0].attrib
except (TypeError, IndexError, AttributeError):
log.error('Could not get children for Plex id %s'
% item['itemId'])
LOG.error('Could not get children for Plex id %s',
item['plex_id'])
item['children'] = []
else:
item['children'] = children_xml
@ -128,4 +119,4 @@ class Threaded_Get_Metadata(Thread):
queue.task_done()
# Empty queue in case PKC was shut down (main thread hangs otherwise)
self.terminate_now()
log.debug('Get metadata thread terminated')
LOG.debug('Get metadata thread terminated')

View file

@ -10,13 +10,15 @@ import itemtypes
import sync_info
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC'])
class Threaded_Process_Metadata(Thread):
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedProcessMetadata(Thread):
"""
Not yet implemented for more than 1 thread - if ever. Only to be called by
ONE thread!
@ -25,12 +27,12 @@ class Threaded_Process_Metadata(Thread):
Input:
queue: Queue.Queue() object that you'll need to fill up with
the downloaded XML eTree objects
item_type: as used to call functions in itemtypes.py e.g. 'Movies' =>
item_class: as used to call functions in itemtypes.py e.g. 'Movies' =>
itemtypes.Movies()
"""
def __init__(self, queue, item_type):
def __init__(self, queue, item_class):
self.queue = queue
self.item_type = item_type
self.item_class = item_class
Thread.__init__(self)
def terminate_now(self):
@ -49,23 +51,12 @@ class Threaded_Process_Metadata(Thread):
self.queue.task_done()
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug('Processing thread started')
LOG.debug('Processing thread started')
# Constructs the method name, e.g. itemtypes.Movies
item_fct = getattr(itemtypes, self.item_type)
item_fct = getattr(itemtypes, self.item_class)
# cache local variables because it's faster
queue = self.queue
stopped = self.stopped
@ -79,24 +70,19 @@ class Threaded_Process_Metadata(Thread):
continue
# Do the work
item_method = getattr(item_class, item['method'])
if item.get('children') is not None:
item_method(item['XML'][0],
viewtag=item['viewName'],
viewid=item['viewId'],
if item.get('children'):
item_method(item['xml'][0],
viewtag=item['view_name'],
viewid=item['view_id'],
children=item['children'])
else:
item_method(item['XML'][0],
viewtag=item['viewName'],
viewid=item['viewId'])
item_method(item['xml'][0],
viewtag=item['view_name'],
viewid=item['view_id'])
# Keep track of where we are at
try:
log.debug('found child: %s'
% item['children'].attrib)
except:
pass
with sync_info.LOCK:
sync_info.PROCESS_METADATA_COUNT += 1
sync_info.PROCESSING_VIEW_NAME = item['title']
queue.task_done()
self.terminate_now()
log.debug('Processing thread terminated')
LOG.debug('Processing thread terminated')

View file

@ -2,14 +2,14 @@
from logging import getLogger
from threading import Thread, Lock
from xbmc import sleep, Player
from xbmc import sleep
from xbmcgui import DialogProgressBG
from utils import thread_methods, language as lang
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
GET_METADATA_COUNT = 0
PROCESS_METADATA_COUNT = 0
@ -19,8 +19,10 @@ LOCK = Lock()
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC'])
class Threaded_Show_Sync_Info(Thread):
@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.
@ -34,38 +36,26 @@ class Threaded_Show_Sync_Info(Thread):
Thread.__init__(self)
def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
"""
Do the work
"""
log.debug('Show sync info thread started')
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"
% (lang(39714), self.item_type, str(total), lang(39715)))
player = Player()
total = 2 * total
totalProgress = 0
while self.stopped() is False and not player.isPlaying():
total_progress = 0
while not self.stopped():
with LOCK:
get_progress = GET_METADATA_COUNT
process_progress = PROCESS_METADATA_COUNT
viewName = PROCESSING_VIEW_NAME
totalProgress = get_progress + process_progress
view_name = PROCESSING_VIEW_NAME
total_progress = get_progress + process_progress
try:
percentage = int(float(totalProgress) / float(total)*100.0)
percentage = int(float(total_progress) / float(total)*100.0)
except ZeroDivisionError:
percentage = 0
dialog.update(percentage,
@ -74,8 +64,8 @@ class Threaded_Show_Sync_Info(Thread):
lang(39712),
process_progress,
lang(39713),
viewName))
view_name))
# Sleep for x milliseconds
sleep(200)
dialog.close()
log.debug('Show sync info thread terminated')
LOG.debug('Show sync info thread terminated')

File diff suppressed because it is too large Load diff

View file

@ -124,8 +124,12 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
LOG.debug('Playing trailers: %s', trailers)
if RESOLVE:
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
sleep_timer = 0
while not state.PKC_CAUSED_STOP_DONE:
sleep(50)
sleep_timer += 1
if sleep_timer > 100:
break
playqueue.clear()
if plex_type != v.PLEX_TYPE_CLIP:
# Post to the PMS to create a playqueue - in any case due to Companion

View file

@ -18,46 +18,50 @@ LOG = getLogger("PLEX." + __name__)
###############################################################################
class Playback_Starter(Thread):
class PlaybackStarter(Thread):
"""
Processes new plays
"""
def triage(self, item):
try:
@staticmethod
def _triage(item):
_, params = item.split('?', 1)
except ValueError:
params = dict(parse_qsl(params))
mode = params.get('mode')
resolve = False if params.get('handle') == '-1' else True
LOG.debug('Received mode: %s, params: %s', mode, params)
if mode == 'play':
playback.playback_triage(plex_id=params.get('plex_id'),
plex_type=params.get('plex_type'),
path=params.get('path'),
resolve=resolve)
elif mode == 'plex_node':
playback.process_indirect(params['key'],
params['offset'],
resolve=resolve)
elif mode == 'navigation':
# e.g. when plugin://...tvshows is called for entire season
with kodidb.GetKodiDB('video') as kodi_db:
show_id = kodi_db.show_id_from_path(item)
show_id = kodi_db.show_id_from_path(params.get('path'))
if show_id:
js.activate_window('videos',
'videodb://tvshows/titles/%s' % show_id)
else:
LOG.error('Could not find tv show id for %s', item)
if resolve:
pickle_me(Playback_Successful())
return
params = dict(parse_qsl(params))
mode = params.get('mode')
LOG.debug('Received mode: %s, params: %s', mode, params)
if mode == 'play':
playback.playback_triage(plex_id=params.get('plex_id'),
plex_type=params.get('plex_type'),
path=params.get('path'))
elif mode == 'plex_node':
playback.process_indirect(params['key'], params['offset'])
elif mode == 'context_menu':
ContextMenu(kodi_id=params['kodi_id'],
kodi_type=params['kodi_type'])
ContextMenu(kodi_id=params.get('kodi_id'),
kodi_type=params.get('kodi_type'))
def run(self):
queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting Playback_Starter ##===----")
LOG.info("----===## Starting PlaybackStarter ##===----")
while True:
item = queue.get()
if item is None:
# Need to shutdown - initiated by command_pipeline
break
else:
self.triage(item)
self._triage(item)
queue.task_done()
LOG.info("----===## Playback_Starter stopped ##===----")
LOG.info("----===## PlaybackStarter stopped ##===----")

View file

@ -237,6 +237,7 @@ class Plex_DB_Functions():
SELECT plex_id, parent_id, plex_type
FROM plex
WHERE kodi_id = ? AND kodi_type = ?
LIMIT 1
'''
self.plexcursor.execute(query, (kodi_id, kodi_type,))
return self.plexcursor.fetchone()

View file

@ -10,6 +10,9 @@ STOP_PKC = False
SUSPEND_LIBRARY_THREAD = False
# Set if user decided to cancel sync
STOP_SYNC = False
# Set e.g. during media playback if PKC should not do any syncs. Will NOT
# suspend synching of playstate progress
SUSPEND_SYNC = False
# Could we access the paths?
PATH_VERIFIED = False
# Set if a Plex-Kodi DB sync is being done - along with
@ -36,8 +39,6 @@ FORCE_RELOAD_SKIN = True
# Stemming from the PKC settings.xml
# Shall we show Kodi dialogs when synching?
SYNC_DIALOG = True
# Have we already checked the Kodi DB on consistency?
KODI_DB_CHECKED = False
# Is synching of Plex music enabled?
ENABLE_MUSIC = True
# How often shall we sync?

View file

@ -1050,10 +1050,10 @@ def thread_methods(cls=None, add_stops=None, add_suspends=None):
suspends
invoke with either
@Newthread_methods
@thread_methods
class MyClass():
or
@Newthread_methods(add_stops=['SUSPEND_LIBRARY_TRHEAD'],
@thread_methods(add_stops=['SUSPEND_LIBRARY_TRHEAD'],
add_suspends=['DB_SCAN', 'WHATEVER'])
class MyClass():
"""

View file

@ -38,7 +38,7 @@ from websocket_client import PMS_Websocket, Alexa_Websocket
from PlexFunctions import check_connection
from PlexCompanion import PlexCompanion
from command_pipeline import Monitor_Window
from playback_starter import Playback_Starter
from playback_starter import PlaybackStarter
from playqueue import PlayqueueMonitor
from artwork import Image_Cache_Thread
import variables as v
@ -111,7 +111,7 @@ class Service():
self.library = LibrarySync()
self.plexCompanion = PlexCompanion()
self.specialMonitor = SpecialMonitor()
self.playback_starter = Playback_Starter()
self.playback_starter = PlaybackStarter()
self.playqueue = PlayqueueMonitor()
if settings('enableTextureCache') == "true":
self.image_cache_thread = Image_Cache_Thread()