Merge master

This commit is contained in:
croneter 2017-09-17 13:36:59 +02:00
parent c33565af4c
commit fa6d95aa61
29 changed files with 816 additions and 652 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-1.8.5-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
[![beta version](https://img.shields.io/badge/beta_version-1.8.5-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
[![stable version](https://img.shields.io/badge/stable_version-1.8.12-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
[![beta version](https://img.shields.io/badge/beta_version-1.8.14-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.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)
@ -105,14 +105,11 @@ I'm not in any way affiliated with Plex. Thank you very much for a small donatio
Solutions are unlikely due to the nature of these issues
- A Plex Media Server "bug" leads to frequent and slow syncs, see [here for more info](https://github.com/croneter/PlexKodiConnect/issues/135)
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issue](https://github.com/croneter/PlexKodiConnect/issues/14) for more details
- *Plex Music when using Addon paths instead of Native Direct Paths:* Kodi tries to scan every(!) single Plex song on startup. This leads to errors in the Kodi log file and potentially even crashes. See the [Github issues](https://github.com/croneter/PlexKodiConnect/issues/14) for more details. **Workaround**: use [PKC direct paths](https://github.com/croneter/PlexKodiConnect/wiki/Set-up-Direct-Paths) instead of addon paths.
*Background Sync:*
The Plex Server does not tell anyone of the following changes. Hence PKC cannot detect these changes instantly but will notice them only on full/delta syncs (standard settings is every 60 minutes)
- Toggle the viewstate of an item to (un)watched outside of Kodi
- Changing details of an item, e.g. replacing a poster
However, some changes to individual items are instantly detected, e.g. if you match a yet unrecognized movie.
### Issues being worked on

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.8.7" provider-name="croneter">
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.8.14" provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.3.0" />
@ -59,7 +59,55 @@
<summary lang="da_DK">Indbygget Integration af Plex i Kodi</summary>
<description lang="da_DK">Tilslut Kodi til din Plex Media Server. Dette plugin forudsætter, at du administrere alle dine videoer med Plex (og ikke med Kodi). Du kan miste data som allerede er gemt i Kodi video og musik-databaser (dette plugin ændrer direkte i dem). Brug på eget ansvar!</description>
<disclaimer lang="da_DK">Brug på eget ansvar</disclaimer>
<news>version 1.8.6:
<news>version 1.8.14 (beta only):
- Greatly speed up displaying context menu
- Fix IndexError e.g. for channels if stream info missing
- Sleep a bit before marking item as fully watched
- Don't sleep before updating playstate to fully watched (if you watch on another Plex client)
- Fix KeyError for TV live channels for getGeople
version 1.8.13 (beta only):
- Background sync now picks up more PMS changes
- Detect Plex item deletion more reliably
- Fix changed Plex metadata not synced repeatedly
- Detect (some, not all) changes to PKC settings and apply them on-the-fly
- Fix resuming interrupted sync
- PKC logging now uses Kodi log levels
- Further code optimizations
version 1.8.12:
- Fix library sync crashing trying to display an error
version 1.8.11:
- version 1.8.10 for everybody
version 1.8.10 (beta only):
- Vastly improve sync speed for music
- Never show library sync dialog if media is playing
- Improvements to sync dialog
- Fix stop synching if path not found
- Resume aborted sync on PKC settings change
- Don't quit library sync if failed repeatedly
- Verify path for every Plex library on install sync
- More descriptive downloadable subtitles
- More code fixes and optimization
version 1.8.9
- Fix playback not starting in some circumstances
- Deactivate some annoying popups on install
version 1.8.8
- Fix playback not starting in some circumstances
- Fix first artist "missing" tag (Reset your DB!)
- Update Czech translation
version 1.8.7 (beta only):
- Some fixes to playstate reporting, thanks @RickDB
- Add Kodi info screen for episodes in context menu
- Fix PKC asking for trailers not working
- Fix PKC not automatically updating
version 1.8.6:
- Portuguese translation, thanks @goncalo532
- Updated other translations

View file

@ -1,52 +1,41 @@
# -*- coding: utf-8 -*-
###############################################################################
from os import path as os_path
from sys import path as sys_path
import logging
import os
import sys
from xbmcaddon import Addon
from xbmc import translatePath, sleep, log, LOGERROR
from xbmcgui import Window
import xbmc
import xbmcaddon
###############################################################################
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
_addon = Addon(id='plugin.video.plexkodiconnect')
try:
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
except TypeError:
_addon_path = _addon.getAddonInfo('path').decode()
try:
_base_resource = xbmc.translatePath(os.path.join(
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode('utf-8')
except TypeError:
_base_resource = xbmc.translatePath(os.path.join(
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode()
sys.path.append(_base_resource)
sys_path.append(_base_resource)
###############################################################################
import loghandler
from context_entry import ContextMenu
###############################################################################
loghandler.config()
log = logging.getLogger("PLEX.contextmenu")
from pickler import unpickle_me, pickl_window
###############################################################################
if __name__ == "__main__":
try:
# Start the context menu
ContextMenu()
except Exception as error:
log.exception(error)
import traceback
log.exception("Traceback:\n%s" % traceback.format_exc())
raise
win = Window(10000)
while win.getProperty('plex_command'):
sleep(20)
win.setProperty('plex_command', 'CONTEXT_menu')
while not pickl_window('plex_result'):
sleep(50)
result = unpickle_me()
if result is None:
log('PLEX.%s: Error encountered, aborting' % __name__, level=LOGERROR)

View file

@ -32,9 +32,9 @@ sys_path.append(_base_resource)
###############################################################################
import entrypoint
from utils import window, pickl_window, reset, passwordsXML, language as lang,\
dialog
from pickler import unpickle_me
from utils import window, reset, passwordsXML, language as lang, dialog, \
plex_command
from pickler import unpickle_me, pickl_window
from PKC_listitem import convert_PKC_to_listitem
import variables as v
@ -127,28 +127,29 @@ class Main():
log.error('Not connected to a PMS.')
else:
if mode == 'repair':
window('plex_runLibScan', value='repair')
log.info('Requesting repair lib sync')
plex_command('RUN_LIB_SCAN', 'repair')
elif mode == 'manualsync':
log.info('Requesting full library scan')
window('plex_runLibScan', value='full')
plex_command('RUN_LIB_SCAN', 'full')
elif mode == 'texturecache':
window('plex_runLibScan', value='del_textures')
log.info('Requesting texture caching of all textures')
plex_command('RUN_LIB_SCAN', 'textures')
elif mode == 'chooseServer':
entrypoint.chooseServer()
elif mode == 'refreshplaylist':
log.info('Requesting playlist/nodes refresh')
window('plex_runLibScan', value='views')
plex_command('RUN_LIB_SCAN', 'views')
elif mode == 'deviceid':
self.deviceid()
elif mode == 'fanart':
log.info('User requested fanarttv refresh')
window('plex_runLibScan', value='fanart')
plex_command('RUN_LIB_SCAN', 'fanart')
elif '/extrafanart' in argv[0]:
plexpath = argv[2][1:]
@ -165,15 +166,13 @@ class Main():
else:
entrypoint.doMainListing(content_type=params.get('content_type'))
def play(self):
@staticmethod
def play():
"""
Start up playback_starter in main Python thread
"""
# Put the request into the 'queue'
while window('plex_command'):
sleep(50)
window('plex_command',
value='play_%s' % argv[2])
plex_command('PLAY', argv[2])
# Wait for the result
while not pickl_window('plex_result'):
sleep(50)
@ -190,7 +189,8 @@ class Main():
listitem = convert_PKC_to_listitem(result.listitem)
setResolvedUrl(HANDLE, True, listitem)
def deviceid(self):
@staticmethod
def deviceid():
deviceId_old = window('plex_client_Id')
from clientinfo import getDeviceId
try:
@ -205,6 +205,7 @@ class Main():
dialog('ok', lang(29999), lang(33033))
executebuiltin('RestartApp')
if __name__ == '__main__':
log.info('%s started' % v.ADDON_ID)
Main()

View file

@ -49,7 +49,7 @@ from xbmcvfs import exists
import clientinfo as client
from downloadutils import DownloadUtils
from utils import window, settings, language as lang, tryDecode, tryEncode, \
DateToKodi, exists_dir
DateToKodi, exists_dir, slugify
from PlexFunctions import PMSHttpsEnabled
import plexdb_functions as plexdb
import variables as v
@ -1346,14 +1346,18 @@ class API():
cast = []
producer = []
for child in self.item:
if child.tag == 'Director':
director.append(child.attrib['tag'])
elif child.tag == 'Writer':
writer.append(child.attrib['tag'])
elif child.tag == 'Role':
cast.append(child.attrib['tag'])
elif child.tag == 'Producer':
producer.append(child.attrib['tag'])
try:
if child.tag == 'Director':
director.append(child.attrib['tag'])
elif child.tag == 'Writer':
writer.append(child.attrib['tag'])
elif child.tag == 'Role':
cast.append(child.attrib['tag'])
elif child.tag == 'Producer':
producer.append(child.attrib['tag'])
except KeyError:
log.warn('Malformed PMS answer for getPeople: %s: %s'
% (child.tag, child.attrib))
return {
'Director': director,
'Writer': writer,
@ -1750,8 +1754,16 @@ class API():
videotracks = []
audiotracks = []
subtitlelanguages = []
# Sometimes, aspectratio is on the "toplevel"
aspectratio = self.item[0].attrib.get('aspectRatio', None)
try:
# Sometimes, aspectratio is on the "toplevel"
aspectratio = self.item[0].attrib.get('aspectRatio', None)
except IndexError:
# There is no stream info at all, returning empty
return {
'video': videotracks,
'audio': audiotracks,
'subtitle': subtitlelanguages
}
# TODO: what if several Media tags exist?!?
# Loop over parts
for child in self.item[0]:
@ -2357,11 +2369,11 @@ class API():
# ext = stream.attrib.get('format')
if key:
# We do know the language - temporarily download
if stream.attrib.get('languageCode') is not None:
if stream.attrib.get('language') is not None:
path = self.download_external_subtitles(
"{server}%s" % key,
"subtitle%02d.%s.%s" % (fileindex,
stream.attrib['languageCode'],
stream.attrib['language'],
stream.attrib['codec']))
fileindex += 1
# We don't know the language - no need to download
@ -2395,9 +2407,14 @@ class API():
log.error('Could not temporarily download subtitle %s' % url)
return
else:
r.encoding = 'utf-8'
with open(path, 'wb') as f:
f.write(r.content)
log.debug('Writing temp subtitle to %s' % path)
try:
with open(path, 'wb') as f:
f.write(r.content)
except UnicodeEncodeError:
log.debug('Need to slugify the filename %s' % path)
with open(slugify(path), 'wb') as f:
f.write(r.content)
return path
def GetKodiPremierDate(self):
@ -2575,16 +2592,16 @@ class API():
if path is None:
return None
typus = v.REMAP_TYPE_FROM_PLEXTYPE[typus]
if window('remapSMB') == 'true':
path = path.replace(window('remapSMB%sOrg' % typus),
window('remapSMB%sNew' % typus),
if state.REMAP_PATH is True:
path = path.replace(getattr(state, 'remapSMB%sOrg' % typus),
getattr(state, 'remapSMB%sNew' % typus),
1)
# There might be backslashes left over:
path = path.replace('\\', '/')
elif window('replaceSMB') == 'true':
elif state.REPLACE_SMB_PATH is True:
if path.startswith('\\\\'):
path = 'smb:' + path.replace('\\', '/')
if ((window('plex_pathverified') == 'true' and forceCheck is False) or
if ((state.PATH_VERIFIED and forceCheck is False) or
omitCheck is True):
return path
@ -2612,12 +2629,12 @@ class API():
if self.askToValidate(path):
state.STOP_SYNC = True
path = None
window('plex_pathverified', value='true')
state.PATH_VERIFIED = True
else:
path = None
elif forceCheck is False:
if window('plex_pathverified') != 'true':
window('plex_pathverified', value='true')
# Only set the flag if we were not force-checking the path
state.PATH_VERIFIED = True
return path
def askToValidate(self, url):

View file

@ -15,7 +15,7 @@ from variables import PLEX_TO_KODI_TIMEFACTOR
log = getLogger("PLEX."+__name__)
CONTAINERSIZE = int(settings('limitindex'))
REGEX_PLEX_KEY = re.compile(r'''/(.+)/(\d+)$''')
###############################################################################
@ -36,9 +36,8 @@ def GetPlexKeyNumber(plexKey):
Returns ('','') if nothing is found
"""
regex = re.compile(r'''/(.+)/(\d+)$''')
try:
result = regex.findall(plexKey)[0]
result = REGEX_PLEX_KEY.findall(plexKey)[0]
except IndexError:
result = ('', '')
return result

View file

@ -126,8 +126,9 @@ def double_urldecode(text):
return unquote(unquote(text))
@thread_methods(add_stops=['STOP_SYNC'],
add_suspends=['SUSPEND_LIBRARY_THREAD', 'DB_SCAN'])
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN',
'STOP_SYNC'])
class Image_Cache_Thread(Thread):
xbmc_host = 'localhost'
xbmc_port, xbmc_username, xbmc_password = setKodiWebServerDetails()

View file

@ -68,13 +68,13 @@ def getDeviceId(reset=False):
# Because Kodi appears to cache file settings!!
if clientId != "" and reset is False:
window('plex_client_Id', value=clientId)
log.warn("Unique device Id plex_client_Id loaded: %s" % clientId)
log.info("Unique device Id plex_client_Id loaded: %s" % clientId)
return clientId
log.warn("Generating a new deviceid.")
log.info("Generating a new deviceid.")
from uuid import uuid4
clientId = str(uuid4())
settings('plex_client_Id', value=clientId)
window('plex_client_Id', value=clientId)
log.warn("Unique device Id plex_client_Id loaded: %s" % clientId)
log.info("Unique device Id plex_client_Id loaded: %s" % clientId)
return clientId

View file

@ -21,9 +21,6 @@ class Monitor_Window(Thread):
Monitors window('plex_command') for new entries that we need to take care
of, e.g. for new plays initiated on the Kodi side with addon paths.
Possible values of window('plex_command'):
'play_....': to start playback using playback_starter
Adjusts state.py accordingly
"""
# Borg - multiple instances, shared state
@ -40,9 +37,8 @@ class Monitor_Window(Thread):
if window('plex_command'):
value = window('plex_command')
window('plex_command', clear=True)
if value.startswith('play_'):
queue.put(value)
if value.startswith('PLAY-'):
queue.put(value.replace('PLAY-', ''))
elif value == 'SUSPEND_LIBRARY_THREAD-True':
state.SUSPEND_LIBRARY_THREAD = True
elif value == 'SUSPEND_LIBRARY_THREAD-False':
@ -64,6 +60,10 @@ class Monitor_Window(Thread):
elif value.startswith('PLEX_USERNAME-'):
state.PLEX_USERNAME = \
value.replace('PLEX_USERNAME-', '') or None
elif value.startswith('RUN_LIB_SCAN-'):
state.RUN_LIB_SCAN = value.replace('RUN_LIB_SCAN-', '')
elif value == 'CONTEXT_menu':
queue.put('dummy?mode=context_menu')
else:
raise NotImplementedError('%s not implemented' % value)
else:

View file

@ -198,12 +198,12 @@ class DownloadUtils():
# THE EXCEPTIONS
except requests.exceptions.ConnectionError as e:
# Connection error
log.debug("Server unreachable at: %s" % url)
log.debug(e)
log.warn("Server unreachable at: %s" % url)
log.warn(e)
except requests.exceptions.Timeout as e:
log.debug("Server timeout at: %s" % url)
log.debug(e)
log.warn("Server timeout at: %s" % url)
log.warn(e)
except requests.exceptions.HTTPError as e:
log.warn('HTTP Error at %s' % url)
@ -300,21 +300,21 @@ class DownloadUtils():
# update
pass
else:
log.error("Unable to convert the response for: "
"%s" % url)
log.info("Received headers were: %s" % r.headers)
log.info('Received text:')
log.info(r.text)
log.warn("Unable to convert the response for: "
"%s" % url)
log.warn("Received headers were: %s" % r.headers)
log.warn('Received text:')
log.warn(r.text)
return True
elif r.status_code == 403:
# E.g. deleting a PMS item
log.error('PMS sent 403: Forbidden error for url %s' % url)
log.warn('PMS sent 403: Forbidden error for url %s' % url)
return None
else:
log.error('Unknown answer from PMS %s with status code %s. '
'Message:' % (url, r.status_code))
log.warn('Unknown answer from PMS %s with status code %s. '
'Message:' % (url, r.status_code))
r.encoding = 'utf-8'
log.info(r.text)
log.warn(r.text)
return True
# And now deal with the consequences of the exceptions

View file

@ -575,14 +575,6 @@ def getExtraFanArt(plexid, plexPath):
xbmcplugin.endOfDirectory(HANDLE)
def RunLibScan(mode):
if window('plex_online') != "true":
# Server is not online, do not run the sync
dialog('ok', lang(29999), lang(39205))
else:
window('plex_runLibScan', value='full')
def getOnDeck(viewid, mediatype, tagname, limit):
"""
Retrieves Plex On Deck items, currently only for TV shows
@ -975,7 +967,7 @@ def __LogIn():
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
out!
"""
window('plex_runLibScan', value='full')
plex_command('RUN_LIB_SCAN', 'full')
# Restart user client
plex_command('SUSPEND_USER_CLIENT', 'False')

View file

@ -496,10 +496,10 @@ class InitialSetup():
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and
# "Parents Movies", be sure to check https://goo.gl/JFtQV9
dialog.ok(heading=lang(29999), line1=lang(39076))
# dialog.ok(heading=lang(29999), line1=lang(39076))
# Need to tell about our image source for collections: themoviedb.org
dialog.ok(heading=lang(29999), line1=lang(39717))
# dialog.ok(heading=lang(29999), line1=lang(39717))
# Make sure that we only ask these questions upon first installation
settings('InstallQuestionsAnswered', value='true')

View file

@ -161,8 +161,9 @@ class Items(object):
# If offset exceeds duration skip update
if item['viewOffset'] > item['duration']:
log.error("Error while updating play state, viewOffset exceeded duration")
return
log.error("Error while updating play state, viewOffset "
"exceeded duration")
return
complete = float(item['viewOffset']) / float(item['duration'])
log.info('Item %s stopped with completion rate %s percent.'
@ -170,7 +171,6 @@ class Items(object):
% (item['ratingKey'], str(complete), MARK_PLAYED_AT), 1)
if complete >= MARK_PLAYED_AT:
log.info('Marking as completely watched in Kodi')
sleep(500)
try:
item['viewCount'] += 1
except TypeError:
@ -1729,7 +1729,7 @@ class Music(Items):
if album is None or album == 401:
log.error('Could not download album, abort')
return
self.add_updateAlbum(album[0])
self.add_updateAlbum(album[0], children=[item])
plex_dbalbum = plex_db.getItem_byId(plex_albumId)
try:
albumid = plex_dbalbum[0]

View file

@ -1280,7 +1280,14 @@ class Kodidb_Functions():
try:
artistid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("select coalesce(max(idArtist),0) from artist")
# Krypton has a dummy first entry idArtist: 1 strArtist:
# [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
if v.KODIVERSION >= 17:
self.cursor.execute(
"select coalesce(max(idArtist),1) from artist")
else:
self.cursor.execute(
"select coalesce(max(idArtist),0) from artist")
artistid = self.cursor.fetchone()[0] + 1
query = (
'''

View file

@ -2,24 +2,48 @@
###############################################################################
import logging
from logging import getLogger
from json import loads
from xbmc import Monitor, Player, sleep
import downloadutils
from downloadutils import DownloadUtils
import plexdb_functions as plexdb
from utils import window, settings, CatchExceptions, tryDecode, tryEncode
from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \
plex_command
from PlexFunctions import scrobble
from kodidb_functions import get_kodiid_from_filename
from PlexAPI import API
from variables import REMAP_TYPE_FROM_PLEXTYPE
import state
###############################################################################
log = logging.getLogger("PLEX."+__name__)
log = getLogger("PLEX."+__name__)
# settings: window-variable
WINDOW_SETTINGS = {
'enableContext': 'plex_context',
'plex_restricteduser': 'plex_restricteduser',
'force_transcode_pix': 'plex_force_transcode_pix',
'fetch_pms_item_number': 'fetch_pms_item_number'
}
# settings: state-variable (state.py)
# Need to use getattr and setattr!
STATE_SETTINGS = {
'dbSyncIndicator': 'SYNC_DIALOG',
'remapSMB': 'REMAP_PATH',
'remapSMBmovieOrg': 'remapSMBmovieOrg',
'remapSMBmovieNew': 'remapSMBmovieNew',
'remapSMBtvOrg': 'remapSMBtvOrg',
'remapSMBtvNew': 'remapSMBtvNew',
'remapSMBmusicOrg': 'remapSMBmusicOrg',
'remapSMBmusicNew': 'remapSMBmusicNew',
'remapSMBphotoOrg': 'remapSMBphotoOrg',
'remapSMBphotoNew': 'remapSMBphotoNew',
'enableMusic': 'ENABLE_MUSIC',
'enableBackgroundSync': 'BACKGROUND_SYNC'
}
###############################################################################
@ -27,7 +51,7 @@ class KodiMonitor(Monitor):
def __init__(self, callback):
self.mgr = callback
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.doUtils = DownloadUtils().downloadUrl
self.xbmcplayer = Player()
self.playqueue = self.mgr.playqueue
Monitor.__init__(self)
@ -47,31 +71,42 @@ class KodiMonitor(Monitor):
"""
Monitor the PKC settings for changes made by the user
"""
# settings: window-variable
items = {
'logLevel': 'plex_logLevel',
'enableContext': 'plex_context',
'plex_restricteduser': 'plex_restricteduser',
'dbSyncIndicator': 'dbSyncIndicator',
'remapSMB': 'remapSMB',
'replaceSMB': 'replaceSMB',
'force_transcode_pix': 'plex_force_transcode_pix',
'fetch_pms_item_number': 'fetch_pms_item_number'
}
# Path replacement
for typus in REMAP_TYPE_FROM_PLEXTYPE.values():
for arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg)
items[key] = key
log.debug('PKC settings change detected')
changed = False
# Reset the window variables from the settings variables
for settings_value, window_value in items.iteritems():
for settings_value, window_value in WINDOW_SETTINGS.iteritems():
if window(window_value) != settings(settings_value):
log.debug('PKC settings changed: %s is now %s'
changed = True
log.debug('PKC window settings changed: %s is now %s'
% (settings_value, settings(settings_value)))
window(window_value, value=settings(settings_value))
if settings_value == 'fetch_pms_item_number':
log.info('Requesting playlist/nodes refresh')
window('plex_runLibScan', value="views")
plex_command('RUN_LIB_SCAN', 'views')
# Reset the state variables in state.py
for settings_value, state_name in STATE_SETTINGS.iteritems():
new = settings(settings_value)
if new == 'true':
new = True
elif new == 'false':
new = False
if getattr(state, state_name) != new:
changed = True
log.debug('PKC state settings %s changed from %s to %s'
% (settings_value, getattr(state, state_name), new))
setattr(state, state_name, new)
# Special cases, overwrite all internal settings
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval'))*60
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
settings('backgroundsync_saftyMargin'))
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
# Never set through the user
# state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
if changed is True:
# Assume that the user changed the settings so that we can now find
# the path to all media files
state.STOP_SYNC = False
state.PATH_VERIFIED = False
@CatchExceptions(warnuser=False)
def onNotification(self, sender, method, data):
@ -137,7 +172,7 @@ class KodiMonitor(Monitor):
elif method == "GUI.OnScreensaverDeactivated":
if settings('dbSyncScreensaver') == "true":
sleep(5000)
window('plex_runLibScan', value="full")
plex_command('RUN_LIB_SCAN', 'full')
elif method == "System.OnQuit":
log.info('Kodi OnQuit detected - shutting down')

View file

@ -17,8 +17,9 @@ log = getLogger("PLEX."+__name__)
###############################################################################
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD', 'DB_SCAN'],
add_stops=['STOP_SYNC'])
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN',
'STOP_SYNC'])
class Process_Fanart_Thread(Thread):
"""
Threaded download of additional fanart in the background

View file

@ -16,7 +16,7 @@ log = getLogger("PLEX."+__name__)
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD'])
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC'])
class Threaded_Get_Metadata(Thread):
"""
Threaded download of Plex XML metadata for a certain library item.
@ -115,17 +115,9 @@ class Threaded_Get_Metadata(Thread):
except (TypeError, IndexError, AttributeError):
log.error('Could not get children for Plex id %s'
% item['itemId'])
else:
item['children'] = []
for child in children_xml:
child_xml = GetPlexMetadata(child.attrib['ratingKey'])
try:
child_xml[0].attrib
except (TypeError, IndexError, AttributeError):
log.error('Could not get child for Plex id %s'
% child.attrib['ratingKey'])
else:
item['children'].append(child_xml[0])
else:
item['children'] = children_xml
# place item into out queue
out_queue.put(item)

View file

@ -15,7 +15,7 @@ log = getLogger("PLEX."+__name__)
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD'])
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC'])
class Threaded_Process_Metadata(Thread):
"""
Not yet implemented for more than 1 thread - if ever. Only to be called by

View file

@ -2,7 +2,8 @@
from logging import getLogger
from threading import Thread, Lock
from xbmc import sleep
from xbmc import sleep, Player
from xbmcgui import DialogProgressBG
from utils import thread_methods, language as lang
@ -18,18 +19,17 @@ LOCK = Lock()
###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD'])
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC'])
class Threaded_Show_Sync_Info(Thread):
"""
Threaded class to show the Kodi statusbar of the metadata download.
Input:
dialog xbmcgui.DialogProgressBG() object to show progress
total: Total number of items to get
item_type:
"""
def __init__(self, dialog, total, item_type):
def __init__(self, total, item_type):
self.total = total
self.dialog = dialog
self.item_type = item_type
Thread.__init__(self)
@ -51,14 +51,15 @@ class Threaded_Show_Sync_Info(Thread):
log.debug('Show sync info thread started')
# cache local variables because it's faster
total = self.total
dialog = self.dialog
dialog = DialogProgressBG('dialoglogProgressBG')
thread_stopped = self.thread_stopped
dialog.create("%s %s: %s %s"
% (lang(39714), self.item_type, str(total), lang(39715)))
player = Player()
total = 2 * total
totalProgress = 0
while thread_stopped() is False:
while thread_stopped() is False and not player.isPlaying():
with LOCK:
get_progress = GET_METADATA_COUNT
process_progress = PROCESS_METADATA_COUNT

File diff suppressed because it is too large Load diff

View file

@ -1,74 +1,47 @@
# -*- coding: utf-8 -*-
##################################################################################################
###############################################################################
import logging
import xbmc
###############################################################################
LEVELS = {
logging.ERROR: xbmc.LOGERROR,
logging.WARNING: xbmc.LOGWARNING,
logging.INFO: xbmc.LOGNOTICE,
logging.DEBUG: xbmc.LOGDEBUG
}
###############################################################################
from utils import window, tryEncode
##################################################################################################
def tryEncode(uniString, encoding='utf-8'):
"""
Will try to encode uniString (in unicode) to encoding. This possibly
fails with e.g. Android TV's Python, which does not accept arguments for
string.encode()
"""
if isinstance(uniString, str):
# already encoded
return uniString
try:
uniString = uniString.encode(encoding, "ignore")
except TypeError:
uniString = uniString.encode()
return uniString
def config():
logger = logging.getLogger('PLEX')
logger.addHandler(LogHandler())
logger.setLevel(logging.DEBUG)
class LogHandler(logging.StreamHandler):
def __init__(self):
logging.StreamHandler.__init__(self)
self.setFormatter(MyFormatter())
self.setFormatter(logging.Formatter(fmt="%(name)s: %(message)s"))
def emit(self, record):
if self._get_log_level(record.levelno):
try:
xbmc.log(self.format(record), level=xbmc.LOGNOTICE)
except UnicodeEncodeError:
xbmc.log(tryEncode(self.format(record)), level=xbmc.LOGNOTICE)
@classmethod
def _get_log_level(cls, level):
levels = {
logging.ERROR: 0,
logging.WARNING: 0,
logging.INFO: 1,
logging.DEBUG: 2
}
try:
log_level = int(window('plex_logLevel'))
except ValueError:
log_level = 0
return log_level >= levels[level]
class MyFormatter(logging.Formatter):
def __init__(self, fmt="%(name)s -> %(message)s"):
logging.Formatter.__init__(self, fmt)
def format(self, record):
# Save the original format configured by the user
# when the logger formatter was instantiated
format_orig = self._fmt
# Replace the original format with one customized by logging level
if record.levelno in (logging.DEBUG, logging.ERROR):
self._fmt = '%(name)s -> %(levelname)s: %(message)s'
# Call the original formatter class to do the grunt work
result = logging.Formatter.format(self, record)
# Restore the original format configured by the user
self._fmt = format_orig
return result
xbmc.log(self.format(record), level=LEVELS[record.levelno])
except UnicodeEncodeError:
xbmc.log(tryEncode(self.format(record)),
level=LEVELS[record.levelno])

View file

@ -1,13 +1,26 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
import cPickle as Pickle
from cPickle import dumps, loads
from utils import pickl_window
from xbmcgui import Window
from xbmc import log, LOGDEBUG
###############################################################################
WINDOW = Window(10000)
PREFIX = 'PLEX.%s: ' % __name__
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
def pickl_window(property, value=None, clear=False):
"""
Get or set window property - thread safe! For use with Pickle
Property and value must be string
"""
if clear:
WINDOW.clearProperty(property)
elif value is not None:
WINDOW.setProperty(property, value)
else:
return WINDOW.getProperty(property)
def pickle_me(obj, window_var='plex_result'):
@ -19,9 +32,9 @@ def pickle_me(obj, window_var='plex_result'):
obj can be pretty much any Python object. However, classes and
functions won't work. See the Pickle documentation
"""
log.debug('Start pickling: %s' % obj)
pickl_window(window_var, value=Pickle.dumps(obj))
log.debug('Successfully pickled')
log('%sStart pickling: %s' % (PREFIX, obj), level=LOGDEBUG)
pickl_window(window_var, value=dumps(obj))
log('%sSuccessfully pickled' % PREFIX, level=LOGDEBUG)
def unpickle_me(window_var='plex_result'):
@ -31,9 +44,9 @@ def unpickle_me(window_var='plex_result'):
"""
result = pickl_window(window_var)
pickl_window(window_var, clear=True)
log.debug('Start unpickling')
obj = Pickle.loads(result)
log.debug('Successfully unpickled: %s' % obj)
log('%sStart unpickling' % PREFIX, level=LOGDEBUG)
obj = loads(result)
log('%sSuccessfully unpickled: %s' % (PREFIX, obj), level=LOGDEBUG)
return obj

View file

@ -17,6 +17,7 @@ import variables as v
from downloadutils import DownloadUtils
from PKC_listitem import convert_PKC_to_listitem
import plexdb_functions as plexdb
from context_entry import ContextMenu
import state
###############################################################################
@ -142,6 +143,9 @@ class Playback_Starter(Thread):
params.get('view_offset'),
directplay=True if params.get('play_directly') else False,
node=False if params.get('node') == 'false' else True)
elif mode == 'context_menu':
ContextMenu()
result = Playback_Successful()
except:
log.error('Error encountered for mode %s, params %s'
% (mode, params))

View file

@ -5,10 +5,8 @@ import logging
import json
import xbmc
import xbmcgui
from utils import window, settings, language as lang, DateToKodi, \
getUnixTimestamp, tryDecode, tryEncode
from utils import window, DateToKodi, getUnixTimestamp, tryDecode, tryEncode
import downloadutils
import plexdb_functions as plexdb
import kodidb_functions as kodidb
@ -354,6 +352,8 @@ class Player(xbmc.Player):
log.info("Percent complete: %s Mark played at: %s"
% (percentComplete, markPlayed))
if percentComplete >= markPlayed:
# Kodi seems to sometimes overwrite our playstate, so wait
xbmc.sleep(500)
# Tell Kodi that we've finished watching (Plex knows)
if (data['fileid'] is not None and
data['itemType'] in (v.KODI_TYPE_MOVIE,

View file

@ -10,6 +10,8 @@ STOP_PKC = False
SUSPEND_LIBRARY_THREAD = False
# Set if user decided to cancel sync
STOP_SYNC = False
# Could we access the paths?
PATH_VERIFIED = False
# Set if a Plex-Kodi DB sync is being done - along with
# window('plex_dbScan') set to 'true'
DB_SCAN = False
@ -24,6 +26,42 @@ RESTRICTED_USER = False
DIRECT_PATHS = False
# Shall we replace custom user ratings with the number of versions available?
INDICATE_MEDIA_VERSIONS = False
# Do we need to run a special library scan?
RUN_LIB_SCAN = None
# 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?
FULL_SYNC_INTERVALL = 0
# Background Sync enabled at all?
BACKGROUND_SYNC = True
# How long shall we wait with synching a new item to make sure Plex got all
# metadata?
BACKGROUNDSYNC_SAFTYMARGIN = 0
# How many threads to download Plex metadata on sync?
SYNC_THREAD_NUMBER = 0
# What's the time offset between the PMS and Kodi?
KODI_PLEX_TIME_OFFSET = 0.0
# Path remapping mechanism (e.g. smb paths)
# Do we replace \\myserver\path to smb://myserver/path?
REPLACE_SMB_PATH = False
# Do we generally remap?
REMAP_PATH = False
# Mappings for REMAP_PATH:
remapSMBmovieOrg = None
remapSMBmovieNew = None
remapSMBtvOrg = None
remapSMBtvNew = None
remapSMBmusicOrg = None
remapSMBmusicNew = None
remapSMBphotoOrg = None
remapSMBphotoNew = None
# Along with window('plex_authenticated')
AUTHENTICATED = False

View file

@ -59,24 +59,6 @@ def window(property, value=None, clear=False, windowid=10000):
return tryDecode(win.getProperty(property))
def pickl_window(property, value=None, clear=False, windowid=10000):
"""
Get or set window property - thread safe! For use with Pickle
Property and value must be string
"""
if windowid != 10000:
win = xbmcgui.Window(windowid)
else:
win = WINDOW
if clear:
win.clearProperty(property)
elif value is not None:
win.setProperty(property, value)
else:
return win.getProperty(property)
def plex_command(key, value):
"""
Used to funnel states between different Python instances. NOT really thread
@ -86,7 +68,7 @@ def plex_command(key, value):
value: either 'True' or 'False'
"""
while window('plex_command'):
xbmc.sleep(5)
xbmc.sleep(20)
window('plex_command', value='%s-%s' % (key, value))
@ -140,6 +122,15 @@ def dialog(typus, *args, **kwargs):
Displays xbmcgui Dialog. Pass a string as typus:
'yesno', 'ok', 'notification', 'input', 'select', 'numeric'
kwargs:
heading='{plex}' title bar (here PlexKodiConnect)
message=lang(30128), Actual dialog content. Don't use with OK
line1=str(), For 'OK' and 'yesno' dialogs use line1...line3!
time=5000,
sound=True,
nolabel=str(), For 'yesno' dialogs
yeslabel=str(), For 'yesno' dialogs
Icons:
icon='{plex}' Display Plex standard icon
icon='{info}' xbmcgui.NOTIFICATION_INFO
@ -221,6 +212,16 @@ def tryDecode(string, encoding='utf-8'):
return string
def slugify(text):
"""
Normalizes text (in unicode or string) to e.g. enable safe filenames.
Returns unicode
"""
if not isinstance(text, unicode):
text = unicode(text)
return unicode(normalize('NFKD', text).encode('ascii', 'ignore'))
def escape_html(string):
"""
Escapes the following:
@ -248,7 +249,7 @@ def DateToKodi(stamp):
None if an error was encountered
"""
try:
stamp = float(stamp) + float(window('kodiplextimeoffset'))
stamp = float(stamp) + state.KODI_PLEX_TIME_OFFSET
date_time = localtime(stamp)
localdate = strftime('%Y-%m-%d %H:%M:%S', date_time)
except:

View file

@ -84,7 +84,8 @@ class WebSocket(Thread):
# No worries if read timed out
pass
except websocket.WebSocketConnectionClosedException:
log.info("Connection closed, (re)connecting")
log.info("%s: connection closed, (re)connecting"
% self.__class__.__name__)
uri, sslopt = self.getUri()
try:
# Low timeout - let's us shut this thread down!
@ -95,7 +96,7 @@ class WebSocket(Thread):
enable_multithread=True)
except IOError:
# Server is probably offline
log.info("Error connecting")
log.info("%s: Error connecting" % self.__class__.__name__)
self.ws = None
counter += 1
if counter > 3:
@ -103,33 +104,41 @@ class WebSocket(Thread):
self.IOError_response()
sleep(1000)
except websocket.WebSocketTimeoutException:
log.info("timeout while connecting, trying again")
log.info("%s: Timeout while connecting, trying again"
% self.__class__.__name__)
self.ws = None
sleep(1000)
except websocket.WebSocketException as e:
log.info('WebSocketException: %s' % e)
log.info('%s: WebSocketException: %s'
% (self.__class__.__name__, e))
if 'Handshake Status 401' in e.args:
handshake_counter += 1
if handshake_counter >= 5:
log.info('Error in handshake detected. Stopping '
'%s now' % self.__class__.__name__)
log.info('%s: Error in handshake detected. '
'Stopping now'
% self.__class__.__name__)
break
self.ws = None
sleep(1000)
except Exception as e:
log.error("Unknown exception encountered in connecting: %s"
% e)
log.error('%s: Unknown exception encountered when '
'connecting: %s' % (self.__class__.__name__, e))
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
log.error("%s: Traceback:\n%s"
% (self.__class__.__name__,
traceback.format_exc()))
self.ws = None
sleep(1000)
else:
counter = 0
handshake_counter = 0
except Exception as e:
log.error("Unknown exception encountered: %s" % e)
log.error("%s: Unknown exception encountered: %s"
% (self.__class__.__name__, e))
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
log.error("%s: Traceback:\n%s"
% (self.__class__.__name__,
traceback.format_exc()))
try:
self.ws.shutdown()
except:
@ -171,37 +180,46 @@ class PMS_Websocket(WebSocket):
sslopt = {}
if settings('sslverify') == "false":
sslopt["cert_reqs"] = CERT_NONE
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
log.debug("%s: Uri: %s, sslopt: %s"
% (self.__class__.__name__, uri, sslopt))
return uri, sslopt
def process(self, opcode, message):
if opcode not in self.opcode_data:
return False
return
try:
message = loads(message)
except Exception as ex:
log.error('Error decoding message from websocket: %s' % ex)
except ValueError:
log.error('%s: Error decoding message from websocket'
% self.__class__.__name__)
log.error(message)
return False
return
try:
message = message['NotificationContainer']
except KeyError:
log.error('Could not parse PMS message: %s' % message)
return False
log.error('%s: Could not parse PMS message: %s'
% (self.__class__.__name__, message))
return
# Triage
typus = message.get('type')
if typus is None:
log.error('No message type, dropping message: %s' % message)
return False
log.debug('Received message from PMS server: %s' % message)
log.error('%s: No message type, dropping message: %s'
% (self.__class__.__name__, message))
return
log.debug('%s: Received message from PMS server: %s'
% (self.__class__.__name__, message))
# Drop everything we're not interested in
if typus not in ('playing', 'timeline'):
return True
# Put PMS message on queue and let libsync take care of it
self.queue.put(message)
return True
if typus not in ('playing', 'timeline', 'activity'):
return
elif typus == 'activity' and state.DB_SCAN is True:
# Only add to processing if PKC is NOT doing a lib scan (and thus
# possibly causing these reprocessing messages en mass)
log.debug('%s: Dropping message as PKC is currently synching'
% self.__class__.__name__)
else:
# Put PMS message on queue and let libsync take care of it
self.queue.put(message)
def IOError_response(self):
log.warn("Repeatedly could not connect to PMS, "
@ -224,32 +242,36 @@ class Alexa_Websocket(WebSocket):
% (state.PLEX_USER_ID,
self.plex_client_Id, state.PLEX_TOKEN))
sslopt = {}
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
log.debug("%s: Uri: %s, sslopt: %s"
% (self.__class__.__name__, uri, sslopt))
return uri, sslopt
def process(self, opcode, message):
if opcode not in self.opcode_data:
return False
log.debug('Received the following message from Alexa:')
log.debug(message)
return
log.debug('%s: Received the following message from Alexa:'
% self.__class__.__name__)
log.debug('%s: %s' % (self.__class__.__name__, message))
try:
message = etree.fromstring(message)
except Exception as ex:
log.error('Error decoding message from Alexa: %s' % ex)
return False
log.error('%s: Error decoding message from Alexa: %s'
% (self.__class__.__name__, ex))
return
try:
if message.attrib['command'] == 'processRemoteControlCommand':
message = message[0]
else:
log.error('Unknown Alexa message received')
return False
log.error('%s: Unknown Alexa message received'
% self.__class__.__name__)
return
except:
log.error('Could not parse Alexa message')
return False
log.error('%s: Could not parse Alexa message'
% self.__class__.__name__)
return
process_command(message.attrib['path'][1:],
message.attrib,
queue=self.mgr.plexCompanion.queue)
return True
def IOError_response(self):
pass

View file

@ -146,7 +146,6 @@
</category>
<category label="30022"><!-- Advanced -->
<setting id="logLevel" type="enum" label="30004" values="Disabled|Info|Debug" default="1" />
<setting id="startupDelay" type="number" label="30529" default="0" option="int" />
<setting label="39018" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect/?mode=repair)" option="close" /> <!-- Repair local database (force update all content) -->
<setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=deviceid)" /><!-- Reset device id uuid -->

View file

@ -30,8 +30,7 @@ sys_path.append(_base_resource)
###############################################################################
from utils import settings, window, language as lang, dialog, tryEncode, \
tryDecode
from utils import settings, window, language as lang, dialog, tryDecode
from userclient import UserClient
import initialsetup
from kodimonitor import KodiMonitor
@ -82,10 +81,8 @@ class Service():
def __init__(self):
logLevel = self.getLogLevel()
self.monitor = Monitor()
window('plex_logLevel', value=str(logLevel))
window('plex_kodiProfile',
value=tryDecode(translatePath("special://profile")))
window('plex_context',
@ -94,27 +91,26 @@ class Service():
value=settings('fetch_pms_item_number'))
# Initial logging
log.warn("======== START %s ========" % v.ADDON_NAME)
log.warn("Platform: %s" % v.PLATFORM)
log.warn("KODI Version: %s" % v.KODILONGVERSION)
log.warn("%s Version: %s" % (v.ADDON_NAME, v.ADDON_VERSION))
log.warn("Using plugin paths: %s"
log.info("======== START %s ========" % v.ADDON_NAME)
log.info("Platform: %s" % v.PLATFORM)
log.info("KODI Version: %s" % v.KODILONGVERSION)
log.info("%s Version: %s" % (v.ADDON_NAME, v.ADDON_VERSION))
log.info("Using plugin paths: %s"
% (settings('useDirectPaths') != "true"))
log.warn("Number of sync threads: %s"
log.info("Number of sync threads: %s"
% settings('syncThreadNumber'))
log.warn("Log Level: %s" % logLevel)
log.warn("Full sys.argv received: %s" % argv)
log.info("Full sys.argv received: %s" % argv)
# Reset window props for profile switch
properties = [
"plex_online", "plex_serverStatus", "plex_onWake",
"plex_dbCheck", "plex_kodiScan",
"plex_kodiScan",
"plex_shouldStop", "plex_dbScan",
"plex_initialScan", "plex_customplayqueue", "plex_playbackProps",
"plex_runLibScan", "pms_token", "plex_token",
"pms_token", "plex_token",
"pms_server", "plex_machineIdentifier", "plex_servername",
"plex_authenticated", "PlexUserImage", "useDirectPaths",
"kodiplextimeoffset", "countError", "countUnauthorized",
"countError", "countUnauthorized",
"plex_restricteduser", "plex_allows_mediaDeletion",
"plex_command", "plex_result", "plex_force_transcode_pix"
]
@ -127,13 +123,6 @@ class Service():
# Set the minimum database version
window('plex_minDBVersion', value="1.5.10")
def getLogLevel(self):
try:
logLevel = int(settings('logLevel'))
except ValueError:
logLevel = 0
return logLevel
def __stop_PKC(self):
"""
Kodi's abortRequested is really unreliable :-(
@ -173,7 +162,7 @@ class Service():
if window('plex_kodiProfile') != kodiProfile:
# Profile change happened, terminate this thread and others
log.warn("Kodi profile was: %s and changed to: %s. "
log.info("Kodi profile was: %s and changed to: %s. "
"Terminating old PlexKodiConnect thread."
% (kodiProfile,
window('plex_kodiProfile')))
@ -332,7 +321,7 @@ class Service():
except:
pass
window('plex_service_started', clear=True)
log.warn("======== STOP %s ========" % v.ADDON_NAME)
log.info("======== STOP %s ========" % v.ADDON_NAME)
# Safety net - Kody starts PKC twice upon first installation!
@ -345,11 +334,11 @@ else:
# Delay option
delay = int(settings('startupDelay'))
log.warn("Delaying Plex startup by: %s sec..." % delay)
log.info("Delaying Plex startup by: %s sec..." % delay)
if exit:
log.error('PKC service.py already started - exiting this instance')
elif delay and Monitor().waitForAbort(delay):
# Start the service
log.warn("Abort requested while waiting. PKC not started.")
log.info("Abort requested while waiting. PKC not started.")
else:
Service().ServiceEntryPoint()