Rather big change of PKC's plumbing

This commit is contained in:
croneter 2018-11-18 14:59:17 +01:00
parent b2615c19bd
commit a045063769
53 changed files with 1246 additions and 1663 deletions

View file

@ -94,9 +94,6 @@ class Main():
elif mode == 'togglePlexTV': elif mode == 'togglePlexTV':
entrypoint.toggle_plex_tv_sign_in() entrypoint.toggle_plex_tv_sign_in()
elif mode == 'resetauth':
entrypoint.reset_authorization()
elif mode == 'passwords': elif mode == 'passwords':
utils.passwords_xml() utils.passwords_xml()
@ -111,14 +108,14 @@ class Main():
else: else:
if mode == 'repair': if mode == 'repair':
log.info('Requesting repair lib sync') log.info('Requesting repair lib sync')
utils.plex_command('RUN_LIB_SCAN', 'repair') utils.plex_command('repair-scan')
elif mode == 'manualsync': elif mode == 'manualsync':
log.info('Requesting full library scan') log.info('Requesting full library scan')
utils.plex_command('RUN_LIB_SCAN', 'full') utils.plex_command('full-scan')
elif mode == 'texturecache': elif mode == 'texturecache':
log.info('Requesting texture caching of all textures') log.info('Requesting texture caching of all textures')
utils.plex_command('RUN_LIB_SCAN', 'textures') utils.plex_command('textures-scan')
elif mode == 'chooseServer': elif mode == 'chooseServer':
entrypoint.choose_pms_server() entrypoint.choose_pms_server()
@ -128,7 +125,7 @@ class Main():
elif mode == 'fanart': elif mode == 'fanart':
log.info('User requested fanarttv refresh') log.info('User requested fanarttv refresh')
utils.plex_command('RUN_LIB_SCAN', 'fanart') utils.plex_command('fanart-scan')
elif '/extrafanart' in path: elif '/extrafanart' in path:
plexpath = arguments[1:] plexpath = arguments[1:]
@ -158,7 +155,7 @@ class Main():
""" """
request = '%s&handle=%s' % (argv[2], HANDLE) request = '%s&handle=%s' % (argv[2], HANDLE)
# Put the request into the 'queue' # Put the request into the 'queue'
utils.plex_command('PLAY', request) utils.plex_command('PLAY-%s' % request)
if HANDLE == -1: if HANDLE == -1:
# Handle -1 received, not waiting for main thread # Handle -1 received, not waiting for main thread
return return

View file

@ -0,0 +1,27 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Used to save PKC's application state and share between modules. Be careful
if you invoke another PKC Python instance (!!) when e.g. PKC.movies is called
"""
from __future__ import absolute_import, division, unicode_literals
from .account import Account
from .application import App
from .connection import Connection
from .libsync import Sync
from .playstate import PlayState
ACCOUNT = None
APP = None
CONN = None
SYNC = None
PLAYSTATE = None
def init():
global ACCOUNT, APP, CONN, SYNC, PLAYSTATE
ACCOUNT = Account()
APP = App()
CONN = Connection()
SYNC = Sync()
PLAYSTATE = PlayState()

View file

@ -0,0 +1,86 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .. import utils
LOG = getLogger('PLEX.account')
class Account(object):
def __init__(self):
# Along with window('plex_authenticated')
self.authenticated = False
self._session = None
utils.window('plex_authenticated', clear=True)
self.load()
def set_authenticated(self):
self.authenticated = True
utils.window('plex_authenticated', value='true')
# Start download session
from .. import downloadutils
self._session = downloadutils.DownloadUtils()
self._session.startSession(reset=True)
def set_unauthenticated(self):
self.authenticated = False
utils.window('plex_authenticated', clear=True)
def load(self):
LOG.debug('Loading account settings')
# plex.tv username
self.plex_username = utils.settings('username') or None
# Plex ID of that user (e.g. for plex.tv) as a STRING
self.plex_user_id = utils.settings('userid') or None
# Token for that user for plex.tv
self.plex_token = utils.settings('plexToken') or None
# Plex token for the active PMS for the active user
# (might be diffent to plex_token)
self.pms_token = utils.settings('accessToken') or None
self.avatar = utils.settings('plexAvatar') or None
self.myplexlogin = utils.settings('myplexlogin') == 'true'
# Plex home user? Then "False"
self.restricted_user = True \
if utils.settings('plex_restricteduser') == 'true' else False
# Force user to enter Pin if set?
self.force_login = utils.settings('enforceUserLogin') == 'true'
# Also load these settings to Kodi window variables - they'll be
# available for other PKC Python instances
utils.window('plex_restricteduser',
value='true' if self.restricted_user else 'false')
utils.window('plex_token', value=self.plex_token or '')
utils.window('pms_token', value=self.pms_token or '')
utils.window('plexAvatar', value=self.avatar or '')
LOG.debug('Loaded user %s, %s with plex token %s... and pms token %s...',
self.plex_username, self.plex_user_id,
self.plex_token[:5] if self.plex_token else None,
self.pms_token[:5] if self.pms_token else None)
LOG.debug('User is restricted Home user: %s', self.restricted_user)
def clear(self):
LOG.debug('Clearing account settings')
self.plex_username = None
self.plex_user_id = None
self.plex_token = None
self.pms_token = None
self.avatar = None
self.restricted_user = None
self.authenticated = False
self._session = None
utils.settings('username', value='')
utils.settings('userid', value='')
utils.settings('plex_restricteduser', value='')
utils.settings('plexToken', value='')
utils.settings('accessToken', value='')
utils.settings('plexAvatar', value='')
utils.window('plex_restricteduser', clear=True)
utils.window('plex_token', clear=True)
utils.window('pms_token', clear=True)
utils.window('plexAvatar', clear=True)
utils.window('plex_authenticated', clear=True)

View file

@ -0,0 +1,43 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import Queue
from threading import Lock, RLock
from .. import utils
class App(object):
"""
This class is used to store variables across PKC modules
"""
def __init__(self, only_reload_settings=False):
self.load_settings()
if only_reload_settings:
return
# Quit PKC?
self.stop_pkc = False
# Need to lock all methods and functions messing with Plex Companion subscribers
self.lock_subscriber = RLock()
# Need to lock everything messing with Kodi/PKC playqueues
self.lock_playqueues = RLock()
# Necessary to temporarily hold back librarysync/websocket listener when doing
# a full sync
self.lock_playlists = Lock()
# Plex Companion Queue()
self.companion_queue = Queue.Queue(maxsize=100)
# Command Pipeline Queue()
self.command_pipeline_queue = Queue.Queue()
# Websocket_client queue to communicate with librarysync
self.websocket_queue = Queue.Queue()
def load_settings(self):
# Number of items to fetch and display in widgets
self.fetch_pms_item_number = int(utils.settings('fetch_pms_item_number'))
# Hack to force Kodi widget for "in progress" to show up if it was empty
# before
self.force_reload_skin = utils.settings('forceReloadSkinOnPlaybackStop') == 'true'
# Stemming from the PKC settings.xml
self.kodi_plex_time_offset = float(utils.settings('kodiplextimeoffset'))

View file

@ -0,0 +1,75 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .. import utils, json_rpc as js
LOG = getLogger('PLEX.connection')
class Connection(object):
def __init__(self, only_reload_settings=False):
self.load_webserver()
self.load()
if only_reload_settings:
return
# TODO: Delete
self.pms_server = None
# Plex Media Server Status - along with window('plex_serverStatus')
# Values:
# 'Stop': set if e.g.
# '401': Token has been revoked - PKC yet to delete tokens
# 'Auth':
self.pms_status = False
# Token passed along, e.g. if playback initiated by Plex Companion. Might be
# another user playing something! Token identifies user
self.plex_transient_token = None
def load_webserver(self):
"""
PKC needs Kodi webserver to work correctly
"""
LOG.debug('Loading Kodi webserver details')
# Kodi webserver details
if js.get_setting('services.webserver') in (None, False):
# Enable the webserver, it is disabled
js.set_setting('services.webserver', True)
self.webserver_host = 'localhost'
self.webserver_port = js.get_setting('services.webserverport')
self.webserver_username = js.get_setting('services.webserverusername')
self.webserver_password = js.get_setting('services.webserverpassword')
def load(self):
LOG.debug('Loading connection settings')
# Shall we verify SSL certificates? "None" will leave SSL enabled
self.verify_ssl_cert = None if utils.settings('sslverify') == 'true' \
else False
# Do we have an ssl certificate for PKC we need to use?
self.ssl_cert_path = utils.settings('sslcert') \
if utils.settings('sslcert') != 'None' else None
self.machine_identifier = utils.settings('plex_machineIdentifier') or None
self.server_name = utils.settings('plex_servername') or None
self.https = utils.settings('https') == 'true'
self.host = utils.settings('ipaddress') or None
self.port = int(utils.settings('port')) if utils.settings('port') else None
if not self.host:
self.server = None
elif self.https:
self.server = 'https://%s:%s' % (self.host, self.port)
else:
self.server = 'http://%s:%s' % (self.host, self.port)
utils.window('pms_server', value=self.server)
LOG.debug('Set server %s (%s) to %s',
self.server_name, self.machine_identifier, self.server)
def clear(self):
LOG.debug('Clearing connection settings')
self.machine_identifier = None
self.server_name = None
self.http = None
self.host = None
self.port = None
self.server = None
utils.window('pms_server', clear=True)

View file

@ -0,0 +1,75 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from .. import utils
class Sync(object):
def __init__(self, only_reload_settings=False):
self.load_settings()
if only_reload_settings:
return
# Do we need to run a special library scan?
self.run_lib_scan = None
# Usually triggered by another Python instance - will have to be set (by
# polling window) through e.g. librarysync thread
self.suspend_library_thread = False
# Set if user decided to cancel sync
self.stop_sync = False
# Set during media playback if PKC should not do any syncs. Will NOT
# suspend synching of playstate progress
self.suspend_sync = False
# Could we access the paths?
self.path_verified = False
# Set if a Plex-Kodi DB sync is being done - along with
# window('plex_dbScan') set to 'true'
self.db_scan = False
def load_settings(self):
# Direct Paths (True) or Addon Paths (False)? Along with
# window('useDirectPaths')
self.direct_paths = True if utils.settings('useDirectPaths') == '1' \
else False
# Is synching of Plex music enabled?
self.enable_music = utils.settings('enableMusic') == 'true'
# Path remapping mechanism (e.g. smb paths)
# Do we replace \\myserver\path to smb://myserver/path?
self.replace_smb_path = utils.settings('replaceSMB') == 'true'
# Do we generally remap?
self.remap_path = utils.settings('remapSMB') == 'true'
# Mappings for REMAP_PATH:
self.remapSMBmovieOrg = utils.settings('remapSMBmovieOrg')
self.remapSMBmovieNew = utils.settings('remapSMBmovieNew')
self.remapSMBtvOrg = utils.settings('remapSMBtvOrg')
self.remapSMBtvNew = utils.settings('remapSMBtvNew')
self.remapSMBmusicOrg = utils.settings('remapSMBmusicOrg')
self.remapSMBmusicNew = utils.settings('remapSMBmusicNew')
self.remapSMBphotoOrg = utils.settings('remapSMBphotoOrg')
self.remapSMBphotoNew = utils.settings('remapSMBphotoNew')
# Shall we replace custom user ratings with the number of versions available?
self.indicate_media_versions = True \
if utils.settings('indicate_media_versions') == "true" else False
# Will sync movie trailer differently: either play trailer directly or show
# all the Plex extras for the user to choose
self.show_extras_instead_of_playing_trailer = utils.settings('showExtrasInsteadOfTrailer') == 'true'
# Only sync specific Plex playlists to Kodi?
self.sync_specific_plex_playlists = utils.settings('syncSpecificPlexPlaylists') == 'true'
# Only sync specific Kodi playlists to Plex?
self.sync_specific_kodi_playlists = utils.settings('syncSpecificKodiPlaylists') == 'true'
# Shall we show Kodi dialogs when synching?
self.sync_dialog = utils.settings('dbSyncIndicator') == 'true'
# How often shall we sync?
self.full_sync_intervall = int(utils.settings('fullSyncInterval')) * 60
# Background Sync disabled?
self.background_sync_disabled = utils.settings('enableBackgroundSync') == 'false'
# How long shall we wait with synching a new item to make sure Plex got all
# metadata?
self.backgroundsync_saftymargin = int(utils.settings('backgroundsync_saftyMargin'))
# How many threads to download Plex metadata on sync?
self.sync_thread_number = int(utils.settings('syncThreadNumber'))
# Shall Kodi show dialogs for syncing/caching images? (e.g. images left
# to sync)
self.image_sync_notifications = utils.settings('imageSyncNotifications') == 'true'

View file

@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
class PlayState(object):
# "empty" dict for the PLAYER_STATES above. Use copy.deepcopy to duplicate!
template = {
'type': None,
'time': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'totaltime': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'speed': 0,
'shuffled': False,
'repeat': 'off',
'position': None,
'playlistid': None,
'currentvideostream': -1,
'currentaudiostream': -1,
'subtitleenabled': False,
'currentsubtitle': -1,
'file': None,
'kodi_id': None,
'kodi_type': None,
'plex_id': None,
'plex_type': None,
'container_key': None,
'volume': 100,
'muted': False,
'playmethod': None,
'playcount': None
}
def __init__(self):
# Kodi player states - here, initial values are set
self.player_states = {
0: {},
1: {},
2: {}
}
# The LAST playstate once playback is finished
self.old_player_states = {
0: {},
1: {},
2: {}
}
self.played_info = {}
# Set by SpecialMonitor - did user choose to resume playback or start from the
# beginning?
self.resume_playback = False
# Was the playback initiated by the user using the Kodi context menu?
self.context_menu_play = False
# Set by context menu - shall we force-transcode the next playing item?
self.force_transcode = False
# Which Kodi player is/has been active? (either int 1, 2 or 3)
self.active_players = set()
# Failsafe for throwing an empty video back to Kodi's setResolvedUrl to set
# up our own playlist from the very beginning
self.pkc_caused_stop = False
# Flag if the 0 length PKC video has already failed so we can start resolving
# playback (set in player.py)
self.pkc_caused_stop_done = True

View file

@ -7,8 +7,7 @@ import requests
import xbmc import xbmc
from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB
from . import backgroundthread, utils from . import app, backgroundthread, utils
from . import state
LOG = getLogger('PLEX.artwork') LOG = getLogger('PLEX.artwork')
@ -19,13 +18,7 @@ requests.packages.urllib3.disable_warnings()
# download is successful # download is successful
TIMEOUT = (35.1, 35.1) TIMEOUT = (35.1, 35.1)
IMAGE_CACHING_SUSPENDS = [ IMAGE_CACHING_SUSPENDS = []
state.SUSPEND_LIBRARY_THREAD,
state.STOP_SYNC,
state.DB_SCAN
]
if not utils.settings('imageSyncDuringPlayback') == 'true':
IMAGE_CACHING_SUSPENDS.append(state.SUSPEND_SYNC)
def double_urlencode(text): def double_urlencode(text):
@ -37,19 +30,9 @@ def double_urldecode(text):
class ImageCachingThread(backgroundthread.KillableThread): class ImageCachingThread(backgroundthread.KillableThread):
def __init__(self):
self._canceled = False
super(ImageCachingThread, self).__init__()
def isCanceled(self):
return self._canceled or state.STOP_PKC
def isSuspended(self): def isSuspended(self):
return any(IMAGE_CACHING_SUSPENDS) return any(IMAGE_CACHING_SUSPENDS)
def cancel(self):
self._canceled = True
@staticmethod @staticmethod
def _art_url_generator(): def _art_url_generator():
for kind in (KodiVideoDB, KodiMusicDB): for kind in (KodiVideoDB, KodiMusicDB):
@ -88,18 +71,18 @@ def cache_url(url):
try: try:
requests.head( requests.head(
url="http://%s:%s/image/image://%s" url="http://%s:%s/image/image://%s"
% (state.WEBSERVER_HOST, % (app.CONN.webserver_host,
state.WEBSERVER_PORT, app.CONN.webserver_port,
url), url),
auth=(state.WEBSERVER_USERNAME, auth=(app.CONN.webserver_username,
state.WEBSERVER_PASSWORD), app.CONN.webserver_password),
timeout=TIMEOUT) timeout=TIMEOUT)
except requests.Timeout: except requests.Timeout:
# We don't need the result, only trigger Kodi to start the # We don't need the result, only trigger Kodi to start the
# download. All is well # download. All is well
break break
except requests.ConnectionError: except requests.ConnectionError:
if state.STOP_PKC: if app.APP.stop_pkc:
# Kodi terminated # Kodi terminated
break break
# Server thinks its a DOS attack, ('error 10053') # Server thinks its a DOS attack, ('error 10053')

View file

@ -14,7 +14,6 @@ LOG = getLogger('PLEX.' + __name__)
class KillableThread(threading.Thread): class KillableThread(threading.Thread):
pass
'''A thread class that supports raising exception in the thread from '''A thread class that supports raising exception in the thread from
another thread. another thread.
''' '''
@ -77,6 +76,45 @@ class KillableThread(threading.Thread):
# except KillThreadException: # except KillThreadException:
# self.onKilled() # self.onKilled()
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
self._canceled = False
self._suspended = False
super(KillableThread, self).__init__(group, target, name, args, kwargs)
def isCanceled(self):
"""
Returns True if the thread is stopped
"""
if self._canceled or xbmc.abortRequested:
return True
return False
def abort(self):
"""
Call to stop this thread
"""
self._canceled = True
def suspend(self):
"""
Call to suspend this thread
"""
self._suspended = True
def resume(self):
"""
Call to revive a suspended thread back to life
"""
self._suspended = False
def isSuspended(self):
"""
Returns True if the thread is suspended
"""
if self._suspended:
return True
return False
class Tasks(list): class Tasks(list):
def add(self, task): def add(self, task):

View file

@ -1,68 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import logging
from threading import Thread
from xbmc import sleep
from . import utils
from . import state
###############################################################################
LOG = logging.getLogger('PLEX.command_pipeline')
###############################################################################
@utils.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.
Adjusts state.py accordingly
"""
def run(self):
stopped = self.stopped
queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting Kodi_Play_Client ##===----")
while not stopped():
if utils.window('plex_command'):
value = utils.window('plex_command')
utils.window('plex_command', clear=True)
if value.startswith('PLAY-'):
queue.put(value.replace('PLAY-', ''))
elif value == 'SUSPEND_LIBRARY_THREAD-True':
state.SUSPEND_LIBRARY_THREAD = True
elif value == 'SUSPEND_LIBRARY_THREAD-False':
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
elif value.startswith('RUN_LIB_SCAN-'):
state.RUN_LIB_SCAN = value.replace('RUN_LIB_SCAN-', '')
elif value.startswith('CONTEXT_menu?'):
queue.put('dummy?mode=context_menu&%s'
% value.replace('CONTEXT_menu?', ''))
elif value.startswith('NAVIGATE'):
queue.put(value.replace('NAVIGATE-', ''))
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 ##===----")

View file

@ -7,11 +7,8 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from xbmc import Player from xbmc import Player
from . import playqueue as PQ from . import playqueue as PQ, plex_functions as PF
from . import plex_functions as PF from . import json_rpc as js, variables as v, app
from . import json_rpc as js
from . import variables as v
from . import state
############################################################################### ###############################################################################
@ -68,12 +65,12 @@ def process_command(request_path, params):
if request_path == 'player/playback/playMedia': if request_path == 'player/playback/playMedia':
# We need to tell service.py # We need to tell service.py
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist' action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
state.COMPANION_QUEUE.put({ app.APP.companion_queue.put({
'action': action, 'action': action,
'data': params 'data': params
}) })
elif request_path == 'player/playback/refreshPlayQueue': elif request_path == 'player/playback/refreshPlayQueue':
state.COMPANION_QUEUE.put({ app.APP.companion_queue.put({
'action': 'refreshPlayQueue', 'action': 'refreshPlayQueue',
'data': params 'data': params
}) })
@ -115,7 +112,7 @@ def process_command(request_path, params):
elif request_path == "player/navigation/back": elif request_path == "player/navigation/back":
js.input_back() js.input_back()
elif request_path == "player/playback/setStreams": elif request_path == "player/playback/setStreams":
state.COMPANION_QUEUE.put({ app.APP.companion_queue.put({
'action': 'setStreams', 'action': 'setStreams',
'data': params 'data': params
}) })

View file

@ -43,8 +43,8 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
return self.selected_option return self.selected_option
def onInit(self): def onInit(self):
if utils.window('PlexUserImage'): if utils.window('plexAvatar'):
self.getControl(USER_IMAGE).setImage(utils.window('PlexUserImage')) self.getControl(USER_IMAGE).setImage(utils.window('plexAvatar'))
height = 479 + (len(self._options) * 55) height = 479 + (len(self._options) * 55)
LOG.debug("options: %s", self._options) LOG.debug("options: %s", self._options)
self.list_ = self.getControl(LIST) self.list_ = self.getControl(LIST)

View file

@ -8,7 +8,7 @@ import xbmcgui
from .plex_api import API from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
from . import context, plex_functions as PF, playqueue as PQ from . import context, plex_functions as PF, playqueue as PQ
from . import utils, variables as v, state from . import utils, variables as v, app
############################################################################### ###############################################################################
@ -84,7 +84,7 @@ class ContextMenu(object):
# if user uses direct paths, give option to initiate playback via PMS # if user uses direct paths, give option to initiate playback via PMS
if self.api and self.api.extras(): if self.api and self.api.extras():
options.append(OPTIONS['Extras']) options.append(OPTIONS['Extras'])
if state.DIRECT_PATHS and self.kodi_type in v.KODI_VIDEOTYPES: if app.PLAYSTATE.direct_paths and self.kodi_type in v.KODI_VIDEOTYPES:
options.append(OPTIONS['PMS_Play']) options.append(OPTIONS['PMS_Play'])
if self.kodi_type in v.KODI_VIDEOTYPES: if self.kodi_type in v.KODI_VIDEOTYPES:
options.append(OPTIONS['Transcode']) options.append(OPTIONS['Transcode'])
@ -112,7 +112,7 @@ class ContextMenu(object):
""" """
selected = self._selected_option selected = self._selected_option
if selected == OPTIONS['Transcode']: if selected == OPTIONS['Transcode']:
state.FORCE_TRANSCODE = True app.PLAYSTATE.force_transcode = True
self._PMS_play() self._PMS_play()
elif selected == OPTIONS['PMS_Play']: elif selected == OPTIONS['PMS_Play']:
self._PMS_play() self._PMS_play()
@ -146,7 +146,7 @@ class ContextMenu(object):
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type]) v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type])
playqueue.clear() playqueue.clear()
state.CONTEXT_MENU_PLAY = True app.PLAYSTATE.context_menu_play = True
handle = self.api.path(force_first_media=False, force_addon=True) handle = self.api.path(force_first_media=False, force_addon=True)
xbmc.executebuiltin('RunPlugin(%s)' % handle) xbmc.executebuiltin('RunPlugin(%s)' % handle)

View file

