Merge pull request #778 from croneter/beta-version

Bump master
This commit is contained in:
croneter 2019-03-17 18:01:07 +01:00 committed by GitHub
commit 34bc708d7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 100 additions and 60 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-2.7.6-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) [![stable version](https://img.shields.io/badge/stable_version-2.7.7-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.7.6-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.7.7-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) [![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) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.7.6" provider-name="croneter"> <addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.7.7" provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" /> <import addon="script.module.requests" version="2.9.1" />
@ -77,7 +77,15 @@
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary> <summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description> <description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer> <disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
<news>version 2.7.6: <news>version 2.7.7:
- Fix sync not working due to non-ASCII Plex library names
- Fix PKC synching playstate to wrong user on profile switch. Be aware that Kodi profile switches are error-prone
- Fix playback sometimes not being reported for direct paths
- Fix float() argument must be a string or a number
- Fix nodes for skin use
- Fix 'NoneType' object has no attribute 'kodi_path'
version 2.7.6:
- Make 2.7.5 available for everyone - Make 2.7.5 available for everyone
version 2.7.5: version 2.7.5:

View file

@ -1,3 +1,11 @@
version 2.7.7:
- Fix sync not working due to non-ASCII Plex library names
- Fix PKC synching playstate to wrong user on profile switch. Be aware that Kodi profile switches are error-prone
- Fix playback sometimes not being reported for direct paths
- Fix float() argument must be a string or a number
- Fix nodes for skin use
- Fix 'NoneType' object has no attribute 'kodi_path'
version 2.7.6: version 2.7.6:
- Make 2.7.5 available for everyone - Make 2.7.5 available for everyone

View file

@ -84,7 +84,7 @@ def show_main_menu(content_type=None):
# Get nodes from the window props # Get nodes from the window props
totalnodes = int(utils.window('Plex.nodes.total') or 0) totalnodes = int(utils.window('Plex.nodes.total') or 0)
for i in range(totalnodes): for i in range(totalnodes):
path = utils.window('Plex.nodes.%s.content' % i) path = utils.window('Plex.nodes.%s.index' % i)
if not path: if not path:
continue continue
label = utils.window('Plex.nodes.%s.title' % i) label = utils.window('Plex.nodes.%s.title' % i)
@ -114,9 +114,9 @@ def show_main_menu(content_type=None):
continue continue
# Add ANOTHER menu item that uses add-on paths instead of direct # Add ANOTHER menu item that uses add-on paths instead of direct
# paths in order to let the user navigate into all submenus # paths in order to let the user navigate into all submenus
addon_path = utils.window('Plex.nodes.%s.addon_path' % i) addon_index = utils.window('Plex.nodes.%s.addon_index' % i)
# Append "(More...)" to the label # Append "(More...)" to the label
directory_item('%s (%s)' % (label, utils.lang(22082)), addon_path) directory_item('%s (%s)' % (label, utils.lang(22082)), addon_index)
# Playlists # Playlists
if content_type != 'image': if content_type != 'image':
path = 'plugin://%s?mode=playlists' % v.ADDON_ID path = 'plugin://%s?mode=playlists' % v.ADDON_ID

View file

@ -22,12 +22,9 @@ def kodiid_from_filename(path, kodi_type=None, db_type=None):
""" """
kodi_id = None kodi_id = None
path = utils.try_decode(path) path = utils.try_decode(path)
try: path, filename = path_ops.path.split(path)
filename = path.rsplit('/', 1)[1] # Make sure path ends in either '/' or '\'
path = path.rsplit('/', 1)[0] + '/' path = path_ops.path.join(path, '')
except IndexError:
filename = path.rsplit('\\', 1)[1]
path = path.rsplit('\\', 1)[0] + '\\'
if kodi_type == v.KODI_TYPE_SONG or db_type == 'music': if kodi_type == v.KODI_TYPE_SONG or db_type == 'music':
with KodiMusicDB(lock=False) as kodidb: with KodiMusicDB(lock=False) as kodidb:
try: try:

View file

@ -258,6 +258,7 @@ class FullSync(common.fullsync_mixin):
for section in (x for x in sections.SECTIONS for section in (x for x in sections.SECTIONS
if x.section_type == kind[1]): if x.section_type == kind[1]):
if self.isCanceled(): if self.isCanceled():
LOG.debug('Need to exit now')
return return
if not section.sync_to_kodi: if not section.sync_to_kodi:
LOG.info('User chose to not sync section %s', section) LOG.info('User chose to not sync section %s', section)
@ -283,6 +284,8 @@ class FullSync(common.fullsync_mixin):
self.section_success = False self.section_success = False
else: else:
queue.put(element) queue.put(element)
except Exception:
utils.ERROR(notify=True)
finally: finally:
queue.put(None) queue.put(None)
@ -410,7 +413,7 @@ class FullSync(common.fullsync_mixin):
def _run(self): def _run(self):
self.current_sync = timing.plex_now() self.current_sync = timing.plex_now()
# Get latest Plex libraries and build playlist and video node files # Get latest Plex libraries and build playlist and video node files
if not sections.sync_from_pms(self): if self.isCanceled() or not sections.sync_from_pms(self):
return return
self.successful = True self.successful = True
try: try:
@ -422,10 +425,7 @@ class FullSync(common.fullsync_mixin):
# Actual syncing - do only new items first # Actual syncing - do only new items first
LOG.info('Running full_library_sync with repair=%s', LOG.info('Running full_library_sync with repair=%s',
self.repair) self.repair)
if not self.full_library_sync(): if self.isCanceled() or not self.full_library_sync():
self.successful = False
return
if self.isCanceled():
self.successful = False self.successful = False
return return
if common.PLAYLIST_SYNC_ENABLED and not playlists.full_sync(): if common.PLAYLIST_SYNC_ENABLED and not playlists.full_sync():
@ -437,6 +437,7 @@ class FullSync(common.fullsync_mixin):
self.dialog.close() self.dialog.close()
if self.threader: if self.threader:
self.threader.shutdown() self.threader.shutdown()
self.threader = None
if not self.successful and not self.isCanceled(): if not self.successful and not self.isCanceled():
# "ERROR in library sync" # "ERROR in library sync"
utils.dialog('notification', utils.dialog('notification',

View file

@ -99,7 +99,7 @@ class Section(object):
"'section_type': '{self.section_type}', " "'section_type': '{self.section_type}', "
"'sync_to_kodi': {self.sync_to_kodi}, " "'sync_to_kodi': {self.sync_to_kodi}, "
"'last_sync': {self.last_sync}" "'last_sync': {self.last_sync}"
"}}").format(self=self) "}}").format(self=self).encode('utf-8')
__str__ = __repr__ __str__ = __repr__
def __nonzero__(self): def __nonzero__(self):
@ -246,20 +246,27 @@ class Section(object):
} }
if not self.sync_to_kodi: if not self.sync_to_kodi:
args['synched'] = 'false' args['synched'] = 'false'
addon_path = self.addon_path(args) addon_index = self.addon_path(args)
if self.sync_to_kodi and self.section_type in v.PLEX_VIDEOTYPES: if self.sync_to_kodi and self.section_type in v.PLEX_VIDEOTYPES:
path = 'library://video/Plex-%s' % self.section_id path = 'library://video/Plex-{0}/{0}_all.xml'
path = path.format(self.section_id)
index = 'library://video/Plex-%s' % self.section_id
else: else:
# No xmls to link to - let's show the listings on the fly # No xmls to link to - let's show the listings on the fly
path = addon_path index = addon_index
args['key'] = '/library/sections/%s/all' % self.section_id
path = self.addon_path(args)
# .index will list all possible nodes for this library
utils.window('%s.index' % self.node, value=index)
utils.window('%s.title' % self.node, value=self.name) utils.window('%s.title' % self.node, value=self.name)
utils.window('%s.type' % self.node, value=self.content) utils.window('%s.type' % self.node, value=self.content)
utils.window('%s.content' % self.node, value=path) utils.window('%s.content' % self.node, value=path)
# .path leads to all elements of this library
utils.window('%s.path' % self.node, utils.window('%s.path' % self.node,
value='ActivateWindow(Videos,%s,return)' % path) value='ActivateWindow(Videos,%s,return)' % path)
utils.window('%s.id' % self.node, value=str(self.section_id)) utils.window('%s.id' % self.node, value=str(self.section_id))
# To let the user navigate into this node when selecting widgets # To let the user navigate into this node when selecting widgets
utils.window('%s.addon_path' % self.node, value=addon_path) utils.window('%s.addon_index' % self.node, value=addon_index)
if not self.sync_to_kodi: if not self.sync_to_kodi:
self.remove_files_from_kodi() self.remove_files_from_kodi()
return return
@ -307,7 +314,7 @@ class Section(object):
xml = getattr(nodes, 'node_%s' % node_type)(self, node_name) xml = getattr(nodes, 'node_%s' % node_type)(self, node_name)
self._write_xml(xml, xml_name) self._write_xml(xml, xml_name)
self.order += 1 self.order += 1
self._window_node(path, node_name, node_type) self._window_node(path, node_name, node_type, pms_node)
def _write_xml(self, xml, xml_name): def _write_xml(self, xml, xml_name):
LOG.debug('Creating xml for section %s: %s', self.name, xml_name) LOG.debug('Creating xml for section %s: %s', self.name, xml_name)
@ -329,17 +336,17 @@ class Section(object):
utils.indent(xml) utils.indent(xml)
etree.ElementTree(xml).write(self.playlist_path, encoding='utf-8') etree.ElementTree(xml).write(self.playlist_path, encoding='utf-8')
def _window_node(self, path, node_name, node_type): def _window_node(self, path, node_name, node_type, pms_node):
""" """
Will save this section's node to the Kodi window variables Will save this section's node to the Kodi window variables
Uses the same conventions/logic as Emby for Kodi does Uses the same conventions/logic as Emby for Kodi does
""" """
if self.section_type == v.PLEX_TYPE_ARTIST: if pms_node or not self.sync_to_kodi:
window_path = 'ActivateWindow(Music,%s,return)' % path
elif self.section_type == v.PLEX_TYPE_PHOTO:
# Check: elif node_type in ('browse', 'homevideos', 'photos'): # Check: elif node_type in ('browse', 'homevideos', 'photos'):
window_path = path window_path = path
elif self.section_type == v.PLEX_TYPE_ARTIST:
window_path = 'ActivateWindow(Music,%s,return)' % path
else: else:
window_path = 'ActivateWindow(Videos,%s,return)' % path window_path = 'ActivateWindow(Videos,%s,return)' % path
# if node_type == 'all': # if node_type == 'all':

