Merge branch 'develop' into translations
This commit is contained in:
commit
9fdab58cdb
36 changed files with 674 additions and 1093 deletions
|
@ -1,5 +1,5 @@
|
|||
[![stable version](https://img.shields.io/badge/stable_version-1.7.7-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.7.17-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-1.7.21-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)
|
||||
|
@ -63,6 +63,7 @@ PKC synchronizes your media from your Plex server to the native Kodi database. H
|
|||
+ Danish, thanks @FIGHT
|
||||
+ Italian, thanks @nikkux, @chicco83
|
||||
+ Dutch, thanks @mvanbaak
|
||||
+ French, thanks @lflforce, @ahivert, @Nox71, @CotzaDev, @vinch100, @Polymorph31, @jbnitro
|
||||
+ Chinese Traditional, thanks @old2tan
|
||||
+ Chinese Simplified, thanks @everdream
|
||||
+ [Please help translating](https://www.transifex.com/croneter/pkc)
|
||||
|
|
30
addon.xml
30
addon.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.17" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.21" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.3.0" />
|
||||
|
@ -44,7 +44,33 @@
|
|||
<disclaimer lang="nl_NL">Gebruik op eigen risico</disclaimer>
|
||||
<disclaimer lang="zh_TW">使用風險由您自己承擔</disclaimer>
|
||||
<disclaimer lang="es_ES">Usar a su propio riesgo</disclaimer>
|
||||
<news>version 1.7.17 (beta only)
|
||||
<news>version 1.7.21 (beta only)
|
||||
- Fix Playback and watched status not syncing
|
||||
- Fix PKC syncing progress to wrong account
|
||||
- Warn user if a xml cannot be parsed
|
||||
|
||||
version 1.7.20 (beta only)
|
||||
- Fix for Windows usernames with non-ASCII chars
|
||||
- Companion: Fix TypeError
|
||||
- Use SSL settings when checking server connection
|
||||
- Fix TypeError when PMS connection lost
|
||||
- Increase timeout
|
||||
|
||||
version 1.7.19 (beta only)
|
||||
- Big code refactoring
|
||||
- Many Plex Companion fixes
|
||||
- Fix WindowsError or alike when deleting video nodes
|
||||
- Remove restart on first setup
|
||||
- Only set advancedsettings tweaks if Music enabled
|
||||
|
||||
version 1.7.18 (beta only)
|
||||
- Fix OperationalError when resetting PKC
|
||||
- Fix possible OperationalErrors
|
||||
- Companion: ensure sockets get closed
|
||||
- Fix TypeError for Plex Companion
|
||||
- Update Czech
|
||||
|
||||
version 1.7.17 (beta only)
|
||||
- Don't add media by other add-ons to queue
|
||||
- Fix KeyError for Plex Companion
|
||||
- Repace Kodi mkdirs with os.makedirs
|
||||
|
|
|
@ -1,3 +1,29 @@
|
|||
version 1.7.21 (beta only)
|
||||
- Fix Playback and watched status not syncing
|
||||
- Fix PKC syncing progress to wrong account
|
||||
- Warn user if a xml cannot be parsed
|
||||
|
||||
version 1.7.20 (beta only)
|
||||
- Fix for Windows usernames with non-ASCII chars
|
||||
- Companion: Fix TypeError
|
||||
- Use SSL settings when checking server connection
|
||||
- Fix TypeError when PMS connection lost
|
||||
- Increase timeout
|
||||
|
||||
version 1.7.19 (beta only)
|
||||
- Big code refactoring
|
||||
- Many Plex Companion fixes
|
||||
- Fix WindowsError or alike when deleting video nodes
|
||||
- Remove restart on first setup
|
||||
- Only set advancedsettings tweaks if Music enabled
|
||||
|
||||
version 1.7.18 (beta only)
|
||||
- Fix OperationalError when resetting PKC
|
||||
- Fix possible OperationalErrors
|
||||
- Companion: ensure sockets get closed
|
||||
- Fix TypeError for Plex Companion
|
||||
- Update Czech
|
||||
|
||||
version 1.7.17 (beta only)
|
||||
- Don't add media by other add-ons to queue
|
||||
- Fix KeyError for Plex Companion
|
||||
|
|
|
@ -170,10 +170,10 @@ class Main():
|
|||
Start up playback_starter in main Python thread
|
||||
"""
|
||||
# Put the request into the 'queue'
|
||||
while window('plex_play_new_item'):
|
||||
while window('plex_command'):
|
||||
sleep(50)
|
||||
window('plex_play_new_item',
|
||||
value='%s%s' % ('play', argv[2]))
|
||||
window('plex_command',
|
||||
value='play_%s' % argv[2])
|
||||
# Wait for the result
|
||||
while not pickl_window('plex_result'):
|
||||
sleep(50)
|
||||
|
|
|
@ -1919,3 +1919,8 @@ msgstr ""
|
|||
msgctxt "#39715"
|
||||
msgid "items"
|
||||
msgstr ""
|
||||
|
||||
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is screwed up; formated the wrong way). Do NOT replace {0} and {1}!
|
||||
msgctxt "#39716"
|
||||
msgid "Kodi cannot parse {0}. PKC will not function correctly. Please visit {1} and correct your file!"
|
||||
msgstr ""
|
||||
|
|
|
@ -53,6 +53,7 @@ from utils import window, settings, language as lang, tryDecode, tryEncode, \
|
|||
from PlexFunctions import PMSHttpsEnabled
|
||||
import plexdb_functions as plexdb
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -628,7 +629,7 @@ class PlexAPI():
|
|||
authenticate=False,
|
||||
headerOptions={'X-Plex-Token': PMS['token']},
|
||||
verifySSL=False,
|
||||
timeout=3)
|
||||
timeout=10)
|
||||
try:
|
||||
xml.attrib['machineIdentifier']
|
||||
except (AttributeError, KeyError):
|
||||
|
@ -879,6 +880,8 @@ class PlexAPI():
|
|||
settings('plex_restricteduser',
|
||||
'true' if answer.attrib.get('restricted', '0') == '1'
|
||||
else 'false')
|
||||
state.RESTRICTED_USER = True if \
|
||||
answer.attrib.get('restricted', '0') == '1' else False
|
||||
|
||||
# Get final token to the PMS we've chosen
|
||||
url = 'https://plex.tv/api/resources?includeHttps=1'
|
||||
|
@ -2550,20 +2553,20 @@ class API():
|
|||
if "\\" in path:
|
||||
if not path.endswith('\\'):
|
||||
# Add the missing backslash
|
||||
check = exists_dir(tryEncode(path + "\\"))
|
||||
check = exists_dir(path + "\\")
|
||||
else:
|
||||
check = exists_dir(tryEncode(path))
|
||||
check = exists_dir(path)
|
||||
else:
|
||||
if not path.endswith('/'):
|
||||
check = exists_dir(tryEncode(path + "/"))
|
||||
check = exists_dir(path + "/")
|
||||
else:
|
||||
check = exists_dir(tryEncode(path))
|
||||
check = exists_dir(path)
|
||||
|
||||
if not check:
|
||||
if forceCheck is False:
|
||||
# Validate the path is correct with user intervention
|
||||
if self.askToValidate(path):
|
||||
window('plex_shouldStop', value="true")
|
||||
state.STOP_SYNC = True
|
||||
path = None
|
||||
window('plex_pathverified', value='true')
|
||||
else:
|
||||
|
|
|
@ -7,13 +7,14 @@ from urllib import urlencode
|
|||
|
||||
from xbmc import sleep, executebuiltin
|
||||
|
||||
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods
|
||||
from utils import settings, thread_methods
|
||||
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||
httppersist, plexsettings
|
||||
from PlexFunctions import ParseContainerKey, GetPlexMetadata
|
||||
from PlexAPI import API
|
||||
import player
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -22,8 +23,7 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_suspends=['PMS_STATUS'])
|
||||
class PlexCompanion(Thread):
|
||||
"""
|
||||
"""
|
||||
|
@ -77,6 +77,8 @@ class PlexCompanion(Thread):
|
|||
log.debug('Processing: %s' % task)
|
||||
data = task['data']
|
||||
|
||||
# Get the token of the user flinging media (might be different one)
|
||||
state.PLEX_TRANSIENT_TOKEN = data.get('token')
|
||||
if task['action'] == 'alexa':
|
||||
# e.g. Alexa
|
||||
xml = GetPlexMetadata(data['key'])
|
||||
|
@ -144,11 +146,28 @@ class PlexCompanion(Thread):
|
|||
offset=data.get('offset'))
|
||||
|
||||
def run(self):
|
||||
httpd = False
|
||||
# Ensure that sockets will be closed no matter what
|
||||
try:
|
||||
self.__run()
|
||||
finally:
|
||||
try:
|
||||
self.httpd.socket.shutdown(SHUT_RDWR)
|
||||
except AttributeError:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
self.httpd.socket.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
log.info("----===## Plex Companion stopped ##===----")
|
||||
|
||||
def __run(self):
|
||||
self.httpd = False
|
||||
httpd = self.httpd
|
||||
# Cache for quicker while loops
|
||||
client = self.client
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
|
||||
# Start up instances
|
||||
requestMgr = httppersist.RequestMgr()
|
||||
|
@ -196,12 +215,12 @@ class PlexCompanion(Thread):
|
|||
if httpd:
|
||||
t = Thread(target=httpd.handle_request)
|
||||
|
||||
while not threadStopped():
|
||||
while not thread_stopped():
|
||||
# If we are not authorized, sleep
|
||||
# Otherwise, we trigger a download which leads to a
|
||||
# re-authorizations
|
||||
while threadSuspended():
|
||||
if threadStopped():
|
||||
while thread_suspended():
|
||||
if thread_stopped():
|
||||
break
|
||||
sleep(1000)
|
||||
try:
|
||||
|
@ -245,11 +264,3 @@ class PlexCompanion(Thread):
|
|||
sleep(50)
|
||||
|
||||
client.stop_all()
|
||||
if httpd:
|
||||
try:
|
||||
httpd.socket.shutdown(SHUT_RDWR)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
httpd.socket.close()
|
||||
log.info("----===## Plex Companion stopped ##===----")
|
||||
|
|
|
@ -13,7 +13,7 @@ from xbmc import executeJSONRPC, sleep, translatePath
|
|||
from xbmcvfs import exists
|
||||
|
||||
from utils import window, settings, language as lang, kodiSQL, tryEncode, \
|
||||
ThreadMethods, ThreadMethodsAdditionalStop, dialog, exists_dir
|
||||
thread_methods, dialog, exists_dir, tryDecode
|
||||
|
||||
# Disable annoying requests warnings
|
||||
import requests.packages.urllib3
|
||||
|
@ -126,8 +126,8 @@ def double_urldecode(text):
|
|||
return unquote(unquote(text))
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_stops=['STOP_SYNC'],
|
||||
add_suspends=['SUSPEND_LIBRARY_THREAD', 'DB_SCAN'])
|
||||
class Image_Cache_Thread(Thread):
|
||||
xbmc_host = 'localhost'
|
||||
xbmc_port, xbmc_username, xbmc_password = setKodiWebServerDetails()
|
||||
|
@ -140,22 +140,16 @@ class Image_Cache_Thread(Thread):
|
|||
self.queue = ARTWORK_QUEUE
|
||||
Thread.__init__(self)
|
||||
|
||||
def threadSuspended(self):
|
||||
# Overwrite method to add TWO additional suspends
|
||||
return (self._threadSuspended or
|
||||
window('suspend_LibraryThread') or
|
||||
window('plex_dbScan'))
|
||||
|
||||
def run(self):
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
queue = self.queue
|
||||
sleep_between = self.sleep_between
|
||||
while not threadStopped():
|
||||
while not thread_stopped():
|
||||
# In the event the server goes offline
|
||||
while threadSuspended():
|
||||
while thread_suspended():
|
||||
# Set in service.py
|
||||
if threadStopped():
|
||||
if thread_stopped():
|
||||
# Abort was requested while waiting. We should exit
|
||||
log.info("---===### Stopped Image_Cache_Thread ###===---")
|
||||
return
|
||||
|
@ -178,7 +172,7 @@ class Image_Cache_Thread(Thread):
|
|||
# download. All is well
|
||||
break
|
||||
except requests.ConnectionError:
|
||||
if threadStopped():
|
||||
if thread_stopped():
|
||||
# Kodi terminated
|
||||
break
|
||||
# Server thinks its a DOS attack, ('error 10053')
|
||||
|
@ -228,7 +222,7 @@ class Artwork():
|
|||
if dialog('yesno', "Image Texture Cache", lang(39251)):
|
||||
log.info("Resetting all cache data first")
|
||||
# Remove all existing textures first
|
||||
path = translatePath("special://thumbnails/")
|
||||
path = tryDecode(translatePath("special://thumbnails/"))
|
||||
if exists_dir(path):
|
||||
rmtree(path, ignore_errors=True)
|
||||
|
||||
|
@ -241,8 +235,7 @@ class Artwork():
|
|||
for row in rows:
|
||||
tableName = row[0]
|
||||
if tableName != "version":
|
||||
query = "DELETE FROM ?"
|
||||
cursor.execute(query, (tableName,))
|
||||
cursor.execute("DELETE FROM %s" % tableName)
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
|
@ -430,7 +423,7 @@ class Artwork():
|
|||
path = translatePath("special://thumbnails/%s" % cachedurl)
|
||||
log.debug("Deleting cached thumbnail: %s" % path)
|
||||
if exists(path):
|
||||
rmtree(path, ignore_errors=True)
|
||||
rmtree(tryDecode(path), ignore_errors=True)
|
||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||
connection.commit()
|
||||
finally:
|
||||
|
|
73
resources/lib/command_pipeline.py
Normal file
73
resources/lib/command_pipeline.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
from threading import Thread
|
||||
from Queue import Queue
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, thread_methods
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods
|
||||
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
|
||||
def __init__(self, callback=None):
|
||||
self.mgr = callback
|
||||
self.playback_queue = Queue()
|
||||
Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
thread_stopped = self.thread_stopped
|
||||
queue = self.playback_queue
|
||||
log.info("----===## Starting Kodi_Play_Client ##===----")
|
||||
while not thread_stopped():
|
||||
if window('plex_command'):
|
||||
value = window('plex_command')
|
||||
window('plex_command', clear=True)
|
||||
if value.startswith('play_'):
|
||||
queue.put(value)
|
||||
|
||||
elif value == 'SUSPEND_LIBRARY_THREAD-True':
|
||||
state.SUSPEND_LIBRARY_THREAD = True
|
||||
elif value == 'SUSPEND_LIBRARY_THREAD-False':
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
elif value == 'STOP_SYNC-True':
|
||||
state.STOP_SYNC = True
|
||||
elif value == 'STOP_SYNC-False':
|
||||
state.STOP_SYNC = False
|
||||
elif value == 'PMS_STATUS-Auth':
|
||||
state.PMS_STATUS = 'Auth'
|
||||
elif value == 'PMS_STATUS-401':
|
||||
state.PMS_STATUS = '401'
|
||||
elif value == 'SUSPEND_USER_CLIENT-True':
|
||||
state.SUSPEND_USER_CLIENT = True
|
||||
elif value == 'SUSPEND_USER_CLIENT-False':
|
||||
state.SUSPEND_USER_CLIENT = False
|
||||
elif value.startswith('PLEX_TOKEN-'):
|
||||
state.PLEX_TOKEN = value.replace('PLEX_TOKEN-', '') or None
|
||||
elif value.startswith('PLEX_USERNAME-'):
|
||||
state.PLEX_USERNAME = \
|
||||
value.replace('PLEX_USERNAME-', '') or None
|
||||
else:
|
||||
raise NotImplementedError('%s not implemented' % value)
|
||||
else:
|
||||
sleep(50)
|
||||
# Put one last item into the queue to let playback_starter end
|
||||
queue.put(None)
|
||||
log.info("----===## Kodi_Play_Client stopped ##===----")
|
|
@ -9,6 +9,8 @@ import xml.etree.ElementTree as etree
|
|||
from utils import settings, window, language as lang, dialog
|
||||
import clientinfo as client
|
||||
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Disable annoying requests warnings
|
||||
|
@ -40,20 +42,6 @@ class DownloadUtils():
|
|||
def __init__(self):
|
||||
self.__dict__ = self._shared_state
|
||||
|
||||
def setUsername(self, username):
|
||||
"""
|
||||
Reserved for userclient only
|
||||
"""
|
||||
self.username = username
|
||||
log.debug("Set username: %s" % username)
|
||||
|
||||
def setUserId(self, userId):
|
||||
"""
|
||||
Reserved for userclient only
|
||||
"""
|
||||
self.userId = userId
|
||||
log.debug("Set userId: %s" % userId)
|
||||
|
||||
def setServer(self, server):
|
||||
"""
|
||||
Reserved for userclient only
|
||||
|
@ -108,8 +96,6 @@ class DownloadUtils():
|
|||
# Set other stuff
|
||||
self.setServer(window('pms_server'))
|
||||
self.setToken(window('pms_token'))
|
||||
self.setUserId(window('currUserId'))
|
||||
self.setUsername(window('plex_username'))
|
||||
|
||||
# Counters to declare PMS dead or unauthorized
|
||||
# Use window variables because start of movies will be called with a
|
||||
|
@ -274,10 +260,11 @@ class DownloadUtils():
|
|||
self.unauthorizedAttempts):
|
||||
log.warn('We seem to be truly unauthorized for PMS'
|
||||
' %s ' % url)
|
||||
if window('plex_serverStatus') not in ('401', 'Auth'):
|
||||
if state.PMS_STATUS not in ('401', 'Auth'):
|
||||
# Tell userclient token has been revoked.
|
||||
log.debug('Setting PMS server status to '
|
||||
'unauthorized')
|
||||
state.PMS_STATUS = '401'
|
||||
window('plex_serverStatus', value="401")
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
|
|
|
@ -12,7 +12,7 @@ from xbmc import sleep, executebuiltin, translatePath
|
|||
from xbmcgui import ListItem
|
||||
|
||||
from utils import window, settings, language as lang, dialog, tryEncode, \
|
||||
CatchExceptions, JSONRPC, exists_dir
|
||||
CatchExceptions, JSONRPC, exists_dir, plex_command, tryDecode
|
||||
import downloadutils
|
||||
|
||||
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
|
||||
|
@ -42,8 +42,8 @@ def chooseServer():
|
|||
server = setup.PickPMS(showDialog=True)
|
||||
if server is None:
|
||||
log.error('We did not connect to a new PMS, aborting')
|
||||
window('suspend_Userclient', clear=True)
|
||||
window('suspend_LibraryThread', clear=True)
|
||||
plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||
plex_command('SUSPEND_LIBRARY_THREAD', 'False')
|
||||
return
|
||||
|
||||
log.info("User chose server %s" % server['name'])
|
||||
|
@ -81,7 +81,8 @@ def togglePlexTV():
|
|||
settings('plex_status', value="Not logged in to plex.tv")
|
||||
|
||||
window('plex_token', clear=True)
|
||||
window('plex_username', clear=True)
|
||||
plex_command('PLEX_TOKEN', '')
|
||||
plex_command('PLEX_USERNAME', '')
|
||||
else:
|
||||
log.info('Login to plex.tv')
|
||||
import initialsetup
|
||||
|
@ -100,7 +101,7 @@ def resetAuth():
|
|||
resp = dialog('yesno', heading="{plex}", line1=lang(39206))
|
||||
if resp == 1:
|
||||
log.info("Reset login attempts.")
|
||||
window('plex_serverStatus', value="Auth")
|
||||
plex_command('PMS_STATUS', 'Auth')
|
||||
else:
|
||||
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
|
||||
|
@ -146,7 +147,7 @@ def doMainListing(content_type=None):
|
|||
addDirectoryItem(lang(30173),
|
||||
"plugin://%s?mode=channels" % v.ADDON_ID)
|
||||
# Plex user switch
|
||||
addDirectoryItem(lang(39200) + window('plex_username'),
|
||||
addDirectoryItem(lang(39200),
|
||||
"plugin://%s?mode=switchuser" % v.ADDON_ID)
|
||||
|
||||
# some extra entries for settings and stuff
|
||||
|
@ -488,7 +489,6 @@ def getVideoFiles(plexId, params):
|
|||
except:
|
||||
log.error('Could not get file path for item %s' % plexId)
|
||||
return xbmcplugin.endOfDirectory(HANDLE)
|
||||
path = tryEncode(path)
|
||||
# Assign network protocol
|
||||
if path.startswith('\\\\'):
|
||||
path = path.replace('\\\\', 'smb://')
|
||||
|
@ -501,14 +501,14 @@ def getVideoFiles(plexId, params):
|
|||
if exists_dir(path):
|
||||
for root, dirs, files in walk(path):
|
||||
for directory in dirs:
|
||||
item_path = join(root, directory)
|
||||
item_path = tryEncode(join(root, directory))
|
||||
li = ListItem(item_path, path=item_path)
|
||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||
url=item_path,
|
||||
listitem=li,
|
||||
isFolder=True)
|
||||
for file in files:
|
||||
item_path = join(root, file)
|
||||
item_path = tryEncode(join(root, file))
|
||||
li = ListItem(item_path, path=item_path)
|
||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||
url=file,
|
||||
|
@ -536,7 +536,8 @@ def getExtraFanArt(plexid, plexPath):
|
|||
|
||||
# We need to store the images locally for this to work
|
||||
# because of the caching system in xbmc
|
||||
fanartDir = translatePath("special://thumbnails/plex/%s/" % plexid)
|
||||
fanartDir = tryDecode(translatePath(
|
||||
"special://thumbnails/plex/%s/" % plexid))
|
||||
if not exists_dir(fanartDir):
|
||||
# Download the images to the cache directory
|
||||
makedirs(fanartDir)
|
||||
|
@ -549,19 +550,19 @@ def getExtraFanArt(plexid, plexPath):
|
|||
backdrops = api.getAllArtwork()['Backdrop']
|
||||
for count, backdrop in enumerate(backdrops):
|
||||
# Same ordering as in artwork
|
||||
fanartFile = join(fanartDir, "fanart%.3d.jpg" % count)
|
||||
fanartFile = tryEncode(join(fanartDir, "fanart%.3d.jpg" % count))
|
||||
li = ListItem("%.3d" % count, path=fanartFile)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=HANDLE,
|
||||
url=fanartFile,
|
||||
listitem=li)
|
||||
copyfile(backdrop, fanartFile)
|
||||
copyfile(backdrop, tryDecode(fanartFile))
|
||||
else:
|
||||
log.info("Found cached backdrop.")
|
||||
# Use existing cached images
|
||||
for root, dirs, files in walk(fanartDir):
|
||||
for file in files:
|
||||
fanartFile = join(root, file)
|
||||
fanartFile = tryEncode(join(root, file))
|
||||
li = ListItem(file, path=fanartFile)
|
||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||
url=fanartFile,
|
||||
|
@ -964,22 +965,19 @@ def enterPMS():
|
|||
|
||||
def __LogIn():
|
||||
"""
|
||||
Resets (clears) window properties to enable (re-)login:
|
||||
suspend_Userclient
|
||||
plex_runLibScan: set to 'full' to trigger lib sync
|
||||
Resets (clears) window properties to enable (re-)login
|
||||
|
||||
suspend_LibraryThread is cleared in service.py if user was signed out!
|
||||
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
|
||||
out!
|
||||
"""
|
||||
window('plex_runLibScan', value='full')
|
||||
# Restart user client
|
||||
window('suspend_Userclient', clear=True)
|
||||
plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||
|
||||
|
||||
def __LogOut():
|
||||
"""
|
||||
Finishes lib scans, logs out user. The following window attributes are set:
|
||||
suspend_LibraryThread: 'true'
|
||||
suspend_Userclient: 'true'
|
||||
Finishes lib scans, logs out user.
|
||||
|
||||
Returns True if successfully signed out, False otherwise
|
||||
"""
|
||||
|
@ -991,7 +989,7 @@ def __LogOut():
|
|||
time=3000,
|
||||
sound=False)
|
||||
# Pause library sync thread
|
||||
window('suspend_LibraryThread', value='true')
|
||||
plex_command('SUSPEND_LIBRARY_THREAD', 'True')
|
||||
# Wait max for 10 seconds for all lib scans to shutdown
|
||||
counter = 0
|
||||
while window('plex_dbScan') == 'true':
|
||||
|
@ -999,17 +997,18 @@ def __LogOut():
|
|||
# Failed to reset PMS and plex.tv connects. Try to restart Kodi.
|
||||
dialog('ok', lang(29999), lang(39208))
|
||||
# Resuming threads, just in case
|
||||
window('suspend_LibraryThread', clear=True)
|
||||
plex_command('SUSPEND_LIBRARY_THREAD', 'False')
|
||||
log.error("Could not stop library sync, aborting")
|
||||
return False
|
||||
counter += 1
|
||||
sleep(50)
|
||||
log.debug("Successfully stopped library sync")
|
||||
|
||||
# Log out currently signed in user:
|
||||
window('plex_serverStatus', value="401")
|
||||
# Above method needs to have run its course! Hence wait
|
||||
counter = 0
|
||||
# Log out currently signed in user:
|
||||
window('plex_serverStatus', value='401')
|
||||
plex_command('PMS_STATUS', '401')
|
||||
# Above method needs to have run its course! Hence wait
|
||||
while window('plex_serverStatus') == "401":
|
||||
if counter > 100:
|
||||
# 'Failed to reset PKC. Try to restart Kodi.'
|
||||
|
@ -1019,5 +1018,5 @@ def __LogOut():
|
|||
counter += 1
|
||||
sleep(50)
|
||||
# Suspend the user client during procedure
|
||||
window('suspend_Userclient', value='true')
|
||||
plex_command('SUSPEND_USER_CLIENT', 'True')
|
||||
return True
|
||||
|
|
|
@ -13,6 +13,7 @@ from userclient import UserClient
|
|||
|
||||
from PlexAPI import PlexAPI
|
||||
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -156,7 +157,7 @@ class InitialSetup():
|
|||
verifySSL = False
|
||||
else:
|
||||
url = server['baseURL']
|
||||
verifySSL = None
|
||||
verifySSL = True
|
||||
chk = self.plx.CheckConnection(url,
|
||||
token=server['accesstoken'],
|
||||
verifySSL=verifySSL)
|
||||
|
@ -450,6 +451,7 @@ class InitialSetup():
|
|||
yeslabel="Native (Direct Paths)"):
|
||||
log.debug("User opted to use direct paths.")
|
||||
settings('useDirectPaths', value="1")
|
||||
state.DIRECT_PATHS = True
|
||||
# Are you on a system where you would like to replace paths
|
||||
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
|
||||
if dialog.yesno(heading=lang(29999), line1=lang(39033)):
|
||||
|
@ -477,9 +479,6 @@ class InitialSetup():
|
|||
if dialog.yesno(heading=lang(29999), line1=lang(39016)):
|
||||
log.debug("User opted to disable Plex music library.")
|
||||
settings('enableMusic', value="false")
|
||||
else:
|
||||
from utils import advancedsettings_tweaks
|
||||
advancedsettings_tweaks()
|
||||
|
||||
# Download additional art from FanArtTV
|
||||
if dialog.yesno(heading=lang(29999), line1=lang(39061)):
|
||||
|
@ -496,12 +495,6 @@ class InitialSetup():
|
|||
# Open Settings page now? You will need to restart!
|
||||
goToSettings = dialog.yesno(heading=lang(29999), line1=lang(39017))
|
||||
if goToSettings:
|
||||
window('plex_serverStatus', value="Stop")
|
||||
state.PMS_STATUS = 'Stop'
|
||||
xbmc.executebuiltin(
|
||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
else:
|
||||
# "Kodi will now restart to apply the changes"
|
||||
dialog.ok(heading=lang(29999), line1=lang(33033))
|
||||
xbmc.executebuiltin('RestartApp')
|
||||
# We should always restart to ensure e.g. Kodi settings for Music
|
||||
# are in use!
|
||||
|
|
|
@ -16,6 +16,7 @@ import kodidb_functions as kodidb
|
|||
import PlexAPI
|
||||
from PlexFunctions import GetPlexMetadata
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -35,10 +36,7 @@ class Items(object):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.directpath = window('useDirectPaths') == 'true'
|
||||
|
||||
self.artwork = artwork.Artwork()
|
||||
self.userid = window('currUserId')
|
||||
self.server = window('pms_server')
|
||||
|
||||
def __enter__(self):
|
||||
|
@ -268,8 +266,8 @@ class Movies(Items):
|
|||
break
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
doIndirect = not self.directpath
|
||||
if self.directpath:
|
||||
doIndirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||
if playurl is None:
|
||||
|
@ -569,8 +567,8 @@ class TVShows(Items):
|
|||
studio = None
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
doIndirect = not self.directpath
|
||||
if self.directpath:
|
||||
doIndirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = API.getTVShowPath()
|
||||
if playurl is None:
|
||||
|
@ -892,9 +890,9 @@ class TVShows(Items):
|
|||
seasonid = self.kodi_db.addSeason(showid, season)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
doIndirect = not self.directpath
|
||||
doIndirect = not state.DIRECT_PATHS
|
||||
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||
if self.directpath:
|
||||
if state.DIRECT_PATHS:
|
||||
# Direct paths is set the Kodi way
|
||||
if playurl is None:
|
||||
# Something went wrong, trying to use non-direct paths
|
||||
|
@ -1116,7 +1114,7 @@ class TVShows(Items):
|
|||
self.kodi_db.addStreams(fileid, streams, runtime)
|
||||
# Process playstates
|
||||
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||
if not self.directpath and resume:
|
||||
if not state.DIRECT_PATHS and resume:
|
||||
# Create additional entry for widgets. This is only required for plugin/episode.
|
||||
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect/tvshows/")
|
||||
tempfileid = self.kodi_db.addFile(filename, temppathid)
|
||||
|
@ -1634,8 +1632,8 @@ class Music(Items):
|
|||
mood = ' / '.join(moods)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
doIndirect = not self.directpath
|
||||
if self.directpath:
|
||||
doIndirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = API.getFilePath(forceFirstMediaStream=True)
|
||||
if playurl is None:
|
||||
|
|
|
@ -1409,8 +1409,8 @@ class Kodidb_Functions():
|
|||
ID = 'idEpisode'
|
||||
elif kodi_type == v.KODI_TYPE_SONG:
|
||||
ID = 'idSong'
|
||||
query = '''UPDATE ? SET userrating = ? WHERE ? = ?'''
|
||||
self.cursor.execute(query, (kodi_type, userrating, ID, kodi_id))
|
||||
query = '''UPDATE %s SET userrating = ? WHERE ? = ?''' % kodi_type
|
||||
self.cursor.execute(query, (userrating, ID, kodi_id))
|
||||
|
||||
def create_entry_uniqueid(self):
|
||||
self.cursor.execute(
|
||||
|
|
|
@ -14,6 +14,7 @@ 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
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -137,6 +138,10 @@ class KodiMonitor(Monitor):
|
|||
sleep(5000)
|
||||
window('plex_runLibScan', value="full")
|
||||
|
||||
elif method == "System.OnQuit":
|
||||
log.info('Kodi OnQuit detected - shutting down')
|
||||
state.STOP_PKC = True
|
||||
|
||||
def PlayBackStart(self, data):
|
||||
"""
|
||||
Called whenever a playback is started
|
||||
|
|
|
@ -5,8 +5,7 @@ from Queue import Empty
|
|||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import ThreadMethodsAdditionalStop, ThreadMethods, window, \
|
||||
ThreadMethodsAdditionalSuspend
|
||||
from utils import thread_methods
|
||||
import plexdb_functions as plexdb
|
||||
import itemtypes
|
||||
import variables as v
|
||||
|
@ -18,9 +17,8 @@ log = getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD', 'DB_SCAN'],
|
||||
add_stops=['STOP_SYNC'])
|
||||
class Process_Fanart_Thread(Thread):
|
||||
"""
|
||||
Threaded download of additional fanart in the background
|
||||
|
@ -55,14 +53,14 @@ class Process_Fanart_Thread(Thread):
|
|||
Do the work
|
||||
"""
|
||||
log.debug("---===### Starting FanartSync ###===---")
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
queue = self.queue
|
||||
while not threadStopped():
|
||||
while not thread_stopped():
|
||||
# In the event the server goes offline
|
||||
while threadSuspended() or window('plex_dbScan'):
|
||||
while thread_suspended():
|
||||
# Set in service.py
|
||||
if threadStopped():
|
||||
if thread_stopped():
|
||||
# Abort was requested while waiting. We should exit
|
||||
log.info("---===### Stopped FanartSync ###===---")
|
||||
return
|
||||
|
|
|
@ -5,7 +5,7 @@ from Queue import Empty
|
|||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import ThreadMethodsAdditionalStop, ThreadMethods, window
|
||||
from utils import thread_methods, window
|
||||
from PlexFunctions import GetPlexMetadata, GetAllPlexChildren
|
||||
import sync_info
|
||||
|
||||
|
@ -16,8 +16,7 @@ log = getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD'])
|
||||
class Threaded_Get_Metadata(Thread):
|
||||
"""
|
||||
Threaded download of Plex XML metadata for a certain library item.
|
||||
|
@ -48,7 +47,7 @@ class Threaded_Get_Metadata(Thread):
|
|||
continue
|
||||
else:
|
||||
self.queue.task_done()
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
# Shutdown from outside requested; purge out_queue as well
|
||||
while not self.out_queue.empty():
|
||||
# Still try because remaining item might have been taken
|
||||
|
@ -79,8 +78,8 @@ class Threaded_Get_Metadata(Thread):
|
|||
# cache local variables because it's faster
|
||||
queue = self.queue
|
||||
out_queue = self.out_queue
|
||||
threadStopped = self.threadStopped
|
||||
while threadStopped() is False:
|
||||
thread_stopped = self.thread_stopped
|
||||
while thread_stopped() is False:
|
||||
# grabs Plex item from queue
|
||||
try:
|
||||
item = queue.get(block=False)
|
||||
|
|
|
@ -5,19 +5,17 @@ from Queue import Empty
|
|||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import ThreadMethodsAdditionalStop, ThreadMethods
|
||||
from utils import thread_methods
|
||||
import itemtypes
|
||||
import sync_info
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD'])
|
||||
class Threaded_Process_Metadata(Thread):
|
||||
"""
|
||||
Not yet implemented for more than 1 thread - if ever. Only to be called by
|
||||
|
@ -70,9 +68,9 @@ class Threaded_Process_Metadata(Thread):
|
|||
item_fct = getattr(itemtypes, self.item_type)
|
||||
# cache local variables because it's faster
|
||||
queue = self.queue
|
||||
threadStopped = self.threadStopped
|
||||
thread_stopped = self.thread_stopped
|
||||
with item_fct() as item_class:
|
||||
while threadStopped() is False:
|
||||
while thread_stopped() is False:
|
||||
# grabs item from queue
|
||||
try:
|
||||
item = queue.get(block=False)
|
||||
|
|
|
@ -4,7 +4,7 @@ from threading import Thread, Lock
|
|||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import ThreadMethodsAdditionalStop, ThreadMethods, language as lang
|
||||
from utils import thread_methods, language as lang
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -18,8 +18,7 @@ LOCK = Lock()
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalStop('suspend_LibraryThread')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD'])
|
||||
class Threaded_Show_Sync_Info(Thread):
|
||||
"""
|
||||
Threaded class to show the Kodi statusbar of the metadata download.
|
||||
|
@ -53,13 +52,13 @@ class Threaded_Show_Sync_Info(Thread):
|
|||
# cache local variables because it's faster
|
||||
total = self.total
|
||||
dialog = self.dialog
|
||||
threadStopped = self.threadStopped
|
||||
thread_stopped = self.thread_stopped
|
||||
dialog.create("%s %s: %s %s"
|
||||
% (lang(39714), self.item_type, str(total), lang(39715)))
|
||||
|
||||
total = 2 * total
|
||||
totalProgress = 0
|
||||
while threadStopped() is False:
|
||||
while thread_stopped() is False:
|
||||
with LOCK:
|
||||
get_progress = GET_METADATA_COUNT
|
||||
process_progress = PROCESS_METADATA_COUNT
|
||||
|
|
|
@ -10,10 +10,9 @@ import xbmcgui
|
|||
from xbmcvfs import exists
|
||||
|
||||
from utils import window, settings, getUnixTimestamp, sourcesXML,\
|
||||
ThreadMethods, ThreadMethodsAdditionalStop, LogTime, getScreensaver,\
|
||||
thread_methods, create_actor_db_index, dialog, LogTime, getScreensaver,\
|
||||
setScreensaver, playlistXSP, language as lang, DateToKodi, reset,\
|
||||
advancedsettings_tweaks, tryDecode, deletePlaylists, deleteNodes, \
|
||||
ThreadMethodsAdditionalSuspend, create_actor_db_index, dialog
|
||||
tryDecode, deletePlaylists, deleteNodes, tryEncode
|
||||
import downloadutils
|
||||
import itemtypes
|
||||
import plexdb_functions as plexdb
|
||||
|
@ -30,6 +29,7 @@ from library_sync.process_metadata import Threaded_Process_Metadata
|
|||
import library_sync.sync_info as sync_info
|
||||
from library_sync.fanart import Process_Fanart_Thread
|
||||
import music
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -38,9 +38,8 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||
@ThreadMethodsAdditionalStop('plex_shouldStop')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_stops=['STOP_SYNC'],
|
||||
add_suspends=['SUSPEND_LIBRARY_THREAD'])
|
||||
class LibrarySync(Thread):
|
||||
"""
|
||||
"""
|
||||
|
@ -72,7 +71,6 @@ class LibrarySync(Thread):
|
|||
self.enableMusic = settings('enableMusic') == "true"
|
||||
self.enableBackgroundSync = settings(
|
||||
'enableBackgroundSync') == "true"
|
||||
self.direct_paths = settings('useDirectPaths') == '1'
|
||||
|
||||
# Init for replacing paths
|
||||
window('remapSMB', value=settings('remapSMB'))
|
||||
|
@ -300,7 +298,7 @@ class LibrarySync(Thread):
|
|||
|
||||
# Do the processing
|
||||
for itemtype in process:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
setScreensaver(value=screensaver)
|
||||
return False
|
||||
|
@ -323,7 +321,7 @@ class LibrarySync(Thread):
|
|||
window('plex_scancrashed', clear=True)
|
||||
elif window('plex_scancrashed') == '401':
|
||||
window('plex_scancrashed', clear=True)
|
||||
if window('plex_serverStatus') not in ('401', 'Auth'):
|
||||
if state.PMS_STATUS not in ('401', 'Auth'):
|
||||
# Plex server had too much and returned ERROR
|
||||
self.dialog.ok(lang(29999), lang(39409))
|
||||
|
||||
|
@ -474,7 +472,7 @@ class LibrarySync(Thread):
|
|||
"""
|
||||
Compare the views to Plex
|
||||
"""
|
||||
if self.direct_paths is True:
|
||||
if state.DIRECT_PATHS is True and self.enableMusic is True:
|
||||
if music.set_excludefromscan_music_folders() is True:
|
||||
log.info('Detected new Music library - restarting now')
|
||||
# 'New Plex music library detected. Sorry, but we need to
|
||||
|
@ -759,8 +757,8 @@ class LibrarySync(Thread):
|
|||
for thread in threads:
|
||||
# Threads might already have quit by themselves (e.g. Kodi exit)
|
||||
try:
|
||||
thread.stopThread()
|
||||
except:
|
||||
thread.stop_thread()
|
||||
except AttributeError:
|
||||
pass
|
||||
log.debug("Stop sent to all threads")
|
||||
# Wait till threads are indeed dead
|
||||
|
@ -805,7 +803,7 @@ class LibrarySync(Thread):
|
|||
# PROCESS MOVIES #####
|
||||
self.updatelist = []
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
# Get items per view
|
||||
viewId = view['id']
|
||||
|
@ -826,7 +824,7 @@ class LibrarySync(Thread):
|
|||
log.info("Processed view")
|
||||
# Update viewstate for EVERY item
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
self.PlexUpdateWatched(view['id'], itemType)
|
||||
|
||||
|
@ -898,7 +896,7 @@ class LibrarySync(Thread):
|
|||
# PROCESS TV Shows #####
|
||||
self.updatelist = []
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
# Get items per view
|
||||
viewId = view['id']
|
||||
|
@ -927,7 +925,7 @@ class LibrarySync(Thread):
|
|||
# PROCESS TV Seasons #####
|
||||
# Cycle through tv shows
|
||||
for tvShowId in allPlexTvShowsId:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
# Grab all seasons to tvshow from PMS
|
||||
seasons = GetAllPlexChildren(tvShowId)
|
||||
|
@ -952,7 +950,7 @@ class LibrarySync(Thread):
|
|||
# PROCESS TV Episodes #####
|
||||
# Cycle through tv shows
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
# Grab all episodes to tvshow from PMS
|
||||
episodes = GetAllPlexLeaves(view['id'])
|
||||
|
@ -987,7 +985,7 @@ class LibrarySync(Thread):
|
|||
|
||||
# Update viewstate:
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
self.PlexUpdateWatched(view['id'], itemType)
|
||||
|
||||
|
@ -1024,7 +1022,7 @@ class LibrarySync(Thread):
|
|||
for kind in (v.PLEX_TYPE_ARTIST,
|
||||
v.PLEX_TYPE_ALBUM,
|
||||
v.PLEX_TYPE_SONG):
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
log.debug("Start processing music %s" % kind)
|
||||
self.allKodiElementsId = {}
|
||||
|
@ -1041,7 +1039,7 @@ class LibrarySync(Thread):
|
|||
|
||||
# Update viewstate for EVERY item
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
self.PlexUpdateWatched(view['id'], itemType)
|
||||
|
||||
|
@ -1066,7 +1064,7 @@ class LibrarySync(Thread):
|
|||
except ValueError:
|
||||
pass
|
||||
for view in views:
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
return False
|
||||
# Get items per view
|
||||
itemsXML = GetPlexSectionResults(view['id'], args=urlArgs)
|
||||
|
@ -1172,7 +1170,7 @@ class LibrarySync(Thread):
|
|||
now = getUnixTimestamp()
|
||||
deleteListe = []
|
||||
for i, item in enumerate(self.itemsToProcess):
|
||||
if self.threadStopped():
|
||||
if self.thread_stopped():
|
||||
# Chances are that Kodi gets shut down
|
||||
break
|
||||
if item['state'] == 9:
|
||||
|
@ -1277,8 +1275,8 @@ class LibrarySync(Thread):
|
|||
# movie or episode)
|
||||
continue
|
||||
typus = int(item.get('type', 0))
|
||||
state = int(item.get('state', 0))
|
||||
if state == 9 or (typus in (1, 4, 10) and state == 5):
|
||||
status = int(item.get('state', 0))
|
||||
if status == 9 or (typus in (1, 4, 10) and status == 5):
|
||||
# Only process deleted items OR movies, episodes, tracks/songs
|
||||
plex_id = str(item.get('itemID', '0'))
|
||||
if plex_id == '0':
|
||||
|
@ -1286,7 +1284,7 @@ class LibrarySync(Thread):
|
|||
continue
|
||||
try:
|
||||
if (now - self.just_processed[plex_id] <
|
||||
self.ignore_just_processed and state != 9):
|
||||
self.ignore_just_processed and status != 9):
|
||||
log.debug('We just processed %s: ignoring' % plex_id)
|
||||
continue
|
||||
except KeyError:
|
||||
|
@ -1299,7 +1297,7 @@ class LibrarySync(Thread):
|
|||
else:
|
||||
# Haven't added this element to the queue yet
|
||||
self.itemsToProcess.append({
|
||||
'state': state,
|
||||
'state': status,
|
||||
'type': typus,
|
||||
'ratingKey': plex_id,
|
||||
'timestamp': getUnixTimestamp(),
|
||||
|
@ -1315,8 +1313,8 @@ class LibrarySync(Thread):
|
|||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
for item in data:
|
||||
# Drop buffering messages immediately
|
||||
state = item.get('state')
|
||||
if state == 'buffering':
|
||||
status = item.get('state')
|
||||
if status == 'buffering':
|
||||
continue
|
||||
ratingKey = item.get('ratingKey')
|
||||
kodiInfo = plex_db.getItem_byId(ratingKey)
|
||||
|
@ -1335,8 +1333,7 @@ class LibrarySync(Thread):
|
|||
}
|
||||
else:
|
||||
# PMS is ours - get all current sessions
|
||||
self.sessionKeys = GetPMSStatus(
|
||||
window('plex_token'))
|
||||
self.sessionKeys = GetPMSStatus(state.PLEX_TOKEN)
|
||||
log.debug('Updated current sessions. They are: %s'
|
||||
% self.sessionKeys)
|
||||
if sessionKey not in self.sessionKeys:
|
||||
|
@ -1349,20 +1346,19 @@ class LibrarySync(Thread):
|
|||
# Identify the user - same one as signed on with PKC? Skip
|
||||
# update if neither session's username nor userid match
|
||||
# (Owner sometime's returns id '1', not always)
|
||||
if (window('plex_token') == '' and
|
||||
currSess['userId'] == '1'):
|
||||
if (not state.PLEX_TOKEN and currSess['userId'] == '1'):
|
||||
# PKC not signed in to plex.tv. Plus owner of PMS is
|
||||
# playing (the '1').
|
||||
# Hence must be us (since several users require plex.tv
|
||||
# token for PKC)
|
||||
pass
|
||||
elif not (currSess['userId'] == window('currUserId')
|
||||
elif not (currSess['userId'] == state.PLEX_USER_ID
|
||||
or
|
||||
currSess['username'] == window('plex_username')):
|
||||
currSess['username'] == state.PLEX_USERNAME):
|
||||
log.debug('Our username %s, userid %s did not match '
|
||||
'the session username %s with userid %s'
|
||||
% (window('plex_username'),
|
||||
window('currUserId'),
|
||||
% (state.PLEX_USERNAME,
|
||||
state.PLEX_USER_ID,
|
||||
currSess['username'],
|
||||
currSess['userId']))
|
||||
continue
|
||||
|
@ -1394,14 +1390,14 @@ class LibrarySync(Thread):
|
|||
'file_id': kodiInfo[1],
|
||||
'kodi_type': kodiInfo[4],
|
||||
'viewOffset': resume,
|
||||
'state': state,
|
||||
'state': status,
|
||||
'duration': currSess['duration'],
|
||||
'viewCount': currSess['viewCount'],
|
||||
'lastViewedAt': DateToKodi(getUnixTimestamp())
|
||||
})
|
||||
log.debug('Update playstate for user %s with id %s: %s'
|
||||
% (window('plex_username'),
|
||||
window('currUserId'),
|
||||
% (state.PLEX_USERNAME,
|
||||
state.PLEX_USER_ID,
|
||||
items[-1]))
|
||||
# Now tell Kodi where we are
|
||||
for item in items:
|
||||
|
@ -1433,6 +1429,7 @@ class LibrarySync(Thread):
|
|||
try:
|
||||
self.run_internal()
|
||||
except Exception as e:
|
||||
state.DB_SCAN = False
|
||||
window('plex_dbScan', clear=True)
|
||||
log.error('LibrarySync thread crashed. Error message: %s' % e)
|
||||
import traceback
|
||||
|
@ -1443,8 +1440,8 @@ class LibrarySync(Thread):
|
|||
|
||||
def run_internal(self):
|
||||
# Re-assign handles to have faster calls
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
installSyncDone = self.installSyncDone
|
||||
enableBackgroundSync = self.enableBackgroundSync
|
||||
fullSync = self.fullSync
|
||||
|
@ -1470,18 +1467,15 @@ class LibrarySync(Thread):
|
|||
# Ensure that DBs exist if called for very first time
|
||||
self.initializeDBs()
|
||||
|
||||
if self.enableMusic:
|
||||
advancedsettings_tweaks()
|
||||
|
||||
if settings('FanartTV') == 'true':
|
||||
self.fanartthread.start()
|
||||
|
||||
while not threadStopped():
|
||||
while not thread_stopped():
|
||||
|
||||
# In the event the server goes offline
|
||||
while threadSuspended():
|
||||
while thread_suspended():
|
||||
# Set in service.py
|
||||
if threadStopped():
|
||||
if thread_stopped():
|
||||
# Abort was requested while waiting. We should exit
|
||||
log.info("###===--- LibrarySync Stopped ---===###")
|
||||
return
|
||||
|
@ -1513,7 +1507,7 @@ class LibrarySync(Thread):
|
|||
# Also runs when first installed
|
||||
# Verify the video database can be found
|
||||
videoDb = v.DB_VIDEO_PATH
|
||||
if not exists(videoDb):
|
||||
if not exists(tryEncode(videoDb)):
|
||||
# Database does not exists
|
||||
log.error("The current Kodi version is incompatible "
|
||||
"to know which Kodi versions are supported.")
|
||||
|
@ -1523,6 +1517,7 @@ class LibrarySync(Thread):
|
|||
self.dialog.ok(heading=lang(29999), line1=lang(39403))
|
||||
break
|
||||
# Run start up sync
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
log.info("Db version: %s" % settings('dbCreatedWithVersion'))
|
||||
lastTimeSync = getUnixTimestamp()
|
||||
|
@ -1547,6 +1542,7 @@ class LibrarySync(Thread):
|
|||
log.info("Initial start-up full sync starting")
|
||||
librarySync = fullSync()
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
if librarySync:
|
||||
log.info("Initial start-up full sync successful")
|
||||
startupComplete = True
|
||||
|
@ -1565,23 +1561,26 @@ class LibrarySync(Thread):
|
|||
break
|
||||
|
||||
# Currently no db scan, so we can start a new scan
|
||||
elif window('plex_dbScan') != "true":
|
||||
elif state.DB_SCAN is False:
|
||||
# Full scan was requested from somewhere else, e.g. userclient
|
||||
if window('plex_runLibScan') in ("full", "repair"):
|
||||
log.info('Full library scan requested, starting')
|
||||
window('plex_dbScan', value="true")
|
||||
state.DB_SCAN = True
|
||||
if window('plex_runLibScan') == "full":
|
||||
fullSync()
|
||||
elif window('plex_runLibScan') == "repair":
|
||||
fullSync(repair=True)
|
||||
window('plex_runLibScan', clear=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
# Full library sync finished
|
||||
self.showKodiNote(lang(39407), forced=False)
|
||||
# Reset views was requested from somewhere else
|
||||
elif window('plex_runLibScan') == "views":
|
||||
log.info('Refresh playlist and nodes requested, starting')
|
||||
window('plex_dbScan', value="true")
|
||||
state.DB_SCAN = True
|
||||
window('plex_runLibScan', clear=True)
|
||||
|
||||
# First remove playlists
|
||||
|
@ -1602,6 +1601,7 @@ class LibrarySync(Thread):
|
|||
forced=True,
|
||||
icon="error")
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
elif window('plex_runLibScan') == 'fanart':
|
||||
window('plex_runLibScan', clear=True)
|
||||
# Only look for missing fanart (No)
|
||||
|
@ -1613,31 +1613,37 @@ class LibrarySync(Thread):
|
|||
yeslabel=lang(39225)))
|
||||
elif window('plex_runLibScan') == 'del_textures':
|
||||
window('plex_runLibScan', clear=True)
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
import artwork
|
||||
artwork.Artwork().fullTextureCacheSync()
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
else:
|
||||
now = getUnixTimestamp()
|
||||
if (now - lastSync > fullSyncInterval and
|
||||
not xbmcplayer.isPlaying()):
|
||||
lastSync = now
|
||||
log.info('Doing scheduled full library scan')
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
if fullSync() is False and not threadStopped():
|
||||
if fullSync() is False and not thread_stopped():
|
||||
log.error('Could not finish scheduled full sync')
|
||||
self.showKodiNote(lang(39410),
|
||||
forced=True,
|
||||
icon='error')
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
# Full library sync finished
|
||||
self.showKodiNote(lang(39407), forced=False)
|
||||
elif now - lastTimeSync > oneDay:
|
||||
lastTimeSync = now
|
||||
log.info('Starting daily time sync')
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
self.syncPMStime()
|
||||
window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
elif enableBackgroundSync:
|
||||
# Check back whether we should process something
|
||||
# Only do this once every while (otherwise, potentially
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
from threading import Thread
|
||||
from Queue import Queue
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, ThreadMethods
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethods
|
||||
class Monitor_Kodi_Play(Thread):
|
||||
"""
|
||||
Monitors for new plays initiated on the Kodi side with addon paths.
|
||||
Immediately throws them into a queue to be processed by playback_starter
|
||||
"""
|
||||
# Borg - multiple instances, shared state
|
||||
def __init__(self, callback=None):
|
||||
self.mgr = callback
|
||||
self.playback_queue = Queue()
|
||||
Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
threadStopped = self.threadStopped
|
||||
queue = self.playback_queue
|
||||
log.info("----===## Starting Kodi_Play_Client ##===----")
|
||||
while not threadStopped():
|
||||
if window('plex_play_new_item'):
|
||||
queue.put(window('plex_play_new_item'))
|
||||
window('plex_play_new_item', clear=True)
|
||||
else:
|
||||
sleep(20)
|
||||
# Put one last item into the queue to let playback_starter end
|
||||
queue.put(None)
|
||||
log.info("----===## Kodi_Play_Client stopped ##===----")
|
|
@ -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
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
@ -39,7 +40,7 @@ class Playback_Starter(Thread):
|
|||
"""
|
||||
log.info("Process_play called with plex_id %s, kodi_id %s"
|
||||
% (plex_id, kodi_id))
|
||||
if window('plex_authenticated') != "true":
|
||||
if not state.AUTHENTICATED:
|
||||
log.error('Not yet authenticated for PMS, abort starting playback')
|
||||
# Todo: Warn user with dialog
|
||||
return
|
||||
|
@ -152,12 +153,12 @@ class Playback_Starter(Thread):
|
|||
pickle_me(result)
|
||||
|
||||
def run(self):
|
||||
queue = self.mgr.monitor_kodi_play.playback_queue
|
||||
queue = self.mgr.command_pipeline.playback_queue
|
||||
log.info("----===## Starting Playback_Starter ##===----")
|
||||
while True:
|
||||
item = queue.get()
|
||||
if item is None:
|
||||
# Need to shutdown - initiated by monitor_kodi_play
|
||||
# Need to shutdown - initiated by command_pipeline
|
||||
break
|
||||
else:
|
||||
self.triage(item)
|
||||
|
|
|
@ -22,6 +22,7 @@ from playlist_func import add_item_to_kodi_playlist, \
|
|||
from pickler import Playback_Successful
|
||||
from plexdb_functions import Get_Plex_DB
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -187,7 +188,7 @@ class PlaybackUtils():
|
|||
kodi_type)
|
||||
|
||||
elif contextmenu_play:
|
||||
if window('useDirectPaths') == 'true':
|
||||
if state.DIRECT_PATHS:
|
||||
# Cannot add via JSON with full metadata because then we
|
||||
# Would be using the direct path
|
||||
log.debug("Adding contextmenu item for direct paths")
|
||||
|
|
|
@ -13,6 +13,7 @@ import downloadutils
|
|||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -308,6 +309,9 @@ class Player(xbmc.Player):
|
|||
'plex_playbackProps',
|
||||
'plex_forcetranscode'):
|
||||
window(item, clear=True)
|
||||
# We might have saved a transient token from a user flinging media via
|
||||
# Companion
|
||||
state.PLEX_TRANSIENT_TOKEN = None
|
||||
log.debug("Cleared playlist properties.")
|
||||
|
||||
def onPlayBackEnded(self):
|
||||
|
|
|
@ -5,7 +5,7 @@ from threading import RLock, Thread
|
|||
|
||||
from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
|
||||
|
||||
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
|
||||
from utils import window, thread_methods
|
||||
import playlist_func as PL
|
||||
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren
|
||||
from PlexAPI import API
|
||||
|
@ -21,8 +21,7 @@ PLUGIN = 'plugin://%s' % v.ADDON_ID
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_suspends=['PMS_STATUS'])
|
||||
class Playqueue(Thread):
|
||||
"""
|
||||
Monitors Kodi's playqueues for changes on the Kodi side
|
||||
|
@ -147,20 +146,24 @@ class Playqueue(Thread):
|
|||
index = list(range(0, len(old)))
|
||||
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
||||
% (new, old))
|
||||
if self.thread_stopped():
|
||||
# Chances are that we got an empty Kodi playlist due to
|
||||
# Kodi exit
|
||||
return
|
||||
for i, new_item in enumerate(new):
|
||||
if (new_item['file'].startswith('plugin://') and
|
||||
not new_item['file'].startswith(PLUGIN)):
|
||||
# Ignore new media added by other addons
|
||||
continue
|
||||
for j, old_item in enumerate(old):
|
||||
if self.threadStopped():
|
||||
# Chances are that we got an empty Kodi playlist due to
|
||||
# Kodi exit
|
||||
return
|
||||
if (old_item['file'].startswith('plugin://') and
|
||||
not old_item['file'].startswith(PLUGIN)):
|
||||
# Ignore media by other addons
|
||||
continue
|
||||
try:
|
||||
if (old_item.file.startswith('plugin://') and
|
||||
not old_item['file'].startswith(PLUGIN)):
|
||||
# Ignore media by other addons
|
||||
continue
|
||||
except (TypeError, AttributeError):
|
||||
# were not passed a filename; ignore
|
||||
pass
|
||||
if new_item.get('id') is None:
|
||||
identical = old_item.file == new_item['file']
|
||||
else:
|
||||
|
@ -193,8 +196,8 @@ class Playqueue(Thread):
|
|||
log.debug('Done comparing playqueues')
|
||||
|
||||
def run(self):
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
log.info("----===## Starting PlayQueue client ##===----")
|
||||
# Initialize the playqueues, if Kodi already got items in them
|
||||
for playqueue in self.playqueues:
|
||||
|
@ -203,9 +206,9 @@ class Playqueue(Thread):
|
|||
PL.init_Plex_playlist(playqueue, kodi_item=item)
|
||||
else:
|
||||
PL.add_item_to_PMS_playlist(playqueue, i, kodi_item=item)
|
||||
while not threadStopped():
|
||||
while threadSuspended():
|
||||
if threadStopped():
|
||||
while not thread_stopped():
|
||||
while thread_suspended():
|
||||
if thread_stopped():
|
||||
break
|
||||
sleep(1000)
|
||||
with lock:
|
||||
|
|
|
@ -24,8 +24,6 @@ class PlayUtils():
|
|||
self.API = PlexAPI.API(item)
|
||||
self.doUtils = DownloadUtils().downloadUrl
|
||||
|
||||
self.userid = window('currUserId')
|
||||
self.server = window('pms_server')
|
||||
self.machineIdentifier = window('plex_machineIdentifier')
|
||||
|
||||
def getPlayUrl(self, partNumber=None):
|
||||
|
@ -335,7 +333,8 @@ class PlayUtils():
|
|||
# We don't know the language - no need to download
|
||||
else:
|
||||
path = self.API.addPlexCredentialsToUrl(
|
||||
"%s%s" % (self.server, stream.attrib['key']))
|
||||
"%s%s" % (window('pms_server'),
|
||||
stream.attrib['key']))
|
||||
downloadable_streams.append(index)
|
||||
download_subs.append(tryEncode(path))
|
||||
else:
|
||||
|
|
|
@ -163,7 +163,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
else:
|
||||
# Throw it to companion.py
|
||||
process_command(request_path, params, self.server.queue)
|
||||
self.response(getOKMsg(), js.getPlexHeaders())
|
||||
self.response('', js.getPlexHeaders())
|
||||
subMgr.notify()
|
||||
except:
|
||||
log.error('Error encountered. Traceback:')
|
||||
|
|
|
@ -3,8 +3,10 @@ import re
|
|||
import threading
|
||||
|
||||
import downloadutils
|
||||
from clientinfo import getXArgsDeviceInfo
|
||||
from utils import window
|
||||
import PlexFunctions as pf
|
||||
import state
|
||||
from functions import *
|
||||
|
||||
###############################################################################
|
||||
|
@ -68,19 +70,16 @@ class SubscriptionManager:
|
|||
info = self.getPlayerProperties(playerid)
|
||||
# save this info off so the server update can use it too
|
||||
self.playerprops[playerid] = info;
|
||||
state = info['state']
|
||||
status = info['state']
|
||||
time = info['time']
|
||||
else:
|
||||
state = "stopped"
|
||||
status = "stopped"
|
||||
time = 0
|
||||
ret = "\n"+' <Timeline state="%s" time="%s" type="%s"' % (state, time, ptype)
|
||||
ret = "\n"+' <Timeline state="%s" time="%s" type="%s"' % (status, time, ptype)
|
||||
if playerid is None:
|
||||
ret += ' seekRange="0-0"'
|
||||
ret += ' />'
|
||||
return ret
|
||||
|
||||
# pbmc_server = str(WINDOW.getProperty('plexbmc.nowplaying.server'))
|
||||
# userId = str(WINDOW.getProperty('currUserId'))
|
||||
pbmc_server = window('pms_server')
|
||||
if pbmc_server:
|
||||
(self.protocol, self.server, self.port) = \
|
||||
|
@ -112,7 +111,6 @@ class SubscriptionManager:
|
|||
ret += ' containerKey="%s"' % self.containerKey
|
||||
|
||||
ret += ' duration="%s"' % info['duration']
|
||||
ret += ' seekRange="0-%s"' % info['duration']
|
||||
ret += ' controllable="%s"' % self.controllable()
|
||||
ret += ' machineIdentifier="%s"' % serv.get('uuid', "")
|
||||
ret += ' protocol="%s"' % serv.get('protocol', "http")
|
||||
|
@ -123,6 +121,8 @@ class SubscriptionManager:
|
|||
ret += ' mute="%s"' % self.mute
|
||||
ret += ' repeat="%s"' % info['repeat']
|
||||
ret += ' itemType="%s"' % info['itemType']
|
||||
if state.PLEX_TRANSIENT_TOKEN:
|
||||
ret += ' token="%s"' % state.PLEX_TRANSIENT_TOKEN
|
||||
# Might need an update in the future
|
||||
if ptype == 'video':
|
||||
ret += ' subtitleStreamID="-1"'
|
||||
|
@ -167,9 +167,10 @@ class SubscriptionManager:
|
|||
# Process the players we have left (to signal a stop)
|
||||
for typus, p in self.lastplayers.iteritems():
|
||||
self.lastinfo[typus]['state'] = 'stopped'
|
||||
self._sendNotification(self.lastinfo[typus])
|
||||
# self._sendNotification(self.lastinfo[typus])
|
||||
|
||||
def _sendNotification(self, info):
|
||||
xargs = getXArgsDeviceInfo()
|
||||
params = {
|
||||
'containerKey': self.containerKey or "/library/metadata/900000",
|
||||
'key': self.lastkey or "/library/metadata/900000",
|
||||
|
@ -178,6 +179,8 @@ class SubscriptionManager:
|
|||
'time': info['time'],
|
||||
'duration': info['duration']
|
||||
}
|
||||
if state.PLEX_TRANSIENT_TOKEN:
|
||||
xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN
|
||||
if info.get('playQueueID'):
|
||||
params['containerKey'] = '/playQueues/%s' % info['playQueueID']
|
||||
params['playQueueVersion'] = info['playQueueVersion']
|
||||
|
@ -186,7 +189,7 @@ class SubscriptionManager:
|
|||
url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
|
||||
serv.get('server', 'localhost'),
|
||||
serv.get('port', '32400'))
|
||||
self.doUtils(url, parameters=params)
|
||||
self.doUtils(url, parameters=params, headerOptions=xargs)
|
||||
log.debug("Sent server notification with parameters: %s to %s"
|
||||
% (params, url))
|
||||
|
||||
|
|
|
@ -1,574 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import logging
|
||||
|
||||
import xbmc
|
||||
|
||||
import downloadutils
|
||||
from utils import window, settings, kodiSQL
|
||||
|
||||
#################################################################################################
|
||||
|
||||
log = logging.getLogger("EMBY."+__name__)
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class Read_EmbyServer():
|
||||
|
||||
limitIndex = int(settings('limitindex'))
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
|
||||
self.userId = window('emby_currUser')
|
||||
self.server = window('emby_server%s' % self.userId)
|
||||
|
||||
|
||||
def split_list(self, itemlist, size):
|
||||
# Split up list in pieces of size. Will generate a list of lists
|
||||
return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]
|
||||
|
||||
def getItem(self, itemid):
|
||||
# This will return the full item
|
||||
item = {}
|
||||
|
||||
result = self.doUtils("{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid)
|
||||
if result:
|
||||
item = result
|
||||
|
||||
return item
|
||||
|
||||
def getItems(self, itemlist):
|
||||
|
||||
items = []
|
||||
|
||||
itemlists = self.split_list(itemlist, 50)
|
||||
for itemlist in itemlists:
|
||||
# Will return basic information
|
||||
params = {
|
||||
|
||||
'Ids': ",".join(itemlist),
|
||||
'Fields': "Etag"
|
||||
}
|
||||
url = "{server}/emby/Users/{UserId}/Items?&format=json"
|
||||
result = self.doUtils(url, parameters=params)
|
||||
if result:
|
||||
items.extend(result['Items'])
|
||||
|
||||
return items
|
||||
|
||||
def getFullItems(self, itemlist):
|
||||
|
||||
items = []
|
||||
|
||||
itemlists = self.split_list(itemlist, 50)
|
||||
for itemlist in itemlists:
|
||||
|
||||
params = {
|
||||
|
||||
"Ids": ",".join(itemlist),
|
||||
"Fields": (
|
||||
|
||||
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
||||
"MediaSources,VoteCount"
|
||||
)
|
||||
}
|
||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
||||
result = self.doUtils(url, parameters=params)
|
||||
if result:
|
||||
items.extend(result['Items'])
|
||||
|
||||
return items
|
||||
|
||||
def getView_embyId(self, itemid):
|
||||
# Returns ancestors using embyId
|
||||
viewId = None
|
||||
|
||||
url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
|
||||
for view in self.doUtils(url):
|
||||
|
||||
if view['Type'] == "CollectionFolder":
|
||||
# Found view
|
||||
viewId = view['Id']
|
||||
|
||||
# Compare to view table in emby database
|
||||
emby = kodiSQL('plex')
|
||||
cursor_emby = emby.cursor()
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_name, media_type",
|
||||
"FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
cursor_emby.execute(query, (viewId,))
|
||||
result = cursor_emby.fetchone()
|
||||
try:
|
||||
viewName = result[0]
|
||||
mediatype = result[1]
|
||||
except TypeError:
|
||||
viewName = None
|
||||
mediatype = None
|
||||
|
||||
cursor_emby.close()
|
||||
|
||||
return [viewName, viewId, mediatype]
|
||||
|
||||
def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True,
|
||||
limit=None, sortorder="Ascending", filter_type=""):
|
||||
params = {
|
||||
|
||||
'ParentId': parentid,
|
||||
'IncludeItemTypes': itemtype,
|
||||
'CollapseBoxSetItems': False,
|
||||
'IsVirtualUnaired': False,
|
||||
'IsMissing': False,
|
||||
'Recursive': recursive,
|
||||
'Limit': limit,
|
||||
'SortBy': sortby,
|
||||
'SortOrder': sortorder,
|
||||
'Filters': filter,
|
||||
'Fields': (
|
||||
|
||||
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
|
||||
)
|
||||
}
|
||||
return self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
|
||||
|
||||
def getTvChannels(self):
|
||||
|
||||
params = {
|
||||
|
||||
'EnableImages': True,
|
||||
'Fields': (
|
||||
|
||||
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
|
||||
)
|
||||
}
|
||||
url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
|
||||
return self.doUtils(url, parameters=params)
|
||||
|
||||
def getTvRecordings(self, groupid):
|
||||
|
||||
if groupid == "root":
|
||||
groupid = ""
|
||||
|
||||
params = {
|
||||
|
||||
'GroupId': groupid,
|
||||
'EnableImages': True,
|
||||
'Fields': (
|
||||
|
||||
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
|
||||
)
|
||||
}
|
||||
url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
|
||||
return self.doUtils(url, parameters=params)
|
||||
|
||||
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
|
||||
|
||||
items = {
|
||||
|
||||
'Items': [],
|
||||
'TotalRecordCount': 0
|
||||
}
|
||||
|
||||
# Get total number of items
|
||||
url = "{server}/emby/Users/{UserId}/Items?format=json"
|
||||
params = {
|
||||
|
||||
'ParentId': parentid,
|
||||
'IncludeItemTypes': itemtype,
|
||||
'CollapseBoxSetItems': False,
|
||||
'IsVirtualUnaired': False,
|
||||
'IsMissing': False,
|
||||
'Recursive': True,
|
||||
'Limit': 1
|
||||
}
|
||||
result = self.doUtils(url, parameters=params)
|
||||
try:
|
||||
total = result['TotalRecordCount']
|
||||
items['TotalRecordCount'] = total
|
||||
|
||||
except TypeError: # Failed to retrieve
|
||||
log.debug("%s:%s Failed to retrieve the server response." % (url, params))
|
||||
|
||||
else:
|
||||
index = 0
|
||||
jump = self.limitIndex
|
||||
throttled = False
|
||||
highestjump = 0
|
||||
|
||||
while index < total:
|
||||
# Get items by chunk to increase retrieval speed at scale
|
||||
params = {
|
||||
|
||||
'ParentId': parentid,
|
||||
'IncludeItemTypes': itemtype,
|
||||
'CollapseBoxSetItems': False,
|
||||
'IsVirtualUnaired': False,
|
||||
'IsMissing': False,
|
||||
'Recursive': True,
|
||||
'StartIndex': index,
|
||||
'Limit': jump,
|
||||
'SortBy': sortby,
|
||||
'SortOrder': "Ascending",
|
||||
}
|
||||
if basic:
|
||||
params['Fields'] = "Etag"
|
||||
else:
|
||||
params['Fields'] = (
|
||||
|
||||
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
|
||||
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
|
||||
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
|
||||
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
|
||||
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
|
||||
"MediaSources,VoteCount"
|
||||
)
|
||||
result = self.doUtils(url, parameters=params)
|
||||
try:
|
||||
items['Items'].extend(result['Items'])
|
||||
except TypeError:
|
||||
# Something happened to the connection
|
||||
if not throttled:
|
||||
throttled = True
|
||||
log.info("Throttle activated.")
|
||||
|
||||
if jump == highestjump:
|
||||
# We already tried with the highestjump, but it failed. Reset value.
|
||||
log.info("Reset highest value.")
|
||||
highestjump = 0
|
||||
|
||||
# Lower the number by half
|
||||
if highestjump:
|
||||
throttled = False
|
||||
jump = highestjump
|
||||
log.info("Throttle deactivated.")
|
||||
else:
|
||||
jump = int(jump/4)
|
||||
log.debug("Set jump limit to recover: %s" % jump)
|
||||
|
||||
retry = 0
|
||||
while window('emby_online') != "true":
|
||||
# Wait server to come back online
|
||||
if retry == 5:
|
||||
log.info("Unable to reconnect to server. Abort process.")
|
||||
return items
|
||||
|
||||
retry += 1
|
||||
if xbmc.Monitor().waitForAbort(1):
|
||||
# Abort was requested while waiting.
|
||||
return items
|
||||
else:
|
||||
# Request succeeded
|
||||
index += jump
|
||||
|
||||
if dialog:
|
||||
percentage = int((float(index) / float(total))*100)
|
||||
dialog.update(percentage)
|
||||
|
||||
if jump > highestjump:
|
||||
# Adjust with the latest number, if it's greater
|
||||
highestjump = jump
|
||||
|
||||
if throttled:
|
||||
# We needed to adjust the number of item requested.
|
||||
# keep increasing until the connection times out again
|
||||
# to find the highest value
|
||||
increment = int(jump*0.33)
|
||||
if not increment: # Incase the increment is 0
|
||||
increment = 10
|
||||
|
||||
jump += increment
|
||||
log.info("Increase jump limit to: %s" % jump)
|
||||
return items
|
||||
|
||||
def getViews(self, mediatype="", root=False, sortedlist=False):
|
||||
# Build a list of user views
|
||||
views = []
|
||||
mediatype = mediatype.lower()
|
||||
|
||||
if not root:
|
||||
url = "{server}/emby/Users/{UserId}/Views?format=json"
|
||||
else: # Views ungrouped
|
||||
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
|
||||
|
||||
result = self.doUtils(url)
|
||||
try:
|
||||
items = result['Items']
|
||||
except TypeError:
|
||||
log.debug("Error retrieving views for type: %s" % mediatype)
|
||||
else:
|
||||
for item in items:
|
||||
|
||||
item['Name'] = item['Name']
|
||||
if item['Type'] == "Channel":
|
||||
# Filter view types
|
||||
continue
|
||||
|
||||
# 3/4/2016 OriginalCollectionType is added
|
||||
itemtype = item.get('OriginalCollectionType', item.get('CollectionType', "mixed"))
|
||||
|
||||
# 11/29/2015 Remove this once OriginalCollectionType is added to stable server.
|
||||
# Assumed missing is mixed then.
|
||||
'''if itemtype is None:
|
||||
url = "{server}/emby/Library/MediaFolders?format=json"
|
||||
result = self.doUtils(url)
|
||||
|
||||
for folder in result['Items']:
|
||||
if item['Id'] == folder['Id']:
|
||||
itemtype = folder.get('CollectionType', "mixed")'''
|
||||
|
||||
if item['Name'] not in ('Collections', 'Trailers'):
|
||||
|
||||
if sortedlist:
|
||||
views.append({
|
||||
|
||||
'name': item['Name'],
|
||||
'type': itemtype,
|
||||
'id': item['Id']
|
||||
})
|
||||
|
||||
elif (itemtype == mediatype or
|
||||
(itemtype == "mixed" and mediatype in ("movies", "tvshows"))):
|
||||
|
||||
views.append({
|
||||
|
||||
'name': item['Name'],
|
||||
'type': itemtype,
|
||||
'id': item['Id']
|
||||
})
|
||||
|
||||
return views
|
||||
|
||||
def verifyView(self, parentid, itemid):
|
||||
|
||||
belongs = False
|
||||
params = {
|
||||
|
||||
'ParentId': parentid,
|
||||
'CollapseBoxSetItems': False,
|
||||
'IsVirtualUnaired': False,
|
||||
'IsMissing': False,
|
||||
'Recursive': True,
|
||||
'Ids': itemid
|
||||
}
|
||||
result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
|
||||
try:
|
||||
total = result['TotalRecordCount']
|
||||
except TypeError:
|
||||
# Something happened to the connection
|
||||
pass
|
||||
else:
|
||||
if total:
|
||||
belongs = True
|
||||
|
||||
return belongs
|
||||
|
||||
def getMovies(self, parentId, basic=False, dialog=None):
|
||||
|
||||
return self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
|
||||
|
||||
def getBoxset(self, dialog=None):
|
||||
|
||||
return self.getSection(None, "BoxSet", dialog=dialog)
|
||||
|
||||
def getMovies_byBoxset(self, boxsetid):
|
||||
return self.getSection(boxsetid, "Movie")
|
||||
|
||||
def getMusicVideos(self, parentId, basic=False, dialog=None):
|
||||
|
||||
return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
|
||||
|
||||
def getHomeVideos(self, parentId):
|
||||
|
||||
return self.getSection(parentId, "Video")
|
||||
|
||||
def getShows(self, parentId, basic=False, dialog=None):
|
||||
|
||||
return self.getSection(parentId, "Series", basic=basic, dialog=dialog)
|
||||
|
||||
def getSeasons(self, showId):
|
||||
|
||||
items = {
|
||||
|
||||
'Items': [],
|
||||
'TotalRecordCount': 0
|
||||
}
|
||||
|
||||
params = {
|
||||
|
||||
'IsVirtualUnaired': False,
|
||||
'Fields': "Etag"
|
||||
}
|
||||
url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
|
||||
result = self.doUtils(url, parameters=params)
|
||||
if result:
|
||||
items = result
|
||||
|
||||
return items
|
||||
|
||||
def getEpisodes(self, parentId, basic=False, dialog=None):
|
||||
|
||||
return self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
|
||||
|
||||
def getEpisodesbyShow(self, showId):
|
||||
|
||||
return self.getSection(showId, "Episode")
|
||||
|
||||
def getEpisodesbySeason(self, seasonId):
|
||||
|
||||
return self.getSection(seasonId, "Episode")
|
||||
|
||||
def getArtists(self, dialog=None):
|
||||
|
||||
items = {
|
||||
|
||||
'Items': [],
|
||||
'TotalRecordCount': 0
|
||||
}
|
||||
|
||||
# Get total number of items
|
||||
url = "{server}/emby/Artists?UserId={UserId}&format=json"
|
||||
params = {
|
||||
|
||||
'Recursive': True,
|
||||
'Limit': 1
|
||||
}
|
||||
result = self.doUtils(url, parameters=params)
|
||||
try:
|
||||
total = result['TotalRecordCount']
|
||||
items['TotalRecordCount'] = total
|
||||
|
||||
except TypeError: # Failed to retrieve
|
||||
log.debug("%s:%s Failed to retrieve the server response." % (url, params))
|
||||
|
||||
else:
|
||||
index = 1
|
||||
jump = self.limitIndex
|
||||
|
||||
while index < total:
|
||||
# Get items by chunk to increase retrieval speed at scale
|
||||
params = {
|
||||
|
||||
'Recursive': True,
|
||||
'IsVirtualUnaired': False,
|
||||
'IsMissing': False,
|
||||
'StartIndex': index,
|
||||
'Limit': jump,
|
||||
'SortBy': "SortName",
|
||||
'SortOrder': "Ascending",
|
||||
'Fields': (
|
||||
|
||||
"Etag,Genres,SortName,Studios,Writer,ProductionYear,"
|
||||
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,"
|
||||
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
|
||||
)
|
||||
}
|
||||
result = self.doUtils(url, parameters=params)
|
||||
items['Items'].extend(result['Items'])
|
||||
|
||||
index += jump
|
||||
if dialog:
|
||||
percentage = int((float(index) / float(total))*100)
|
||||
dialog.update(percentage)
|
||||
return items
|
||||
|
||||
def getAlbums(self, basic=False, dialog=None):
|
||||
|
||||
return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
|
||||
|
||||
def getAlbumsbyArtist(self, artistId):
|
||||
|
||||
return self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
|
||||
|
||||
def getSongs(self, basic=False, dialog=None):
|
||||
|
||||
return self.getSection(None, "Audio", basic=basic, dialog=dialog)
|
||||
|
||||
def getSongsbyAlbum(self, albumId):
|
||||
|
||||
return self.getSection(albumId, "Audio")
|
||||
|
||||
def getAdditionalParts(self, itemId):
|
||||
|
||||
items = {
|
||||
|
||||
'Items': [],
|
||||
'TotalRecordCount': 0
|
||||
}
|
||||
url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
|
||||
result = self.doUtils(url)
|
||||
if result:
|
||||
items = result
|
||||
|
||||
return items
|
||||
|
||||
def sortby_mediatype(self, itemids):
|
||||
|
||||
sorted_items = {}
|
||||
|
||||
# Sort items
|
||||
items = self.getFullItems(itemids)
|
||||
for item in items:
|
||||
|
||||
mediatype = item.get('Type')
|
||||
if mediatype:
|
||||
sorted_items.setdefault(mediatype, []).append(item)
|
||||
|
||||
return sorted_items
|
||||
|
||||
def updateUserRating(self, itemid, favourite=None):
|
||||
# Updates the user rating to Emby
|
||||
doUtils = self.doUtils
|
||||
|
||||
if favourite:
|
||||
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
|
||||
doUtils(url, action_type="POST")
|
||||
elif not favourite:
|
||||
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
|
||||
doUtils(url, action_type="DELETE")
|
||||
else:
|
||||
log.info("Error processing user rating.")
|
||||
|
||||
log.info("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite))
|
||||
|
||||
def refreshItem(self, itemid):
|
||||
|
||||
url = "{server}/emby/Items/%s/Refresh?format=json" % itemid
|
||||
params = {
|
||||
|
||||
'Recursive': True,
|
||||
'ImageRefreshMode': "FullRefresh",
|
||||
'MetadataRefreshMode': "FullRefresh",
|
||||
'ReplaceAllImages': False,
|
||||
'ReplaceAllMetadata': True
|
||||
|
||||
}
|
||||
self.doUtils(url, postBody=params, action_type="POST")
|
||||
|
||||
def deleteItem(self, itemid):
|
||||
|
||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||
self.doUtils(url, action_type="DELETE")
|
36
resources/lib/state.py
Normal file
36
resources/lib/state.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# THREAD SAFE
|
||||
|
||||
# Quit PKC
|
||||
STOP_PKC = False
|
||||
|
||||
|
||||
# Usually triggered by another Python instance - will have to be set (by
|
||||
# polling window) through e.g. librarysync thread
|
||||
SUSPEND_LIBRARY_THREAD = False
|
||||
# Set if user decided to cancel sync
|
||||
STOP_SYNC = False
|
||||
# Set if a Plex-Kodi DB sync is being done - along with
|
||||
# window('plex_dbScan') set to 'true'
|
||||
DB_SCAN = False
|
||||
# Plex Media Server Status - along with window('plex_serverStatus')
|
||||
PMS_STATUS = False
|
||||
# When the userclient needs to wait
|
||||
SUSPEND_USER_CLIENT = False
|
||||
# Plex home user? Then "False". Along with window('plex_restricteduser')
|
||||
RESTRICTED_USER = False
|
||||
# Direct Paths (True) or Addon Paths (False)? Along with
|
||||
# window('useDirectPaths')
|
||||
DIRECT_PATHS = False
|
||||
|
||||
# Along with window('plex_authenticated')
|
||||
AUTHENTICATED = False
|
||||
# plex.tv username
|
||||
PLEX_USERNAME = None
|
||||
# Token for that user for plex.tv
|
||||
PLEX_TOKEN = None
|
||||
# Plex ID of that user (e.g. for plex.tv) as a STRING
|
||||
PLEX_USER_ID = None
|
||||
# Token passed along, e.g. if playback initiated by Plex Companion. Might be
|
||||
# another user playing something! Token identifies user
|
||||
PLEX_TRANSIENT_TOKEN = None
|
|
@ -10,12 +10,12 @@ import xbmcaddon
|
|||
from xbmcvfs import exists
|
||||
|
||||
|
||||
from utils import window, settings, language as lang, ThreadMethods, \
|
||||
ThreadMethodsAdditionalSuspend
|
||||
from utils import window, settings, language as lang, thread_methods
|
||||
import downloadutils
|
||||
|
||||
import PlexAPI
|
||||
from PlexFunctions import GetMachineIdentifier
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -24,8 +24,7 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('suspend_Userclient')
|
||||
@ThreadMethods
|
||||
@thread_methods(add_suspends=['SUSPEND_USER_CLIENT'])
|
||||
class UserClient(threading.Thread):
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
|
@ -40,7 +39,6 @@ class UserClient(threading.Thread):
|
|||
self.retry = 0
|
||||
|
||||
self.currUser = None
|
||||
self.currUserId = None
|
||||
self.currServer = None
|
||||
self.currToken = None
|
||||
self.HasAccess = True
|
||||
|
@ -118,37 +116,19 @@ class UserClient(threading.Thread):
|
|||
def hasAccess(self):
|
||||
# Plex: always return True for now
|
||||
return True
|
||||
# hasAccess is verified in service.py
|
||||
url = "{server}/emby/Users?format=json"
|
||||
result = self.doUtils.downloadUrl(url)
|
||||
|
||||
if result is False:
|
||||
# Access is restricted, set in downloadutils.py via exception
|
||||
log.info("Access is restricted.")
|
||||
self.HasAccess = False
|
||||
|
||||
elif window('plex_online') != "true":
|
||||
# Server connection failed
|
||||
pass
|
||||
|
||||
elif window('plex_serverStatus') == "restricted":
|
||||
log.info("Access is granted.")
|
||||
self.HasAccess = True
|
||||
window('plex_serverStatus', clear=True)
|
||||
xbmcgui.Dialog().notification(lang(29999),
|
||||
lang(33007))
|
||||
|
||||
def loadCurrUser(self, username, userId, usertoken, authenticated=False):
|
||||
log.debug('Loading current user')
|
||||
doUtils = self.doUtils
|
||||
|
||||
self.currUserId = userId
|
||||
self.currToken = usertoken
|
||||
self.currServer = self.getServer()
|
||||
self.ssl = self.getSSLverify()
|
||||
self.sslcert = self.getSSL()
|
||||
|
||||
if authenticated is False:
|
||||
if self.currServer is None:
|
||||
return False
|
||||
log.debug('Testing validity of current token')
|
||||
res = PlexAPI.PlexAPI().CheckConnection(self.currServer,
|
||||
token=self.currToken,
|
||||
|
@ -164,21 +144,27 @@ class UserClient(threading.Thread):
|
|||
return False
|
||||
|
||||
# Set to windows property
|
||||
window('currUserId', value=userId)
|
||||
window('plex_username', value=username)
|
||||
state.PLEX_USER_ID = userId or None
|
||||
state.PLEX_USERNAME = username
|
||||
# This is the token for the current PMS (might also be '')
|
||||
window('pms_token', value=self.currToken)
|
||||
# This is the token for plex.tv for the current user
|
||||
# Is only '' if user is not signed in to plex.tv
|
||||
window('plex_token', value=settings('plexToken'))
|
||||
state.PLEX_TOKEN = settings('plexToken') or None
|
||||
window('plex_restricteduser', value=settings('plex_restricteduser'))
|
||||
state.RESTRICTED_USER = True \
|
||||
if settings('plex_restricteduser') == 'true' else False
|
||||
window('pms_server', value=self.currServer)
|
||||
window('plex_machineIdentifier', value=self.machineIdentifier)
|
||||
window('plex_servername', value=self.servername)
|
||||
window('plex_authenticated', value='true')
|
||||
state.AUTHENTICATED = True
|
||||
|
||||
window('useDirectPaths', value='true'
|
||||
if settings('useDirectPaths') == "1" else 'false')
|
||||
state.DIRECT_PATHS = True if settings('useDirectPaths') == "1" \
|
||||
else False
|
||||
window('plex_force_transcode_pix', value='true'
|
||||
if settings('force_transcode_pix') == "1" else 'false')
|
||||
|
||||
|
@ -202,7 +188,7 @@ class UserClient(threading.Thread):
|
|||
# Give attempts at entering password / selecting user
|
||||
if self.retry >= 2:
|
||||
log.error("Too many retries to login.")
|
||||
window('plex_serverStatus', value="Stop")
|
||||
state.PMS_STATUS = 'Stop'
|
||||
dialog.ok(lang(33001),
|
||||
lang(39023))
|
||||
xbmc.executebuiltin(
|
||||
|
@ -283,14 +269,17 @@ class UserClient(threading.Thread):
|
|||
self.doUtils.stopSession()
|
||||
|
||||
window('plex_authenticated', clear=True)
|
||||
state.AUTHENTICATED = False
|
||||
window('pms_token', clear=True)
|
||||
state.PLEX_TOKEN = None
|
||||
window('plex_token', clear=True)
|
||||
window('pms_server', clear=True)
|
||||
window('plex_machineIdentifier', clear=True)
|
||||
window('plex_servername', clear=True)
|
||||
window('currUserId', clear=True)
|
||||
window('plex_username', clear=True)
|
||||
state.PLEX_USER_ID = None
|
||||
state.PLEX_USERNAME = None
|
||||
window('plex_restricteduser', clear=True)
|
||||
state.RESTRICTED_USER = False
|
||||
|
||||
settings('username', value='')
|
||||
settings('userid', value='')
|
||||
|
@ -298,44 +287,42 @@ class UserClient(threading.Thread):
|
|||
|
||||
# Reset token in downloads
|
||||
self.doUtils.setToken('')
|
||||
self.doUtils.setUserId('')
|
||||
self.doUtils.setUsername('')
|
||||
|
||||
self.currToken = None
|
||||
self.auth = True
|
||||
self.currUser = None
|
||||
self.currUserId = None
|
||||
|
||||
self.retry = 0
|
||||
|
||||
def run(self):
|
||||
log.info("----===## Starting UserClient ##===----")
|
||||
while not self.threadStopped():
|
||||
while self.threadSuspended():
|
||||
if self.threadStopped():
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
while not thread_stopped():
|
||||
while thread_suspended():
|
||||
if thread_stopped():
|
||||
break
|
||||
xbmc.sleep(1000)
|
||||
|
||||
status = window('plex_serverStatus')
|
||||
|
||||
if status == "Stop":
|
||||
if state.PMS_STATUS == "Stop":
|
||||
xbmc.sleep(500)
|
||||
continue
|
||||
|
||||
# Verify the connection status to server
|
||||
elif status == "restricted":
|
||||
elif state.PMS_STATUS == "restricted":
|
||||
# Parental control is restricting access
|
||||
self.HasAccess = False
|
||||
|
||||
elif status == "401":
|
||||
elif state.PMS_STATUS == "401":
|
||||
# Unauthorized access, revoke token
|
||||
window('plex_serverStatus', value="Auth")
|
||||
state.PMS_STATUS = 'Auth'
|
||||
window('plex_serverStatus', value='Auth')
|
||||
self.resetClient()
|
||||
xbmc.sleep(2000)
|
||||
xbmc.sleep(3000)
|
||||
|
||||
if self.auth and (self.currUser is None):
|
||||
# Try to authenticate user
|
||||
if not status or status == "Auth":
|
||||
if not state.PMS_STATUS or state.PMS_STATUS == "Auth":
|
||||
# Set auth flag because we no longer need
|
||||
# to authenticate the user
|
||||
self.auth = False
|
||||
|
@ -343,10 +330,11 @@ class UserClient(threading.Thread):
|
|||
# Successfully authenticated and loaded a user
|
||||
log.info("Successfully authenticated!")
|
||||
log.info("Current user: %s" % self.currUser)
|
||||
log.info("Current userId: %s" % self.currUserId)
|
||||
log.info("Current userId: %s" % state.PLEX_USER_ID)
|
||||
self.retry = 0
|
||||
window('suspend_LibraryThread', clear=True)
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
window('plex_serverStatus', clear=True)
|
||||
state.PMS_STATUS = False
|
||||
|
||||
if not self.auth and (self.currUser is None):
|
||||
# Loop if no server found
|
||||
|
@ -354,7 +342,7 @@ class UserClient(threading.Thread):
|
|||
|
||||
# The status Stop is for when user cancelled password dialog.
|
||||
# Or retried too many times
|
||||
if server and status != "Stop":
|
||||
if server and state.PMS_STATUS != "Stop":
|
||||
# Only if there's information found to login
|
||||
log.debug("Server found: %s" % server)
|
||||
self.auth = True
|
||||
|
@ -362,5 +350,4 @@ class UserClient(threading.Thread):
|
|||
# Minimize CPU load
|
||||
xbmc.sleep(100)
|
||||
|
||||
self.doUtils.stopSession()
|
||||
log.info("##===---- UserClient Stopped ----===##")
|
||||
|
|
|
@ -11,7 +11,7 @@ from StringIO import StringIO
|
|||
from time import localtime, strftime, strptime
|
||||
from unicodedata import normalize
|
||||
import xml.etree.ElementTree as etree
|
||||
from functools import wraps
|
||||
from functools import wraps, partial
|
||||
from calendar import timegm
|
||||
from os.path import join
|
||||
from os import remove, walk, makedirs
|
||||
|
@ -25,6 +25,7 @@ from xbmcvfs import exists, delete
|
|||
|
||||
from variables import DB_VIDEO_PATH, DB_MUSIC_PATH, DB_TEXTURE_PATH, \
|
||||
DB_PLEX_PATH, KODI_PROFILE, KODIVERSION
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -76,6 +77,19 @@ def pickl_window(property, value=None, clear=False, windowid=10000):
|
|||
return win.getProperty(property)
|
||||
|
||||
|
||||
def plex_command(key, value):
|
||||
"""
|
||||
Used to funnel states between different Python instances. NOT really thread
|
||||
safe - let's hope the Kodi user can't click fast enough
|
||||
|
||||
key: state.py variable
|
||||
value: either 'True' or 'False'
|
||||
"""
|
||||
while window('plex_command'):
|
||||
xbmc.sleep(5)
|
||||
window('plex_command', value='%s-%s' % (key, value))
|
||||
|
||||
|
||||
def settings(setting, value=None):
|
||||
"""
|
||||
Get or add addon setting. Returns unicode
|
||||
|
@ -97,12 +111,12 @@ def exists_dir(path):
|
|||
Safe way to check whether the directory path exists already (broken in Kodi
|
||||
<17)
|
||||
|
||||
Feed with encoded string
|
||||
Feed with encoded string or unicode
|
||||
"""
|
||||
if KODIVERSION >= 17:
|
||||
answ = exists(path)
|
||||
answ = exists(tryEncode(path))
|
||||
else:
|
||||
dummyfile = join(path, 'dummyfile.txt')
|
||||
dummyfile = join(tryDecode(path), 'dummyfile.txt')
|
||||
try:
|
||||
with open(dummyfile, 'w') as f:
|
||||
f.write('text')
|
||||
|
@ -111,7 +125,7 @@ def exists_dir(path):
|
|||
answ = 0
|
||||
else:
|
||||
# Folder exists. Delete file again.
|
||||
delete(dummyfile)
|
||||
delete(tryEncode(dummyfile))
|
||||
answ = 1
|
||||
return answ
|
||||
|
||||
|
@ -319,7 +333,7 @@ def reset():
|
|||
return
|
||||
|
||||
# first stop any db sync
|
||||
window('plex_shouldStop', value="true")
|
||||
plex_command('STOP_SYNC', 'True')
|
||||
count = 10
|
||||
while window('plex_dbScan') == "true":
|
||||
log.debug("Sync is running, will retry: %s..." % count)
|
||||
|
@ -347,7 +361,7 @@ def reset():
|
|||
for row in rows:
|
||||
tablename = row[0]
|
||||
if tablename != "version":
|
||||
cursor.execute("DELETE FROM ?", (tablename,))
|
||||
cursor.execute("DELETE FROM %s" % tablename)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
|
||||
|
@ -360,7 +374,7 @@ def reset():
|
|||
for row in rows:
|
||||
tablename = row[0]
|
||||
if tablename != "version":
|
||||
cursor.execute("DELETE FROM ?", (tablename, ))
|
||||
cursor.execute("DELETE FROM %s" % tablename)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
|
||||
|
@ -373,7 +387,7 @@ def reset():
|
|||
for row in rows:
|
||||
tablename = row[0]
|
||||
if tablename != "version":
|
||||
cursor.execute("DELETE FROM ?", (tablename, ))
|
||||
cursor.execute("DELETE FROM %s" % tablename)
|
||||
cursor.execute('DROP table IF EXISTS plex')
|
||||
cursor.execute('DROP table IF EXISTS view')
|
||||
connection.commit()
|
||||
|
@ -387,7 +401,7 @@ def reset():
|
|||
# Remove all existing textures first
|
||||
path = xbmc.translatePath("special://thumbnails/")
|
||||
if exists(path):
|
||||
rmtree(path, ignore_errors=True)
|
||||
rmtree(tryDecode(path), ignore_errors=True)
|
||||
# remove all existing data from texture DB
|
||||
connection = kodiSQL('texture')
|
||||
cursor = connection.cursor()
|
||||
|
@ -397,7 +411,7 @@ def reset():
|
|||
for row in rows:
|
||||
tableName = row[0]
|
||||
if(tableName != "version"):
|
||||
cursor.execute("DELETE FROM ?", (tableName, ))
|
||||
cursor.execute("DELETE FROM %s" % tableName)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
|
||||
|
@ -411,7 +425,7 @@ def reset():
|
|||
line1=language(39603)):
|
||||
# Delete the settings
|
||||
addon = xbmcaddon.Addon()
|
||||
addondir = xbmc.translatePath(addon.getAddonInfo('profile'))
|
||||
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
||||
dataPath = "%ssettings.xml" % addondir
|
||||
log.info("Deleting: settings.xml")
|
||||
remove(dataPath)
|
||||
|
@ -522,9 +536,16 @@ def guisettingsXML():
|
|||
|
||||
try:
|
||||
xmlparse = etree.parse(xmlpath)
|
||||
except:
|
||||
except IOError:
|
||||
# Document is blank or missing
|
||||
root = etree.Element('settings')
|
||||
except etree.ParseError:
|
||||
log.error('Error parsing %s' % xmlpath)
|
||||
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit
|
||||
# {1} and correct your file!"
|
||||
dialog('ok', language(29999), language(39716).format(
|
||||
'guisettings.xml', 'http://kodi.wiki/view/userdata'))
|
||||
return
|
||||
else:
|
||||
root = xmlparse.getroot()
|
||||
return root
|
||||
|
@ -606,6 +627,14 @@ def advancedsettings_xml(node_list, new_value=None, attrib=None,
|
|||
return None, None
|
||||
# Create topmost xml entry
|
||||
tree = etree.ElementTree(element=etree.Element('advancedsettings'))
|
||||
except etree.ParseError:
|
||||
log.error('Error parsing %s' % path)
|
||||
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit
|
||||
# {1} and correct your file!"
|
||||
dialog('ok', language(29999), language(39716).format(
|
||||
'advancedsettings.xml',
|
||||
'http://kodi.wiki/view/Advancedsettings.xml'))
|
||||
return None, None
|
||||
root = tree.getroot()
|
||||
element = root
|
||||
|
||||
|
@ -632,17 +661,6 @@ def advancedsettings_xml(node_list, new_value=None, attrib=None,
|
|||
return element, tree
|
||||
|
||||
|
||||
def advancedsettings_tweaks():
|
||||
"""
|
||||
Kodi tweaks
|
||||
|
||||
Changes advancedsettings.xml, musiclibrary:
|
||||
backgroundupdate set to "true"
|
||||
"""
|
||||
advancedsettings_xml(['musiclibrary', 'backgroundupdate'],
|
||||
new_value='true')
|
||||
|
||||
|
||||
def sourcesXML():
|
||||
# To make Master lock compatible
|
||||
path = tryDecode(xbmc.translatePath("special://profile/"))
|
||||
|
@ -650,8 +668,15 @@ def sourcesXML():
|
|||
|
||||
try:
|
||||
xmlparse = etree.parse(xmlpath)
|
||||
except: # Document is blank or missing
|
||||
except IOError: # Document is blank or missing
|
||||
root = etree.Element('sources')
|
||||
except etree.ParseError:
|
||||
log.error('Error parsing %s' % xmlpath)
|
||||
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit
|
||||
# {1} and correct your file!"
|
||||
dialog('ok', language(29999), language(39716).format(
|
||||
'sources.xml', 'http://kodi.wiki/view/sources.xml'))
|
||||
return
|
||||
else:
|
||||
root = xmlparse.getroot()
|
||||
|
||||
|
@ -685,20 +710,27 @@ def sourcesXML():
|
|||
|
||||
def passwordsXML():
|
||||
# To add network credentials
|
||||
path = xbmc.translatePath("special://userdata/")
|
||||
path = tryDecode(xbmc.translatePath("special://userdata/"))
|
||||
xmlpath = "%spasswords.xml" % path
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
try:
|
||||
xmlparse = etree.parse(xmlpath)
|
||||
except:
|
||||
except IOError:
|
||||
# Document is blank or missing
|
||||
root = etree.Element('passwords')
|
||||
skipFind = True
|
||||
except etree.ParseError:
|
||||
log.error('Error parsing %s' % xmlpath)
|
||||
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit
|
||||
# {1} and correct your file!"
|
||||
dialog.ok(language(29999), language(39716).format(
|
||||
'passwords.xml', 'http://forum.kodi.tv/'))
|
||||
return
|
||||
else:
|
||||
root = xmlparse.getroot()
|
||||
skipFind = False
|
||||
|
||||
dialog = xbmcgui.Dialog()
|
||||
credentials = settings('networkCreds')
|
||||
if credentials:
|
||||
# Present user with options
|
||||
|
@ -798,7 +830,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
"""
|
||||
Feed with tagname as unicode
|
||||
"""
|
||||
path = xbmc.translatePath("special://profile/playlists/video/")
|
||||
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||
if viewtype == "mixed":
|
||||
plname = "%s - %s" % (tagname, mediatype)
|
||||
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
|
||||
|
@ -807,12 +839,12 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
xsppath = "%sPlex %s.xsp" % (path, viewid)
|
||||
|
||||
# Create the playlist directory
|
||||
if not exists(path):
|
||||
if not exists(tryEncode(path)):
|
||||
log.info("Creating directory: %s" % path)
|
||||
makedirs(path)
|
||||
|
||||
# Only add the playlist if it doesn't already exists
|
||||
if exists(xsppath):
|
||||
if exists(tryEncode(xsppath)):
|
||||
log.info('Path %s does exist' % xsppath)
|
||||
if delete:
|
||||
remove(xsppath)
|
||||
|
@ -827,27 +859,22 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
'show': 'tvshows'
|
||||
}
|
||||
log.info("Writing playlist file to: %s" % xsppath)
|
||||
try:
|
||||
with open(xsppath, 'wb'):
|
||||
tryEncode(
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
||||
'<smartplaylist type="%s">\n\t'
|
||||
'<name>Plex %s</name>\n\t'
|
||||
'<match>all</match>\n\t'
|
||||
'<rule field="tag" operator="is">\n\t\t'
|
||||
'<value>%s</value>\n\t'
|
||||
'</rule>\n'
|
||||
'</smartplaylist>\n'
|
||||
% (itemtypes.get(mediatype, mediatype), plname, tagname))
|
||||
except Exception as e:
|
||||
log.error("Failed to create playlist: %s" % xsppath)
|
||||
log.error(e)
|
||||
return
|
||||
with open(xsppath, 'wb'):
|
||||
tryEncode(
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
||||
'<smartplaylist type="%s">\n\t'
|
||||
'<name>Plex %s</name>\n\t'
|
||||
'<match>all</match>\n\t'
|
||||
'<rule field="tag" operator="is">\n\t\t'
|
||||
'<value>%s</value>\n\t'
|
||||
'</rule>\n'
|
||||
'</smartplaylist>\n'
|
||||
% (itemtypes.get(mediatype, mediatype), plname, tagname))
|
||||
log.info("Successfully added playlist: %s" % tagname)
|
||||
|
||||
def deletePlaylists():
|
||||
# Clean up the playlists
|
||||
path = xbmc.translatePath("special://profile/playlists/video/")
|
||||
path = tryDecode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||
for root, _, files in walk(path):
|
||||
for file in files:
|
||||
if file.startswith('Plex'):
|
||||
|
@ -855,7 +882,7 @@ def deletePlaylists():
|
|||
|
||||
def deleteNodes():
|
||||
# Clean up video nodes
|
||||
path = xbmc.translatePath("special://profile/library/video/")
|
||||
path = tryDecode(xbmc.translatePath("special://profile/library/video/"))
|
||||
for root, dirs, _ in walk(path):
|
||||
for directory in dirs:
|
||||
if directory.startswith('Plex-'):
|
||||
|
@ -906,78 +933,78 @@ def LogTime(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
def ThreadMethodsAdditionalStop(windowAttribute):
|
||||
"""
|
||||
Decorator to replace stopThread method to include the Kodi windowAttribute
|
||||
|
||||
Use with any sync threads. @ThreadMethods still required FIRST
|
||||
"""
|
||||
def wrapper(cls):
|
||||
def threadStopped(self):
|
||||
return (self._threadStopped or
|
||||
(window('plex_terminateNow') == "true") or
|
||||
window(windowAttribute) == "true")
|
||||
cls.threadStopped = threadStopped
|
||||
return cls
|
||||
return wrapper
|
||||
|
||||
|
||||
def ThreadMethodsAdditionalSuspend(windowAttribute):
|
||||
"""
|
||||
Decorator to replace threadSuspended(): thread now also suspends if a
|
||||
Kodi windowAttribute is set to 'true', e.g. 'suspend_LibraryThread'
|
||||
|
||||
Use with any library sync threads. @ThreadMethods still required FIRST
|
||||
"""
|
||||
def wrapper(cls):
|
||||
def threadSuspended(self):
|
||||
return (self._threadSuspended or
|
||||
window(windowAttribute) == 'true')
|
||||
cls.threadSuspended = threadSuspended
|
||||
return cls
|
||||
return wrapper
|
||||
|
||||
|
||||
def ThreadMethods(cls):
|
||||
def thread_methods(cls=None, add_stops=None, add_suspends=None):
|
||||
"""
|
||||
Decorator to add the following methods to a threading class:
|
||||
|
||||
suspendThread(): pauses the thread
|
||||
resumeThread(): resumes the thread
|
||||
stopThread(): stopps/kills the thread
|
||||
suspend_thread(): pauses the thread
|
||||
resume_thread(): resumes the thread
|
||||
stop_thread(): stopps/kills the thread
|
||||
|
||||
threadSuspended(): returns True if thread is suspend_thread
|
||||
threadStopped(): returns True if thread is stopped (or should stop ;-))
|
||||
ALSO stops if Kodi is exited
|
||||
thread_suspended(): returns True if thread is suspended
|
||||
thread_stopped(): returns True if thread is stopped (or should stop ;-))
|
||||
ALSO returns True if PKC should exit
|
||||
|
||||
Also adds the following class attributes:
|
||||
_threadStopped
|
||||
_threadSuspended
|
||||
__thread_stopped
|
||||
__thread_suspended
|
||||
__stops
|
||||
__suspends
|
||||
|
||||
invoke with either
|
||||
@Newthread_methods
|
||||
class MyClass():
|
||||
or
|
||||
@Newthread_methods(add_stops=['SUSPEND_LIBRARY_TRHEAD'],
|
||||
add_suspends=['DB_SCAN', 'WHATEVER'])
|
||||
class MyClass():
|
||||
"""
|
||||
# So we don't need to invoke with ()
|
||||
if cls is None:
|
||||
return partial(thread_methods,
|
||||
add_stops=add_stops,
|
||||
add_suspends=add_suspends)
|
||||
# Because we need a reference, not a copy of the immutable objects in
|
||||
# state, we need to look up state every time explicitly
|
||||
cls.__stops = ['STOP_PKC']
|
||||
if add_stops is not None:
|
||||
cls.__stops.extend(add_stops)
|
||||
cls.__suspends = add_suspends or []
|
||||
|
||||
# Attach new attributes to class
|
||||
cls._threadStopped = False
|
||||
cls._threadSuspended = False
|
||||
cls.__thread_stopped = False
|
||||
cls.__thread_suspended = False
|
||||
|
||||
# Define new class methods and attach them to class
|
||||
def stopThread(self):
|
||||
self._threadStopped = True
|
||||
cls.stopThread = stopThread
|
||||
def stop_thread(self):
|
||||
self.__thread_stopped = True
|
||||
cls.stop_thread = stop_thread
|
||||
|
||||
def suspendThread(self):
|
||||
self._threadSuspended = True
|
||||
cls.suspendThread = suspendThread
|
||||
def suspend_thread(self):
|
||||
self.__thread_suspended = True
|
||||
cls.suspend_thread = suspend_thread
|
||||
|
||||
def resumeThread(self):
|
||||
self._threadSuspended = False
|
||||
cls.resumeThread = resumeThread
|
||||
def resume_thread(self):
|
||||
self.__thread_suspended = False
|
||||
cls.resume_thread = resume_thread
|
||||
|
||||
def threadSuspended(self):
|
||||
return self._threadSuspended
|
||||
cls.threadSuspended = threadSuspended
|
||||
def thread_suspended(self):
|
||||
if self.__thread_suspended is True:
|
||||
return True
|
||||
for suspend in self.__suspends:
|
||||
if getattr(state, suspend):
|
||||
return True
|
||||
return False
|
||||
cls.thread_suspended = thread_suspended
|
||||
|
||||
def threadStopped(self):
|
||||
return self._threadStopped or (window('plex_terminateNow') == 'true')
|
||||
cls.threadStopped = threadStopped
|
||||
def thread_stopped(self):
|
||||
if self.__thread_stopped is True:
|
||||
return True
|
||||
for stop in self.__stops:
|
||||
if getattr(state, stop):
|
||||
return True
|
||||
return False
|
||||
cls.thread_stopped = thread_stopped
|
||||
|
||||
# Return class to render this a decorator
|
||||
return cls
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
import xbmc
|
||||
from xbmcaddon import Addon
|
||||
|
||||
# Paths are in string, not unicode!
|
||||
# Paths are in unicode, otherwise Windows will throw fits
|
||||
# For any file operations with KODI function, use encoded strings!
|
||||
|
||||
|
||||
def tryDecode(string, encoding='utf-8'):
|
||||
|
@ -29,7 +30,7 @@ ADDON_VERSION = _ADDON.getAddonInfo('version')
|
|||
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
||||
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
||||
KODI_PROFILE = xbmc.translatePath("special://profile")
|
||||
KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile"))
|
||||
|
||||
if xbmc.getCondVisibility('system.platform.osx'):
|
||||
PLATFORM = "MacOSX"
|
||||
|
@ -70,8 +71,8 @@ _DB_VIDEO_VERSION = {
|
|||
17: 107, # Krypton
|
||||
18: 108 # Leia
|
||||
}
|
||||
DB_VIDEO_PATH = xbmc.translatePath(
|
||||
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION])
|
||||
DB_VIDEO_PATH = tryDecode(xbmc.translatePath(
|
||||
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]))
|
||||
|
||||
_DB_MUSIC_VERSION = {
|
||||
13: 46, # Gotham
|
||||
|
@ -81,8 +82,8 @@ _DB_MUSIC_VERSION = {
|
|||
17: 60, # Krypton
|
||||
18: 62 # Leia
|
||||
}
|
||||
DB_MUSIC_PATH = xbmc.translatePath(
|
||||
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION])
|
||||
DB_MUSIC_PATH = tryDecode(xbmc.translatePath(
|
||||
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]))
|
||||
|
||||
_DB_TEXTURE_VERSION = {
|
||||
13: 13, # Gotham
|
||||
|
@ -92,13 +93,13 @@ _DB_TEXTURE_VERSION = {
|
|||
17: 13, # Krypton
|
||||
18: 13 # Leia
|
||||
}
|
||||
DB_TEXTURE_PATH = xbmc.translatePath(
|
||||
"special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION])
|
||||
DB_TEXTURE_PATH = tryDecode(xbmc.translatePath(
|
||||
"special://database/Textures%s.db" % _DB_TEXTURE_VERSION[KODIVERSION]))
|
||||
|
||||
DB_PLEX_PATH = xbmc.translatePath("special://database/plex.db")
|
||||
DB_PLEX_PATH = tryDecode(xbmc.translatePath("special://database/plex.db"))
|
||||
|
||||
EXTERNAL_SUBTITLE_TEMP_PATH = xbmc.translatePath(
|
||||
"special://profile/addon_data/%s/temp/" % ADDON_ID)
|
||||
EXTERNAL_SUBTITLE_TEMP_PATH = tryDecode(xbmc.translatePath(
|
||||
"special://profile/addon_data/%s/temp/" % ADDON_ID))
|
||||
|
||||
|
||||
# Multiply Plex time by this factor to receive Kodi time
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
import logging
|
||||
from shutil import copytree
|
||||
import xml.etree.ElementTree as etree
|
||||
from os import remove, listdir, makedirs
|
||||
from os.path import isfile, join
|
||||
from os import makedirs
|
||||
|
||||
import xbmc
|
||||
from xbmcvfs import exists
|
||||
|
||||
from utils import window, settings, language as lang, tryEncode, indent, \
|
||||
normalize_nodes, exists_dir
|
||||
normalize_nodes, exists_dir, tryDecode
|
||||
import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
@ -63,22 +62,25 @@ class VideoNodes(object):
|
|||
dirname = viewid
|
||||
|
||||
# Returns strings
|
||||
path = xbmc.translatePath("special://profile/library/video/")
|
||||
nodepath = xbmc.translatePath(
|
||||
"special://profile/library/video/Plex-%s/" % dirname)
|
||||
path = tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video/"))
|
||||
nodepath = tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video/Plex-%s/" % dirname))
|
||||
|
||||
if delete:
|
||||
files = [f for f in listdir(nodepath) if isfile(join(nodepath, f))]
|
||||
for file in files:
|
||||
remove(nodepath + file)
|
||||
log.info("Sucessfully removed videonode: %s." % tagname)
|
||||
if exists_dir(nodepath):
|
||||
from shutil import rmtree
|
||||
rmtree(nodepath)
|
||||
log.info("Sucessfully removed videonode: %s." % tagname)
|
||||
return
|
||||
|
||||
# Verify the video directory
|
||||
if not exists_dir(path):
|
||||
copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video"),
|
||||
dst=xbmc.translatePath("special://profile/library/video"))
|
||||
src=tryDecode(xbmc.translatePath(
|
||||
"special://xbmc/system/library/video")),
|
||||
dst=tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video")))
|
||||
|
||||
# Create the node directory
|
||||
if mediatype != "photos":
|
||||
|
@ -290,7 +292,7 @@ class VideoNodes(object):
|
|||
# To do: add our photos nodes to kodi picture sources somehow
|
||||
continue
|
||||
|
||||
if exists(nodeXML):
|
||||
if exists(tryEncode(nodeXML)):
|
||||
# Don't recreate xml if already exists
|
||||
continue
|
||||
|
||||
|
@ -377,8 +379,9 @@ class VideoNodes(object):
|
|||
|
||||
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
||||
tagname = tryEncode(tagname)
|
||||
cleantagname = normalize_nodes(tagname)
|
||||
nodepath = xbmc.translatePath("special://profile/library/video/")
|
||||
cleantagname = tryDecode(normalize_nodes(tagname))
|
||||
nodepath = tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video/"))
|
||||
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
||||
path = "library://video/plex_%s.xml" % cleantagname
|
||||
if v.KODIVERSION >= 17:
|
||||
|
@ -391,8 +394,10 @@ class VideoNodes(object):
|
|||
if not exists_dir(nodepath):
|
||||
# We need to copy over the default items
|
||||
copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video"),
|
||||
dst=xbmc.translatePath("special://profile/library/video"))
|
||||
src=tryDecode(xbmc.translatePath(
|
||||
"special://xbmc/system/library/video")),
|
||||
dst=tryDecode(xbmc.translatePath(
|
||||
"special://profile/library/video")))
|
||||
|
||||
labels = {
|
||||
'Favorite movies': 30180,
|
||||
|
@ -406,7 +411,7 @@ class VideoNodes(object):
|
|||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=itemtype)
|
||||
|
||||
if exists(nodeXML):
|
||||
if exists(tryEncode(nodeXML)):
|
||||
# Don't recreate xml if already exists
|
||||
return
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ from ssl import CERT_NONE
|
|||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, settings, ThreadMethodsAdditionalSuspend, \
|
||||
ThreadMethods
|
||||
from utils import window, settings, thread_methods
|
||||
from companion import process_command
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -22,8 +22,6 @@ log = logging.getLogger("PLEX."+__name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||
@ThreadMethods
|
||||
class WebSocket(Thread):
|
||||
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
||||
|
||||
|
@ -62,11 +60,11 @@ class WebSocket(Thread):
|
|||
|
||||
counter = 0
|
||||
handshake_counter = 0
|
||||
threadStopped = self.threadStopped
|
||||
threadSuspended = self.threadSuspended
|
||||
while not threadStopped():
|
||||
thread_stopped = self.thread_stopped
|
||||
thread_suspended = self.thread_suspended
|
||||
while not thread_stopped():
|
||||
# In the event the server goes offline
|
||||
while threadSuspended():
|
||||
while thread_suspended():
|
||||
# Set in service.py
|
||||
if self.ws is not None:
|
||||
try:
|
||||
|
@ -74,7 +72,7 @@ class WebSocket(Thread):
|
|||
except:
|
||||
pass
|
||||
self.ws = None
|
||||
if threadStopped():
|
||||
if thread_stopped():
|
||||
# Abort was requested while waiting. We should exit
|
||||
log.info("##===---- %s Stopped ----===##"
|
||||
% self.__class__.__name__)
|
||||
|
@ -141,16 +139,17 @@ class WebSocket(Thread):
|
|||
|
||||
def stopThread(self):
|
||||
"""
|
||||
Overwrite this method from ThreadMethods to close websockets
|
||||
Overwrite this method from thread_methods to close websockets
|
||||
"""
|
||||
log.info("Stopping %s thread." % self.__class__.__name__)
|
||||
self._threadStopped = True
|
||||
self.__threadStopped = True
|
||||
try:
|
||||
self.ws.shutdown()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD'])
|
||||
class PMS_Websocket(WebSocket):
|
||||
"""
|
||||
Websocket connection with the PMS for Plex Companion
|
||||
|
@ -160,16 +159,15 @@ class PMS_Websocket(WebSocket):
|
|||
|
||||
def getUri(self):
|
||||
server = window('pms_server')
|
||||
# Need to use plex.tv token, if any. NOT user token
|
||||
token = window('plex_token')
|
||||
# Get the appropriate prefix for the websocket
|
||||
if server.startswith('https'):
|
||||
server = "wss%s" % server[5:]
|
||||
else:
|
||||
server = "ws%s" % server[4:]
|
||||
uri = "%s/:/websockets/notifications" % server
|
||||
if token:
|
||||
uri += '?X-Plex-Token=%s' % token
|
||||
# Need to use plex.tv token, if any. NOT user token
|
||||
if state.PLEX_TOKEN:
|
||||
uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN
|
||||
sslopt = {}
|
||||
if settings('sslverify') == "false":
|
||||
sslopt["cert_reqs"] = CERT_NONE
|
||||
|
@ -213,14 +211,18 @@ class PMS_Websocket(WebSocket):
|
|||
|
||||
class Alexa_Websocket(WebSocket):
|
||||
"""
|
||||
Websocket connection to talk to Amazon Alexa
|
||||
Websocket connection to talk to Amazon Alexa.
|
||||
|
||||
Can't use thread_methods!
|
||||
"""
|
||||
__thread_stopped = False
|
||||
__thread_suspended = False
|
||||
|
||||
def getUri(self):
|
||||
self.plex_client_Id = window('plex_client_Id')
|
||||
uri = ('wss://pubsub.plex.tv/sub/websockets/%s/%s?X-Plex-Token=%s'
|
||||
% (window('currUserId'),
|
||||
self.plex_client_Id,
|
||||
window('plex_token')))
|
||||
% (state.PLEX_USER_ID,
|
||||
self.plex_client_Id, state.PLEX_TOKEN))
|
||||
sslopt = {}
|
||||
log.debug("Uri: %s, sslopt: %s" % (uri, sslopt))
|
||||
return uri, sslopt
|
||||
|
@ -252,11 +254,32 @@ class Alexa_Websocket(WebSocket):
|
|||
def IOError_response(self):
|
||||
pass
|
||||
|
||||
def threadSuspended(self):
|
||||
# Path in thread_methods
|
||||
def stop_thread(self):
|
||||
self.__thread_stopped = True
|
||||
|
||||
def suspend_thread(self):
|
||||
self.__thread_suspended = True
|
||||
|
||||
def resume_thread(self):
|
||||
self.__thread_suspended = False
|
||||
|
||||
def thread_stopped(self):
|
||||
if self.__thread_stopped is True:
|
||||
return True
|
||||
if state.STOP_PKC:
|
||||
return True
|
||||
return False
|
||||
|
||||
# The culprit
|
||||
def thread_suspended(self):
|
||||
"""
|
||||
Overwrite to ignore library sync stuff and allow to check for
|
||||
plex_restricteduser
|
||||
Overwrite method since we need to check for plex token
|
||||
"""
|
||||
return (self._threadSuspended or
|
||||
window('plex_restricteduser') == 'true' or
|
||||
not window('plex_token'))
|
||||
if self.__thread_suspended is True:
|
||||
return True
|
||||
if not state.PLEX_TOKEN:
|
||||
return True
|
||||
if state.RESTRICTED_USER:
|
||||
return True
|
||||
return False
|
||||
|
|
70
service.py
70
service.py
|
@ -6,7 +6,7 @@ import logging
|
|||
from os import path as os_path
|
||||
from sys import path as sys_path, argv
|
||||
|
||||
from xbmc import translatePath, Monitor, sleep
|
||||
from xbmc import translatePath, Monitor
|
||||
from xbmcaddon import Addon
|
||||
|
||||
###############################################################################
|
||||
|
@ -30,7 +30,8 @@ sys_path.append(_base_resource)
|
|||
|
||||
###############################################################################
|
||||
|
||||
from utils import settings, window, language as lang, dialog, tryEncode
|
||||
from utils import settings, window, language as lang, dialog, tryEncode, \
|
||||
tryDecode
|
||||
from userclient import UserClient
|
||||
import initialsetup
|
||||
from kodimonitor import KodiMonitor
|
||||
|
@ -42,10 +43,11 @@ from playqueue import Playqueue
|
|||
|
||||
import PlexAPI
|
||||
from PlexCompanion import PlexCompanion
|
||||
from monitor_kodi_play import Monitor_Kodi_Play
|
||||
from command_pipeline import Monitor_Window
|
||||
from playback_starter import Playback_Starter
|
||||
from artwork import Image_Cache_Thread
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -85,7 +87,7 @@ class Service():
|
|||
|
||||
window('plex_logLevel', value=str(logLevel))
|
||||
window('plex_kodiProfile',
|
||||
value=translatePath("special://profile"))
|
||||
value=tryDecode(translatePath("special://profile")))
|
||||
window('plex_context',
|
||||
value='true' if settings('enableContext') == "true" else "")
|
||||
window('fetch_pms_item_number',
|
||||
|
@ -105,18 +107,16 @@ class Service():
|
|||
|
||||
# Reset window props for profile switch
|
||||
properties = [
|
||||
|
||||
"plex_online", "plex_serverStatus", "plex_onWake",
|
||||
"plex_dbCheck", "plex_kodiScan",
|
||||
"plex_shouldStop", "currUserId", "plex_dbScan",
|
||||
"plex_shouldStop", "plex_dbScan",
|
||||
"plex_initialScan", "plex_customplayqueue", "plex_playbackProps",
|
||||
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
||||
"plex_runLibScan", "pms_token", "plex_token",
|
||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||
"suspend_LibraryThread", "plex_terminateNow",
|
||||
"kodiplextimeoffset", "countError", "countUnauthorized",
|
||||
"plex_restricteduser", "plex_allows_mediaDeletion",
|
||||
"plex_play_new_item", "plex_result", "plex_force_transcode_pix"
|
||||
"plex_command", "plex_result", "plex_force_transcode_pix"
|
||||
]
|
||||
for prop in properties:
|
||||
window(prop, clear=True)
|
||||
|
@ -134,15 +134,22 @@ class Service():
|
|||
logLevel = 0
|
||||
return logLevel
|
||||
|
||||
def __stop_PKC(self):
|
||||
"""
|
||||
Kodi's abortRequested is really unreliable :-(
|
||||
"""
|
||||
return self.monitor.abortRequested() or state.STOP_PKC
|
||||
|
||||
def ServiceEntryPoint(self):
|
||||
# Important: Threads depending on abortRequest will not trigger
|
||||
# if profile switch happens more than once.
|
||||
__stop_PKC = self.__stop_PKC
|
||||
monitor = self.monitor
|
||||
kodiProfile = v.KODI_PROFILE
|
||||
|
||||
# Detect playback start early on
|
||||
self.monitor_kodi_play = Monitor_Kodi_Play(self)
|
||||
self.monitor_kodi_play.start()
|
||||
self.command_pipeline = Monitor_Window(self)
|
||||
self.command_pipeline.start()
|
||||
|
||||
# Server auto-detect
|
||||
initialsetup.InitialSetup().setup()
|
||||
|
@ -162,14 +169,14 @@ class Service():
|
|||
|
||||
welcome_msg = True
|
||||
counter = 0
|
||||
while not monitor.abortRequested():
|
||||
while not __stop_PKC():
|
||||
|
||||
if tryEncode(window('plex_kodiProfile')) != kodiProfile:
|
||||
if window('plex_kodiProfile') != kodiProfile:
|
||||
# Profile change happened, terminate this thread and others
|
||||
log.warn("Kodi profile was: %s and changed to: %s. "
|
||||
"Terminating old PlexKodiConnect thread."
|
||||
% (kodiProfile,
|
||||
tryEncode(window('plex_kodiProfile'))))
|
||||
window('plex_kodiProfile')))
|
||||
break
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
|
@ -242,14 +249,13 @@ class Service():
|
|||
# Server went offline
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(5):
|
||||
if monitor.waitForAbort(3):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
sleep(50)
|
||||
else:
|
||||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not monitor.abortRequested():
|
||||
while not self.__stop_PKC():
|
||||
server = self.user.getServer()
|
||||
if server is False:
|
||||
# No server info set in add-on settings
|
||||
|
@ -261,7 +267,7 @@ class Service():
|
|||
self.server_online = False
|
||||
window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
window('suspend_LibraryThread', value='true')
|
||||
state.SUSPEND_LIBRARY_THREAD = True
|
||||
log.error("Plex Media Server went offline")
|
||||
if settings('show_pms_offline') == 'true':
|
||||
dialog('notification',
|
||||
|
@ -298,10 +304,10 @@ class Service():
|
|||
sound=False)
|
||||
log.info("Server %s is online and ready." % server)
|
||||
window('plex_online', value="true")
|
||||
if window('plex_authenticated') == 'true':
|
||||
if state.AUTHENTICATED:
|
||||
# Server got offline when we were authenticated.
|
||||
# Hence resume threads
|
||||
window('suspend_LibraryThread', clear=True)
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
|
||||
# Start the userclient thread
|
||||
if not self.user_running:
|
||||
|
@ -317,31 +323,10 @@ class Service():
|
|||
if monitor.waitForAbort(0.05):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
# Terminating PlexKodiConnect
|
||||
|
||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||
window('plex_terminateNow', value='true')
|
||||
try:
|
||||
self.plexCompanion.stopThread()
|
||||
except:
|
||||
log.warn('plexCompanion already shut down')
|
||||
try:
|
||||
self.library.stopThread()
|
||||
except:
|
||||
log.warn('Library sync already shut down')
|
||||
try:
|
||||
self.ws.stopThread()
|
||||
except:
|
||||
log.warn('Websocket client already shut down')
|
||||
try:
|
||||
self.alexa.stopThread()
|
||||
except:
|
||||
log.warn('Websocket client already shut down')
|
||||
try:
|
||||
self.user.stopThread()
|
||||
except:
|
||||
log.warn('User client already shut down')
|
||||
state.STOP_PKC = True
|
||||
try:
|
||||
downloadutils.DownloadUtils().stopSession()
|
||||
except:
|
||||
|
@ -349,6 +334,7 @@ class Service():
|
|||
window('plex_service_started', clear=True)
|
||||
log.warn("======== STOP %s ========" % v.ADDON_NAME)
|
||||
|
||||
|
||||
# Safety net - Kody starts PKC twice upon first installation!
|
||||
if window('plex_service_started') == 'true':
|
||||
exit = True
|
||||
|
|
Loading…
Reference in a new issue