@ -4,9 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
import requests import requests
from . import utils from . import utils, clientinfo, app
from . import clientinfo
from . import state
############################################################################### ###############################################################################
@ -14,7 +12,7 @@ from . import state
import requests.packages.urllib3 import requests.packages.urllib3
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
LOG = getLogger('PLEX.downloadutils') LOG = getLogger('PLEX.download')
############################################################################### ###############################################################################
@ -39,25 +37,16 @@ class DownloadUtils():
def __init__(self): def __init__(self):
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
def setServer(self, server):
"""
Reserved for userclient only
"""
self.server = server
LOG.debug("Set server: %s", server)
def setSSL(self, verifySSL=None, certificate=None): def setSSL(self, verifySSL=None, certificate=None):
""" """
Reserved for userclient only
verifySSL must be 'true' to enable certificate validation verifySSL must be 'true' to enable certificate validation
certificate must be path to certificate or 'None' certificate must be path to certificate or 'None'
""" """
if verifySSL is None: if verifySSL is None:
verifySSL = state.VERIFY_SSL_CERT verifySSL = app.CONN.verify_ssl_cert
if certificate is None: if certificate is None:
certificate = state.SSL_CERT_PATH certificate = app.CONN.ssl_cert_path
# Set the session's parameters # Set the session's parameters
self.s.verify = verifySSL self.s.verify = verifySSL
if certificate: if certificate:
@ -67,8 +56,7 @@ class DownloadUtils():
def startSession(self, reset=False): def startSession(self, reset=False):
""" """
User should be authenticated when this method is called (via User should be authenticated when this method is called
userclient)
""" """
# Start session # Start session
self.s = requests.Session() self.s = requests.Session()
@ -80,9 +68,6 @@ class DownloadUtils():
# Set SSL settings # Set SSL settings
self.setSSL() self.setSSL()
# Set other stuff
self.setServer(utils.window('pms_server'))
# 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
# new plugin instance - it's impossible to share data otherwise # new plugin instance - it's impossible to share data otherwise
@ -94,7 +79,7 @@ class DownloadUtils():
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1)) self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
LOG.info("Requests session started on: %s", self.server) LOG.info("Requests session started on: %s", app.CONN.server)
def stopSession(self): def stopSession(self):
try: try:
@ -155,7 +140,7 @@ class DownloadUtils():
self.startSession() self.startSession()
s = self.s s = self.s
# Replace for the real values # Replace for the real values
url = url.replace("{server}", self.server) url = url.replace("{server}", app.CONN.server)
else: else:
# User is not (yet) authenticated. Used to communicate with # User is not (yet) authenticated. Used to communicate with
# plex.tv and to check for PMS servers # plex.tv and to check for PMS servers
@ -164,9 +149,9 @@ class DownloadUtils():
headerOptions = self.getHeader(options=headerOptions) headerOptions = self.getHeader(options=headerOptions)
else: else:
headerOptions = headerOverride headerOptions = headerOverride
kwargs['verify'] = state.VERIFY_SSL_CERT kwargs['verify'] = app.CONN.verify_ssl_cert
if state.SSL_CERT_PATH: if app.CONN.ssl_cert_path:
kwargs['cert'] = state.SSL_CERT_PATH kwargs['cert'] = app.CONN.ssl_cert_path
# Set the variables we were passed (fallback to request session # Set the variables we were passed (fallback to request session
# otherwise - faster) # otherwise - faster)
@ -252,12 +237,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 state.PMS_STATUS not in ('401', 'Auth'): if app.CONN.pms_status not in ('401', 'Auth'):
# Tell userclient token has been revoked. # Tell others token has been revoked.
LOG.debug('Setting PMS server status to ' LOG.debug('Setting PMS server status to '
'unauthorized') 'unauthorized')
state.PMS_STATUS = '401' app.CONN.pms_status = '401'
utils.window('plex_serverStatus', value="401")
utils.dialog('notification', utils.dialog('notification',
utils.lang(29999), utils.lang(29999),
utils.lang(30017), utils.lang(30017),

View file

@ -20,8 +20,8 @@ from .plex_api import API
from . import plex_functions as PF from . import plex_functions as PF
from . import json_rpc as js from . import json_rpc as js
from . import variables as v from . import variables as v
# Be careful - your using state in another Python instance! # Be careful - your using app in another Python instance!
from . import state from . import app
############################################################################### ###############################################################################
LOG = getLogger('PLEX.entrypoint') LOG = getLogger('PLEX.entrypoint')
@ -34,34 +34,7 @@ def choose_pms_server():
Lets user choose from list of PMS Lets user choose from list of PMS
""" """
LOG.info("Choosing PMS server requested, starting") LOG.info("Choosing PMS server requested, starting")
utils.plex_command('choose_pms_server')
setup = initialsetup.InitialSetup()
server = setup.pick_pms(showDialog=True)
if server is None:
LOG.error('We did not connect to a new PMS, aborting')
utils.plex_command('SUSPEND_USER_CLIENT', 'False')
utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
return
LOG.info("User chose server %s", server['name'])
setup.write_pms_to_settings(server)
if not _log_out():
return
# Wipe Kodi and Plex database as well as playlists and video nodes
utils.wipe_database()
# Log in again
_log_in()
LOG.info("Choosing new PMS complete")
# '<PMS> connected'
utils.dialog('notification',
utils.lang(29999),
'%s %s' % (server['name'], utils.lang(39220)),
icon='{plex}',
time=3000,
sound=False)
def toggle_plex_tv_sign_in(): def toggle_plex_tv_sign_in():
@ -69,37 +42,8 @@ def toggle_plex_tv_sign_in():
Signs out of Plex.tv if there was a token saved and thus deletes the token. Signs out of Plex.tv if there was a token saved and thus deletes the token.
Or signs in to plex.tv if the user was not logged in before. Or signs in to plex.tv if the user was not logged in before.
""" """
if utils.settings('plexToken'): LOG.info('Toggle of Plex.tv sign-in requested')
LOG.info('Reseting plex.tv credentials in settings') utils.plex_command('toggle_plex_tv_sign_in')
utils.settings('plexLogin', value="")
utils.settings('plexToken', value="")
utils.settings('plexid', value="")
utils.settings('plexAvatar', value="")
utils.settings('plex_status', value=utils.lang(39226))
utils.window('plex_token', clear=True)
utils.plex_command('PLEX_TOKEN', '')
utils.plex_command('PLEX_USERNAME', '')
else:
LOG.info('Login to plex.tv')
initialsetup.InitialSetup().plex_tv_sign_in()
utils.dialog('notification',
utils.lang(29999),
utils.lang(39221),
icon='{plex}',
time=3000,
sound=False)
def reset_authorization():
"""
User tried login and failed too many times. Reset # of logins
"""
if utils.yesno_dialog(utils.lang(29999), utils.lang(39206)):
LOG.info("Reset login attempts.")
utils.plex_command('PMS_STATUS', 'Auth')
else:
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
def directory_item(label, path, folder=True): def directory_item(label, path, folder=True):
@ -185,13 +129,7 @@ def switch_plex_user():
# position = 0 # position = 0
# utils.window('EmbyAdditionalUserImage.%s' % position, clear=True) # utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
LOG.info("Plex home user switch requested") LOG.info("Plex home user switch requested")
if not _log_out(): utils.plex_command('switch_plex_user')
return
# First remove playlists of old user
utils.delete_playlists()
# Remove video nodes
utils.delete_nodes()
_log_in()
def create_listitem(item, append_show_title=False, append_sxxexx=False): def create_listitem(item, append_show_title=False, append_sxxexx=False):
@ -573,13 +511,13 @@ def on_deck_episodes(viewid, tagname, limit):
return return
# We're using another python instance - need to load some vars # We're using another python instance - need to load some vars
if utils.settings('useDirectPaths') == '1': if utils.settings('useDirectPaths') == '1':
state.DIRECT_PATHS = True app.SYNC.direct_paths = True
state.REPLACE_SMB_PATH = utils.settings('replaceSMB') == 'true' app.SYNC.replace_smb_path = utils.settings('replaceSMB') == 'true'
state.REMAP_PATH = utils.settings('remapSMB') == 'true' app.SYNC.remap_path = utils.settings('remapSMB') == 'true'
if state.REMAP_PATH: if app.SYNC.remap_path:
initialsetup.set_replace_paths() initialsetup.set_replace_paths()
# Let's NOT check paths for widgets! # Let's NOT check paths for widgets!
state.PATH_VERIFIED = True app.SYNC.path_verified = True
counter = 0 counter = 0
for item in xml: for item in xml:
api = API(item) api = API(item)
@ -964,107 +902,5 @@ def create_new_pms():
""" """
Opens dialogs for the user the plug in the PMS details Opens dialogs for the user the plug in the PMS details
""" """
# "Enter your Plex Media Server's IP or URL. Examples are:" LOG.info('Request to manually enter new PMS address')
utils.messageDialog(utils.lang(29999), utils.plex_command('enter_new_pms_address')
'%s\n%s\n%s' % (utils.lang(39215),
'192.168.1.2',
'plex.myServer.org'))
address = utils.dialog('input', "Enter PMS IP or URL")
if address == '':
return
port = utils.dialog('input', "Enter PMS port", '32400', type='{numeric}')
if port == '':
return
url = '%s:%s' % (address, port)
# "Does your Plex Media Server support SSL connections?
# (https instead of http)"
https = utils.yesno_dialog(utils.lang(29999), utils.lang(39217))
if https:
url = 'https://%s' % url
else:
url = 'http://%s' % url
https = 'true' if https else 'false'
machine_identifier = PF.GetMachineIdentifier(url)
if machine_identifier is None:
# "Error contacting url
# Abort (Yes) or save address anyway (No)"
if utils.yesno_dialog(utils.lang(29999),
'%s %s. %s' % (utils.lang(39218),
url,
utils.lang(39219))):
return
else:
utils.settings('plex_machineIdentifier', '')
else:
utils.settings('plex_machineIdentifier', machine_identifier)
LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s',
https, address, port, machine_identifier)
utils.settings('https', value=https)
utils.settings('ipaddress', value=address)
utils.settings('port', value=port)
# Chances are this is a local PMS, so disable SSL certificate check
utils.settings('sslverify', value='false')
# Sign out to trigger new login
if _log_out():
# Only login again if logout was successful
_log_in()
def _log_in():
"""
Resets (clears) window properties to enable (re-)login
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
out!
"""
utils.plex_command('RUN_LIB_SCAN', 'full')
# Restart user client
utils.plex_command('SUSPEND_USER_CLIENT', 'False')
def _log_out():
"""
Finishes lib scans, logs out user.
Returns True if successfully signed out, False otherwise
"""
# Resetting, please wait
utils.dialog('notification',
utils.lang(29999),
utils.lang(39207),
icon='{plex}',
time=3000,
sound=False)
# Pause library sync thread
utils.plex_command('SUSPEND_LIBRARY_THREAD', 'True')
# Wait max for 10 seconds for all lib scans to shutdown
counter = 0
while utils.window('plex_dbScan') == 'true':
if counter > 200:
# Failed to reset PMS and plex.tv connects. Try to restart Kodi.
utils.messageDialog(utils.lang(29999), utils.lang(39208))
# Resuming threads, just in case
utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
LOG.error("Could not stop library sync, aborting")
return False
counter += 1
sleep(50)
LOG.debug("Successfully stopped library sync")
counter = 0
# Log out currently signed in user:
utils.window('plex_serverStatus', value='401')
utils.plex_command('PMS_STATUS', '401')
# Above method needs to have run its course! Hence wait
while utils.window('plex_serverStatus') == "401":
if counter > 100:
# 'Failed to reset PKC. Try to restart Kodi.'
utils.messageDialog(utils.lang(29999), utils.lang(39208))
LOG.error("Could not sign out user, aborting")
return False
counter += 1
sleep(50)
# Suspend the user client during procedure
utils.plex_command('SUSPEND_USER_CLIENT', 'True')
return True

View file

@ -2,22 +2,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from Queue import Queue
from xbmc import executebuiltin, translatePath from xbmc import executebuiltin
from . import utils from . import utils
from .utils import etree from .utils import etree
from . import path_ops from . import path_ops
from . import migration from . import migration
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import userclient
from . import clientinfo
from . import plex_functions as PF from . import plex_functions as PF
from . import plex_tv from . import plex_tv
from . import json_rpc as js from . import json_rpc as js
from . import playqueue as PQ from . import app
from . import state
from . import variables as v from . import variables as v
############################################################################### ###############################################################################
@ -30,96 +26,6 @@ if not path_ops.exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
path_ops.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH) path_ops.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
WINDOW_PROPERTIES = (
"plex_online", "plex_serverStatus", "plex_shouldStop", "plex_dbScan",
"plex_customplayqueue", "plex_playbackProps",
"pms_token", "plex_token", "pms_server", "plex_machineIdentifier",
"plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths",
"countError", "countUnauthorized", "plex_restricteduser",
"plex_allows_mediaDeletion", "plex_command", "plex_result",
"plex_force_transcode_pix"
)
def reload_pkc():
"""
Will reload state.py entirely and then initiate some values from the Kodi
settings file
"""
LOG.info('Start (re-)loading PKC settings')
# Reset state.py
reload(state)
# Reset window props
for prop in WINDOW_PROPERTIES:
utils.window(prop, clear=True)
# Clear video nodes properties
from .library_sync import videonodes
videonodes.VideoNodes().clearProperties()
# Initializing
state.VERIFY_SSL_CERT = utils.settings('sslverify') == 'true'
state.SSL_CERT_PATH = utils.settings('sslcert') \
if utils.settings('sslcert') != 'None' else None
state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
state.SYNC_DIALOG = utils.settings('dbSyncIndicator') == 'true'
state.ENABLE_MUSIC = utils.settings('enableMusic') == 'true'
state.BACKGROUND_SYNC_DISABLED = utils.settings(
'enableBackgroundSync') == 'false'
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
utils.settings('backgroundsync_saftyMargin'))
state.REPLACE_SMB_PATH = utils.settings('replaceSMB') == 'true'
state.REMAP_PATH = utils.settings('remapSMB') == 'true'
state.KODI_PLEX_TIME_OFFSET = float(utils.settings('kodiplextimeoffset'))
state.FETCH_PMS_ITEM_NUMBER = utils.settings('fetch_pms_item_number')
state.FORCE_RELOAD_SKIN = \
utils.settings('forceReloadSkinOnPlaybackStop') == 'true'
# Init some Queues()
state.COMMAND_PIPELINE_QUEUE = Queue()
state.COMPANION_QUEUE = Queue(maxsize=100)
state.WEBSOCKET_QUEUE = Queue()
set_replace_paths()
set_webserver()
# To detect Kodi profile switches
utils.window('plex_kodiProfile',
value=utils.try_decode(translatePath("special://profile")))
clientinfo.getDeviceId()
# Initialize the PKC playqueues
PQ.init_playqueues()
LOG.info('Done (re-)loading PKC settings')
def set_replace_paths():
"""
Sets our values for direct paths correctly (including using lower-case
protocols like smb:// and NOT SMB://)
"""
for typus in v.REMAP_TYPE_FROM_PLEXTYPE.values():
for arg in ('Org', 'New'):
key = 'remapSMB%s%s' % (typus, arg)
value = utils.settings(key)
if '://' in value:
protocol = value.split('://', 1)[0]
value = value.replace(protocol, protocol.lower())
setattr(state, key, value)
def set_webserver():
"""
Set the Kodi webserver details - used to set the texture cache
"""
if js.get_setting('services.webserver') in (None, False):
# Enable the webserver, it is disabled
js.set_setting('services.webserver', True)
# Set standard port and username
# set_setting('services.webserverport', 8080)
# set_setting('services.webserverusername', 'kodi')
# Webserver already enabled
state.WEBSERVER_PORT = js.get_setting('services.webserverport')
state.WEBSERVER_USERNAME = js.get_setting('services.webserverusername')
state.WEBSERVER_PASSWORD = js.get_setting('services.webserverpassword')
def _write_pms_settings(url, token): def _write_pms_settings(url, token):
""" """
Sets certain settings for server by asking for the PMS' settings Sets certain settings for server by asking for the PMS' settings
@ -145,11 +51,8 @@ class InitialSetup(object):
""" """
def __init__(self): def __init__(self):
LOG.debug('Entering initialsetup class') LOG.debug('Entering initialsetup class')
self.server = userclient.UserClient().get_server()
self.serverid = utils.settings('plex_machineIdentifier')
# Get Plex credentials from settings file, if they exist # Get Plex credentials from settings file, if they exist
plexdict = PF.GetPlexLoginFromSettings() plexdict = PF.GetPlexLoginFromSettings()
self.myplexlogin = plexdict['myplexlogin'] == 'true'
self.plex_login = plexdict['plexLogin'] self.plex_login = plexdict['plexLogin']
self.plex_token = plexdict['plexToken'] self.plex_token = plexdict['plexToken']
self.plexid = plexdict['plexid'] self.plexid = plexdict['plexid']
@ -158,6 +61,48 @@ class InitialSetup(object):
if self.plex_token: if self.plex_token:
LOG.debug('Found a plex.tv token in the settings') LOG.debug('Found a plex.tv token in the settings')
def enter_new_pms_address(self):
# "Enter your Plex Media Server's IP or URL. Examples are:"
utils.messageDialog(utils.lang(29999),
'%s\n%s\n%s' % (utils.lang(39215),
'192.168.1.2',
'plex.myServer.org'))
address = utils.dialog('input', "Enter PMS IP or URL")
if address == '':
return False
port = utils.dialog('input', "Enter PMS port", '32400', type='{numeric}')
if port == '':
return False
url = '%s:%s' % (address, port)
# "Does your Plex Media Server support SSL connections?
# (https instead of http)"
https = utils.yesno_dialog(utils.lang(29999), utils.lang(39217))
if https:
url = 'https://%s' % url
else:
url = 'http://%s' % url
https = 'true' if https else 'false'
machine_identifier = PF.GetMachineIdentifier(url)
if machine_identifier is None:
# "Error contacting url
# Abort (Yes) or save address anyway (No)"
if utils.yesno_dialog(utils.lang(29999),
'%s %s. %s' % (utils.lang(39218),
url,
utils.lang(39219))):
return False
else:
utils.settings('plex_machineIdentifier', '')
else:
utils.settings('plex_machineIdentifier', machine_identifier)
LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s',
https, address, port, machine_identifier)
utils.settings('https', value=https)
utils.settings('ipaddress', value=address)
utils.settings('port', value=port)
# Chances are this is a local PMS, so disable SSL certificate check
utils.settings('sslverify', value='false')
def plex_tv_sign_in(self): def plex_tv_sign_in(self):
""" """
Signs (freshly) in to plex.tv (will be saved to file settings) Signs (freshly) in to plex.tv (will be saved to file settings)
@ -227,26 +172,26 @@ class InitialSetup(object):
not set before not set before
""" """
answer = True answer = True
chk = PF.check_connection(self.server, verifySSL=False) chk = PF.check_connection(app.CONN.server, verifySSL=False)
if chk is False: if chk is False:
LOG.warn('Could not reach PMS %s', self.server) LOG.warn('Could not reach PMS %s', app.CONN.server)
answer = False answer = False
if answer is True and not self.serverid: if answer is True and not app.CONN.machine_identifier:
LOG.info('No PMS machineIdentifier found for %s. Trying to ' LOG.info('No PMS machineIdentifier found for %s. Trying to '
'get the PMS unique ID', self.server) 'get the PMS unique ID', app.CONN.server)
self.serverid = PF.GetMachineIdentifier(self.server) app.CONN.machine_identifier = PF.GetMachineIdentifier(app.CONN.server)
if self.serverid is None: if app.CONN.machine_identifier is None:
LOG.warn('Could not retrieve machineIdentifier') LOG.warn('Could not retrieve machineIdentifier')
answer = False answer = False
else: else:
utils.settings('plex_machineIdentifier', value=self.serverid) utils.settings('plex_machineIdentifier', value=app.CONN.machine_identifier)
elif answer is True: elif answer is True:
temp_server_id = PF.GetMachineIdentifier(self.server) temp_server_id = PF.GetMachineIdentifier(app.CONN.server)
if temp_server_id != self.serverid: if temp_server_id != app.CONN.machine_identifier:
LOG.warn('The current PMS %s was expected to have a ' LOG.warn('The current PMS %s was expected to have a '
'unique machineIdentifier of %s. But we got ' 'unique machineIdentifier of %s. But we got '
'%s. Pick a new server to be sure', '%s. Pick a new server to be sure',
self.server, self.serverid, temp_server_id) app.CONN.server, app.CONN.machine_identifier, temp_server_id)
answer = False answer = False
return answer return answer
@ -305,7 +250,7 @@ class InitialSetup(object):
""" """
server = None server = None
# If no server is set, let user choose one # If no server is set, let user choose one
if not self.server or not self.serverid: if not app.CONN.server or not app.CONN.machine_identifier:
showDialog = True showDialog = True
if showDialog is True: if showDialog is True:
server = self._user_pick_pms() server = self._user_pick_pms()
@ -328,13 +273,13 @@ class InitialSetup(object):
if https_updated is False: if https_updated is False:
serverlist = PF.discover_pms(self.plex_token) serverlist = PF.discover_pms(self.plex_token)
for item in serverlist: for item in serverlist:
if item.get('machineIdentifier') == self.serverid: if item.get('machineIdentifier') == app.CONN.machine_identifier:
server = item server = item
if server is None: if server is None:
name = utils.settings('plex_servername') name = utils.settings('plex_servername')
LOG.warn('The PMS you have used before with a unique ' LOG.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is ' 'machineIdentifier of %s and name %s is '
'offline', self.serverid, name) 'offline', app.CONN.machine_identifier, name)
return return
chk = self._check_pms_connectivity(server) chk = self._check_pms_connectivity(server)
if chk == 504 and https_updated is False: if chk == 504 and https_updated is False:
@ -535,7 +480,8 @@ class InitialSetup(object):
# Do we need to migrate stuff? # Do we need to migrate stuff?
migration.check_migration() migration.check_migration()
# Reload the server IP cause we might've deleted it during migration # Reload the server IP cause we might've deleted it during migration
self.server = userclient.UserClient().get_server() app.CONN.load()
app.CONN.server = app.CONN.server
# Display a warning if Kodi puts ALL movies into the queue, basically # Display a warning if Kodi puts ALL movies into the queue, basically
# breaking playback reporting for PKC # breaking playback reporting for PKC
@ -556,19 +502,19 @@ class InitialSetup(object):
# If a Plex server IP has already been set # If a Plex server IP has already been set
# return only if the right machine identifier is found # return only if the right machine identifier is found
if self.server: if app.CONN.server:
LOG.info("PMS is already set: %s. Checking now...", self.server) LOG.info("PMS is already set: %s. Checking now...", app.CONN.server)
if self.check_existing_pms(): if self.check_existing_pms():
LOG.info("Using PMS %s with machineIdentifier %s", LOG.info("Using PMS %s with machineIdentifier %s",
self.server, self.serverid) app.CONN.server, app.CONN.machine_identifier)
_write_pms_settings(self.server, self.pms_token) _write_pms_settings(app.CONN.server, self.pms_token)
if reboot is True: if reboot is True:
utils.reboot_kodi() utils.reboot_kodi()
return return
# If not already retrieved myplex info, optionally let user sign in # If not already retrieved myplex info, optionally let user sign in
# to plex.tv. This DOES get called on very first install run # to plex.tv. This DOES get called on very first install run
if not self.plex_token and self.myplexlogin: if not self.plex_token and app.ACCOUNT.myplexlogin:
self.plex_tv_sign_in() self.plex_tv_sign_in()
server = self.pick_pms() server = self.pick_pms()

