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