View file

@ -90,9 +90,9 @@ def sync_pms_time():
# Toggle watched state back # Toggle watched state back
PF.scrobble(plex_id, 'unwatched') PF.scrobble(plex_id, 'unwatched')
try: try:
plextime = xml[0].get('lastViewedAt') plextime = xml[0].attrib['lastViewedAt']
except (IndexError, TypeError, AttributeError): except (IndexError, TypeError, AttributeError, KeyError):
LOG.error('Could not get lastViewedAt - aborting') LOG.warn('Could not get lastViewedAt - aborting')
return False return False
# Calculate time offset Kodi-PMS # Calculate time offset Kodi-PMS

View file

@ -372,19 +372,15 @@ def verify_kodi_item(plex_id, kodi_item):
raise PlaylistError raise PlaylistError
LOG.debug('Starting research for Kodi id since we didnt get one: %s', LOG.debug('Starting research for Kodi id since we didnt get one: %s',
kodi_item) kodi_item)
kodi_id, _ = kodiid_from_filename(kodi_item['file'], # Try the VIDEO DB first - will find both movies and episodes
v.KODI_TYPE_MOVIE) kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
kodi_item['type'] = v.KODI_TYPE_MOVIE db_type='video')
if kodi_id is None: if not kodi_id:
kodi_id, _ = kodiid_from_filename(kodi_item['file'], # No movie or episode found - try MUSIC DB now for songs
v.KODI_TYPE_EPISODE) kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
kodi_item['type'] = v.KODI_TYPE_EPISODE db_type='music')
if kodi_id is None:
kodi_id, _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG)
kodi_item['type'] = v.KODI_TYPE_SONG
kodi_item['id'] = kodi_id kodi_item['id'] = kodi_id
kodi_item['type'] = None if kodi_id is None else kodi_item['type'] kodi_item['type'] = None if kodi_id is None else kodi_type
LOG.debug('Research results for kodi_item: %s', kodi_item) LOG.debug('Research results for kodi_item: %s', kodi_item)
return kodi_item return kodi_item

View file

@ -202,6 +202,8 @@ def _full_sync():
return False return False
playlist = db.get_playlist(plex_id=plex_id) playlist = db.get_playlist(plex_id=plex_id)
LOG.debug('Removing outdated Plex playlist from Kodi: %s', playlist) LOG.debug('Removing outdated Plex playlist from Kodi: %s', playlist)
if playlist is None:
continue
try: try:
kodi_pl.delete(playlist) kodi_pl.delete(playlist)
except PlaylistError: except PlaylistError:

View file

@ -289,7 +289,9 @@ class SubscriptionMgr(object):
# To avoid RuntimeError, don't use self.lastplayers # To avoid RuntimeError, don't use self.lastplayers
for playerid in (0, 1, 2): for playerid in (0, 1, 2):
self.last_params['state'] = 'stopped' self.last_params['state'] = 'stopped'
self._send_pms_notification(playerid, self.last_params) self._send_pms_notification(playerid,
self.last_params,
timeout=0.0001)
def _plex_stream_index(self, playerid, stream_type): def _plex_stream_index(self, playerid, stream_type):
""" """
@ -399,7 +401,11 @@ class SubscriptionMgr(object):
self.last_params = params self.last_params = params
return params return params
def _send_pms_notification(self, playerid, params): def _send_pms_notification(self, playerid, params, timeout=None):
"""
Pass a really low timeout in seconds if shutting down Kodi and we don't
need the PMS' response
"""
serv = self._server_by_host(self.server) serv = self._server_by_host(self.server)
playqueue = PQ.PLAYQUEUES[playerid] playqueue = PQ.PLAYQUEUES[playerid]
xargs = params_pms() xargs = params_pms()
@ -416,7 +422,8 @@ class SubscriptionMgr(object):
DU().downloadUrl(url, DU().downloadUrl(url,
authenticate=False, authenticate=False,
parameters=xargs, parameters=xargs,
headerOverride=HEADERS_PMS) headerOverride=HEADERS_PMS,
timeout=timeout)
LOG.debug("Sent server notification with parameters: %s to %s", LOG.debug("Sent server notification with parameters: %s to %s",
xargs, url) xargs, url)

View file

@ -451,12 +451,15 @@ class Service(object):
self.choose_plex_libraries() self.choose_plex_libraries()
elif plex_command == 'RESET-PKC': elif plex_command == 'RESET-PKC':
utils.reset() utils.reset()
elif plex_command == 'EXIT-PKC':
LOG.info('Received command from another instance to quit')
app.APP.stop_pkc = True
if task: if task:
backgroundthread.BGThreader.addTasksToFront([task]) backgroundthread.BGThreader.addTasksToFront([task])
continue continue
if app.APP.suspend: if app.APP.suspend:
app.APP.monitor.waitForAbort(0.1) xbmc.sleep(100)
continue continue
# Before proceeding, need to make sure: # Before proceeding, need to make sure:
@ -495,7 +498,7 @@ class Service(object):
if utils.settings('enable_alexa') == 'true': if utils.settings('enable_alexa') == 'true':
self.alexa.start() self.alexa.start()
app.APP.monitor.waitForAbort(0.1) xbmc.sleep(100)
# EXITING PKC # EXITING PKC
# Tell all threads to terminate (e.g. several lib sync threads) # Tell all threads to terminate (e.g. several lib sync threads)
@ -506,26 +509,37 @@ class Service(object):
library_sync.clear_window_vars() library_sync.clear_window_vars()
# Will block until threads have quit # Will block until threads have quit
app.APP.stop_threads() app.APP.stop_threads()
utils.window('plex_service_started', clear=True)
LOG.info("======== STOP %s ========", v.ADDON_NAME)
def start(): def start():
# Safety net - Kody starts PKC twice upon first installation! # Safety net - Kody starts PKC twice upon first installation!
if utils.window('plex_service_started') == 'true': if utils.window('plex_service_started') == 'true':
EXIT = True LOG.info('Another service.py instance is already running - shutting '
else: 'it down now')
utils.window('plex_service_started', value='true') # Telling the other Python instance of PKC to shut down now
EXIT = False i = 0
while utils.window('plexkodiconnect.command'):
# Delay option xbmc.sleep(20)
i += 1
if i > 300:
LOG.error('Could not tell other PKC instance to shut down')
return
utils.window('plexkodiconnect.command', value='EXIT-PKC')
# Telling successful - now wait for actual shut-down
i = 0
while utils.window('plex_service_started'):
xbmc.sleep(20)
i += 1
if i > 300:
LOG.error('Could not shut down other PKC instance')
return
utils.window('plex_service_started', value='true')
DELAY = int(utils.settings('startupDelay')) DELAY = int(utils.settings('startupDelay'))
LOG.info("Delaying Plex startup by: %s sec...", DELAY) LOG.info("Delaying Plex startup by: %s sec...", DELAY)
if EXIT: if DELAY and xbmc.Monitor().waitForAbort(DELAY):
LOG.error('PKC service.py already started - exiting this instance')
elif DELAY and xbmc.Monitor().waitForAbort(DELAY):
# Start the service # Start the service
LOG.info("Abort requested while waiting. PKC not started.") LOG.info("Abort requested while waiting. PKC not started.")
else: else:
Service().ServiceEntryPoint() Service().ServiceEntryPoint()
utils.window('plex_service_started', clear=True)
LOG.info("======== STOP PlexKodiConnect service ========")