View file

@ -7,7 +7,7 @@ from ntpath import dirname
from ..plex_api import API from ..plex_api import API
from ..plex_db import PlexDB from ..plex_db import PlexDB
from ..kodi_db import KodiVideoDB from ..kodi_db import KodiVideoDB
from .. import utils from .. import utils, timing
LOG = getLogger('PLEX.itemtypes.common') LOG = getLogger('PLEX.itemtypes.common')
@ -135,5 +135,5 @@ class ItemBase(object):
resume, resume,
duration, duration,
view_count, view_count,
utils.unix_date_to_kodi(lastViewedAt), timing.plex_date_to_kodi(lastViewedAt),
plex_type) plex_type)

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase from .common import ItemBase
from ..plex_api import API from ..plex_api import API
from .. import state, variables as v, plex_functions as PF from .. import app, variables as v, plex_functions as PF
LOG = getLogger('PLEX.movies') LOG = getLogger('PLEX.movies')
@ -50,8 +50,8 @@ class Movie(ItemBase):
studios = api.music_studio_list() studios = api.music_studio_list()
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
do_indirect = not state.DIRECT_PATHS do_indirect = not app.SYNC.direct_paths
if state.DIRECT_PATHS: if app.SYNC.direct_paths:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
playurl = api.file_path(force_first_media=True) playurl = api.file_path(force_first_media=True)
if playurl is None: if playurl is None:

View file

@ -7,7 +7,7 @@ from .common import ItemBase
from ..plex_api import API from ..plex_api import API
from ..plex_db import PlexDB from ..plex_db import PlexDB
from ..kodi_db import KodiMusicDB from ..kodi_db import KodiMusicDB
from .. import plex_functions as PF, utils, state, variables as v from .. import plex_functions as PF, utils, timing, app, variables as v
LOG = getLogger('PLEX.music') LOG = getLogger('PLEX.music')
@ -165,12 +165,11 @@ class Artist(MusicMixin, ItemBase):
# Kodi doesn't allow that. In case that happens we just merge the # Kodi doesn't allow that. In case that happens we just merge the
# artist entries. # artist entries.
kodi_id = self.kodidb.add_artist(api.title(), musicBrainzId) kodi_id = self.kodidb.add_artist(api.title(), musicBrainzId)
# Create the reference in plex table
self.kodidb.update_artist(api.list_to_string(api.genre_list()), self.kodidb.update_artist(api.list_to_string(api.genre_list()),
api.plot(), api.plot(),
thumb, thumb,
fanart, fanart,
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
kodi_id) kodi_id)
# Update artwork # Update artwork
self.kodidb.modify_artwork(artworks, self.kodidb.modify_artwork(artworks,
@ -256,7 +255,7 @@ class Album(MusicMixin, ItemBase):
thumb, thumb,
api.music_studio(), api.music_studio(),
userdata['UserRating'], userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album', 'album',
kodi_id) kodi_id)
else: else:
@ -270,7 +269,7 @@ class Album(MusicMixin, ItemBase):
thumb, thumb,
api.music_studio(), api.music_studio(),
userdata['UserRating'], userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album', 'album',
kodi_id) kodi_id)
# OR ADD THE ALBUM ##### # OR ADD THE ALBUM #####
@ -289,7 +288,7 @@ class Album(MusicMixin, ItemBase):
thumb, thumb,
api.music_studio(), api.music_studio(),
userdata['UserRating'], userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album') 'album')
else: else:
self.kodidb.add_album_17(kodi_id, self.kodidb.add_album_17(kodi_id,
@ -303,7 +302,7 @@ class Album(MusicMixin, ItemBase):
thumb, thumb,
api.music_studio(), api.music_studio(),
userdata['UserRating'], userdata['UserRating'],
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album') 'album')
self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name()) self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name())
self.kodidb.add_discography(artist_id, name, api.year()) self.kodidb.add_discography(artist_id, name, api.year())
@ -397,7 +396,7 @@ class Song(MusicMixin, ItemBase):
None, None,
None, None,
None, None,
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'single') 'single')
else: else:
self.kodidb.add_album_17(kodi_id, self.kodidb.add_album_17(kodi_id,
@ -411,7 +410,7 @@ class Song(MusicMixin, ItemBase):
None, None,
None, None,
None, None,
utils.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'single') 'single')
else: else:
album = self.plexdb.album(album_id) album = self.plexdb.album(album_id)
@ -469,8 +468,8 @@ class Song(MusicMixin, ItemBase):
mood = api.list_to_string(moods) mood = api.list_to_string(moods)
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
do_indirect = not state.DIRECT_PATHS do_indirect = not app.SYNC.direct_paths
if state.DIRECT_PATHS: if app.SYNC.direct_paths:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
playurl = api.file_path(force_first_media=True) playurl = api.file_path(force_first_media=True)
if playurl is None: if playurl is None:
@ -489,8 +488,7 @@ class Song(MusicMixin, ItemBase):
path = playurl.replace(filename, "") path = playurl.replace(filename, "")
if do_indirect: if do_indirect:
# Plex works a bit differently # Plex works a bit differently
path = "%s%s" % (utils.window('pms_server'), path = "%s%s" % (app.CONN.server, xml[0][0].get('key'))
xml[0][0].get('key'))
path = api.attach_plex_token_to_url(path) path = api.attach_plex_token_to_url(path)
filename = path.rsplit('/', 1)[1] filename = path.rsplit('/', 1)[1]
path = path.replace(filename, '') path = path.replace(filename, '')

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase, process_path from .common import ItemBase, process_path
from ..plex_api import API from ..plex_api import API
from .. import plex_functions as PF, state, variables as v from .. import plex_functions as PF, app, variables as v
LOG = getLogger('PLEX.tvshows') LOG = getLogger('PLEX.tvshows')
@ -135,7 +135,7 @@ class Show(ItemBase, TvShowMixin):
studio = api.list_to_string(studios) studio = api.list_to_string(studios)
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
if state.DIRECT_PATHS: if app.SYNC.direct_paths:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
playurl = api.validate_playurl(api.tv_show_path(), playurl = api.validate_playurl(api.tv_show_path(),
api.plex_type(), api.plex_type(),
@ -378,8 +378,8 @@ class Episode(ItemBase, TvShowMixin):
parent_id = season['kodi_id'] parent_id = season['kodi_id']
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
do_indirect = not state.DIRECT_PATHS do_indirect = not app.SYNC.direct_paths
if state.DIRECT_PATHS: if app.SYNC.direct_paths:
playurl = api.file_path(force_first_media=True) playurl = api.file_path(force_first_media=True)
if playurl is None: if playurl is None:
do_indirect = True do_indirect = True
@ -513,7 +513,7 @@ class Episode(ItemBase, TvShowMixin):
userdata['PlayCount'], userdata['PlayCount'],
userdata['LastPlayedDate'], userdata['LastPlayedDate'],
None) # Do send None, we check here None) # Do send None, we check here
if not state.DIRECT_PATHS: if not app.SYNC.direct_paths:
# need to set a SECOND file entry for a path without plex show id # need to set a SECOND file entry for a path without plex show id
filename = api.file_name(force_first_media=True) filename = api.file_name(force_first_media=True)
path = 'plugin://%s.tvshows/' % v.ADDON_ID path = 'plugin://%s.tvshows/' % v.ADDON_ID

View file

@ -8,7 +8,7 @@ from __future__ import absolute_import, division, unicode_literals
from json import loads, dumps from json import loads, dumps
from xbmc import executeJSONRPC from xbmc import executeJSONRPC
from . import utils from . import timing
class JsonRPC(object): class JsonRPC(object):
@ -156,7 +156,7 @@ def seek_to(offset):
for playerid in get_player_ids(): for playerid in get_player_ids():
JsonRPC("Player.Seek").execute( JsonRPC("Player.Seek").execute(
{"playerid": playerid, {"playerid": playerid,
"value": utils.millis_to_kodi_time(offset)}) "value": timing.millis_to_kodi_time(offset)})
def smallforward(): def smallforward():

View file

@ -7,7 +7,7 @@ from .video import KodiVideoDB
from .music import KodiMusicDB from .music import KodiMusicDB
from .texture import KodiTextureDB from .texture import KodiTextureDB
from .. import path_ops, utils, variables as v from .. import path_ops, utils, timing, variables as v
LOG = getLogger('PLEX.kodi_db') LOG = getLogger('PLEX.kodi_db')
@ -68,7 +68,7 @@ def setup_kodi_default_entries():
VALUES (?, ?, ?) VALUES (?, ?, ?)
''', (v.DB_MUSIC_VERSION[v.KODIVERSION], ''', (v.DB_MUSIC_VERSION[v.KODIVERSION],
0, 0,
utils.unix_date_to_kodi(utils.unix_timestamp()))) timing.kodi_now()))
def reset_cached_images(): def reset_cached_images():

View file

@ -5,7 +5,7 @@ from logging import getLogger
from sqlite3 import IntegrityError from sqlite3 import IntegrityError
from . import common from . import common
from .. import path_ops, utils, variables as v, state from .. import path_ops, timing, variables as v, app
LOG = getLogger('PLEX.kodi_db.video') LOG = getLogger('PLEX.kodi_db.video')
@ -74,12 +74,11 @@ class KodiVideoDB(common.KodiDBBase):
if pathid is None: if pathid is None:
self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path") self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path")
pathid = self.cursor.fetchone()[0] + 1 pathid = self.cursor.fetchone()[0] + 1
datetime = utils.unix_date_to_kodi(utils.unix_timestamp())
self.cursor.execute(''' self.cursor.execute('''
INSERT INTO path(idPath, strPath, dateAdded) INSERT INTO path(idPath, strPath, dateAdded)
VALUES (?, ?, ?) VALUES (?, ?, ?)
''', ''',
(pathid, parentpath, datetime)) (pathid, parentpath, timing.kodi_now()))
if parentpath != path: if parentpath != path:
# In case we end up having media in the filesystem root, C:\ # In case we end up having media in the filesystem root, C:\
parent_id = self.parent_path_id(parentpath) parent_id = self.parent_path_id(parentpath)
@ -198,7 +197,7 @@ class KodiVideoDB(common.KodiDBBase):
Passing plex_type = v.PLEX_TYPE_EPISODE deletes any secondary files for Passing plex_type = v.PLEX_TYPE_EPISODE deletes any secondary files for
add-on paths add-on paths
""" """
if not state.DIRECT_PATHS and plex_type == v.PLEX_TYPE_EPISODE: if not app.SYNC.direct_paths and plex_type == v.PLEX_TYPE_EPISODE:
# Hack for the 2 entries for episodes for addon paths # Hack for the 2 entries for episodes for addon paths
self.cursor.execute('SELECT strFilename FROM files WHERE idFile = ? LIMIT 1', self.cursor.execute('SELECT strFilename FROM files WHERE idFile = ? LIMIT 1',
(file_id, )) (file_id, ))
@ -598,7 +597,7 @@ class KodiVideoDB(common.KodiDBBase):
Adds a resume marker for a video library item. Will even set 2, Adds a resume marker for a video library item. Will even set 2,
considering add-on path widget hacks. considering add-on path widget hacks.
""" """
if not state.DIRECT_PATHS and plex_type == v.PLEX_TYPE_EPISODE: if not app.SYNC.direct_paths and plex_type == v.PLEX_TYPE_EPISODE:
# Need to make sure to set a SECOND bookmark entry for another, # Need to make sure to set a SECOND bookmark entry for another,
# second file_id that points to the path .tvshows instead of # second file_id that points to the path .tvshows instead of
# .tvshows/<plex show id/! # .tvshows/<plex show id/!

View file

@ -6,23 +6,16 @@ PKC Kodi Monitoring implementation
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from json import loads from json import loads
from threading import Thread
import copy import copy
import xbmc import xbmc
from xbmcgui import Window from xbmcgui import Window
from .plex_db import PlexDB from .plex_db import PlexDB
from . import kodi_db from . import kodi_db
from . import utils
from . import plex_functions as PF
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import playback from . import utils, timing, plex_functions as PF, playback, initialsetup
from . import initialsetup from . import json_rpc as js, playqueue as PQ, playlist_func as PL
from . import playqueue as PQ from . import backgroundthread, app, variables as v
from . import json_rpc as js
from . import playlist_func as PL
from . import state
from . import variables as v
############################################################################### ###############################################################################
@ -68,9 +61,9 @@ class KodiMonitor(xbmc.Monitor):
self._already_slept = False self._already_slept = False
self.hack_replay = None self.hack_replay = None
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
for playerid in state.PLAYER_STATES: for playerid in app.PLAYSTATE.player_states:
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE) app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE) app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
LOG.info("Kodi monitor started.") LOG.info("Kodi monitor started.")
def onScanStarted(self, library): def onScanStarted(self, library):
@ -142,8 +135,8 @@ class KodiMonitor(xbmc.Monitor):
self.hack_replay = None self.hack_replay = None
if method == "Player.OnPlay": if method == "Player.OnPlay":
state.SUSPEND_SYNC = True app.SYNC.suspend_sync = True
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self.PlayBackStart(data) self.PlayBackStart(data)
elif method == "Player.OnStop": elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck # Should refresh our video nodes, e.g. on deck
@ -152,27 +145,27 @@ class KodiMonitor(xbmc.Monitor):
self.hack_replay == data['item']): self.hack_replay == data['item']):
# Hack for add-on paths # Hack for add-on paths
self.hack_replay = None self.hack_replay = None
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self._hack_addon_paths_replay_video() self._hack_addon_paths_replay_video()
elif data.get('end'): elif data.get('end'):
if state.PKC_CAUSED_STOP is True: if app.PLAYSTATE.pkc_caused_stop is True:
state.PKC_CAUSED_STOP = False app.PLAYSTATE.pkc_caused_stop = False
LOG.debug('PKC caused this playback stop - ignoring') LOG.debug('PKC caused this playback stop - ignoring')
else: else:
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
_playback_cleanup(ended=True) _playback_cleanup(ended=True)
else: else:
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
_playback_cleanup() _playback_cleanup()
state.PKC_CAUSED_STOP_DONE = True app.PLAYSTATE.pkc_caused_stop_done = True
state.SUSPEND_SYNC = False app.SYNC.suspend_sync = False
elif method == 'Playlist.OnAdd': elif method == 'Playlist.OnAdd':
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self._playlist_onadd(data) self._playlist_onadd(data)
elif method == 'Playlist.OnRemove': elif method == 'Playlist.OnRemove':
self._playlist_onremove(data) self._playlist_onremove(data)
elif method == 'Playlist.OnClear': elif method == 'Playlist.OnClear':
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self._playlist_onclear(data) self._playlist_onclear(data)
elif method == "VideoLibrary.OnUpdate": elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched # Manually marking as watched/unwatched
@ -214,7 +207,7 @@ class KodiMonitor(xbmc.Monitor):
utils.plex_command('RUN_LIB_SCAN', 'full') utils.plex_command('RUN_LIB_SCAN', 'full')
elif method == "System.OnQuit": elif method == "System.OnQuit":
LOG.info('Kodi OnQuit detected - shutting down') LOG.info('Kodi OnQuit detected - shutting down')
state.STOP_PKC = True app.APP.stop_pkc = True
@staticmethod @staticmethod
def _hack_addon_paths_replay_video(): def _hack_addon_paths_replay_video():
@ -237,7 +230,7 @@ class KodiMonitor(xbmc.Monitor):
(within the same micro-second!) (within the same micro-second!)
""" """
LOG.info('Detected re-start of playback of last item') LOG.info('Detected re-start of playback of last item')
old = state.OLD_PLAYER_STATES[1] old = app.PLAYSTATE.old_player_states[1]
kwargs = { kwargs = {
'plex_id': old['plex_id'], 'plex_id': old['plex_id'],
'plex_type': old['plex_type'], 'plex_type': old['plex_type'],
@ -261,8 +254,8 @@ class KodiMonitor(xbmc.Monitor):
""" """
if 'id' not in data['item']: if 'id' not in data['item']:
return return
old = state.OLD_PLAYER_STATES[data['playlistid']] old = app.PLAYSTATE.old_player_states[data['playlistid']]
if (not state.DIRECT_PATHS and if (not app.SYNC.direct_paths and
data['position'] == 0 and data['playlistid'] == 1 and data['position'] == 0 and data['playlistid'] == 1 and
not PQ.PLAYQUEUES[data['playlistid']].items and not PQ.PLAYQUEUES[data['playlistid']].items and
data['item']['type'] == old['kodi_type'] and data['item']['type'] == old['kodi_type'] and
@ -399,7 +392,7 @@ class KodiMonitor(xbmc.Monitor):
else: else:
pos = info['position'] if info['position'] != -1 else 0 pos = info['position'] if info['position'] != -1 else 0
LOG.debug('Detected position %s for %s', pos, playqueue) LOG.debug('Detected position %s for %s', pos, playqueue)
status = state.PLAYER_STATES[playerid] status = app.PLAYSTATE.player_states[playerid]
try: try:
item = playqueue.items[pos] item = playqueue.items[pos]
except IndexError: except IndexError:
@ -431,7 +424,7 @@ class KodiMonitor(xbmc.Monitor):
plex_id, plex_type = self._get_ids(kodi_id, kodi_type, path) plex_id, plex_type = self._get_ids(kodi_id, kodi_type, path)
if not plex_id: if not plex_id:
LOG.debug('No Plex id obtained - aborting playback report') LOG.debug('No Plex id obtained - aborting playback report')
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE) app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
return return
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id) item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
item.file = path item.file = path
@ -455,7 +448,7 @@ class KodiMonitor(xbmc.Monitor):
else: else:
container_key = '/library/metadata/%s' % plex_id container_key = '/library/metadata/%s' % plex_id
# Remember that this player has been active # Remember that this player has been active
state.ACTIVE_PLAYERS.add(playerid) app.PLAYSTATE.active_players.add(playerid)
status.update(info) status.update(info)
LOG.debug('Set the Plex container_key to: %s', container_key) LOG.debug('Set the Plex container_key to: %s', container_key)
status['container_key'] = container_key status['container_key'] = container_key
@ -469,8 +462,7 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug('Set the player state: %s', status) LOG.debug('Set the player state: %s', status)
@utils.thread_methods class SpecialMonitor(backgroundthread.KillableThread):
class SpecialMonitor(Thread):
""" """
Detect the resume dialog for widgets. Detect the resume dialog for widgets.
Could also be used to detect external players (see Emby implementation) Could also be used to detect external players (see Emby implementation)
@ -480,15 +472,15 @@ class SpecialMonitor(Thread):
# "Start from beginning", "Play from beginning" # "Start from beginning", "Play from beginning"
strings = (utils.try_encode(utils.lang(12021)), strings = (utils.try_encode(utils.lang(12021)),
utils.try_encode(utils.lang(12023))) utils.try_encode(utils.lang(12023)))
while not self.stopped(): while not self.isCanceled():
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'): if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings: if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
# Remember that the item IS indeed resumable # Remember that the item IS indeed resumable
control = int(Window(10106).getFocusId()) control = int(Window(10106).getFocusId())
state.RESUME_PLAYBACK = True if control == 1001 else False app.PLAYSTATE.resume_playback = True if control == 1001 else False
else: else:
# Different context menu is displayed # Different context menu is displayed
state.RESUME_PLAYBACK = False app.PLAYSTATE.resume_playback = False
if xbmc.getCondVisibility('Window.IsVisible(MyVideoNav.xml)'): if xbmc.getCondVisibility('Window.IsVisible(MyVideoNav.xml)'):
path = xbmc.getInfoLabel('container.folderpath') path = xbmc.getInfoLabel('container.folderpath')
if (isinstance(path, str) and if (isinstance(path, str) and
@ -507,14 +499,14 @@ def _playback_cleanup(ended=False):
timing data otherwise) timing data otherwise)
""" """
LOG.debug('playback_cleanup called. Active players: %s', LOG.debug('playback_cleanup called. Active players: %s',
state.ACTIVE_PLAYERS) app.PLAYSTATE.active_players)
# We might have saved a transient token from a user flinging media via # We might have saved a transient token from a user flinging media via
# Companion (if we could not use the playqueue to store the token) # Companion (if we could not use the playqueue to store the token)
state.PLEX_TRANSIENT_TOKEN = None app.CONN.plex_transient_token = None
for playerid in state.ACTIVE_PLAYERS: for playerid in app.PLAYSTATE.active_players:
status = state.PLAYER_STATES[playerid] status = app.PLAYSTATE.player_states[playerid]
# Remember the last played item later # Remember the last played item later
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(status) app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
# Stop transcoding # Stop transcoding
if status['playmethod'] == 'Transcode': if status['playmethod'] == 'Transcode':
LOG.debug('Tell the PMS to stop transcoding') LOG.debug('Tell the PMS to stop transcoding')
@ -527,9 +519,9 @@ def _playback_cleanup(ended=False):
# started playback via PMS # started playback via PMS
_record_playstate(status, ended) _record_playstate(status, ended)
# Reset the player's status # Reset the player's status
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE) app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
# As all playback has halted, reset the players that have been active # As all playback has halted, reset the players that have been active
state.ACTIVE_PLAYERS = set() app.PLAYSTATE.active_players = set()
LOG.info('Finished PKC playback cleanup') LOG.info('Finished PKC playback cleanup')
@ -543,12 +535,12 @@ def _record_playstate(status, ended):
# Item not (yet) in Kodi library # Item not (yet) in Kodi library
LOG.debug('No playstate update due to Plex id not found: %s', status) LOG.debug('No playstate update due to Plex id not found: %s', status)
return return
totaltime = float(utils.kodi_time_to_millis(status['totaltime'])) / 1000 totaltime = float(timing.kodi_time_to_millis(status['totaltime'])) / 1000
if ended: if ended:
progress = 0.99 progress = 0.99
time = v.IGNORE_SECONDS_AT_START + 1 time = v.IGNORE_SECONDS_AT_START + 1
else: else:
time = float(utils.kodi_time_to_millis(status['time'])) / 1000 time = float(timing.kodi_time_to_millis(status['time'])) / 1000
try: try:
progress = time / totaltime progress = time / totaltime
except ZeroDivisionError: except ZeroDivisionError:
@ -556,7 +548,7 @@ def _record_playstate(status, ended):
LOG.debug('Playback progress %s (%s of %s seconds)', LOG.debug('Playback progress %s (%s of %s seconds)',
progress, time, totaltime) progress, time, totaltime)
playcount = status['playcount'] playcount = status['playcount']
last_played = utils.unix_date_to_kodi(utils.unix_timestamp()) last_played = timing.now()
if playcount is None: if playcount is None:
LOG.debug('playcount not found, looking it up in the Kodi DB') LOG.debug('playcount not found, looking it up in the Kodi DB')
with kodi_db.KodiVideoDB() as kodidb: with kodi_db.KodiVideoDB() as kodidb:
@ -582,7 +574,7 @@ def _record_playstate(status, ended):
last_played, last_played,
status['plex_type']) status['plex_type'])
# Hack to force "in progress" widget to appear if it wasn't visible before # Hack to force "in progress" widget to appear if it wasn't visible before
if (state.FORCE_RELOAD_SKIN and if (app.APP.force_reload_skin and
xbmc.getCondVisibility('Window.IsVisible(Home.xml)')): xbmc.getCondVisibility('Window.IsVisible(Home.xml)')):
LOG.debug('Refreshing skin to update widgets') LOG.debug('Refreshing skin to update widgets')
xbmc.executebuiltin('ReloadSkin()') xbmc.executebuiltin('ReloadSkin()')

