Merge branch 'master' into translations
This commit is contained in:
commit
056285f7ae
20 changed files with 913 additions and 791 deletions
|
@ -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)
|
||||
|
|
16
addon.xml
16
addon.xml
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,))
|
||||
|
|
|
@ -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,))
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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 ###===---")
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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 ##===----")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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():
|
||||
"""
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue