commit
34bc708d7b
12 changed files with 100 additions and 60 deletions
|
@ -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)
|
||||
[![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)
|
||||
[![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.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)
|
||||
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
||||
|
|
12
addon.xml
12
addon.xml
|
@ -1,5 +1,5 @@
|
|||
<?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>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.9.1" />
|
||||
|
@ -77,7 +77,15 @@
|
|||
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
|
||||
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
|
||||
<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
|
||||
|
||||
version 2.7.5:
|
||||
|
|
|
@ -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:
|
||||
- Make 2.7.5 available for everyone
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ def show_main_menu(content_type=None):
|
|||
# Get nodes from the window props
|
||||
totalnodes = int(utils.window('Plex.nodes.total') or 0)
|
||||
for i in range(totalnodes):
|
||||
path = utils.window('Plex.nodes.%s.content' % i)
|
||||
path = utils.window('Plex.nodes.%s.index' % i)
|
||||
if not path:
|
||||
continue
|
||||
label = utils.window('Plex.nodes.%s.title' % i)
|
||||
|
@ -114,9 +114,9 @@ def show_main_menu(content_type=None):
|
|||
continue
|
||||
# Add ANOTHER menu item that uses add-on paths instead of direct
|
||||
# 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
|
||||
directory_item('%s (%s)' % (label, utils.lang(22082)), addon_path)
|
||||
directory_item('%s (%s)' % (label, utils.lang(22082)), addon_index)
|
||||
# Playlists
|
||||
if content_type != 'image':
|
||||
path = 'plugin://%s?mode=playlists' % v.ADDON_ID
|
||||
|
|
|
@ -22,12 +22,9 @@ def kodiid_from_filename(path, kodi_type=None, db_type=None):
|
|||
"""
|
||||
kodi_id = None
|
||||
path = utils.try_decode(path)
|
||||
try:
|
||||
filename = path.rsplit('/', 1)[1]
|
||||
path = path.rsplit('/', 1)[0] + '/'
|
||||
except IndexError:
|
||||
filename = path.rsplit('\\', 1)[1]
|
||||
path = path.rsplit('\\', 1)[0] + '\\'
|
||||
path, filename = path_ops.path.split(path)
|
||||
# Make sure path ends in either '/' or '\'
|
||||
path = path_ops.path.join(path, '')
|
||||
if kodi_type == v.KODI_TYPE_SONG or db_type == 'music':
|
||||
with KodiMusicDB(lock=False) as kodidb:
|
||||
try:
|
||||
|
|
|
@ -258,6 +258,7 @@ class FullSync(common.fullsync_mixin):
|
|||
for section in (x for x in sections.SECTIONS
|
||||
if x.section_type == kind[1]):
|
||||
if self.isCanceled():
|
||||
LOG.debug('Need to exit now')
|
||||
return
|
||||
if not section.sync_to_kodi:
|
||||
LOG.info('User chose to not sync section %s', section)
|
||||
|
@ -283,6 +284,8 @@ class FullSync(common.fullsync_mixin):
|
|||
self.section_success = False
|
||||
else:
|
||||
queue.put(element)
|
||||
except Exception:
|
||||
utils.ERROR(notify=True)
|
||||
finally:
|
||||
queue.put(None)
|
||||
|
||||
|
@ -410,7 +413,7 @@ class FullSync(common.fullsync_mixin):
|
|||
def _run(self):
|
||||
self.current_sync = timing.plex_now()
|
||||
# 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
|
||||
self.successful = True
|
||||
try:
|
||||
|
@ -422,10 +425,7 @@ class FullSync(common.fullsync_mixin):
|
|||
# Actual syncing - do only new items first
|
||||
LOG.info('Running full_library_sync with repair=%s',
|
||||
self.repair)
|
||||
if not self.full_library_sync():
|
||||
self.successful = False
|
||||
return
|
||||
if self.isCanceled():
|
||||
if self.isCanceled() or not self.full_library_sync():
|
||||
self.successful = False
|
||||
return
|
||||
if common.PLAYLIST_SYNC_ENABLED and not playlists.full_sync():
|
||||
|
@ -437,6 +437,7 @@ class FullSync(common.fullsync_mixin):
|
|||
self.dialog.close()
|
||||
if self.threader:
|
||||
self.threader.shutdown()
|
||||
self.threader = None
|
||||
if not self.successful and not self.isCanceled():
|
||||
# "ERROR in library sync"
|
||||
utils.dialog('notification',
|
||||
|
|
|
@ -99,7 +99,7 @@ class Section(object):
|
|||
"'section_type': '{self.section_type}', "
|
||||
"'sync_to_kodi': {self.sync_to_kodi}, "
|
||||
"'last_sync': {self.last_sync}"
|
||||
"}}").format(self=self)
|
||||
"}}").format(self=self).encode('utf-8')
|
||||
__str__ = __repr__
|
||||
|
||||
def __nonzero__(self):
|
||||
|
@ -246,20 +246,27 @@ class Section(object):
|
|||
}
|
||||
if not self.sync_to_kodi:
|
||||
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:
|
||||
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:
|
||||
# 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.type' % self.node, value=self.content)
|
||||
utils.window('%s.content' % self.node, value=path)
|
||||
# .path leads to all elements of this library
|
||||
utils.window('%s.path' % self.node,
|
||||
value='ActivateWindow(Videos,%s,return)' % path)
|
||||
utils.window('%s.id' % self.node, value=str(self.section_id))
|
||||
# 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:
|
||||
self.remove_files_from_kodi()
|
||||
return
|
||||
|
@ -307,7 +314,7 @@ class Section(object):
|
|||
xml = getattr(nodes, 'node_%s' % node_type)(self, node_name)
|
||||
self._write_xml(xml, xml_name)
|
||||
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):
|
||||
LOG.debug('Creating xml for section %s: %s', self.name, xml_name)
|
||||
|
@ -329,17 +336,17 @@ class Section(object):
|
|||
utils.indent(xml)
|
||||
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
|
||||
|
||||
Uses the same conventions/logic as Emby for Kodi does
|
||||
"""
|
||||
if self.section_type == v.PLEX_TYPE_ARTIST:
|
||||
window_path = 'ActivateWindow(Music,%s,return)' % path
|
||||
elif self.section_type == v.PLEX_TYPE_PHOTO:
|
||||
if pms_node or not self.sync_to_kodi:
|
||||
# Check: elif node_type in ('browse', 'homevideos', 'photos'):
|
||||
window_path = path
|
||||
elif self.section_type == v.PLEX_TYPE_ARTIST:
|
||||
window_path = 'ActivateWindow(Music,%s,return)' % path
|
||||
else:
|
||||
window_path = 'ActivateWindow(Videos,%s,return)' % path
|
||||
# if node_type == 'all':
|
||||
|
|
|
@ -90,9 +90,9 @@ def sync_pms_time():
|
|||
# Toggle watched state back
|
||||
PF.scrobble(plex_id, 'unwatched')
|
||||
try:
|
||||
plextime = xml[0].get('lastViewedAt')
|
||||
except (IndexError, TypeError, AttributeError):
|
||||
LOG.error('Could not get lastViewedAt - aborting')
|
||||
plextime = xml[0].attrib['lastViewedAt']
|
||||
except (IndexError, TypeError, AttributeError, KeyError):
|
||||
LOG.warn('Could not get lastViewedAt - aborting')
|
||||
return False
|
||||
|
||||
# Calculate time offset Kodi-PMS
|
||||
|
|
|
@ -372,19 +372,15 @@ def verify_kodi_item(plex_id, kodi_item):
|
|||
raise PlaylistError
|
||||
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
|
||||
kodi_item)
|
||||
kodi_id, _ = kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_MOVIE)
|
||||
kodi_item['type'] = v.KODI_TYPE_MOVIE
|
||||
if kodi_id is None:
|
||||
kodi_id, _ = kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_EPISODE)
|
||||
kodi_item['type'] = v.KODI_TYPE_EPISODE
|
||||
if kodi_id is None:
|
||||
kodi_id, _ = kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_SONG)
|
||||
kodi_item['type'] = v.KODI_TYPE_SONG
|
||||
# Try the VIDEO DB first - will find both movies and episodes
|
||||
kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
|
||||
db_type='video')
|
||||
if not kodi_id:
|
||||
# No movie or episode found - try MUSIC DB now for songs
|
||||
kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
|
||||
db_type='music')
|
||||
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)
|
||||
return kodi_item
|
||||
|
||||
|
|
|
@ -202,6 +202,8 @@ def _full_sync():
|
|||
return False
|
||||
playlist = db.get_playlist(plex_id=plex_id)
|
||||
LOG.debug('Removing outdated Plex playlist from Kodi: %s', playlist)
|
||||
if playlist is None:
|
||||
continue
|
||||
try:
|
||||
kodi_pl.delete(playlist)
|
||||
except PlaylistError:
|
||||
|
|
|
@ -289,7 +289,9 @@ class SubscriptionMgr(object):
|
|||
# To avoid RuntimeError, don't use self.lastplayers
|
||||
for playerid in (0, 1, 2):
|
||||
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):
|
||||
"""
|
||||
|
@ -399,7 +401,11 @@ class SubscriptionMgr(object):
|
|||
self.last_params = 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)
|
||||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
xargs = params_pms()
|
||||
|
@ -416,7 +422,8 @@ class SubscriptionMgr(object):
|
|||
DU().downloadUrl(url,
|
||||
authenticate=False,
|
||||
parameters=xargs,
|
||||
headerOverride=HEADERS_PMS)
|
||||
headerOverride=HEADERS_PMS,
|
||||
timeout=timeout)
|
||||
LOG.debug("Sent server notification with parameters: %s to %s",
|
||||
xargs, url)
|
||||
|
||||
|
|
|
@ -451,12 +451,15 @@ class Service(object):
|
|||
self.choose_plex_libraries()
|
||||
elif plex_command == 'RESET-PKC':
|
||||
utils.reset()
|
||||
elif plex_command == 'EXIT-PKC':
|
||||
LOG.info('Received command from another instance to quit')
|
||||
app.APP.stop_pkc = True
|
||||
if task:
|
||||
backgroundthread.BGThreader.addTasksToFront([task])
|
||||
continue
|
||||
|
||||
if app.APP.suspend:
|
||||
app.APP.monitor.waitForAbort(0.1)
|
||||
xbmc.sleep(100)
|
||||
continue
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
|
@ -495,7 +498,7 @@ class Service(object):
|
|||
if utils.settings('enable_alexa') == 'true':
|
||||
self.alexa.start()
|
||||
|
||||
app.APP.monitor.waitForAbort(0.1)
|
||||
xbmc.sleep(100)
|
||||
|
||||
# EXITING PKC
|
||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||
|
@ -506,26 +509,37 @@ class Service(object):
|
|||
library_sync.clear_window_vars()
|
||||
# Will block until threads have quit
|
||||
app.APP.stop_threads()
|
||||
utils.window('plex_service_started', clear=True)
|
||||
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
||||
|
||||
|
||||
def start():
|
||||
# Safety net - Kody starts PKC twice upon first installation!
|
||||
if utils.window('plex_service_started') == 'true':
|
||||
EXIT = True
|
||||
else:
|
||||
utils.window('plex_service_started', value='true')
|
||||
EXIT = False
|
||||
|
||||
# Delay option
|
||||
LOG.info('Another service.py instance is already running - shutting '
|
||||
'it down now')
|
||||
# Telling the other Python instance of PKC to shut down now
|
||||
i = 0
|
||||
while utils.window('plexkodiconnect.command'):
|
||||
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'))
|
||||
|
||||
LOG.info("Delaying Plex startup by: %s sec...", DELAY)
|
||||
if EXIT:
|
||||
LOG.error('PKC service.py already started - exiting this instance')
|
||||
elif DELAY and xbmc.Monitor().waitForAbort(DELAY):
|
||||
if DELAY and xbmc.Monitor().waitForAbort(DELAY):
|
||||
# Start the service
|
||||
LOG.info("Abort requested while waiting. PKC not started.")
|
||||
else:
|
||||
Service().ServiceEntryPoint()
|
||||
utils.window('plex_service_started', clear=True)
|
||||
LOG.info("======== STOP PlexKodiConnect service ========")
|
||||
|
|
Loading…
Reference in a new issue