View file

@ -3,13 +3,13 @@
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
import xbmc import xbmc
from .. import state from .. import app
class libsync_mixin(object): class libsync_mixin(object):
def isCanceled(self): def isCanceled(self):
return (self._canceled or state.STOP_PKC or state.STOP_SYNC or return (self._canceled or app.APP.stop_pkc or app.SYNC.stop_sync or
state.SUSPEND_LIBRARY_THREAD or state.SUSPEND_SYNC) app.SYNC.suspend_library_thread or app.SYNC.suspend_sync)
def update_kodi_library(video=True, music=True): def update_kodi_library(video=True, music=True):

View file

@ -8,7 +8,7 @@ from ..plex_api import API
from ..plex_db import PlexDB from ..plex_db import PlexDB
from ..kodi_db import KodiVideoDB from ..kodi_db import KodiVideoDB
from .. import backgroundthread, utils from .. import backgroundthread, utils
from .. import itemtypes, plex_functions as PF, variables as v, state from .. import itemtypes, plex_functions as PF, variables as v, app
LOG = getLogger('PLEX.sync.fanart') LOG = getLogger('PLEX.sync.fanart')
@ -27,14 +27,11 @@ class FanartThread(backgroundthread.KillableThread):
self.refresh = refresh self.refresh = refresh
super(FanartThread, self).__init__() super(FanartThread, self).__init__()
def isCanceled(self):
return state.STOP_PKC
def isSuspended(self): def isSuspended(self):
return (state.SUSPEND_LIBRARY_THREAD or return (app.SYNC.suspend_library_thread or
state.STOP_SYNC or app.SYNC.stop_sync or
state.DB_SCAN or app.SYNC.db_scan or
state.SUSPEND_SYNC) app.SYNC.suspend_sync)
def run(self): def run(self):
try: try:
@ -80,8 +77,6 @@ class FanartTask(backgroundthread.Task, common.libsync_mixin):
self.refresh = refresh self.refresh = refresh
def run(self): def run(self):
if self.isCanceled():
return
process_fanart(self.plex_id, self.plex_type, self.refresh) process_fanart(self.plex_id, self.plex_type, self.refresh)

View file

@ -7,7 +7,7 @@ import xbmc
from .get_metadata import GetMetadataTask, reset_collections from .get_metadata import GetMetadataTask, reset_collections
from .process_metadata import InitNewSection, UpdateLastSync, ProcessMetadata from .process_metadata import InitNewSection, UpdateLastSync, ProcessMetadata
from . import common, sections from . import common, sections
from .. import utils, backgroundthread, variables as v, state from .. import utils, timing, backgroundthread, variables as v, app
from .. import plex_functions as PF, itemtypes from .. import plex_functions as PF, itemtypes
from ..plex_db import PlexDB from ..plex_db import PlexDB
@ -97,7 +97,7 @@ class FullSync(common.libsync_mixin):
if self.isCanceled(): if self.isCanceled():
return False return False
if not self.install_sync_done: if not self.install_sync_done:
state.PATH_VERIFIED = False app.SYNC.path_verified = False
try: try:
# Sync new, updated and deleted items # Sync new, updated and deleted items
iterator = PF.SectionItems(section['section_id'], iterator = PF.SectionItems(section['section_id'],
@ -157,7 +157,7 @@ class FullSync(common.libsync_mixin):
(v.PLEX_TYPE_SEASON, v.PLEX_TYPE_SHOW, itemtypes.Season, False), (v.PLEX_TYPE_SEASON, v.PLEX_TYPE_SHOW, itemtypes.Season, False),
(v.PLEX_TYPE_EPISODE, v.PLEX_TYPE_SHOW, itemtypes.Episode, False) (v.PLEX_TYPE_EPISODE, v.PLEX_TYPE_SHOW, itemtypes.Episode, False)
] ]
if state.ENABLE_MUSIC: if app.SYNC.enable_music:
kinds.extend([ kinds.extend([
(v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_ARTIST, itemtypes.Artist, False), (v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
(v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ARTIST, itemtypes.Album, True), (v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ARTIST, itemtypes.Album, True),
@ -181,7 +181,7 @@ class FullSync(common.libsync_mixin):
if self.isCanceled(): if self.isCanceled():
return return
successful = False successful = False
self.current_sync = utils.unix_timestamp() self.current_sync = timing.unix_timestamp()
# Delete playlist and video node files from Kodi # Delete playlist and video node files from Kodi
utils.delete_playlists() utils.delete_playlists()
utils.delete_nodes() utils.delete_nodes()

View file

@ -102,8 +102,8 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
while not self.isCanceled(): while not self.isCanceled():
if section is None: if section is None:
break break
LOG.debug('Start processing section %s: %s', LOG.debug('Start processing section %s (%ss)',
section.plex_type, section.name) section.name, section.plex_type)
self.current = 1 self.current = 1
self.processed = 0 self.processed = 0
self.total = section.total self.total = section.total

View file

@ -9,7 +9,7 @@ from ..utils import cast
from ..plex_db import PlexDB from ..plex_db import PlexDB
from .. import kodi_db from .. import kodi_db
from .. import itemtypes from .. import itemtypes
from .. import plex_functions as PF, music, utils, state, variables as v from .. import plex_functions as PF, music, utils, variables as v, app
LOG = getLogger('PLEX.sync.sections') LOG = getLogger('PLEX.sync.sections')
@ -29,7 +29,7 @@ def sync_from_pms():
except AttributeError: except AttributeError:
LOG.error("Error download PMS sections, abort") LOG.error("Error download PMS sections, abort")
return False return False
if state.DIRECT_PATHS is True and state.ENABLE_MUSIC is True: if app.SYNC.direct_paths is True and app.SYNC.enable_music is True:
# Will reboot Kodi is new library detected # Will reboot Kodi is new library detected
music.excludefromscan_music_folders(xml=sections) music.excludefromscan_music_folders(xml=sections)

View file

@ -5,7 +5,7 @@ from logging import getLogger
import xbmc import xbmc
from .. import plex_functions as PF, utils, variables as v, state from .. import plex_functions as PF, utils, timing, variables as v
LOG = getLogger('PLEX.sync.time') LOG = getLogger('PLEX.sync.time')
@ -84,7 +84,7 @@ def sync_pms_time():
LOG.debug('No timestamp; using 0') LOG.debug('No timestamp; using 0')
timestamp = utils.cast(int, timestamp) timestamp = utils.cast(int, timestamp)
# Set the timer # Set the timer
koditime = utils.unix_timestamp() koditime = timing.unix_timestamp()
# Toggle watched state # Toggle watched state
PF.scrobble(plex_id, 'watched') PF.scrobble(plex_id, 'watched')
# Let the PMS process this first! # Let the PMS process this first!
@ -100,9 +100,9 @@ def sync_pms_time():
return False return False
# Calculate time offset Kodi-PMS # Calculate time offset Kodi-PMS
state.KODI_PLEX_TIME_OFFSET = float(koditime) - float(plextime) timing.KODI_PLEX_TIME_OFFSET = float(koditime) - float(plextime)
utils.settings('kodiplextimeoffset', utils.settings('kodiplextimeoffset',
value=str(state.KODI_PLEX_TIME_OFFSET)) value=str(timing.KODI_PLEX_TIME_OFFSET))
LOG.info("Time offset Koditime - Plextime in seconds: %s", LOG.info("Time offset Koditime - Plextime in seconds: %s",
state.KODI_PLEX_TIME_OFFSET) timing.KODI_PLEX_TIME_OFFSET)
return True return True

View file

@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from ..utils import etree from ..utils import etree
from .. import utils, path_ops, variables as v, state from .. import utils, path_ops, variables as v, app
############################################################################### ###############################################################################
@ -44,7 +44,7 @@ class VideoNodes(object):
delete=False): delete=False):
# Plex: reassign mediatype due to Kodi inner workings # Plex: reassign mediatype due to Kodi inner workings
# How many items do we get at most? # How many items do we get at most?
limit = state.FETCH_PMS_ITEM_NUMBER limit = unicode(app.APP.fetch_pms_item_number)
mediatypes = { mediatypes = {
'movie': 'movies', 'movie': 'movies',
'show': 'tvshows', 'show': 'tvshows',

View file

@ -10,7 +10,7 @@ from ..plex_api import API
from ..plex_db import PlexDB from ..plex_db import PlexDB
from .. import kodi_db from .. import kodi_db
from .. import backgroundthread, playlists, plex_functions as PF, itemtypes from .. import backgroundthread, playlists, plex_functions as PF, itemtypes
from .. import artwork, utils, variables as v, state from .. import artwork, utils, timing, variables as v, app
LOG = getLogger('PLEX.sync.websocket') LOG = getLogger('PLEX.sync.websocket')
@ -22,7 +22,7 @@ PLAYSTATE_SESSIONS = {}
def interrupt_processing(): def interrupt_processing():
return state.STOP_PKC or state.SUSPEND_LIBRARY_THREAD or state.STOP_SYNC return app.APP.stop_pkc or app.SYNC.suspend_library_thread or app.SYNC.stop_sync
def multi_delete(input_list, delete_list): def multi_delete(input_list, delete_list):
@ -75,7 +75,7 @@ def process_websocket_messages():
9: 'deleted' 9: 'deleted'
""" """
global WEBSOCKET_MESSAGES global WEBSOCKET_MESSAGES
now = utils.unix_timestamp() now = timing.unix_timestamp()
update_kodi_video_library, update_kodi_music_library = False, False update_kodi_video_library, update_kodi_music_library = False, False
delete_list = [] delete_list = []
for i, message in enumerate(WEBSOCKET_MESSAGES): for i, message in enumerate(WEBSOCKET_MESSAGES):
@ -84,7 +84,7 @@ def process_websocket_messages():
break break
if message['state'] == 9: if message['state'] == 9:
successful, video, music = process_delete_message(message) successful, video, music = process_delete_message(message)
elif now - message['timestamp'] < state.BACKGROUNDSYNC_SAFTYMARGIN: elif now - message['timestamp'] < app.SYNC.backgroundsync_saftymargin:
# We haven't waited long enough for the PMS to finish processing the # We haven't waited long enough for the PMS to finish processing the
# item. Do it later (excepting deletions) # item. Do it later (excepting deletions)
continue continue
@ -127,7 +127,7 @@ def process_new_item_message(message):
LOG.error('Could not download metadata for %s', message['plex_id']) LOG.error('Could not download metadata for %s', message['plex_id'])
return False, False, False return False, False, False
LOG.debug("Processing new/updated PMS item: %s", message['plex_id']) LOG.debug("Processing new/updated PMS item: %s", message['plex_id'])
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](utils.unix_timestamp()) as typus: with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](timing.unix_timestamp()) as typus:
typus.add_update(xml[0], typus.add_update(xml[0],
section_name=xml.get('librarySectionTitle'), section_name=xml.get('librarySectionTitle'),
section_id=xml.get('librarySectionID')) section_id=xml.get('librarySectionID'))
@ -169,7 +169,7 @@ def store_timeline_message(data):
'state': status, 'state': status,
'plex_type': typus, 'plex_type': typus,
'plex_id': utils.cast(int, message['itemID']), 'plex_id': utils.cast(int, message['itemID']),
'timestamp': utils.unix_timestamp(), 'timestamp': timing.unix_timestamp(),
'attempt': 0 'attempt': 0
}) })
elif typus in (v.PLEX_TYPE_MOVIE, elif typus in (v.PLEX_TYPE_MOVIE,
@ -186,7 +186,7 @@ def store_timeline_message(data):
'state': status, 'state': status,
'plex_type': typus, 'plex_type': typus,
'plex_id': plex_id, 'plex_id': plex_id,
'timestamp': utils.unix_timestamp(), 'timestamp': timing.unix_timestamp(),
'attempt': 0 'attempt': 0
}) })
@ -227,7 +227,7 @@ def store_activity_message(data):
'state': None, # Don't need a state here 'state': None, # Don't need a state here
'plex_type': typus['plex_type'], 'plex_type': typus['plex_type'],
'plex_id': plex_id, 'plex_id': plex_id,
'timestamp': utils.unix_timestamp(), 'timestamp': timing.unix_timestamp(),
'attempt': 0 'attempt': 0
}) })
@ -246,7 +246,7 @@ def process_playing(data):
plex_id = utils.cast(int, message['ratingKey']) plex_id = utils.cast(int, message['ratingKey'])
skip = False skip = False
for pid in (0, 1, 2): for pid in (0, 1, 2):
if plex_id == state.PLAYER_STATES[pid]['plex_id']: if plex_id == app.PLAYSTATE.player_states[pid]['plex_id']:
# Kodi is playing this message - no need to set the playstate # Kodi is playing this message - no need to set the playstate
skip = True skip = True
if skip: if skip:
@ -265,7 +265,7 @@ def process_playing(data):
PLAYSTATE_SESSIONS[session_key] = {} PLAYSTATE_SESSIONS[session_key] = {}
else: else:
# PMS is ours - get all current sessions # PMS is ours - get all current sessions
PLAYSTATE_SESSIONS.update(PF.GetPMSStatus(state.PLEX_TOKEN)) PLAYSTATE_SESSIONS.update(PF.GetPMSStatus(app.CONN.plex_token))
LOG.debug('Updated current sessions. They are: %s', LOG.debug('Updated current sessions. They are: %s',
PLAYSTATE_SESSIONS) PLAYSTATE_SESSIONS)
if session_key not in PLAYSTATE_SESSIONS: if session_key not in PLAYSTATE_SESSIONS:
@ -281,18 +281,18 @@ def process_playing(data):
# 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 not state.PLEX_TOKEN and session['userId'] == '1': if not app.CONN.plex_token and session['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 (session['userId'] == state.PLEX_USER_ID or elif not (session['userId'] == app.CONN.plex_user_id or
session['username'] == state.PLEX_USERNAME): session['username'] == app.CONN.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',
state.PLEX_USERNAME, app.CONN.plex_username,
state.PLEX_USER_ID, app.CONN.plex_user_id,
session['username'], session['username'],
session['userId']) session['userId'])
continue continue
@ -334,7 +334,7 @@ def process_playing(data):
mark_played = False mark_played = False
LOG.debug('Update playstate for user %s for %s with plex id %s to ' LOG.debug('Update playstate for user %s for %s with plex id %s to '
'viewCount %s, resume %s, mark_played %s', 'viewCount %s, resume %s, mark_played %s',
state.PLEX_USERNAME, session['kodi_type'], plex_id, app.CONN.plex_username, session['kodi_type'], plex_id,
session['viewCount'], resume, mark_played) session['viewCount'], resume, mark_played)
func = itemtypes.ITEMTYPE_FROM_KODITYPE[session['kodi_type']] func = itemtypes.ITEMTYPE_FROM_KODITYPE[session['kodi_type']]
with func(None) as fkt: with func(None) as fkt:
@ -343,7 +343,7 @@ def process_playing(data):
resume, resume,
session['duration'], session['duration'],
session['file_id'], session['file_id'],
utils.unix_timestamp(), timing.unix_timestamp(),
v.PLEX_TYPE_FROM_KODI_TYPE[session['kodi_type']]) v.PLEX_TYPE_FROM_KODI_TYPE[session['kodi_type']])

View file

@ -21,7 +21,7 @@ from . import pickler
from .playutils import PlayUtils from .playutils import PlayUtils
from .pkc_listitem import PKCListItem from .pkc_listitem import PKCListItem
from . import variables as v from . import variables as v
from . import state from . import app
############################################################################### ###############################################################################
LOG = getLogger('PLEX.playback') LOG = getLogger('PLEX.playback')
@ -51,14 +51,14 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
'resolve %s', plex_id, plex_type, path, resolve) 'resolve %s', plex_id, plex_type, path, resolve)
global RESOLVE global RESOLVE
# If started via Kodi context menu, we never resolve # If started via Kodi context menu, we never resolve
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False RESOLVE = resolve if not app.PLAYSTATE.context_menu_play else False
if not state.AUTHENTICATED: if not app.CONN.authenticated:
LOG.error('Not yet authenticated for PMS, abort starting playback') LOG.error('Not yet authenticated for PMS, abort starting playback')
# "Unauthorized for PMS" # "Unauthorized for PMS"
utils.dialog('notification', utils.lang(29999), utils.lang(30017)) utils.dialog('notification', utils.lang(29999), utils.lang(30017))
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
try: try:
@ -208,7 +208,7 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
if RESOLVE: if RESOLVE:
# Sleep a bit to let setResolvedUrl do its thing - bit ugly # Sleep a bit to let setResolvedUrl do its thing - bit ugly
sleep_timer = 0 sleep_timer = 0
while not state.PKC_CAUSED_STOP_DONE: while not app.PLAYSTATE.pkc_caused_stop_done:
sleep(50) sleep(50)
sleep_timer += 1 sleep_timer += 1
if sleep_timer > 100: if sleep_timer > 100:
@ -229,19 +229,19 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
utils.lang(30128), utils.lang(30128),
icon='{error}') icon='{error}')
# Do NOT use _ensure_resolve() because we resolved above already # Do NOT use _ensure_resolve() because we resolved above already
state.CONTEXT_MENU_PLAY = False app.PLAYSTATE.context_menu_play = False
state.FORCE_TRANSCODE = False app.PLAYSTATE.force_transcode = False
state.RESUME_PLAYBACK = False app.PLAYSTATE.resume_playback = False
return return
PL.get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
stack = _prep_playlist_stack(xml) stack = _prep_playlist_stack(xml)
_process_stack(playqueue, stack) _process_stack(playqueue, stack)
# Always resume if playback initiated via PMS and there IS a resume # Always resume if playback initiated via PMS and there IS a resume
# point # point
offset = api.resume_point() * 1000 if state.CONTEXT_MENU_PLAY else None offset = api.resume_point() * 1000 if app.PLAYSTATE.context_menu_play else None
# Reset some playback variables # Reset some playback variables
state.CONTEXT_MENU_PLAY = False app.PLAYSTATE.context_menu_play = False
state.FORCE_TRANSCODE = False app.PLAYSTATE.force_transcode = False
# New thread to release this one sooner (e.g. harddisk spinning up) # New thread to release this one sooner (e.g. harddisk spinning up)
thread = Thread(target=threaded_playback, thread = Thread(target=threaded_playback,
args=(playqueue.kodi_pl, pos, offset)) args=(playqueue.kodi_pl, pos, offset))
@ -272,8 +272,8 @@ def _ensure_resolve(abort=False):
LOG.debug('Passing dummy path to Kodi') LOG.debug('Passing dummy path to Kodi')
# if not state.CONTEXT_MENU_PLAY: # if not state.CONTEXT_MENU_PLAY:
# Because playback won't start with context menu play # Because playback won't start with context menu play
state.PKC_CAUSED_STOP = True app.PLAYSTATE.pkc_caused_stop = True
state.PKC_CAUSED_STOP_DONE = False app.PLAYSTATE.pkc_caused_stop_done = False
if not abort: if not abort:
result = pickler.Playback_Successful() result = pickler.Playback_Successful()
result.listitem = PKCListItem(path=v.NULL_VIDEO) result.listitem = PKCListItem(path=v.NULL_VIDEO)
@ -283,9 +283,9 @@ def _ensure_resolve(abort=False):
pickler.pickle_me(None) pickler.pickle_me(None)
if abort: if abort:
# Reset some playback variables # Reset some playback variables
state.CONTEXT_MENU_PLAY = False app.PLAYSTATE.context_menu_play = False
state.FORCE_TRANSCODE = False app.PLAYSTATE.force_transcode = False
state.RESUME_PLAYBACK = False app.PLAYSTATE.resume_playback = False
def _init_existing_kodi_playlist(playqueue, pos): def _init_existing_kodi_playlist(playqueue, pos):
@ -299,7 +299,7 @@ def _init_existing_kodi_playlist(playqueue, pos):
LOG.error('No Kodi items returned') LOG.error('No Kodi items returned')
raise PL.PlaylistError('No Kodi items returned') raise PL.PlaylistError('No Kodi items returned')
item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos]) item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos])
item.force_transcode = state.FORCE_TRANSCODE item.force_transcode = app.PLAYSTATE.force_transcode
# playqueue.py will add the rest - this will likely put the PMS under # playqueue.py will add the rest - this will likely put the PMS under
# a LOT of strain if the following Kodi setting is enabled: # a LOT of strain if the following Kodi setting is enabled:
# Settings -> Player -> Videos -> Play next video automatically # Settings -> Player -> Videos -> Play next video automatically
@ -310,7 +310,7 @@ def _prep_playlist_stack(xml):
stack = [] stack = []
for item in xml: for item in xml:
api = API(item) api = API(item)
if (state.CONTEXT_MENU_PLAY is False and if (app.PLAYSTATE.context_menu_play is False and
api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)): api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)):
# If user chose to play via PMS or force transcode, do not # If user chose to play via PMS or force transcode, do not
# use the item path stored in the Kodi DB # use the item path stored in the Kodi DB
@ -378,7 +378,7 @@ def _process_stack(playqueue, stack):
playlist_item.offset = item['offset'] playlist_item.offset = item['offset']
playlist_item.part = item['part'] playlist_item.part = item['part']
playlist_item.id = item['id'] playlist_item.id = item['id']
playlist_item.force_transcode = state.FORCE_TRANSCODE playlist_item.force_transcode = app.PLAYSTATE.force_transcode
pos += 1 pos += 1
@ -413,7 +413,7 @@ def _conclude_playback(playqueue, pos):
playurl = item.file playurl = item.file
if not playurl: if not playurl:
LOG.info('Did not get a playurl, aborting playback silently') LOG.info('Did not get a playurl, aborting playback silently')
state.RESUME_PLAYBACK = False app.PLAYSTATE.resume_playback = False
pickler.pickle_me(result) pickler.pickle_me(result)
return return
listitem.setPath(utils.try_encode(playurl)) listitem.setPath(utils.try_encode(playurl))
@ -422,8 +422,8 @@ def _conclude_playback(playqueue, pos):
elif item.playmethod == 'Transcode': elif item.playmethod == 'Transcode':
playutils.audio_subtitle_prefs(listitem) playutils.audio_subtitle_prefs(listitem)
if state.RESUME_PLAYBACK is True: if app.PLAYSTATE.resume_playback is True:
state.RESUME_PLAYBACK = False app.PLAYSTATE.resume_playback = False
if (item.offset is None and if (item.offset is None and
item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP)): item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP)):
with PlexDB() as plexdb: with PlexDB() as plexdb:

View file

@ -10,7 +10,7 @@ from . import playback
from . import context_entry from . import context_entry
from . import json_rpc as js from . import json_rpc as js
from . import pickler from . import pickler
from . import state from . import app
############################################################################### ###############################################################################
@ -61,7 +61,7 @@ class PlaybackStarter(Thread):
kodi_type=params.get('kodi_type')) kodi_type=params.get('kodi_type'))
def run(self): def run(self):
queue = state.COMMAND_PIPELINE_QUEUE queue = app.APP.command_pipeline_queue
LOG.info("----===## Starting PlaybackStarter ##===----") LOG.info("----===## Starting PlaybackStarter ##===----")
while True: while True:
item = queue.get() item = queue.get()

View file

@ -16,7 +16,7 @@ from .downloadutils import DownloadUtils as DU
from . import utils from . import utils
from . import json_rpc as js from . import json_rpc as js
from . import variables as v from . import variables as v
from . import state from . import app
############################################################################### ###############################################################################
@ -357,7 +357,7 @@ def verify_kodi_item(plex_id, kodi_item):
# Got all the info we need # Got all the info we need
return kodi_item return kodi_item
# Special case playlist startup - got type but no id # Special case playlist startup - got type but no id
if (not state.DIRECT_PATHS and state.ENABLE_MUSIC and if (not app.SYNC.direct_paths and app.SYNC.enable_music and
kodi_item.get('type') == v.KODI_TYPE_SONG and kodi_item.get('type') == v.KODI_TYPE_SONG and
kodi_item['file'].startswith('http')): kodi_item['file'].startswith('http')):
kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'], kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'],

View file

@ -19,7 +19,7 @@ from . import pms, db, kodi_pl, plex_pl
from ..watchdog import events from ..watchdog import events
from ..plex_api import API from ..plex_api import API
from .. import utils, path_ops, variables as v, state from .. import utils, path_ops, variables as v, app
############################################################################### ###############################################################################
LOG = getLogger('PLEX.playlists') LOG = getLogger('PLEX.playlists')
@ -92,7 +92,7 @@ def websocket(plex_id, status):
* 9: 'deleted' * 9: 'deleted'
""" """
create = False create = False
with state.LOCK_PLAYLISTS: with app.APP.lock_playlists:
playlist = db.get_playlist(plex_id=plex_id) playlist = db.get_playlist(plex_id=plex_id)
if plex_id in IGNORE_PLEX_PLAYLIST_CHANGE: if plex_id in IGNORE_PLEX_PLAYLIST_CHANGE:
LOG.debug('Ignoring detected Plex playlist change for %s', LOG.debug('Ignoring detected Plex playlist change for %s',
@ -155,7 +155,7 @@ def full_sync():
fetch the PMS playlists) fetch the PMS playlists)
""" """
LOG.info('Starting playlist full sync') LOG.info('Starting playlist full sync')
with state.LOCK_PLAYLISTS: with app.APP.lock_playlists:
# Need to lock because we're messing with playlists # Need to lock because we're messing with playlists
return _full_sync() return _full_sync()
@ -283,7 +283,7 @@ def sync_kodi_playlist(path):
return False return False
if extension not in SUPPORTED_FILETYPES: if extension not in SUPPORTED_FILETYPES:
return False return False
if not state.SYNC_SPECIFIC_KODI_PLAYLISTS: if not app.SYNC.sync_specific_kodi_playlists:
return True return True
playlist = Playlist() playlist = Playlist()
playlist.kodi_path = path playlist.kodi_path = path
@ -341,10 +341,10 @@ def sync_plex_playlist(playlist=None, xml=None, plex_id=None):
return False return False
name = api.title() name = api.title()
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()] typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()]
if (not state.ENABLE_MUSIC and typus == v.PLEX_PLAYLIST_TYPE_AUDIO): if (not app.SYNC.enable_music and typus == v.PLEX_PLAYLIST_TYPE_AUDIO):
LOG.debug('Not synching Plex audio playlist') LOG.debug('Not synching Plex audio playlist')
return False return False
if not state.SYNC_SPECIFIC_PLEX_PLAYLISTS: if not app.SYNC.sync_specific_plex_playlists:
return True return True
prefix = utils.settings('syncSpecificPlexPlaylistsPrefix').lower() prefix = utils.settings('syncSpecificPlexPlaylistsPrefix').lower()
if name and name.lower().startswith(prefix): if name and name.lower().startswith(prefix):
@ -387,7 +387,7 @@ class PlaylistEventhandler(events.FileSystemEventHandler):
events.EVENT_TYPE_CREATED: self.on_created, events.EVENT_TYPE_CREATED: self.on_created,
events.EVENT_TYPE_DELETED: self.on_deleted, events.EVENT_TYPE_DELETED: self.on_deleted,
} }
with state.LOCK_PLAYLISTS: with app.APP.lock_playlists:
_method_map[event.event_type](event) _method_map[event.event_type](event)
def on_created(self, event): def on_created(self, event):

View file

@ -5,16 +5,11 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
""" """
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from threading import Thread
import xbmc import xbmc
from . import utils
from . import playlist_func as PL
from . import plex_functions as PF
from .plex_api import API from .plex_api import API
from . import json_rpc as js from . import playlist_func as PL, plex_functions as PF
from . import variables as v from . import backgroundthread, utils, json_rpc as js, app, variables as v
from . import state
############################################################################### ###############################################################################
LOG = getLogger('PLEX.playqueue') LOG = getLogger('PLEX.playqueue')
@ -35,7 +30,7 @@ def init_playqueues():
LOG.debug('Playqueues have already been initialized') LOG.debug('Playqueues have already been initialized')
return return
# Initialize Kodi playqueues # Initialize Kodi playqueues
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
for i in (0, 1, 2): for i in (0, 1, 2):
# Just in case the Kodi response is not sorted correctly # Just in case the Kodi response is not sorted correctly
for queue in js.get_playlists(): for queue in js.get_playlists():
@ -96,13 +91,18 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
return playqueue return playqueue
@utils.thread_methods(add_suspends=['PMS_STATUS']) class PlayqueueMonitor(backgroundthread.KillableThread):
class PlayqueueMonitor(Thread):
""" """
Unfortunately, Kodi does not tell if items within a Kodi playqueue Unfortunately, Kodi does not tell if items within a Kodi playqueue
(playlist) are swapped. This is what this monitor is for. Don't replace (playlist) are swapped. This is what this monitor is for. Don't replace
this mechanism till Kodi's implementation of playlists has improved this mechanism till Kodi's implementation of playlists has improved
""" """
def isSuspended(self):
"""
Returns True if the thread is suspended
"""
return self._suspended or app.CONN.pms_status
def _compare_playqueues(self, playqueue, new): def _compare_playqueues(self, playqueue, new):
""" """
Used to poll the Kodi playqueue and update the Plex playqueue if needed Used to poll the Kodi playqueue and update the Plex playqueue if needed
@ -117,7 +117,7 @@ class PlayqueueMonitor(Thread):
# 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.stopped(): if self.isCanceled():
# Chances are that we got an empty Kodi playlist due to # Chances are that we got an empty Kodi playlist due to
# Kodi exit # Kodi exit
return return
@ -178,7 +178,7 @@ class PlayqueueMonitor(Thread):
for j in range(i, len(index)): for j in range(i, len(index)):
index[j] += 1 index[j] += 1
for i in reversed(index): for i in reversed(index):
if self.stopped(): if self.isCanceled():
# Chances are that we got an empty Kodi playlist due to # Chances are that we got an empty Kodi playlist due to
# Kodi exit # Kodi exit
return return
@ -192,20 +192,18 @@ class PlayqueueMonitor(Thread):
LOG.debug('Done comparing playqueues') LOG.debug('Done comparing playqueues')
def run(self): def run(self):
stopped = self.stopped
suspended = self.suspended
LOG.info("----===## Starting PlayqueueMonitor ##===----") LOG.info("----===## Starting PlayqueueMonitor ##===----")
while not stopped(): while not self.isCanceled():
while suspended(): while self.isSuspended():
if stopped(): if self.isCanceled():
break break
xbmc.sleep(1000) xbmc.sleep(1000)
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
for playqueue in PLAYQUEUES: for playqueue in PLAYQUEUES:
kodi_pl = js.playlist_get_items(playqueue.playlistid) kodi_pl = js.playlist_get_items(playqueue.playlistid)
if playqueue.old_kodi_pl != kodi_pl: if playqueue.old_kodi_pl != kodi_pl:
if playqueue.id is None and (not state.DIRECT_PATHS or if playqueue.id is None and (not app.PLAYSTATE.direct_paths or
state.CONTEXT_MENU_PLAY): app.PLAYSTATE.context_menu_play):
# Only initialize if directly fired up using direct # Only initialize if directly fired up using direct
# paths. Otherwise let default.py do its magic # paths. Otherwise let default.py do its magic
LOG.debug('Not yet initiating playback') LOG.debug('Not yet initiating playback')

View file

@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import utils from . import utils, app
from . import variables as v from . import variables as v
############################################################################### ###############################################################################
@ -304,7 +304,7 @@ 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.attach_plex_token_to_url( path = self.api.attach_plex_token_to_url(
"%s%s" % (utils.window('pms_server'), "%s%s" % (app.CONN.server,
stream.attrib['key'])) stream.attrib['key']))
downloadable_streams.append(index) downloadable_streams.append(index)
download_subs.append(utils.try_encode(path)) download_subs.append(utils.try_encode(path))

View file

@ -42,11 +42,11 @@ from .kodi_db import KodiVideoDB, KodiMusicDB
from .utils import cast from .utils import cast
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import clientinfo from . import clientinfo
from . import utils from . import utils, timing
from . import path_ops from . import path_ops
from . import plex_functions as PF from . import plex_functions as PF
from . import variables as v from . import variables as v
from . import state from . import app
############################################################################### ###############################################################################
LOG = getLogger('PLEX.plex_api') LOG = getLogger('PLEX.plex_api')
@ -121,7 +121,7 @@ class API(object):
Pass direct_path=True if you're calling from another Plex python Pass direct_path=True if you're calling from another Plex python
instance - because otherwise direct paths will evaluate to False! instance - because otherwise direct paths will evaluate to False!
""" """
direct_paths = direct_paths or state.DIRECT_PATHS direct_paths = direct_paths or app.SYNC.direct_paths
filename = self.file_path(force_first_media=force_first_media) filename = self.file_path(force_first_media=force_first_media)
if (not direct_paths or force_addon or if (not direct_paths or force_addon or
self.plex_type() == v.PLEX_TYPE_CLIP): self.plex_type() == v.PLEX_TYPE_CLIP):
@ -219,15 +219,15 @@ class API(object):
extension not in v.KODI_SUPPORTED_IMAGES): extension not in v.KODI_SUPPORTED_IMAGES):
# Let Plex transcode # Let Plex transcode
# max width/height supported by plex image transcoder is 1920x1080 # max width/height supported by plex image transcoder is 1920x1080
path = state.PMS_SERVER + PF.transcode_image_path( path = app.CONN.server + PF.transcode_image_path(
self.item[0][0].get('key'), self.item[0][0].get('key'),
state.PMS_TOKEN, app.CONN.pms_token,
"%s%s" % (state.PMS_SERVER, self.item[0][0].get('key')), "%s%s" % (app.CONN.server, self.item[0][0].get('key')),
1920, 1920,
1080) 1080)
else: else:
path = self.attach_plex_token_to_url( path = self.attach_plex_token_to_url(
'%s%s' % (state.PMS_SERVER, self.item[0][0].attrib['key'])) '%s%s' % (app.CONN.server, self.item[0][0].attrib['key']))
# Attach Plex id to url to let it be picked up by our playqueue agent # Attach Plex id to url to let it be picked up by our playqueue agent
# later # later
return utils.try_encode('%s&plex_id=%s' % (path, self.plex_id())) return utils.try_encode('%s&plex_id=%s' % (path, self.plex_id()))
@ -263,10 +263,9 @@ class API(object):
""" """
res = self.item.get('addedAt') res = self.item.get('addedAt')
if res is not None: if res is not None:
res = utils.unix_date_to_kodi(res) return timing.plex_date_to_kodi(res)
else: else:
res = '2000-01-01 10:00:00' return '2000-01-01 10:00:00'
return res
def viewcount(self): def viewcount(self):
""" """
@ -300,11 +299,11 @@ class API(object):
played = True if playcount else False played = True if playcount else False
try: try:
last_played = utils.unix_date_to_kodi(int(item['lastViewedAt'])) last_played = utils.plex_date_to_kodi(int(item['lastViewedAt']))
except (KeyError, ValueError): except (KeyError, ValueError):
last_played = None last_played = None
if state.INDICATE_MEDIA_VERSIONS is True: if app.SYNC.indicate_media_versions is True:
userrating = 0 userrating = 0
for _ in self.item.findall('./Media'): for _ in self.item.findall('./Media'):
userrating += 1 userrating += 1
@ -685,12 +684,12 @@ class API(object):
url may or may not already contain a '?' url may or may not already contain a '?'
""" """
if not state.PMS_TOKEN: if not app.CONN.pms_token:
return url return url
if '?' not in url: if '?' not in url:
url = "%s?X-Plex-Token=%s" % (url, state.PMS_TOKEN) url = "%s?X-Plex-Token=%s" % (url, app.CONN.pms_token)
else: else:
url = "%s&X-Plex-Token=%s" % (url, state.PMS_TOKEN) url = "%s&X-Plex-Token=%s" % (url, app.CONN.pms_token)
return url return url
def item_id(self): def item_id(self):
@ -770,7 +769,7 @@ class API(object):
for extras in self.item.iterfind('Extras'): for extras in self.item.iterfind('Extras'):
# There will always be only 1 extras element # There will always be only 1 extras element
if (len(extras) > 0 and if (len(extras) > 0 and
state.SHOW_EXTRAS_INSTEAD_OF_PLAYING_TRAILER): app.SYNC.show_extras_instead_of_playing_trailer):
return ('plugin://%s?mode=route_to_extras&plex_id=%s' return ('plugin://%s?mode=route_to_extras&plex_id=%s'
% (v.ADDON_ID, self.plex_id())) % (v.ADDON_ID, self.plex_id()))
for extra in extras: for extra in extras:
@ -888,7 +887,7 @@ class API(object):
artwork = '%s?width=%s&height=%s' % (artwork, width, height) artwork = '%s?width=%s&height=%s' % (artwork, width, height)
artwork = ('%s/photo/:/transcode?width=3840&height=3840&' artwork = ('%s/photo/:/transcode?width=3840&height=3840&'
'minSize=1&upscale=0&url=%s' 'minSize=1&upscale=0&url=%s'
% (state.PMS_SERVER, quote(artwork))) % (app.CONN.server, quote(artwork)))
artwork = self.attach_plex_token_to_url(artwork) artwork = self.attach_plex_token_to_url(artwork)
return artwork return artwork
@ -1406,7 +1405,7 @@ class API(object):
# trailers are 'clip' with PMS xmls # trailers are 'clip' with PMS xmls
if action == "DirectStream": if action == "DirectStream":
path = self.item[self.mediastream][self.part].attrib['key'] path = self.item[self.mediastream][self.part].attrib['key']
url = state.PMS_SERVER + path url = app.CONN.server + path
# e.g. Trailers already feature an '?'! # e.g. Trailers already feature an '?'!
if '?' in url: if '?' in url:
url += '&' + urlencode(xargs) url += '&' + urlencode(xargs)
@ -1423,7 +1422,7 @@ class API(object):
} }
# Path/key to VIDEO item of xml PMS response is needed, not part # Path/key to VIDEO item of xml PMS response is needed, not part
path = self.item.attrib['key'] path = self.item.attrib['key']
transcode_path = state.PMS_SERVER + \ transcode_path = app.CONN.server + \
'/video/:/transcode/universal/start.m3u8?' '/video/:/transcode/universal/start.m3u8?'
args = { args = {
'audioBoost': utils.settings('audioBoost'), 'audioBoost': utils.settings('audioBoost'),
@ -1481,7 +1480,7 @@ class API(object):
# We don't know the language - no need to download # We don't know the language - no need to download
else: else:
path = self.attach_plex_token_to_url( path = self.attach_plex_token_to_url(
"%s%s" % (state.PMS_SERVER, key)) "%s%s" % (app.CONN.server, key))
externalsubs.append(path) externalsubs.append(path)
kodiindex += 1 kodiindex += 1
LOG.info('Found external subs: %s', externalsubs) LOG.info('Found external subs: %s', externalsubs)
@ -1735,16 +1734,16 @@ class API(object):
if path is None: if path is None:
return return
typus = v.REMAP_TYPE_FROM_PLEXTYPE[typus] typus = v.REMAP_TYPE_FROM_PLEXTYPE[typus]
if state.REMAP_PATH is True: if app.SYNC.remap_path is True:
path = path.replace(getattr(state, 'remapSMB%sOrg' % typus), path = path.replace(getattr(app.SYNC, 'remapSMB%sOrg' % typus),
getattr(state, 'remapSMB%sNew' % typus), getattr(app.SYNC, 'remapSMB%sNew' % typus),
1) 1)
# There might be backslashes left over: # There might be backslashes left over:
path = path.replace('\\', '/') path = path.replace('\\', '/')
elif state.REPLACE_SMB_PATH is True: elif app.SYNC.replace_smb_path is True:
if path.startswith('\\\\'): if path.startswith('\\\\'):
path = 'smb:' + path.replace('\\', '/') path = 'smb:' + path.replace('\\', '/')
if ((state.PATH_VERIFIED and force_check is False) or if ((app.SYNC.path_verified and force_check is False) or
omit_check is True): omit_check is True):
return path return path
@ -1769,14 +1768,14 @@ class API(object):
if force_check is False: if force_check is False:
# Validate the path is correct with user intervention # Validate the path is correct with user intervention
if self.ask_to_validate(path): if self.ask_to_validate(path):
state.STOP_SYNC = True app.SYNC.stop_sync = True
path = None path = None
state.PATH_VERIFIED = True app.SYNC.path_verified = True
else: else:
path = None path = None
elif force_check is False: elif force_check is False:
# Only set the flag if we were not force-checking the path # Only set the flag if we were not force-checking the path
state.PATH_VERIFIED = True app.SYNC.path_verified = True
return path return path
@staticmethod @staticmethod

View file

@ -20,7 +20,8 @@ from . import playback
from . import json_rpc as js from . import json_rpc as js
from . import playqueue as PQ from . import playqueue as PQ
from . import variables as v from . import variables as v
from . import state from . import backgroundthread
from . import app
############################################################################### ###############################################################################
@ -46,7 +47,7 @@ def update_playqueue_from_PMS(playqueue,
# Safe transient token from being deleted # Safe transient token from being deleted
if transient_token is None: if transient_token is None:
transient_token = playqueue.plex_transient_token transient_token = playqueue.plex_transient_token
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
xml = PL.get_PMS_playlist(playqueue, playqueue_id) xml = PL.get_PMS_playlist(playqueue, playqueue_id)
try: try:
xml.attrib xml.attrib
@ -64,8 +65,7 @@ def update_playqueue_from_PMS(playqueue,
playback.play_xml(playqueue, xml, offset) playback.play_xml(playqueue, xml, offset)
@utils.thread_methods(add_suspends=['PMS_STATUS']) class PlexCompanion(backgroundthread.KillableThread):
class PlexCompanion(Thread):
""" """
Plex Companion monitoring class. Invoke only once Plex Companion monitoring class. Invoke only once
""" """
@ -80,7 +80,13 @@ class PlexCompanion(Thread):
self.player = Player() self.player = Player()
self.httpd = False self.httpd = False
self.subscription_manager = None self.subscription_manager = None
Thread.__init__(self) super(PlexCompanion, self).__init__()
def isSuspended(self):
"""
Returns True if the thread is suspended
"""
return self._suspended or app.CONN.pms_status
def _process_alexa(self, data): def _process_alexa(self, data):
xml = PF.GetPlexMetadata(data['key']) xml = PF.GetPlexMetadata(data['key'])
@ -116,10 +122,9 @@ class PlexCompanion(Thread):
offset = None offset = None
playback.play_xml(playqueue, xml, offset) playback.play_xml(playqueue, xml, offset)
else: else:
state.PLEX_TRANSIENT_TOKEN = data.get('token') app.CONN.plex_transient_token = data.get('token')
if data.get('offset') != '0': if data.get('offset') != '0':
state.RESUMABLE = True app.PLAYSTATE.resume_playback = True
state.RESUME_PLAYBACK = True
playback.playback_triage(api.plex_id(), playback.playback_triage(api.plex_id(),
api.plex_type(), api.plex_type(),
resolve=False) resolve=False)
@ -129,7 +134,7 @@ class PlexCompanion(Thread):
""" """
E.g. watch later initiated by Companion. Basically navigating Plex E.g. watch later initiated by Companion. Basically navigating Plex
""" """
state.PLEX_TRANSIENT_TOKEN = data.get('key') app.CONN.plex_transient_token = data.get('key')
params = { params = {
'mode': 'plex_node', 'mode': 'plex_node',
'key': '{server}%s' % data.get('key'), 'key': '{server}%s' % data.get('key'),
@ -221,16 +226,16 @@ class PlexCompanion(Thread):
LOG.debug('Processing: %s', task) LOG.debug('Processing: %s', task)
data = task['data'] data = task['data']
if task['action'] == 'alexa': if task['action'] == 'alexa':
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self._process_alexa(data) self._process_alexa(data)
elif (task['action'] == 'playlist' and elif (task['action'] == 'playlist' and
data.get('address') == 'node.plexapp.com'): data.get('address') == 'node.plexapp.com'):
self._process_node(data) self._process_node(data)
elif task['action'] == 'playlist': elif task['action'] == 'playlist':
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self._process_playlist(data) self._process_playlist(data)
elif task['action'] == 'refreshPlayQueue': elif task['action'] == 'refreshPlayQueue':
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
self._process_refresh(data) self._process_refresh(data)
elif task['action'] == 'setStreams': elif task['action'] == 'setStreams':
try: try:
@ -260,8 +265,6 @@ class PlexCompanion(Thread):
httpd = self.httpd httpd = self.httpd
# Cache for quicker while loops # Cache for quicker while loops
client = self.client client = self.client
stopped = self.stopped
suspended = self.suspended
# Start up instances # Start up instances
request_mgr = httppersist.RequestMgr() request_mgr = httppersist.RequestMgr()
@ -298,12 +301,12 @@ class PlexCompanion(Thread):
if httpd: if httpd:
thread = Thread(target=httpd.handle_request) thread = Thread(target=httpd.handle_request)
while not stopped(): while not self.isCanceled():
# 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 suspended(): while self.isSuspended():
if stopped(): if self.isCanceled():
break break
sleep(1000) sleep(1000)
try: try:
@ -335,13 +338,13 @@ class PlexCompanion(Thread):
LOG.warn(traceback.format_exc()) LOG.warn(traceback.format_exc())
# See if there's anything we need to process # See if there's anything we need to process
try: try:
task = state.COMPANION_QUEUE.get(block=False) task = app.APP.companion_queue.get(block=False)
except Empty: except Empty:
pass pass
else: else:
# Got instructions, process them # Got instructions, process them
self._process_tasks(task) self._process_tasks(task)
state.COMPANION_QUEUE.task_done() app.APP.companion_queue.task_done()
# Don't sleep # Don't sleep
continue continue
sleep(50) sleep(50)

View file

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from .. import variables as v
class Playlists(object): class Playlists(object):

View file

@ -5,10 +5,9 @@ from logging import getLogger
import time import time
import threading import threading
import xbmc import xbmc
import xbmcgui
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import utils, variables as v, state from . import utils, app
############################################################################### ###############################################################################
LOG = getLogger('PLEX.plex_tv') LOG = getLogger('PLEX.plex_tv')
@ -87,7 +86,7 @@ def switch_home_user(userid, pin, token, machine_identifier):
utils.settings('plex_restricteduser', utils.settings('plex_restricteduser',
'true' if xml.get('restricted', '0') == '1' 'true' if xml.get('restricted', '0') == '1'
else 'false') else 'false')
state.RESTRICTED_USER = True if \ app.CONN.restricted_user = True if \
xml.get('restricted', '0') == '1' else False xml.get('restricted', '0') == '1' else False
# Get final token to the PMS we've chosen # Get final token to the PMS we've chosen
@ -174,7 +173,7 @@ class PinLogin(object):
start = time.time() start = time.time()
while (not self._abort and while (not self._abort and
time.time() - start < 300 and time.time() - start < 300 and
not state.STOP_PKC): not app.APP.stop_pkc):
xml = DU().downloadUrl(self.POLL.format(self.id), xml = DU().downloadUrl(self.POLL.format(self.id),
authenticate=False) authenticate=False)
try: try:

View file

@ -31,7 +31,7 @@ import time
from xbmc import sleep from xbmc import sleep
from ..downloadutils import DownloadUtils as DU from ..downloadutils import DownloadUtils as DU
from .. import utils from .. import utils, app
from .. import variables as v from .. import variables as v
############################################################################### ###############################################################################
@ -228,7 +228,7 @@ class plexgdm:
return self.server_list return self.server_list
def discover(self): def discover(self):
currServer = utils.window('pms_server') currServer = app.CONN.server
if not currServer: if not currServer:
return return
currServerProt, currServerIP, currServerPort = \ currServerProt, currServerIP, currServerPort = \

View file

@ -9,8 +9,8 @@ from logging import getLogger
from threading import Thread from threading import Thread
from ..downloadutils import DownloadUtils as DU from ..downloadutils import DownloadUtils as DU
from .. import utils from .. import utils, timing
from .. import state from .. import app
from .. import variables as v from .. import variables as v
from .. import json_rpc as js from .. import json_rpc as js
from .. import playqueue as PQ from .. import playqueue as PQ
@ -101,9 +101,9 @@ def update_player_info(playerid):
""" """
Updates all player info for playerid [int] in state.py. Updates all player info for playerid [int] in state.py.
""" """
state.PLAYER_STATES[playerid].update(js.get_player_props(playerid)) app.PLAYSTATE.playerstates[playerid].update(js.get_player_props(playerid))
state.PLAYER_STATES[playerid]['volume'] = js.get_volume() app.PLAYSTATE.playerstates[playerid]['volume'] = js.get_volume()
state.PLAYER_STATES[playerid]['muted'] = js.get_muted() app.PLAYSTATE.playerstates[playerid]['muted'] = js.get_muted()
class SubscriptionMgr(object): class SubscriptionMgr(object):
@ -189,9 +189,9 @@ class SubscriptionMgr(object):
return answ return answ
def _timeline_dict(self, player, ptype): def _timeline_dict(self, player, ptype):
with state.LOCK_PLAYQUEUES: with app.APP.lock_playqueues:
playerid = player['playerid'] playerid = player['playerid']
info = state.PLAYER_STATES[playerid] info = app.PLAYSTATE.player_states[playerid]
playqueue = PQ.PLAYQUEUES[playerid] playqueue = PQ.PLAYQUEUES[playerid]
position = self._get_correct_position(info, playqueue) position = self._get_correct_position(info, playqueue)
try: try:
@ -208,12 +208,12 @@ class SubscriptionMgr(object):
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO, if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO,
v.PLEX_PLAYLIST_TYPE_PHOTO): v.PLEX_PLAYLIST_TYPE_PHOTO):
self.location = 'fullScreenVideo' self.location = 'fullScreenVideo'
pbmc_server = utils.window('pms_server') pbmc_server = app.CONN.server
if pbmc_server: if pbmc_server:
(self.protocol, self.server, self.port) = pbmc_server.split(':') (self.protocol, self.server, self.port) = pbmc_server.split(':')
self.server = self.server.replace('/', '') self.server = self.server.replace('/', '')
status = 'paused' if int(info['speed']) == 0 else 'playing' status = 'paused' if int(info['speed']) == 0 else 'playing'
duration = utils.kodi_time_to_millis(info['totaltime']) duration = timing.kodi_time_to_millis(info['totaltime'])
shuffle = '1' if info['shuffled'] else '0' shuffle = '1' if info['shuffled'] else '0'
mute = '1' if info['muted'] is True else '0' mute = '1' if info['muted'] is True else '0'
answ = { answ = {
@ -225,7 +225,7 @@ class SubscriptionMgr(object):
'state': status, 'state': status,
'type': ptype, 'type': ptype,
'itemType': ptype, 'itemType': ptype,
'time': utils.kodi_time_to_millis(info['time']), 'time': timing.kodi_time_to_millis(info['time']),
'duration': duration, 'duration': duration,
'seekRange': '0-%s' % duration, 'seekRange': '0-%s' % duration,
'shuffle': shuffle, 'shuffle': shuffle,
@ -254,8 +254,8 @@ class SubscriptionMgr(object):
if playqueue.items[position].guid: if playqueue.items[position].guid:
answ['guid'] = item.guid answ['guid'] = item.guid
# Temp. token set? # Temp. token set?
if state.PLEX_TRANSIENT_TOKEN: if app.CONN.plex_transient_token:
answ['token'] = state.PLEX_TRANSIENT_TOKEN answ['token'] = app.CONN.plex_transient_token
elif playqueue.plex_transient_token: elif playqueue.plex_transient_token:
answ['token'] = playqueue.plex_transient_token answ['token'] = playqueue.plex_transient_token
# Process audio and subtitle streams # Process audio and subtitle streams
@ -301,7 +301,7 @@ class SubscriptionMgr(object):
stream_type: 'video', 'audio', 'subtitle' stream_type: 'video', 'audio', 'subtitle'
""" """
playqueue = PQ.PLAYQUEUES[playerid] playqueue = PQ.PLAYQUEUES[playerid]
info = state.PLAYER_STATES[playerid] info = app.PLAYSTATE.player_states[playerid]
position = self._get_correct_position(info, playqueue) position = self._get_correct_position(info, playqueue)
if info[STREAM_DETAILS[stream_type]] == -1: if info[STREAM_DETAILS[stream_type]] == -1:
kodi_stream_index = -1 kodi_stream_index = -1
@ -315,7 +315,7 @@ class SubscriptionMgr(object):
Updates the Plex Companien client with the machine identifier uuid with Updates the Plex Companien client with the machine identifier uuid with
command_id command_id
""" """
with state.LOCK_SUBSCRIBER: with app.APP.lock_subscriber:
if command_id and self.subscribers.get(uuid): if command_id and self.subscribers.get(uuid):
self.subscribers[uuid].command_id = int(command_id) self.subscribers[uuid].command_id = int(command_id)
@ -326,7 +326,7 @@ class SubscriptionMgr(object):
playqueues. playqueues.
""" """
for player in players.values(): for player in players.values():
info = state.PLAYER_STATES[player['playerid']] info = app.PLAYSTATE.player_states[player['playerid']]
playqueue = PQ.PLAYQUEUES[player['playerid']] playqueue = PQ.PLAYQUEUES[player['playerid']]
position = self._get_correct_position(info, playqueue) position = self._get_correct_position(info, playqueue)
try: try:
@ -345,7 +345,7 @@ class SubscriptionMgr(object):
Causes PKC to tell the PMS and Plex Companion players to receive a Causes PKC to tell the PMS and Plex Companion players to receive a
notification what's being played. notification what's being played.
""" """
with state.LOCK_SUBSCRIBER: with app.APP.lock_subscriber:
self._cleanup() self._cleanup()
# Get all the active/playing Kodi players (video, audio, pictures) # Get all the active/playing Kodi players (video, audio, pictures)
players = js.get_players() players = js.get_players()
@ -378,7 +378,7 @@ class SubscriptionMgr(object):
self._send_pms_notification(player['playerid'], self.last_params) self._send_pms_notification(player['playerid'], self.last_params)
def _get_pms_params(self, playerid): def _get_pms_params(self, playerid):
info = state.PLAYER_STATES[playerid] info = app.PLAYSTATE.player_states[playerid]
playqueue = PQ.PLAYQUEUES[playerid] playqueue = PQ.PLAYQUEUES[playerid]
position = self._get_correct_position(info, playqueue) position = self._get_correct_position(info, playqueue)
try: try:
@ -390,8 +390,8 @@ class SubscriptionMgr(object):
'state': status, 'state': status,
'ratingKey': item.plex_id, 'ratingKey': item.plex_id,
'key': '/library/metadata/%s' % item.plex_id, 'key': '/library/metadata/%s' % item.plex_id,
'time': utils.kodi_time_to_millis(info['time']), 'time': timing.kodi_time_to_millis(info['time']),
'duration': utils.kodi_time_to_millis(info['totaltime']) 'duration': timing.kodi_time_to_millis(info['totaltime'])
} }
if info['container_key'] is not None: if info['container_key'] is not None:
# params['containerKey'] = info['container_key'] # params['containerKey'] = info['container_key']
@ -407,12 +407,12 @@ class SubscriptionMgr(object):
playqueue = PQ.PLAYQUEUES[playerid] playqueue = PQ.PLAYQUEUES[playerid]
xargs = params_pms() xargs = params_pms()
xargs.update(params) xargs.update(params)
if state.PLEX_TRANSIENT_TOKEN: if app.CONN.plex_transient_token:
xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN xargs['X-Plex-Token'] = app.CONN.plex_transient_token
elif playqueue.plex_transient_token: elif playqueue.plex_transient_token:
xargs['X-Plex-Token'] = playqueue.plex_transient_token xargs['X-Plex-Token'] = playqueue.plex_transient_token
elif state.PMS_TOKEN: elif app.ACCOUNT.pms_token:
xargs['X-Plex-Token'] = state.PMS_TOKEN xargs['X-Plex-Token'] = app.ACCOUNT.pms_token
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'))
@ -434,7 +434,7 @@ class SubscriptionMgr(object):
command_id, command_id,
self, self,
self.request_mgr) self.request_mgr)
with state.LOCK_SUBSCRIBER: with app.APP.lock_subscriber:
self.subscribers[subscriber.uuid] = subscriber self.subscribers[subscriber.uuid] = subscriber
return subscriber return subscriber
@ -444,7 +444,7 @@ class SubscriptionMgr(object):
uuid from PKC notifications. uuid from PKC notifications.
(Calls the cleanup() method of the subscriber) (Calls the cleanup() method of the subscriber)
""" """
with state.LOCK_SUBSCRIBER: with app.APP.lock_subscriber:
for subscriber in self.subscribers.values(): for subscriber in self.subscribers.values():
if subscriber.uuid == uuid or subscriber.host == uuid: if subscriber.uuid == uuid or subscriber.host == uuid:
subscriber.cleanup() subscriber.cleanup()

View file

@ -5,38 +5,107 @@ import logging
import sys import sys
import xbmc import xbmc
from . import utils from . import utils, clientinfo, timing
from . import userclient from . import initialsetup, artwork
from . import initialsetup
from . import kodimonitor from . import kodimonitor
from . import sync from . import sync
from . import websocket_client from . import websocket_client
from . import plex_companion from . import plex_companion
from . import plex_functions as PF from . import plex_functions as PF, playqueue as PQ
from . import command_pipeline
from . import playback_starter from . import playback_starter
from . import playqueue from . import playqueue
from . import variables as v from . import variables as v
from . import state from . import app
from . import loghandler from . import loghandler
from .windows import userselect
############################################################################### ###############################################################################
loghandler.config() loghandler.config()
LOG = logging.getLogger("PLEX.service_entry") LOG = logging.getLogger("PLEX.service")
############################################################################### ###############################################################################
WINDOW_PROPERTIES = (
"plex_online", "plex_command_processed", "plex_shouldStop", "plex_dbScan",
"plex_customplayqueue", "plex_playbackProps",
"pms_token", "plex_token", "pms_server", "plex_machineIdentifier",
"plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths",
"countError", "countUnauthorized", "plex_restricteduser",
"plex_allows_mediaDeletion", "plex_command", "plex_result",
"plex_force_transcode_pix"
)
def authenticate():
"""
Authenticate the current user or prompt to log-in
Returns True if successful, False if not. 'aborted' if user chose to
abort
"""
LOG.info('Authenticating user')
if app.ACCOUNT.plex_username and not app.ACCOUNT.force_login:
# Found a user in the settings, try to authenticate
LOG.info('Trying to authenticate with old settings')
res = PF.check_connection(app.CONN.server,
token=app.ACCOUNT.pms_token,
verifySSL=app.CONN.verify_ssl_cert)
if res is False:
LOG.error('Something went wrong while checking connection')
return False
elif res == 401:
LOG.error('User token no longer valid. Sign user out')
app.ACCOUNT.clear()
return False
elif res >= 400:
LOG.error('Answer from PMS is not as expected')
return False
LOG.info('Successfully authenticated using old settings')
app.ACCOUNT.set_authenticated()
return True
# Could not use settings - try to get Plex user list from plex.tv
if app.ACCOUNT.plex_token:
LOG.info("Trying to connect to plex.tv to get a user list")
user, _ = userselect.start()
if not user:
LOG.info('No user received')
app.CONN.pms_status = 'Stop'
return False
username = user.title
user_id = user.id
token = user.authToken
else:
LOG.info("Trying to authenticate without a token")
username = ''
user_id = ''
token = ''
res = PF.check_connection(app.CONN.server,
token=token,
verifySSL=app.CONN.verify_ssl_cert)
if res is False:
LOG.error('Something went wrong while checking connection')
return False
elif res == 401:
LOG.error('Token not valid')
return False
elif res >= 400:
LOG.error('Answer from PMS is not as expected')
return False
LOG.info('Successfully authenticated')
# Got new values that need to be saved
utils.settings('username', value=username)
utils.settings('userid', value=user_id)
utils.settings('accessToken', value=token)
app.ACCOUNT.load()
app.ACCOUNT.set_authenticated()
return True
class Service(): class Service():
server_online = True
warn_auth = True
user = None
ws = None ws = None
sync = None sync = None
plexcompanion = None plexcompanion = None
user_running = False
ws_running = False ws_running = False
alexa_running = False alexa_running = False
sync_running = False sync_running = False
@ -67,28 +136,110 @@ class Service():
utils.settings('syncSpecificPlexPlaylistsPrefix')) utils.settings('syncSpecificPlexPlaylistsPrefix'))
LOG.info('XML decoding being used: %s', utils.ETREE) LOG.info('XML decoding being used: %s', utils.ETREE)
LOG.info("Db version: %s", utils.settings('dbCreatedWithVersion')) LOG.info("Db version: %s", utils.settings('dbCreatedWithVersion'))
# Reset window props
for prop in WINDOW_PROPERTIES:
utils.window(prop, clear=True)
# To detect Kodi profile switches
utils.window('plex_kodiProfile',
value=utils.try_decode(xbmc.translatePath("special://profile")))
self.monitor = xbmc.Monitor() self.monitor = xbmc.Monitor()
# Load/Reset PKC entirely - important for user/Kodi profile switch # Load/Reset PKC entirely - important for user/Kodi profile switch
initialsetup.reload_pkc() # Clear video nodes properties
from .library_sync import videonodes
videonodes.VideoNodes().clearProperties()
clientinfo.getDeviceId()
# Init time-offset between Kodi and Plex
timing.KODI_PLEX_TIME_OFFSET = utils.settings('kodiplextimeoffset') or 0.0
def _stop_pkc(self): def isCanceled(self):
return xbmc.abortRequested or state.STOP_PKC return xbmc.abortRequested or app.APP.stop_pkc
def log_out(self):
"""
Ensures that lib sync threads are suspended; signs out user
"""
LOG.info('Log-out requested')
app.SYNC.suspend_library_thread = True
i = 0
while app.SYNC.db_scan:
i += 1
xbmc.sleep(50)
if i > 100:
LOG.error('Could not stop library sync, aborting log-out')
# Failed to reset PMS and plex.tv connects. Try to restart Kodi
utils.messageDialog(utils.lang(29999), utils.lang(39208))
# Resuming threads, just in case
app.SYNC.suspend_library_thread = False
return False
LOG.info('Successfully stopped library sync')
app.ACCOUNT.clear()
LOG.info('User has been logged out')
return True
def choose_pms_server(self, manual=False):
LOG.info("Choosing PMS server requested, starting")
if manual:
if not self.setup.enter_new_pms_address():
return False
else:
server = self.setup.pick_pms(showDialog=True)
if server is None:
LOG.info('We did not connect to a new PMS, aborting')
return False
LOG.info("User chose server %s", server['name'])
if server['baseURL'] == app.CONN.server:
LOG.info('User chose old PMS to connect to')
return False
self.setup.write_pms_to_settings(server)
if not self.log_out():
return False
# Wipe Kodi and Plex database as well as playlists and video nodes
utils.wipe_database()
app.CONN.load()
LOG.info("Choosing new PMS complete")
return True
def switch_plex_user(self):
if not self.log_out():
return False
# First remove playlists of old user
utils.delete_playlists()
# Remove video nodes
utils.delete_nodes()
return True
def toggle_plex_tv(self):
if utils.settings('plexToken'):
LOG.info('Reseting plex.tv credentials in settings')
app.ACCOUNT.clear()
return True
else:
LOG.info('Login to plex.tv')
return self.setup.plex_tv_sign_in()
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 app.init()
monitor = self.monitor # Some plumbing
artwork.IMAGE_CACHING_SUSPENDS = [
app.SYNC.suspend_library_thread,
app.SYNC.stop_sync,
app.SYNC.db_scan
]
if not utils.settings('imageSyncDuringPlayback') == 'true':
artwork.IMAGE_CACHING_SUSPENDS.append(app.SYNC.suspend_sync)
# Initialize the PKC playqueues
PQ.init_playqueues()
# Server auto-detect # Server auto-detect
initialsetup.InitialSetup().setup() self.setup = initialsetup.InitialSetup()
self.setup.setup()
# Detect playback start early on # Initialize important threads
self.command_pipeline = command_pipeline.Monitor_Window()
self.command_pipeline.start()
# Initialize important threads, handing over self for callback purposes
self.user = userclient.UserClient()
self.ws = websocket_client.PMS_Websocket() self.ws = websocket_client.PMS_Websocket()
self.alexa = websocket_client.Alexa_Websocket() self.alexa = websocket_client.Alexa_Websocket()
self.sync = sync.Sync() self.sync = sync.Sync()
@ -97,9 +248,10 @@ class Service():
self.playback_starter = playback_starter.PlaybackStarter() self.playback_starter = playback_starter.PlaybackStarter()
self.playqueue = playqueue.PlayqueueMonitor() self.playqueue = playqueue.PlayqueueMonitor()
server_online = True
welcome_msg = True welcome_msg = True
counter = 0 counter = 0
while not _stop_pkc(): while not self.isCanceled():
if utils.window('plex_kodiProfile') != v.KODI_PROFILE: if utils.window('plex_kodiProfile') != v.KODI_PROFILE:
# Profile change happened, terminate this thread and others # Profile change happened, terminate this thread and others
@ -108,149 +260,167 @@ class Service():
v.KODI_PROFILE, utils.window('plex_kodiProfile')) v.KODI_PROFILE, utils.window('plex_kodiProfile'))
break break
plex_command = utils.window('plex_command')
if plex_command:
# Commands/user interaction received from other PKC Python
# instances (default.py and context.py instead of service.py)
utils.window('plex_command', clear=True)
if plex_command.startswith('PLAY-'):
# Add-on path playback!
app.APP.command_pipeline_queue.put(
plex_command.replace('PLAY-', ''))
elif plex_command.startswith('NAVIGATE-'):
app.APP.command_pipeline_queue.put(
plex_command.replace('NAVIGATE-', ''))
elif plex_command.startswith('CONTEXT_menu?'):
app.APP.command_pipeline_queue.put(
'dummy?mode=context_menu&%s'
% plex_command.replace('CONTEXT_menu?', ''))
elif plex_command == 'choose_pms_server':
if self.choose_pms_server():
utils.window('plex_online', clear=True)
app.ACCOUNT.set_unauthenticated()
server_online = False
welcome_msg = False
elif plex_command == 'switch_plex_user':
if self.switch_plex_user():
app.ACCOUNT.set_unauthenticated()
elif plex_command == 'enter_new_pms_address':
if self.setup.enter_new_pms_address():
if self.log_out():
utils.window('plex_online', clear=True)
app.ACCOUNT.set_unauthenticated()
server_online = False
welcome_msg = False
elif plex_command == 'toggle_plex_tv_sign_in':
if self.toggle_plex_tv():
app.ACCOUNT.set_unauthenticated()
elif plex_command == 'repair-scan':
app.SYNC.run_lib_scan = 'repair'
elif plex_command == 'full-scan':
app.SYNC.run_lib_scan = 'full'
elif plex_command == 'fanart-scan':
app.SYNC.run_lib_scan = 'fanart'
elif plex_command == 'textures-scan':
app.SYNC.run_lib_scan = 'textures'
continue
# Before proceeding, need to make sure: # Before proceeding, need to make sure:
# 1. Server is online # 1. Server is online
# 2. User is set # 2. User is set
# 3. User has access to the server # 3. User has access to the server
if utils.window('plex_online') == "true": if utils.window('plex_online') == "true":
# Plex server is online # Plex server is online
# Verify if user is set and has access to the server if app.CONN.pms_status == 'Stop':
if (self.user.user is not None) and self.user.has_access: xbmc.sleep(500)
continue
elif app.CONN.pms_status == '401':
# Unauthorized access, revoke token
LOG.info('401 received - revoking token')
app.ACCOUNT.clear()
app.CONN.pms_status = 'Auth'
utils.window('plex_serverStatus', value='Auth')
continue
if not app.ACCOUNT.authenticated:
LOG.info('Not yet authenticated')
# Do authentication
if not authenticate():
continue
# Start up events
if welcome_msg is True:
# Reset authentication warnings
welcome_msg = False
utils.dialog('notification',
utils.lang(29999),
"%s %s" % (utils.lang(33000),
app.ACCOUNT.plex_username),
icon='{plex}',
time=2000,
sound=False)
# Start monitoring kodi events
if not self.kodimonitor_running: if not self.kodimonitor_running:
# Start up events
self.warn_auth = True
if welcome_msg is True:
# Reset authentication warnings
welcome_msg = False
utils.dialog('notification',
utils.lang(29999),
"%s %s" % (utils.lang(33000),
self.user.user),
icon='{plex}',
time=2000,
sound=False)
# Start monitoring kodi events
self.kodimonitor_running = kodimonitor.KodiMonitor() self.kodimonitor_running = kodimonitor.KodiMonitor()
self.specialmonitor.start() self.specialmonitor.start()
# Start the Websocket Client # Start the Websocket Client
if not self.ws_running: if not self.ws_running:
self.ws_running = True self.ws_running = True
self.ws.start() self.ws.start()
# Start the Alexa thread # Start the Alexa thread
if (not self.alexa_running and if (not self.alexa_running and
utils.settings('enable_alexa') == 'true'): utils.settings('enable_alexa') == 'true'):
self.alexa_running = True self.alexa_running = True
self.alexa.start() self.alexa.start()
# Start the syncing thread # Start the syncing thread
if not self.sync_running: if not self.sync_running:
self.sync_running = True self.sync_running = True
self.sync.start() self.sync.start()
# Start the Plex Companion thread # Start the Plex Companion thread
if not self.plexcompanion_running: if not self.plexcompanion_running:
self.plexcompanion_running = True self.plexcompanion_running = True
self.plexcompanion.start() self.plexcompanion.start()
if not self.playback_starter_running: if not self.playback_starter_running:
self.playback_starter_running = True self.playback_starter_running = True
self.playback_starter.start() self.playback_starter.start()
self.playqueue.start() self.playqueue.start()
else:
if (self.user.user is None) and self.warn_auth:
# Alert user is not authenticated and suppress future
# warning
self.warn_auth = False
LOG.warn("Not authenticated yet.")
# User access is restricted.
# Keep verifying until access is granted
# unless server goes offline or Kodi is shut down.
while self.user.has_access is False:
# Verify access with an API call
self.user.check_access()
if utils.window('plex_online') != "true":
# Server went offline
break
if monitor.waitForAbort(3):
# Abort was requested while waiting. We should exit
break
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 self._stop_pkc(): server = app.CONN.server
server = self.user.get_server() if not server:
if server is False: # No server info set in add-on settings
# No server info set in add-on settings pass
pass elif PF.check_connection(server, verifySSL=True) is False:
elif PF.check_connection(server, verifySSL=True) is False: # Server is offline or cannot be reached
# Server is offline or cannot be reached # Alert the user and suppress future warning
# Alert the user and suppress future warning if server_online:
if self.server_online: server_online = False
self.server_online = False utils.window('plex_online', value="false")
utils.window('plex_online', value="false") # Suspend threads
# Suspend threads app.SYNC.suspend_library_thread = True
state.SUSPEND_LIBRARY_THREAD = True LOG.warn("Plex Media Server went offline")
LOG.error("Plex Media Server went offline") if utils.settings('show_pms_offline') == 'true':
if utils.settings('show_pms_offline') == 'true': utils.dialog('notification',
utils.dialog('notification', utils.lang(33001),
utils.lang(33001), "%s %s" % (utils.lang(29999),
"%s %s" % (utils.lang(29999), utils.lang(33002)),
utils.lang(33002)), icon='{plex}',
icon='{plex}', sound=False)
sound=False) counter += 1
counter += 1 # Periodically check if the IP changed, e.g. per minute
# Periodically check if the IP changed, e.g. per minute if counter > 20:
if counter > 20:
counter = 0
setup = initialsetup.InitialSetup()
tmp = setup.pick_pms()
if tmp is not None:
setup.write_pms_to_settings(tmp)
else:
# Server is online
counter = 0 counter = 0
if not self.server_online: setup = initialsetup.InitialSetup()
# Server was offline when Kodi started. tmp = setup.pick_pms()
# Wait for server to be fully established. if tmp:
if monitor.waitForAbort(5): setup.write_pms_to_settings(tmp)
# Abort was requested while waiting. app.CONN.load()
break else:
self.server_online = True # Server is online
# Alert the user that server is online. counter = 0
if (welcome_msg is False and if not server_online:
utils.settings('show_pms_offline') == 'true'): # Server was offline when Kodi started.
utils.dialog('notification', server_online = True
utils.lang(29999), # Alert the user that server is online.
utils.lang(33003), if (welcome_msg is False and
icon='{plex}', utils.settings('show_pms_offline') == 'true'):
time=5000, utils.dialog('notification',
sound=False) utils.lang(29999),
LOG.info("Server %s is online and ready.", server) utils.lang(33003),
utils.window('plex_online', value="true") icon='{plex}',
if state.AUTHENTICATED: time=5000,
# Server got offline when we were authenticated. sound=False)
# Hence resume threads LOG.info("Server %s is online and ready.", server)
state.SUSPEND_LIBRARY_THREAD = False utils.window('plex_online', value="true")
if app.ACCOUNT.authenticated:
# Server got offline when we were authenticated.
# Hence resume threads
app.SYNC.suspend_library_thread = False
# Start the userclient thread if self.monitor.waitForAbort(0.05):
if not self.user_running:
self.user_running = True
self.user.start()
break
if monitor.waitForAbort(3):
# Abort was requested while waiting.
break
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
# Tell all threads to terminate (e.g. several lib sync threads) # Tell all threads to terminate (e.g. several lib sync threads)
state.STOP_PKC = True app.APP.stop_pkc = True
utils.window('plex_service_started', clear=True) utils.window('plex_service_started', clear=True)
LOG.info("======== STOP %s ========", v.ADDON_NAME) LOG.info("======== STOP %s ========", v.ADDON_NAME)

View file

@ -1,190 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
THREAD SAFE
"""
from __future__ import absolute_import, division, unicode_literals
from threading import Lock, RLock
# LOCKS
####################
# Need to lock all methods and functions messing with Plex Companion subscribers
LOCK_SUBSCRIBER = RLock()
# Need to lock everything messing with Kodi/PKC playqueues
LOCK_PLAYQUEUES = RLock()
# Necessary to temporarily hold back librarysync/websocket listener when doing
# a full sync
LOCK_PLAYLISTS = Lock()
# Quit PKC
STOP_PKC = False
# URL of our current PMS
PMS_SERVER = None
# 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 during media playback if PKC should not do any syncs. Will NOT
# suspend synching of playstate progress
SUSPEND_SYNC = False
# Could we access the paths?
PATH_VERIFIED = False
# Set if a Plex-Kodi DB sync is being done - along with
# window('plex_dbScan') set to 'true'
DB_SCAN = False
# 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
# Shall we replace custom user ratings with the number of versions available?
INDICATE_MEDIA_VERSIONS = False
# Will sync movie trailer differently: either play trailer directly or show
# all the Plex extras for the user to choose
SHOW_EXTRAS_INSTEAD_OF_PLAYING_TRAILER = False
# Do we need to run a special library scan?
RUN_LIB_SCAN = None
# Number of items to fetch and display in widgets
FETCH_PMS_ITEM_NUMBER = None
# Hack to force Kodi widget for "in progress" to show up if it was empty before
FORCE_RELOAD_SKIN = True
# Stemming from the PKC settings.xml
# Shall we show Kodi dialogs when synching?
SYNC_DIALOG = True
# Shall Kodi show dialogs for syncing/caching images? (e.g. images left to sync)
IMAGE_SYNC_NOTIFICATIONS = True
# Only sync specific Plex playlists to Kodi?
SYNC_SPECIFIC_PLEX_PLAYLISTS = False
# Only sync specific Kodi playlists to Plex?
SYNC_SPECIFIC_KODI_PLAYLISTS = False
# Is synching of Plex music enabled?
ENABLE_MUSIC = True
# How often shall we sync?
FULL_SYNC_INTERVALL = 0
# Background Sync disabled?
BACKGROUND_SYNC_DISABLED = False
# How long shall we wait with synching a new item to make sure Plex got all
# metadata?
BACKGROUNDSYNC_SAFTYMARGIN = 0
# How many threads to download Plex metadata on sync?
SYNC_THREAD_NUMBER = 0
# What's the time offset between the PMS and Kodi?
KODI_PLEX_TIME_OFFSET = 0.0
# Path remapping mechanism (e.g. smb paths)
# Do we replace \\myserver\path to smb://myserver/path?
REPLACE_SMB_PATH = False
# Do we generally remap?
REMAP_PATH = False
# Mappings for REMAP_PATH:
remapSMBmovieOrg = None
remapSMBmovieNew = None
remapSMBtvOrg = None
remapSMBtvNew = None
remapSMBmusicOrg = None
remapSMBmusicNew = None
remapSMBphotoOrg = None
remapSMBphotoNew = None
# Shall we verify SSL certificates?
VERIFY_SSL_CERT = False
# Do we have an ssl certificate for PKC we need to use?
SSL_CERT_PATH = None
# Along with window('plex_authenticated')
AUTHENTICATED = False
# plex.tv username
PLEX_USERNAME = None
# Token for that user for plex.tv
PLEX_TOKEN = None
# Plex token for the active PMS for the active user
# (might be diffent to PLEX_TOKEN)
PMS_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
# Plex Companion Queue()
COMPANION_QUEUE = None
# Command Pipeline Queue()
COMMAND_PIPELINE_QUEUE = None
# Websocket_client queue to communicate with librarysync
WEBSOCKET_QUEUE = None
# Which Kodi player is/has been active? (either int 1, 2 or 3)
ACTIVE_PLAYERS = set()
# Failsafe for throwing an empty video back to Kodi's setResolvedUrl to set
# up our own playlist from the very beginning
PKC_CAUSED_STOP = False
# Flag if the 0 length PKC video has already failed so we can start resolving
# playback (set in player.py)
PKC_CAUSED_STOP_DONE = True
# Kodi player states - here, initial values are set
PLAYER_STATES = {
0: {},
1: {},
2: {}
}
# The LAST playstate once playback is finished
OLD_PLAYER_STATES = {
0: {},
1: {},
2: {}
}
# "empty" dict for the PLAYER_STATES above. Use copy.deepcopy to duplicate!
PLAYSTATE = {
'type': None,
'time': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'totaltime': {
'hours': 0,
'minutes': 0,
'seconds': 0,
'milliseconds': 0},
'speed': 0,
'shuffled': False,
'repeat': 'off',
'position': None,
'playlistid': None,
'currentvideostream': -1,
'currentaudiostream': -1,
'subtitleenabled': False,
'currentsubtitle': -1,
'file': None,
'kodi_id': None,
'kodi_type': None,
'plex_id': None,
'plex_type': None,
'container_key': None,
'volume': 100,
'muted': False,
'playmethod': None,
'playcount': None
}
PLAYED_INFO = {}
# Set by SpecialMonitor - did user choose to resume playback or start from the
# beginning?
RESUME_PLAYBACK = False
# Was the playback initiated by the user using the Kodi context menu?
CONTEXT_MENU_PLAY = False
# Set by context menu - shall we force-transcode the next playing item?
FORCE_TRANSCODE = False
# Kodi webserver details
WEBSERVER_PORT = 8080
WEBSERVER_USERNAME = 'kodi'
WEBSERVER_PASSWORD = ''
WEBSERVER_HOST = 'localhost'

View file

@ -5,8 +5,8 @@ from logging import getLogger
import xbmc import xbmc
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import library_sync from . import library_sync, timing
from . import backgroundthread, utils, path_ops, artwork, variables as v, state from . import backgroundthread, utils, path_ops, artwork, variables as v, app
from . import plex_db, kodi_db from . import plex_db, kodi_db
LOG = getLogger('PLEX.sync') LOG = getLogger('PLEX.sync')
@ -18,10 +18,10 @@ def set_library_scan_toggle(boolean=True):
""" """
if not boolean: if not boolean:
# Deactivate # Deactivate
state.DB_SCAN = False app.SYNC.db_scan = False
utils.window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
else: else:
state.DB_SCAN = True app.SYNC.db_scan = True
utils.window('plex_dbScan', value="true") utils.window('plex_dbScan', value="true")
@ -40,11 +40,8 @@ class Sync(backgroundthread.KillableThread):
# self.lock = backgroundthread.threading.Lock() # self.lock = backgroundthread.threading.Lock()
super(Sync, self).__init__() super(Sync, self).__init__()
def isCanceled(self):
return state.STOP_PKC
def isSuspended(self): def isSuspended(self):
return state.SUSPEND_LIBRARY_THREAD return self._suspended or app.SYNC.suspend_library_thread
def show_kodi_note(self, message, icon="plex", force=False): def show_kodi_note(self, message, icon="plex", force=False):
""" """
@ -54,7 +51,7 @@ class Sync(backgroundthread.KillableThread):
icon: "plex": shows Plex icon icon: "plex": shows Plex icon
"error": shows Kodi error icon "error": shows Kodi error icon
""" """
if not force and state.SYNC_DIALOG is not True and self.force_dialog is not True: if not force and app.SYNC.sync_dialog is not True and self.force_dialog is not True:
return return
if icon == "plex": if icon == "plex":
utils.dialog('notification', utils.dialog('notification',
@ -70,14 +67,14 @@ class Sync(backgroundthread.KillableThread):
def triage_lib_scans(self): def triage_lib_scans(self):
""" """
Decides what to do if state.RUN_LIB_SCAN has been set. E.g. manually Decides what to do if app.SYNC.run_lib_scan has been set. E.g. manually
triggered full or repair syncs triggered full or repair syncs
""" """
if state.RUN_LIB_SCAN in ("full", "repair"): if app.SYNC.run_lib_scan in ("full", "repair"):
set_library_scan_toggle() set_library_scan_toggle()
LOG.info('Full library scan requested, starting') LOG.info('Full library scan requested, starting')
self.start_library_sync(show_dialog=True, self.start_library_sync(show_dialog=True,
repair=state.RUN_LIB_SCAN == 'repair', repair=app.SYNC.run_lib_scan == 'repair',
block=True) block=True)
if self.sync_successful: if self.sync_successful:
# Full library sync finished # Full library sync finished
@ -85,7 +82,7 @@ class Sync(backgroundthread.KillableThread):
elif not self.isSuspended() and not self.isCanceled(): elif not self.isSuspended() and not self.isCanceled():
# ERROR in library sync # ERROR in library sync
self.show_kodi_note(utils.lang(39410), icon='error') self.show_kodi_note(utils.lang(39410), icon='error')
elif state.RUN_LIB_SCAN == 'fanart': elif app.SYNC.run_lib_scan == 'fanart':
# Only look for missing fanart (No) or refresh all fanart (Yes) # Only look for missing fanart (No) or refresh all fanart (Yes)
from .windows import optionsdialog from .windows import optionsdialog
refresh = optionsdialog.show(utils.lang(29999), refresh = optionsdialog.show(utils.lang(29999),
@ -99,7 +96,7 @@ class Sync(backgroundthread.KillableThread):
message=utils.lang(30015), message=utils.lang(30015),
icon='{plex}', icon='{plex}',
sound=False) sound=False)
elif state.RUN_LIB_SCAN == 'textures': elif app.SYNC.run_lib_scan == 'textures':
LOG.info("Caching of images requested") LOG.info("Caching of images requested")
if not utils.yesno_dialog("Image Texture Cache", utils.lang(39250)): if not utils.yesno_dialog("Image Texture Cache", utils.lang(39250)):
return return
@ -113,7 +110,7 @@ class Sync(backgroundthread.KillableThread):
Hit this after the full sync has finished Hit this after the full sync has finished
""" """
self.sync_successful = successful self.sync_successful = successful
self.last_full_sync = utils.unix_timestamp() self.last_full_sync = timing.unix_timestamp()
set_library_scan_toggle(boolean=False) set_library_scan_toggle(boolean=False)
if successful: if successful:
self.show_kodi_note(utils.lang(39407)) self.show_kodi_note(utils.lang(39407))
@ -129,7 +126,7 @@ class Sync(backgroundthread.KillableThread):
def start_library_sync(self, show_dialog=None, repair=False, block=False): def start_library_sync(self, show_dialog=None, repair=False, block=False):
set_library_scan_toggle(boolean=True) set_library_scan_toggle(boolean=True)
show_dialog = show_dialog if show_dialog is not None else state.SYNC_DIALOG show_dialog = show_dialog if show_dialog is not None else app.SYNC.sync_dialog
library_sync.start(show_dialog, repair, self.on_library_scan_finished) library_sync.start(show_dialog, repair, self.on_library_scan_finished)
# if block: # if block:
# self.lock.acquire() # self.lock.acquire()
@ -153,7 +150,7 @@ class Sync(backgroundthread.KillableThread):
def on_fanart_download_finished(self, successful): def on_fanart_download_finished(self, successful):
# FanartTV lookup completed # FanartTV lookup completed
if successful and state.SYNC_DIALOG: if successful and app.SYNC.sync_dialog:
utils.dialog('notification', utils.dialog('notification',
heading='{plex}', heading='{plex}',
message=utils.lang(30019), message=utils.lang(30019),
@ -174,7 +171,7 @@ class Sync(backgroundthread.KillableThread):
try: try:
self._run_internal() self._run_internal()
except: except:
state.DB_SCAN = False app.SYNC.db_scan = False
utils.window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
utils.ERROR(txt='sync.py crashed', notify=True) utils.ERROR(txt='sync.py crashed', notify=True)
raise raise
@ -188,12 +185,12 @@ class Sync(backgroundthread.KillableThread):
last_time_sync = 0 last_time_sync = 0
one_day_in_seconds = 60 * 60 * 24 one_day_in_seconds = 60 * 60 * 24
# Link to Websocket queue # Link to Websocket queue
queue = state.WEBSOCKET_QUEUE queue = app.APP.websocket_queue
# Kodi Version supported by PKC? # Kodi Version supported by PKC?
if (not path_ops.exists(v.DB_VIDEO_PATH) or if (not path_ops.exists(v.DB_VIDEO_PATH) or
not path_ops.exists(v.DB_TEXTURE_PATH) or not path_ops.exists(v.DB_TEXTURE_PATH) or
(state.ENABLE_MUSIC and not path_ops.exists(v.DB_MUSIC_PATH))): (app.SYNC.enable_music and not path_ops.exists(v.DB_MUSIC_PATH))):
# Database does not exists # Database does not exists
LOG.error('The current Kodi version is incompatible') LOG.error('The current Kodi version is incompatible')
LOG.error('Current Kodi version: %s', utils.try_decode( LOG.error('Current Kodi version: %s', utils.try_decode(
@ -242,7 +239,7 @@ class Sync(backgroundthread.KillableThread):
self.force_dialog = True self.force_dialog = True
# Initialize time offset Kodi - PMS # Initialize time offset Kodi - PMS
library_sync.sync_pms_time() library_sync.sync_pms_time()
last_time_sync = utils.unix_timestamp() last_time_sync = timing.unix_timestamp()
LOG.info('Initial start-up full sync starting') LOG.info('Initial start-up full sync starting')
xbmc.executebuiltin('InhibitIdleShutdown(true)') xbmc.executebuiltin('InhibitIdleShutdown(true)')
# This call will block until scan is completed # This call will block until scan is completed
@ -268,9 +265,9 @@ class Sync(backgroundthread.KillableThread):
# First sync upon PKC restart. Skipped if very first sync upon # First sync upon PKC restart. Skipped if very first sync upon
# PKC installation has been completed # PKC installation has been completed
LOG.info('Doing initial sync on Kodi startup') LOG.info('Doing initial sync on Kodi startup')
if state.SUSPEND_SYNC: if app.SYNC.suspend_sync:
LOG.warning('Forcing startup sync even if Kodi is playing') LOG.warning('Forcing startup sync even if Kodi is playing')
state.SUSPEND_SYNC = False app.SYNC.suspend_sync = False
self.start_library_sync(block=True) self.start_library_sync(block=True)
if self.sync_successful: if self.sync_successful:
initial_sync_done = True initial_sync_done = True
@ -285,27 +282,27 @@ class Sync(backgroundthread.KillableThread):
xbmc.sleep(1000) xbmc.sleep(1000)
# Currently no db scan, so we could start a new scan # Currently no db scan, so we could start a new scan
elif state.DB_SCAN is False: elif app.SYNC.db_scan is False:
# Full scan was requested from somewhere else, e.g. userclient # Full scan was requested from somewhere else
if state.RUN_LIB_SCAN is not None: if app.SYNC.run_lib_scan is not None:
# Force-show dialogs since they are user-initiated # Force-show dialogs since they are user-initiated
self.force_dialog = True self.force_dialog = True
self.triage_lib_scans() self.triage_lib_scans()
self.force_dialog = False self.force_dialog = False
# Reset the flag # Reset the flag
state.RUN_LIB_SCAN = None app.SYNC.run_lib_scan = None
continue continue
# Standard syncs - don't force-show dialogs # Standard syncs - don't force-show dialogs
now = utils.unix_timestamp() now = timing.unix_timestamp()
if (now - self.last_full_sync > state.FULL_SYNC_INTERVALL): if (now - self.last_full_sync > app.SYNC.full_sync_intervall):
LOG.info('Doing scheduled full library scan') LOG.info('Doing scheduled full library scan')
self.start_library_sync() self.start_library_sync()
elif now - last_time_sync > one_day_in_seconds: elif now - last_time_sync > one_day_in_seconds:
LOG.info('Starting daily time sync') LOG.info('Starting daily time sync')
library_sync.sync_pms_time() library_sync.sync_pms_time()
last_time_sync = now last_time_sync = now
elif not state.BACKGROUND_SYNC_DISABLED: elif not app.SYNC.background_sync_disabled:
# Check back whether we should process something Only do # Check back whether we should process something Only do
# this once a while (otherwise, potentially many screen # this once a while (otherwise, potentially many screen
# refreshes lead to flickering) # refreshes lead to flickering)

94
resources/lib/timing.py Normal file
View file

@ -0,0 +1,94 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from datetime import datetime, timedelta
from time import localtime, strftime
EPOCH = datetime.utcfromtimestamp(0)
# What's the time offset between the PMS and Kodi?
KODI_PLEX_TIME_OFFSET = 0.0
def unix_timestamp(seconds_into_the_future=None):
"""
Returns a Unix time stamp (seconds passed since January 1 1970) for NOW as
an integer.
Optionally, pass seconds_into_the_future: positive int's will result in a
future timestamp, negative the past
"""
if seconds_into_the_future:
future = datetime.utcnow() + timedelta(seconds=seconds_into_the_future)
else:
future = datetime.utcnow()
return int((future - EPOCH).total_seconds())
def unix_date_to_kodi(unix_kodi_time):
"""
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
propper, human-readable time stamp used by Kodi
Output: Y-m-d h:m:s = 2009-04-05 23:16:04
"""
return strftime('%Y-%m-%d %H:%M:%S', localtime(float(unix_kodi_time)))
def plex_date_to_kodi(plex_timestamp):
"""
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
propper, human-readable time stamp used by Kodi
Output: Y-m-d h:m:s = 2009-04-05 23:16:04
"""
return strftime('%Y-%m-%d %H:%M:%S',
localtime(float(plex_timestamp) + KODI_PLEX_TIME_OFFSET))
def kodi_timestamp(plex_timestamp):
return unix_date_to_kodi(plex_timestamp)
def kodi_now():
return unix_date_to_kodi(unix_timestamp())
def millis_to_kodi_time(milliseconds):
"""
Converts time in milliseconds to the time dict used by the Kodi JSON RPC:
{
'hours': [int],
'minutes': [int],
'seconds'[int],
'milliseconds': [int]
}
Pass in the time in milliseconds as an int
"""
seconds = int(milliseconds / 1000)
minutes = int(seconds / 60)
seconds = seconds % 60
hours = int(minutes / 60)
minutes = minutes % 60
milliseconds = milliseconds % 1000
return {'hours': hours,
'minutes': minutes,
'seconds': seconds,
'milliseconds': milliseconds}
def kodi_time_to_millis(time):
"""
Converts the Kodi time dict
{
'hours': [int],
'minutes': [int],
'seconds'[int],
'milliseconds': [int]
}
to milliseconds [int]. Will not return negative results but 0!
"""
ret = (time['hours'] * 3600 +
time['minutes'] * 60 +
time['seconds']) * 1000 + time['milliseconds']
return 0 if ret < 0 else ret

View file

@ -1,356 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from xbmc import sleep, executebuiltin
from .windows import userselect
from .downloadutils import DownloadUtils as DU
from . import utils
from . import path_ops
from . import plex_functions as PF
from . import variables as v
from . import state
###############################################################################
LOG = getLogger('PLEX.userclient')
###############################################################################
@utils.thread_methods(add_suspends=['SUSPEND_USER_CLIENT'])
class UserClient(Thread):
"""
Manage Plex users
"""
# Borg - multiple instances, shared state
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
self.auth = True
self.retry = 0
self.aborted = False
self.user = None
self.has_access = True
self.server = None
self.server_name = None
self.machine_identifier = None
self.token = None
self.ssl = None
self.sslcert = None
self.do_utils = None
Thread.__init__(self)
def get_server(self):
"""
Get the current PMS' URL
"""
# Original host
self.server_name = utils.settings('plex_servername')
https = utils.settings('https') == "true"
host = utils.settings('ipaddress')
port = utils.settings('port')
self.machine_identifier = utils.settings('plex_machineIdentifier')
if not host:
LOG.debug("No server information saved.")
return False
server = host + ":" + port
# If https is true
if https:
server = "https://%s" % server
# If https is false
else:
server = "http://%s" % server
# User entered IP; we need to get the machineIdentifier
if not self.machine_identifier:
self.machine_identifier = PF.GetMachineIdentifier(server)
if not self.machine_identifier:
self.machine_identifier = ''
utils.settings('plex_machineIdentifier',
value=self.machine_identifier)
LOG.debug('Returning active server: %s', server)
return server
@staticmethod
def get_ssl_verify():
"""
Do we need to verify the SSL certificate? Return None if that is the
case, else False
"""
return None if utils.settings('sslverify') == 'true' else False
@staticmethod
def get_ssl_certificate():
"""
Client side certificate
"""
return None if utils.settings('sslcert') == 'None' \
else utils.settings('sslcert')
def set_user_prefs(self):
"""
Load a user's profile picture
"""
LOG.debug('Setting user preferences')
# Only try to get user avatar if there is a token
if self.token:
url = PF.GetUserArtworkURL(self.user)
if url:
utils.window('PlexUserImage', value=url)
@staticmethod
def check_access():
# Plex: always return True for now
return True
def load_user(self, username, user_id, usertoken, authenticated=False):
"""
Load the current user's details for PKC
"""
LOG.debug('Loading current user')
self.token = usertoken
self.server = self.get_server()
self.ssl = self.get_ssl_verify()
self.sslcert = self.get_ssl_certificate()
if authenticated is False:
if self.server is None:
return False
LOG.debug('Testing validity of current token')
res = PF.check_connection(self.server,
token=self.token,
verifySSL=self.ssl)
if res is False:
# PMS probably offline
return False
elif res == 401:
LOG.error('Token is no longer valid')
return 401
elif res >= 400:
LOG.error('Answer from PMS is not as expected. Retrying')
return False
# Set to windows property
state.PLEX_USER_ID = user_id or None
state.PLEX_USERNAME = username
# This is the token for the current PMS (might also be '')
utils.window('pms_token', value=usertoken)
state.PMS_TOKEN = usertoken
# This is the token for plex.tv for the current user
# Is only '' if user is not signed in to plex.tv
utils.window('plex_token', value=utils.settings('plexToken'))
state.PLEX_TOKEN = utils.settings('plexToken') or None
utils.window('plex_restricteduser',
value=utils.settings('plex_restricteduser'))
state.RESTRICTED_USER = True \
if utils.settings('plex_restricteduser') == 'true' else False
utils.window('pms_server', value=self.server)
state.PMS_SERVER = self.server
utils.window('plex_machineIdentifier', value=self.machine_identifier)
utils.window('plex_servername', value=self.server_name)
utils.window('plex_authenticated', value='true')
state.AUTHENTICATED = True
utils.window('useDirectPaths',
value='true' if utils.settings('useDirectPaths') == "1"
else 'false')
state.DIRECT_PATHS = True if utils.settings('useDirectPaths') == "1" \
else False
state.INDICATE_MEDIA_VERSIONS = True \
if utils.settings('indicate_media_versions') == "true" else False
utils.window('plex_force_transcode_pix',
value='true' if utils.settings('force_transcode_pix') == "1"
else 'false')
# Start DownloadUtils session
self.do_utils = DU()
self.do_utils.startSession(reset=True)
# Set user preferences in settings
self.user = username
self.set_user_prefs()
# Writing values to settings file
utils.settings('username', value=username)
utils.settings('userid', value=user_id)
utils.settings('accessToken', value=usertoken)
return True
def authenticate(self):
"""
Authenticate the current user
"""
LOG.debug('Authenticating user')
# Give attempts at entering password / selecting user
if self.retry > 0:
if not self.aborted:
LOG.error("Too many retries to login.")
state.PMS_STATUS = 'Stop'
# Failed to authenticate. Did you login to plex.tv?
utils.messageDialog(utils.lang(29999),utils.lang(39023))
executebuiltin(
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
return False
# If there's no settings.xml
if not path_ops.exists("%ssettings.xml" % v.ADDON_PROFILE):
LOG.error("Error, no settings.xml found.")
self.auth = False
return False
server = self.get_server()
# If there is no server we can connect to
if not server:
LOG.info("Missing server information.")
self.auth = False
return False
# If there is a username in the settings, try authenticating
username = utils.settings('username')
userId = utils.settings('userid')
usertoken = utils.settings('accessToken')
enforceLogin = utils.settings('enforceUserLogin')
# Found a user in the settings, try to authenticate
if username and enforceLogin == 'false':
LOG.debug('Trying to authenticate with old settings')
answ = self.load_user(username,
userId,
usertoken,
authenticated=False)
if answ is True:
# SUCCESS: loaded a user from the settings
return True
elif answ == 401:
LOG.error("User token no longer valid. Sign user out")
utils.settings('username', value='')
utils.settings('userid', value='')
utils.settings('accessToken', value='')
else:
LOG.debug("Could not yet authenticate user")
return False
# Could not use settings - try to get Plex user list from plex.tv
plextoken = utils.settings('plexToken')
if plextoken:
LOG.info("Trying to connect to plex.tv to get a user list")
user, self.aborted = userselect.start()
if not user:
# FAILURE: Something went wrong, try again
self.auth = True
self.retry += 1
return False
username = user.title
user_id = user.id
usertoken = user.authToken
else:
LOG.info("Trying to authenticate without a token")
username = ''
user_id = ''
usertoken = ''
if self.load_user(username, user_id, usertoken, authenticated=False):
# SUCCESS: loaded a user from the settings
return True
# Something went wrong, try again
self.auth = True
self.retry += 1
return False
def reset_client(self):
"""
Reset all user settings
"""
LOG.debug("Reset UserClient authentication.")
try:
self.do_utils.stopSession()
except AttributeError:
pass
utils.window('plex_authenticated', clear=True)
state.AUTHENTICATED = False
utils.window('pms_token', clear=True)
state.PLEX_TOKEN = None
state.PLEX_TRANSIENT_TOKEN = None
state.PMS_TOKEN = None
utils.window('plex_token', clear=True)
utils.window('pms_server', clear=True)
state.PMS_SERVER = None
utils.window('plex_machineIdentifier', clear=True)
utils.window('plex_servername', clear=True)
state.PLEX_USER_ID = None
state.PLEX_USERNAME = None
utils.window('plex_restricteduser', clear=True)
state.RESTRICTED_USER = False
utils.settings('username', value='')
utils.settings('userid', value='')
utils.settings('accessToken', value='')
self.token = None
self.auth = True
self.user = None
self.retry = 0
def run(self):
"""
Do the work
"""
LOG.info("----===## Starting UserClient ##===----")
stopped = self.stopped
suspended = self.suspended
while not stopped():
while suspended():
if stopped():
break
sleep(1000)
if state.PMS_STATUS == "Stop":
sleep(500)
continue
elif state.PMS_STATUS == "401":
# Unauthorized access, revoke token
state.PMS_STATUS = 'Auth'
utils.window('plex_serverStatus', value='Auth')
self.reset_client()
sleep(3000)
if self.auth and (self.user is None):
# Try to authenticate user
if not state.PMS_STATUS or state.PMS_STATUS == "Auth":
# Set auth flag because we no longer need
# to authenticate the user
self.auth = False
if self.authenticate():
# Successfully authenticated and loaded a user
LOG.info("Successfully authenticated!")
LOG.info("Current user: %s", self.user)
LOG.info("Current userId: %s", state.PLEX_USER_ID)
self.retry = 0
state.SUSPEND_LIBRARY_THREAD = False
utils.window('plex_serverStatus', clear=True)
state.PMS_STATUS = False
if not self.auth and (self.user is None):
# Loop if no server found
server = self.get_server()
# The status Stop is for when user cancelled password dialog.
# Or retried too many times
if server and state.PMS_STATUS != "Stop":
# Only if there's information found to login
LOG.debug("Server found: %s", server)
self.auth = True
# Minimize CPU load
sleep(100)
LOG.info("##===---- UserClient Stopped ----===##")

View file

@ -6,8 +6,7 @@ Various functions and decorators for PKC
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from sqlite3 import connect, OperationalError from sqlite3 import connect, OperationalError
from datetime import datetime, timedelta from datetime import datetime
from time import localtime, strftime
from unicodedata import normalize from unicodedata import normalize
try: try:
import xml.etree.cElementTree as etree import xml.etree.cElementTree as etree
@ -19,7 +18,7 @@ except ImportError:
import defusedxml.ElementTree as defused_etree # etree parse unsafe import defusedxml.ElementTree as defused_etree # etree parse unsafe
from xml.etree.ElementTree import ParseError from xml.etree.ElementTree import ParseError
ETREE = 'ElementTree' ETREE = 'ElementTree'
from functools import wraps, partial from functools import wraps
from urllib import quote_plus from urllib import quote_plus
import hashlib import hashlib
import re import re
@ -28,7 +27,7 @@ import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
from . import path_ops, variables as v, state from . import path_ops, variables as v
############################################################################### ###############################################################################
@ -36,7 +35,6 @@ LOG = getLogger('PLEX.utils')
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect') ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
EPOCH = datetime.utcfromtimestamp(0)
# Grab Plex id from '...plex_id=XXXX....' # Grab Plex id from '...plex_id=XXXX....'
REGEX_PLEX_ID = re.compile(r'''plex_id=(\d+)''') REGEX_PLEX_ID = re.compile(r'''plex_id=(\d+)''')
@ -107,17 +105,14 @@ def window(prop, value=None, clear=False, windowid=10000):
return try_decode(win.getProperty(prop)) return try_decode(win.getProperty(prop))
def plex_command(key, value): def plex_command(value):
""" """
Used to funnel states between different Python instances. NOT really thread Used to funnel states between different Python instances. NOT really thread
safe - let's hope the Kodi user can't click fast enough 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'): while window('plex_command'):
xbmc.sleep(20) xbmc.sleep(20)
window('plex_command', value='%s-%s' % (key, value)) window('plex_command', value=value)
def settings(setting, value=None): def settings(setting, value=None):
@ -238,7 +233,8 @@ def ERROR(txt='', hide_tb=False, notify=False, cancel_sync=False):
short = str(sys.exc_info()[1]) short = str(sys.exc_info()[1])
LOG.error('Error encountered: %s - %s', txt, short) LOG.error('Error encountered: %s - %s', txt, short)
if cancel_sync: if cancel_sync:
state.STOP_SYNC = True import app
app.SYNC.stop_sync = True
if hide_tb: if hide_tb:
return short return short
@ -275,47 +271,6 @@ class AttributeDict(dict):
return self.__unicode__().encode('utf-8') return self.__unicode__().encode('utf-8')
def millis_to_kodi_time(milliseconds):
"""
Converts time in milliseconds to the time dict used by the Kodi JSON RPC:
{
'hours': [int],
'minutes': [int],
'seconds'[int],
'milliseconds': [int]
}
Pass in the time in milliseconds as an int
"""
seconds = int(milliseconds / 1000)
minutes = int(seconds / 60)
seconds = seconds % 60
hours = int(minutes / 60)
minutes = minutes % 60
milliseconds = milliseconds % 1000
return {'hours': hours,
'minutes': minutes,
'seconds': seconds,
'milliseconds': milliseconds}
def kodi_time_to_millis(time):
"""
Converts the Kodi time dict
{
'hours': [int],
'minutes': [int],
'seconds'[int],
'milliseconds': [int]
}
to milliseconds [int]. Will not return negative results but 0!
"""
ret = (time['hours'] * 3600 +
time['minutes'] * 60 +
time['seconds']) * 1000 + time['milliseconds']
ret = 0 if ret < 0 else ret
return ret
def cast(func, value): def cast(func, value):
""" """
Cast the specified value to the specified type (returned by func). Currently this Cast the specified value to the specified type (returned by func). Currently this
@ -434,47 +389,6 @@ def escape_html(string):
return string return string
def unix_date_to_kodi(stamp):
"""
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
propper, human-readable time stamp used by Kodi
Output: Y-m-d h:m:s = 2009-04-05 23:16:04
None if an error was encountered
"""
try:
stamp = float(stamp) + state.KODI_PLEX_TIME_OFFSET
date_time = localtime(stamp)
localdate = strftime('%Y-%m-%d %H:%M:%S', date_time)
except:
localdate = None
return localdate
def kodi_time_to_plex(stamp):
"""
Returns a Kodi timestamp (int/float) in Plex time (subtracting the
KODI_PLEX_TIME_OFFSET)
"""
return stamp - state.KODI_PLEX_TIME_OFFSET
def unix_timestamp(seconds_into_the_future=None):
"""
Returns a Unix time stamp (seconds passed since January 1 1970) for NOW as
an integer.
Optionally, pass seconds_into_the_future: positive int's will result in a
future timestamp, negative the past
"""
if seconds_into_the_future:
future = datetime.utcnow() + timedelta(seconds=seconds_into_the_future)
else:
future = datetime.utcnow()
return int((future - EPOCH).total_seconds())
def kodi_sql(media_type=None): def kodi_sql(media_type=None):
""" """
Open a connection to the Kodi database. Open a connection to the Kodi database.
@ -1135,95 +1049,3 @@ def log_time(func):
elapsedtotal, func.__name__) elapsedtotal, func.__name__)
return result return result
return wrapper return wrapper
def thread_methods(cls=None, add_stops=None, add_suspends=None):
"""
Decorator to add the following methods to a threading class:
suspend(): pauses the thread
resume(): resumes the thread
stop(): stopps/kills the thread
suspended(): returns True if thread is suspended
stopped(): returns True if thread is stopped (or should stop ;-))
ALSO returns True if PKC should exit
Also adds the following class attributes:
thread_stopped
thread_suspended
stops
suspends
invoke with either
@thread_methods
class MyClass():
or
@thread_methods(add_stops=['SUSPEND_LIBRARY_TRHEAD'],
add_suspends=['DB_SCAN', 'WHATEVER'])
class MyClass():
"""
# So we don't need to invoke with ()
if cls is None:
return partial(thread_methods,
add_stops=add_stops,
add_suspends=add_suspends)
# Because we need a reference, not a copy of the immutable objects in
# state, we need to look up state every time explicitly
cls.stops = ['STOP_PKC']
if add_stops is not None:
cls.stops.extend(add_stops)
cls.suspends = add_suspends or []
# Attach new attributes to class
cls.thread_stopped = False
cls.thread_suspended = False
# Define new class methods and attach them to class
def stop(self):
"""
Call to stop this thread
"""
self.thread_stopped = True
cls.stop = stop
def suspend(self):
"""
Call to suspend this thread
"""
self.thread_suspended = True
cls.suspend = suspend
def resume(self):
"""
Call to revive a suspended thread back to life
"""
self.thread_suspended = False
cls.resume = resume
def suspended(self):
"""
Returns True if the thread is suspended
"""
if self.thread_suspended is True:
return True
for suspend in self.suspends:
if getattr(state, suspend):
return True
return False
cls.suspended = suspended
def stopped(self):
"""
Returns True if the thread is stopped
"""
if self.thread_stopped is True:
return True
for stop in self.stops:
if getattr(state, stop):
return True
return False
cls.stopped = stopped
# Return class to render this a decorator
return cls

View file

@ -2,11 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from logging import getLogger from logging import getLogger
from json import loads from json import loads
from threading import Thread
from ssl import CERT_NONE from ssl import CERT_NONE
from xbmc import sleep from xbmc import sleep
from . import websocket, utils, companion, state, variables as v from . import backgroundthread, websocket, utils, companion, app, variables as v
############################################################################### ###############################################################################
@ -15,7 +14,7 @@ LOG = getLogger('PLEX.websocket_client')
############################################################################### ###############################################################################
class WebSocket(Thread): class WebSocket(backgroundthread.KillableThread):
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
def __init__(self): def __init__(self):
@ -48,18 +47,15 @@ class WebSocket(Thread):
def run(self): def run(self):
LOG.info("----===## Starting %s ##===----", self.__class__.__name__) LOG.info("----===## Starting %s ##===----", self.__class__.__name__)
counter = 0 counter = 0
stopped = self.stopped while not self.isCanceled():
suspended = self.suspended
while not stopped():
# In the event the server goes offline # In the event the server goes offline
while suspended(): while self.isSuspended():
# Set in service.py # Set in service.py
if self.ws is not None: if self.ws is not None:
self.ws.close() self.ws.close()
self.ws = None self.ws = None
if stopped(): if self.isCanceled():
# 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__)
@ -133,14 +129,20 @@ class WebSocket(Thread):
LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__) LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'BACKGROUND_SYNC_DISABLED'])
class PMS_Websocket(WebSocket): class PMS_Websocket(WebSocket):
""" """
Websocket connection with the PMS for Plex Companion Websocket connection with the PMS for Plex Companion
""" """
def isSuspended(self):
"""
Returns True if the thread is suspended
"""
return (self._suspended or
app.SYNC.suspend_library_thread or
app.SYNC.background_sync_disabled)
def getUri(self): def getUri(self):
server = utils.window('pms_server') server = app.CONN.server
# 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:]
@ -148,8 +150,8 @@ class PMS_Websocket(WebSocket):
server = "ws%s" % server[4:] server = "ws%s" % server[4:]
uri = "%s/:/websockets/notifications" % server uri = "%s/:/websockets/notifications" % server
# Need to use plex.tv token, if any. NOT user token # Need to use plex.tv token, if any. NOT user token
if state.PLEX_TOKEN: if app.ACCOUNT.plex_token:
uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN uri += '?X-Plex-Token=%s' % app.ACCOUNT.plex_token
sslopt = {} sslopt = {}
if utils.settings('sslverify') == "false": if utils.settings('sslverify') == "false":
sslopt["cert_reqs"] = CERT_NONE sslopt["cert_reqs"] = CERT_NONE
@ -185,30 +187,33 @@ class PMS_Websocket(WebSocket):
# Drop everything we're not interested in # Drop everything we're not interested in
if typus not in ('playing', 'timeline', 'activity'): if typus not in ('playing', 'timeline', 'activity'):
return return
elif typus == 'activity' and state.DB_SCAN is True: elif typus == 'activity' and app.SYNC.db_scan is True:
# Only add to processing if PKC is NOT doing a lib scan (and thus # Only add to processing if PKC is NOT doing a lib scan (and thus
# possibly causing these reprocessing messages en mass) # possibly causing these reprocessing messages en mass)
LOG.debug('%s: Dropping message as PKC is currently synching', LOG.debug('%s: Dropping message as PKC is currently synching',
self.__class__.__name__) self.__class__.__name__)
else: else:
# Put PMS message on queue and let libsync take care of it # Put PMS message on queue and let libsync take care of it
state.WEBSOCKET_QUEUE.put(message) app.APP.websocket_queue.put(message)
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 utils.thread_methods!
""" """
thread_stopped = False def isSuspended(self):
thread_suspended = False """
Overwrite method since we need to check for plex token
"""
return (self._suspended or
not app.ACCOUNT.plex_token or
app.ACCOUNT.restricted_user)
def getUri(self): def getUri(self):
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'
% (state.PLEX_USER_ID, % (app.ACCOUNT.plex_user_id,
v.PKC_MACHINE_IDENTIFIER, v.PKC_MACHINE_IDENTIFIER,
state.PLEX_TOKEN)) app.ACCOUNT.plex_token))
sslopt = {} sslopt = {}
LOG.debug("%s: Uri: %s, sslopt: %s", LOG.debug("%s: Uri: %s, sslopt: %s",
self.__class__.__name__, uri, sslopt) self.__class__.__name__, uri, sslopt)
@ -238,33 +243,3 @@ class Alexa_Websocket(WebSocket):
self.__class__.__name__) self.__class__.__name__)
return return
companion.process_command(message.attrib['path'][1:], message.attrib) companion.process_command(message.attrib['path'][1:], message.attrib)
# Path in utils.thread_methods
def stop(self):
self.thread_stopped = True
def suspend(self):
self.thread_suspended = True
def resume(self):
self.thread_suspended = False
def stopped(self):
if self.thread_stopped is True:
return True
if state.STOP_PKC:
return True
return False
# The culprit
def suspended(self):
"""
Overwrite method since we need to check for plex token
"""
if self.thread_suspended is True:
return True
if not state.PLEX_TOKEN:
return True
if state.RESTRICTED_USER:
return True
return False

View file

@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
import xbmcgui import xbmcgui
from . import kodigui from . import kodigui
from .. import utils, variables as v from .. import variables as v
class Background(kodigui.BaseWindow): class Background(kodigui.BaseWindow):

View file

@ -16,7 +16,6 @@
<setting id="enforceUserLogin" label="30536" type="bool" default="false" /> <setting id="enforceUserLogin" label="30536" type="bool" default="false" />
<setting label="30517" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=passwords)" option="close" /><!-- Network credentials --> <setting label="30517" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=passwords)" option="close" /><!-- Network credentials -->
<setting label="30505" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=resetauth)" option="close" /><!-- reset connection attempts -->
<setting id="accessToken" type="text" visible="false" default="" /> <setting id="accessToken" type="text" visible="false" default="" />
</category> </category>