Rather big change of PKC's plumbing
This commit is contained in:
parent
b2615c19bd
commit
a045063769
53 changed files with 1246 additions and 1663 deletions
13
default.py
13
default.py
|
@ -94,9 +94,6 @@ class Main():
|
|||
elif mode == 'togglePlexTV':
|
||||
entrypoint.toggle_plex_tv_sign_in()
|
||||
|
||||
elif mode == 'resetauth':
|
||||
entrypoint.reset_authorization()
|
||||
|
||||
elif mode == 'passwords':
|
||||
utils.passwords_xml()
|
||||
|
||||
|
@ -111,14 +108,14 @@ class Main():
|
|||
else:
|
||||
if mode == 'repair':
|
||||
log.info('Requesting repair lib sync')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'repair')
|
||||
utils.plex_command('repair-scan')
|
||||
elif mode == 'manualsync':
|
||||
log.info('Requesting full library scan')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'full')
|
||||
utils.plex_command('full-scan')
|
||||
|
||||
elif mode == 'texturecache':
|
||||
log.info('Requesting texture caching of all textures')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'textures')
|
||||
utils.plex_command('textures-scan')
|
||||
|
||||
elif mode == 'chooseServer':
|
||||
entrypoint.choose_pms_server()
|
||||
|
@ -128,7 +125,7 @@ class Main():
|
|||
|
||||
elif mode == 'fanart':
|
||||
log.info('User requested fanarttv refresh')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'fanart')
|
||||
utils.plex_command('fanart-scan')
|
||||
|
||||
elif '/extrafanart' in path:
|
||||
plexpath = arguments[1:]
|
||||
|
@ -158,7 +155,7 @@ class Main():
|
|||
"""
|
||||
request = '%s&handle=%s' % (argv[2], HANDLE)
|
||||
# Put the request into the 'queue'
|
||||
utils.plex_command('PLAY', request)
|
||||
utils.plex_command('PLAY-%s' % request)
|
||||
if HANDLE == -1:
|
||||
# Handle -1 received, not waiting for main thread
|
||||
return
|
||||
|
|
27
resources/lib/app/__init__.py
Normal file
27
resources/lib/app/__init__.py
Normal 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()
|
86
resources/lib/app/account.py
Normal file
86
resources/lib/app/account.py
Normal 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)
|
43
resources/lib/app/application.py
Normal file
43
resources/lib/app/application.py
Normal 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'))
|
75
resources/lib/app/connection.py
Normal file
75
resources/lib/app/connection.py
Normal 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)
|
75
resources/lib/app/libsync.py
Normal file
75
resources/lib/app/libsync.py
Normal 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'
|
71
resources/lib/app/playstate.py
Normal file
71
resources/lib/app/playstate.py
Normal 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
|
|
@ -7,8 +7,7 @@ import requests
|
|||
import xbmc
|
||||
|
||||
from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB
|
||||
from . import backgroundthread, utils
|
||||
from . import state
|
||||
from . import app, backgroundthread, utils
|
||||
|
||||
LOG = getLogger('PLEX.artwork')
|
||||
|
||||
|
@ -19,13 +18,7 @@ requests.packages.urllib3.disable_warnings()
|
|||
# download is successful
|
||||
TIMEOUT = (35.1, 35.1)
|
||||
|
||||
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)
|
||||
IMAGE_CACHING_SUSPENDS = []
|
||||
|
||||
|
||||
def double_urlencode(text):
|
||||
|
@ -37,19 +30,9 @@ def double_urldecode(text):
|
|||
|
||||
|
||||
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):
|
||||
return any(IMAGE_CACHING_SUSPENDS)
|
||||
|
||||
def cancel(self):
|
||||
self._canceled = True
|
||||
|
||||
@staticmethod
|
||||
def _art_url_generator():
|
||||
for kind in (KodiVideoDB, KodiMusicDB):
|
||||
|
@ -88,18 +71,18 @@ def cache_url(url):
|
|||
try:
|
||||
requests.head(
|
||||
url="http://%s:%s/image/image://%s"
|
||||
% (state.WEBSERVER_HOST,
|
||||
state.WEBSERVER_PORT,
|
||||
% (app.CONN.webserver_host,
|
||||
app.CONN.webserver_port,
|
||||
url),
|
||||
auth=(state.WEBSERVER_USERNAME,
|
||||
state.WEBSERVER_PASSWORD),
|
||||
auth=(app.CONN.webserver_username,
|
||||
app.CONN.webserver_password),
|
||||
timeout=TIMEOUT)
|
||||
except requests.Timeout:
|
||||
# We don't need the result, only trigger Kodi to start the
|
||||
# download. All is well
|
||||
break
|
||||
except requests.ConnectionError:
|
||||
if state.STOP_PKC:
|
||||
if app.APP.stop_pkc:
|
||||
# Kodi terminated
|
||||
break
|
||||
# Server thinks its a DOS attack, ('error 10053')
|
||||
|
|
|
@ -14,7 +14,6 @@ LOG = getLogger('PLEX.' + __name__)
|
|||
|
||||
|
||||
class KillableThread(threading.Thread):
|
||||
pass
|
||||
'''A thread class that supports raising exception in the thread from
|
||||
another thread.
|
||||
'''
|
||||
|
@ -77,6 +76,45 @@ class KillableThread(threading.Thread):
|
|||
# except KillThreadException:
|
||||
# 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):
|
||||
def add(self, task):
|
||||
|
|
|
@ -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 ##===----")
|
|
@ -7,11 +7,8 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from logging import getLogger
|
||||
from xbmc import Player
|
||||
|
||||
from . import playqueue as PQ
|
||||
from . import plex_functions as PF
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
from . import state
|
||||
from . import playqueue as PQ, plex_functions as PF
|
||||
from . import json_rpc as js, variables as v, app
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -68,12 +65,12 @@ def process_command(request_path, params):
|
|||
if request_path == 'player/playback/playMedia':
|
||||
# We need to tell service.py
|
||||
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
|
||||
state.COMPANION_QUEUE.put({
|
||||
app.APP.companion_queue.put({
|
||||
'action': action,
|
||||
'data': params
|
||||
})
|
||||
elif request_path == 'player/playback/refreshPlayQueue':
|
||||
state.COMPANION_QUEUE.put({
|
||||
app.APP.companion_queue.put({
|
||||
'action': 'refreshPlayQueue',
|
||||
'data': params
|
||||
})
|
||||
|
@ -115,7 +112,7 @@ def process_command(request_path, params):
|
|||
elif request_path == "player/navigation/back":
|
||||
js.input_back()
|
||||
elif request_path == "player/playback/setStreams":
|
||||
state.COMPANION_QUEUE.put({
|
||||
app.APP.companion_queue.put({
|
||||
'action': 'setStreams',
|
||||
'data': params
|
||||
})
|
||||
|
|
|
@ -43,8 +43,8 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
|
|||
return self.selected_option
|
||||
|
||||
def onInit(self):
|
||||
if utils.window('PlexUserImage'):
|
||||
self.getControl(USER_IMAGE).setImage(utils.window('PlexUserImage'))
|
||||
if utils.window('plexAvatar'):
|
||||
self.getControl(USER_IMAGE).setImage(utils.window('plexAvatar'))
|
||||
height = 479 + (len(self._options) * 55)
|
||||
LOG.debug("options: %s", self._options)
|
||||
self.list_ = self.getControl(LIST)
|
||||
|
|
|
@ -8,7 +8,7 @@ import xbmcgui
|
|||
from .plex_api import API
|
||||
from .plex_db import PlexDB
|
||||
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 self.api and self.api.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'])
|
||||
if self.kodi_type in v.KODI_VIDEOTYPES:
|
||||
options.append(OPTIONS['Transcode'])
|
||||
|
@ -112,7 +112,7 @@ class ContextMenu(object):
|
|||
"""
|
||||
selected = self._selected_option
|
||||
if selected == OPTIONS['Transcode']:
|
||||
state.FORCE_TRANSCODE = True
|
||||
app.PLAYSTATE.force_transcode = True
|
||||
self._PMS_play()
|
||||
elif selected == OPTIONS['PMS_Play']:
|
||||
self._PMS_play()
|
||||
|
@ -146,7 +146,7 @@ class ContextMenu(object):
|
|||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type])
|
||||
playqueue.clear()
|
||||
state.CONTEXT_MENU_PLAY = True
|
||||
app.PLAYSTATE.context_menu_play = True
|
||||
handle = self.api.path(force_first_media=False, force_addon=True)
|
||||
xbmc.executebuiltin('RunPlugin(%s)' % handle)
|
||||
|
||||
|
|
|
@ -4,9 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from logging import getLogger
|
||||
import requests
|
||||
|
||||
from . import utils
|
||||
from . import clientinfo
|
||||
from . import state
|
||||
from . import utils, clientinfo, app
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -14,7 +12,7 @@ from . import state
|
|||
import requests.packages.urllib3
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
LOG = getLogger('PLEX.downloadutils')
|
||||
LOG = getLogger('PLEX.download')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -39,25 +37,16 @@ class DownloadUtils():
|
|||
def __init__(self):
|
||||
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):
|
||||
"""
|
||||
Reserved for userclient only
|
||||
|
||||
verifySSL must be 'true' to enable certificate validation
|
||||
|
||||
certificate must be path to certificate or 'None'
|
||||
"""
|
||||
if verifySSL is None:
|
||||
verifySSL = state.VERIFY_SSL_CERT
|
||||
verifySSL = app.CONN.verify_ssl_cert
|
||||
if certificate is None:
|
||||
certificate = state.SSL_CERT_PATH
|
||||
certificate = app.CONN.ssl_cert_path
|
||||
# Set the session's parameters
|
||||
self.s.verify = verifySSL
|
||||
if certificate:
|
||||
|
@ -67,8 +56,7 @@ class DownloadUtils():
|
|||
|
||||
def startSession(self, reset=False):
|
||||
"""
|
||||
User should be authenticated when this method is called (via
|
||||
userclient)
|
||||
User should be authenticated when this method is called
|
||||
"""
|
||||
# Start session
|
||||
self.s = requests.Session()
|
||||
|
@ -80,9 +68,6 @@ class DownloadUtils():
|
|||
# Set SSL settings
|
||||
self.setSSL()
|
||||
|
||||
# Set other stuff
|
||||
self.setServer(utils.window('pms_server'))
|
||||
|
||||
# Counters to declare PMS dead or unauthorized
|
||||
# Use window variables because start of movies will be called with a
|
||||
# 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("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):
|
||||
try:
|
||||
|
@ -155,7 +140,7 @@ class DownloadUtils():
|
|||
self.startSession()
|
||||
s = self.s
|
||||
# Replace for the real values
|
||||
url = url.replace("{server}", self.server)
|
||||
url = url.replace("{server}", app.CONN.server)
|
||||
else:
|
||||
# User is not (yet) authenticated. Used to communicate with
|
||||
# plex.tv and to check for PMS servers
|
||||
|
@ -164,9 +149,9 @@ class DownloadUtils():
|
|||
headerOptions = self.getHeader(options=headerOptions)
|
||||
else:
|
||||
headerOptions = headerOverride
|
||||
kwargs['verify'] = state.VERIFY_SSL_CERT
|
||||
if state.SSL_CERT_PATH:
|
||||
kwargs['cert'] = state.SSL_CERT_PATH
|
||||
kwargs['verify'] = app.CONN.verify_ssl_cert
|
||||
if app.CONN.ssl_cert_path:
|
||||
kwargs['cert'] = app.CONN.ssl_cert_path
|
||||
|
||||
# Set the variables we were passed (fallback to request session
|
||||
# otherwise - faster)
|
||||
|
@ -252,12 +237,11 @@ class DownloadUtils():
|
|||
self.unauthorizedAttempts):
|
||||
LOG.warn('We seem to be truly unauthorized for PMS'
|
||||
' %s ', url)
|
||||
if state.PMS_STATUS not in ('401', 'Auth'):
|
||||
# Tell userclient token has been revoked.
|
||||
if app.CONN.pms_status not in ('401', 'Auth'):
|
||||
# Tell others token has been revoked.
|
||||
LOG.debug('Setting PMS server status to '
|
||||
'unauthorized')
|
||||
state.PMS_STATUS = '401'
|
||||
utils.window('plex_serverStatus', value="401")
|
||||
app.CONN.pms_status = '401'
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30017),
|
||||
|
|
|
@ -20,8 +20,8 @@ from .plex_api import API
|
|||
from . import plex_functions as PF
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
# Be careful - your using state in another Python instance!
|
||||
from . import state
|
||||
# Be careful - your using app in another Python instance!
|
||||
from . import app
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger('PLEX.entrypoint')
|
||||
|
@ -34,34 +34,7 @@ def choose_pms_server():
|
|||
Lets user choose from list of PMS
|
||||
"""
|
||||
LOG.info("Choosing PMS server requested, starting")
|
||||
|
||||
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)
|
||||
utils.plex_command('choose_pms_server')
|
||||
|
||||
|
||||
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.
|
||||
Or signs in to plex.tv if the user was not logged in before.
|
||||
"""
|
||||
if utils.settings('plexToken'):
|
||||
LOG.info('Reseting plex.tv credentials in settings')
|
||||
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)')
|
||||
LOG.info('Toggle of Plex.tv sign-in requested')
|
||||
utils.plex_command('toggle_plex_tv_sign_in')
|
||||
|
||||
|
||||
def directory_item(label, path, folder=True):
|
||||
|
@ -185,13 +129,7 @@ def switch_plex_user():
|
|||
# position = 0
|
||||
# utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
|
||||
LOG.info("Plex home user switch requested")
|
||||
if not _log_out():
|
||||
return
|
||||
# First remove playlists of old user
|
||||
utils.delete_playlists()
|
||||
# Remove video nodes
|
||||
utils.delete_nodes()
|
||||
_log_in()
|
||||
utils.plex_command('switch_plex_user')
|
||||
|
||||
|
||||
def create_listitem(item, append_show_title=False, append_sxxexx=False):
|
||||
|
@ -573,13 +511,13 @@ def on_deck_episodes(viewid, tagname, limit):
|
|||
return
|
||||
# We're using another python instance - need to load some vars
|
||||
if utils.settings('useDirectPaths') == '1':
|
||||
state.DIRECT_PATHS = True
|
||||
state.REPLACE_SMB_PATH = utils.settings('replaceSMB') == 'true'
|
||||
state.REMAP_PATH = utils.settings('remapSMB') == 'true'
|
||||
if state.REMAP_PATH:
|
||||
app.SYNC.direct_paths = True
|
||||
app.SYNC.replace_smb_path = utils.settings('replaceSMB') == 'true'
|
||||
app.SYNC.remap_path = utils.settings('remapSMB') == 'true'
|
||||
if app.SYNC.remap_path:
|
||||
initialsetup.set_replace_paths()
|
||||
# Let's NOT check paths for widgets!
|
||||
state.PATH_VERIFIED = True
|
||||
app.SYNC.path_verified = True
|
||||
counter = 0
|
||||
for item in xml:
|
||||
api = API(item)
|
||||
|
@ -964,107 +902,5 @@ def create_new_pms():
|
|||
"""
|
||||
Opens dialogs for the user the plug in the PMS details
|
||||
"""
|
||||
# "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
|
||||
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
|
||||
LOG.info('Request to manually enter new PMS address')
|
||||
utils.plex_command('enter_new_pms_address')
|
||||
|
|
|
@ -2,22 +2,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from Queue import Queue
|
||||
|
||||
from xbmc import executebuiltin, translatePath
|
||||
from xbmc import executebuiltin
|
||||
|
||||
from . import utils
|
||||
from .utils import etree
|
||||
from . import path_ops
|
||||
from . import migration
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import userclient
|
||||
from . import clientinfo
|
||||
from . import plex_functions as PF
|
||||
from . import plex_tv
|
||||
from . import json_rpc as js
|
||||
from . import playqueue as PQ
|
||||
from . import state
|
||||
from . import app
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Sets certain settings for server by asking for the PMS' settings
|
||||
|
@ -145,11 +51,8 @@ class InitialSetup(object):
|
|||
"""
|
||||
def __init__(self):
|
||||
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
|
||||
plexdict = PF.GetPlexLoginFromSettings()
|
||||
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
||||
self.plex_login = plexdict['plexLogin']
|
||||
self.plex_token = plexdict['plexToken']
|
||||
self.plexid = plexdict['plexid']
|
||||
|
@ -158,6 +61,48 @@ class InitialSetup(object):
|
|||
if self.plex_token:
|
||||
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):
|
||||
"""
|
||||
Signs (freshly) in to plex.tv (will be saved to file settings)
|
||||
|
@ -227,26 +172,26 @@ class InitialSetup(object):
|
|||
not set before
|
||||
"""
|
||||
answer = True
|
||||
chk = PF.check_connection(self.server, verifySSL=False)
|
||||
chk = PF.check_connection(app.CONN.server, verifySSL=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
|
||||
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 '
|
||||
'get the PMS unique ID', self.server)
|
||||
self.serverid = PF.GetMachineIdentifier(self.server)
|
||||
if self.serverid is None:
|
||||
'get the PMS unique ID', app.CONN.server)
|
||||
app.CONN.machine_identifier = PF.GetMachineIdentifier(app.CONN.server)
|
||||
if app.CONN.machine_identifier is None:
|
||||
LOG.warn('Could not retrieve machineIdentifier')
|
||||
answer = False
|
||||
else:
|
||||
utils.settings('plex_machineIdentifier', value=self.serverid)
|
||||
utils.settings('plex_machineIdentifier', value=app.CONN.machine_identifier)
|
||||
elif answer is True:
|
||||
temp_server_id = PF.GetMachineIdentifier(self.server)
|
||||
if temp_server_id != self.serverid:
|
||||
temp_server_id = PF.GetMachineIdentifier(app.CONN.server)
|
||||
if temp_server_id != app.CONN.machine_identifier:
|
||||
LOG.warn('The current PMS %s was expected to have a '
|
||||
'unique machineIdentifier of %s. But we got '
|
||||
'%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
|
||||
return answer
|
||||
|
||||
|
@ -305,7 +250,7 @@ class InitialSetup(object):
|
|||
"""
|
||||
server = None
|
||||
# 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
|
||||
if showDialog is True:
|
||||
server = self._user_pick_pms()
|
||||
|
@ -328,13 +273,13 @@ class InitialSetup(object):
|
|||
if https_updated is False:
|
||||
serverlist = PF.discover_pms(self.plex_token)
|
||||
for item in serverlist:
|
||||
if item.get('machineIdentifier') == self.serverid:
|
||||
if item.get('machineIdentifier') == app.CONN.machine_identifier:
|
||||
server = item
|
||||
if server is None:
|
||||
name = utils.settings('plex_servername')
|
||||
LOG.warn('The PMS you have used before with a unique '
|
||||
'machineIdentifier of %s and name %s is '
|
||||
'offline', self.serverid, name)
|
||||
'offline', app.CONN.machine_identifier, name)
|
||||
return
|
||||
chk = self._check_pms_connectivity(server)
|
||||
if chk == 504 and https_updated is False:
|
||||
|
@ -535,7 +480,8 @@ class InitialSetup(object):
|
|||
# Do we need to migrate stuff?
|
||||
migration.check_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
|
||||
# breaking playback reporting for PKC
|
||||
|
@ -556,19 +502,19 @@ class InitialSetup(object):
|
|||
|
||||
# If a Plex server IP has already been set
|
||||
# return only if the right machine identifier is found
|
||||
if self.server:
|
||||
LOG.info("PMS is already set: %s. Checking now...", self.server)
|
||||
if app.CONN.server:
|
||||
LOG.info("PMS is already set: %s. Checking now...", app.CONN.server)
|
||||
if self.check_existing_pms():
|
||||
LOG.info("Using PMS %s with machineIdentifier %s",
|
||||
self.server, self.serverid)
|
||||
_write_pms_settings(self.server, self.pms_token)
|
||||
app.CONN.server, app.CONN.machine_identifier)
|
||||
_write_pms_settings(app.CONN.server, self.pms_token)
|
||||
if reboot is True:
|
||||
utils.reboot_kodi()
|
||||
return
|
||||
|
||||
# If not already retrieved myplex info, optionally let user sign in
|
||||
# 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()
|
||||
|
||||
server = self.pick_pms()
|
||||
|
|
|
@ -7,7 +7,7 @@ from ntpath import dirname
|
|||
from ..plex_api import API
|
||||
from ..plex_db import PlexDB
|
||||
from ..kodi_db import KodiVideoDB
|
||||
from .. import utils
|
||||
from .. import utils, timing
|
||||
|
||||
LOG = getLogger('PLEX.itemtypes.common')
|
||||
|
||||
|
@ -135,5 +135,5 @@ class ItemBase(object):
|
|||
resume,
|
||||
duration,
|
||||
view_count,
|
||||
utils.unix_date_to_kodi(lastViewedAt),
|
||||
timing.plex_date_to_kodi(lastViewedAt),
|
||||
plex_type)
|
||||
|
|
|
@ -5,7 +5,7 @@ from logging import getLogger
|
|||
|
||||
from .common import ItemBase
|
||||
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')
|
||||
|
||||
|
@ -50,8 +50,8 @@ class Movie(ItemBase):
|
|||
studios = api.music_studio_list()
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
do_indirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
do_indirect = not app.SYNC.direct_paths
|
||||
if app.SYNC.direct_paths:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = api.file_path(force_first_media=True)
|
||||
if playurl is None:
|
||||
|
|
|
@ -7,7 +7,7 @@ from .common import ItemBase
|
|||
from ..plex_api import API
|
||||
from ..plex_db import PlexDB
|
||||
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')
|
||||
|
||||
|
@ -165,12 +165,11 @@ class Artist(MusicMixin, ItemBase):
|
|||
# Kodi doesn't allow that. In case that happens we just merge the
|
||||
# artist entries.
|
||||
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()),
|
||||
api.plot(),
|
||||
thumb,
|
||||
fanart,
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
kodi_id)
|
||||
# Update artwork
|
||||
self.kodidb.modify_artwork(artworks,
|
||||
|
@ -256,7 +255,7 @@ class Album(MusicMixin, ItemBase):
|
|||
thumb,
|
||||
api.music_studio(),
|
||||
userdata['UserRating'],
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
'album',
|
||||
kodi_id)
|
||||
else:
|
||||
|
@ -270,7 +269,7 @@ class Album(MusicMixin, ItemBase):
|
|||
thumb,
|
||||
api.music_studio(),
|
||||
userdata['UserRating'],
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
'album',
|
||||
kodi_id)
|
||||
# OR ADD THE ALBUM #####
|
||||
|
@ -289,7 +288,7 @@ class Album(MusicMixin, ItemBase):
|
|||
thumb,
|
||||
api.music_studio(),
|
||||
userdata['UserRating'],
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
'album')
|
||||
else:
|
||||
self.kodidb.add_album_17(kodi_id,
|
||||
|
@ -303,7 +302,7 @@ class Album(MusicMixin, ItemBase):
|
|||
thumb,
|
||||
api.music_studio(),
|
||||
userdata['UserRating'],
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
'album')
|
||||
self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name())
|
||||
self.kodidb.add_discography(artist_id, name, api.year())
|
||||
|
@ -397,7 +396,7 @@ class Song(MusicMixin, ItemBase):
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
'single')
|
||||
else:
|
||||
self.kodidb.add_album_17(kodi_id,
|
||||
|
@ -411,7 +410,7 @@ class Song(MusicMixin, ItemBase):
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
utils.unix_date_to_kodi(self.last_sync),
|
||||
timing.unix_date_to_kodi(self.last_sync),
|
||||
'single')
|
||||
else:
|
||||
album = self.plexdb.album(album_id)
|
||||
|
@ -469,8 +468,8 @@ class Song(MusicMixin, ItemBase):
|
|||
mood = api.list_to_string(moods)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
do_indirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
do_indirect = not app.SYNC.direct_paths
|
||||
if app.SYNC.direct_paths:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = api.file_path(force_first_media=True)
|
||||
if playurl is None:
|
||||
|
@ -489,8 +488,7 @@ class Song(MusicMixin, ItemBase):
|
|||
path = playurl.replace(filename, "")
|
||||
if do_indirect:
|
||||
# Plex works a bit differently
|
||||
path = "%s%s" % (utils.window('pms_server'),
|
||||
xml[0][0].get('key'))
|
||||
path = "%s%s" % (app.CONN.server, xml[0][0].get('key'))
|
||||
path = api.attach_plex_token_to_url(path)
|
||||
filename = path.rsplit('/', 1)[1]
|
||||
path = path.replace(filename, '')
|
||||
|
|
|
@ -5,7 +5,7 @@ from logging import getLogger
|
|||
|
||||
from .common import ItemBase, process_path
|
||||
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')
|
||||
|
||||
|
@ -135,7 +135,7 @@ class Show(ItemBase, TvShowMixin):
|
|||
studio = api.list_to_string(studios)
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
if state.DIRECT_PATHS:
|
||||
if app.SYNC.direct_paths:
|
||||
# Direct paths is set the Kodi way
|
||||
playurl = api.validate_playurl(api.tv_show_path(),
|
||||
api.plex_type(),
|
||||
|
@ -378,8 +378,8 @@ class Episode(ItemBase, TvShowMixin):
|
|||
parent_id = season['kodi_id']
|
||||
|
||||
# GET THE FILE AND PATH #####
|
||||
do_indirect = not state.DIRECT_PATHS
|
||||
if state.DIRECT_PATHS:
|
||||
do_indirect = not app.SYNC.direct_paths
|
||||
if app.SYNC.direct_paths:
|
||||
playurl = api.file_path(force_first_media=True)
|
||||
if playurl is None:
|
||||
do_indirect = True
|
||||
|
@ -513,7 +513,7 @@ class Episode(ItemBase, TvShowMixin):
|
|||
userdata['PlayCount'],
|
||||
userdata['LastPlayedDate'],
|
||||
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
|
||||
filename = api.file_name(force_first_media=True)
|
||||
path = 'plugin://%s.tvshows/' % v.ADDON_ID
|
||||
|
|
|
@ -8,7 +8,7 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from json import loads, dumps
|
||||
from xbmc import executeJSONRPC
|
||||
|
||||
from . import utils
|
||||
from . import timing
|
||||
|
||||
|
||||
class JsonRPC(object):
|
||||
|
@ -156,7 +156,7 @@ def seek_to(offset):
|
|||
for playerid in get_player_ids():
|
||||
JsonRPC("Player.Seek").execute(
|
||||
{"playerid": playerid,
|
||||
"value": utils.millis_to_kodi_time(offset)})
|
||||
"value": timing.millis_to_kodi_time(offset)})
|
||||
|
||||
|
||||
def smallforward():
|
||||
|
|
|
@ -7,7 +7,7 @@ from .video import KodiVideoDB
|
|||
from .music import KodiMusicDB
|
||||
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')
|
||||
|
||||
|
@ -68,7 +68,7 @@ def setup_kodi_default_entries():
|
|||
VALUES (?, ?, ?)
|
||||
''', (v.DB_MUSIC_VERSION[v.KODIVERSION],
|
||||
0,
|
||||
utils.unix_date_to_kodi(utils.unix_timestamp())))
|
||||
timing.kodi_now()))
|
||||
|
||||
|
||||
def reset_cached_images():
|
||||
|
|
|
@ -5,7 +5,7 @@ from logging import getLogger
|
|||
from sqlite3 import IntegrityError
|
||||
|
||||
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')
|
||||
|
||||
|
@ -74,12 +74,11 @@ class KodiVideoDB(common.KodiDBBase):
|
|||
if pathid is None:
|
||||
self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path")
|
||||
pathid = self.cursor.fetchone()[0] + 1
|
||||
datetime = utils.unix_date_to_kodi(utils.unix_timestamp())
|
||||
self.cursor.execute('''
|
||||
INSERT INTO path(idPath, strPath, dateAdded)
|
||||
VALUES (?, ?, ?)
|
||||
''',
|
||||
(pathid, parentpath, datetime))
|
||||
(pathid, parentpath, timing.kodi_now()))
|
||||
if parentpath != path:
|
||||
# In case we end up having media in the filesystem root, C:\
|
||||
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
|
||||
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
|
||||
self.cursor.execute('SELECT strFilename FROM files WHERE idFile = ? LIMIT 1',
|
||||
(file_id, ))
|
||||
|
@ -598,7 +597,7 @@ class KodiVideoDB(common.KodiDBBase):
|
|||
Adds a resume marker for a video library item. Will even set 2,
|
||||
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,
|
||||
# second file_id that points to the path .tvshows instead of
|
||||
# .tvshows/<plex show id/!
|
||||
|
|
|
@ -6,23 +6,16 @@ PKC Kodi Monitoring implementation
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from json import loads
|
||||
from threading import Thread
|
||||
import copy
|
||||
import xbmc
|
||||
from xbmcgui import Window
|
||||
|
||||
from .plex_db import PlexDB
|
||||
from . import kodi_db
|
||||
from . import utils
|
||||
from . import plex_functions as PF
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import playback
|
||||
from . import initialsetup
|
||||
from . import playqueue as PQ
|
||||
from . import json_rpc as js
|
||||
from . import playlist_func as PL
|
||||
from . import state
|
||||
from . import variables as v
|
||||
from . import utils, timing, plex_functions as PF, playback, initialsetup
|
||||
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
|
||||
from . import backgroundthread, app, variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -68,9 +61,9 @@ class KodiMonitor(xbmc.Monitor):
|
|||
self._already_slept = False
|
||||
self.hack_replay = None
|
||||
xbmc.Monitor.__init__(self)
|
||||
for playerid in state.PLAYER_STATES:
|
||||
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
||||
state.OLD_PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
||||
for playerid in app.PLAYSTATE.player_states:
|
||||
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
||||
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
||||
LOG.info("Kodi monitor started.")
|
||||
|
||||
def onScanStarted(self, library):
|
||||
|
@ -142,8 +135,8 @@ class KodiMonitor(xbmc.Monitor):
|
|||
self.hack_replay = None
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
state.SUSPEND_SYNC = True
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
app.SYNC.suspend_sync = True
|
||||
with app.APP.lock_playqueues:
|
||||
self.PlayBackStart(data)
|
||||
elif method == "Player.OnStop":
|
||||
# Should refresh our video nodes, e.g. on deck
|
||||
|
@ -152,27 +145,27 @@ class KodiMonitor(xbmc.Monitor):
|
|||
self.hack_replay == data['item']):
|
||||
# Hack for add-on paths
|
||||
self.hack_replay = None
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
self._hack_addon_paths_replay_video()
|
||||
elif data.get('end'):
|
||||
if state.PKC_CAUSED_STOP is True:
|
||||
state.PKC_CAUSED_STOP = False
|
||||
if app.PLAYSTATE.pkc_caused_stop is True:
|
||||
app.PLAYSTATE.pkc_caused_stop = False
|
||||
LOG.debug('PKC caused this playback stop - ignoring')
|
||||
else:
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
_playback_cleanup(ended=True)
|
||||
else:
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
_playback_cleanup()
|
||||
state.PKC_CAUSED_STOP_DONE = True
|
||||
state.SUSPEND_SYNC = False
|
||||
app.PLAYSTATE.pkc_caused_stop_done = True
|
||||
app.SYNC.suspend_sync = False
|
||||
elif method == 'Playlist.OnAdd':
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
self._playlist_onadd(data)
|
||||
elif method == 'Playlist.OnRemove':
|
||||
self._playlist_onremove(data)
|
||||
elif method == 'Playlist.OnClear':
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
self._playlist_onclear(data)
|
||||
elif method == "VideoLibrary.OnUpdate":
|
||||
# Manually marking as watched/unwatched
|
||||
|
@ -214,7 +207,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
utils.plex_command('RUN_LIB_SCAN', 'full')
|
||||
elif method == "System.OnQuit":
|
||||
LOG.info('Kodi OnQuit detected - shutting down')
|
||||
state.STOP_PKC = True
|
||||
app.APP.stop_pkc = True
|
||||
|
||||
@staticmethod
|
||||
def _hack_addon_paths_replay_video():
|
||||
|
@ -237,7 +230,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
(within the same micro-second!)
|
||||
"""
|
||||
LOG.info('Detected re-start of playback of last item')
|
||||
old = state.OLD_PLAYER_STATES[1]
|
||||
old = app.PLAYSTATE.old_player_states[1]
|
||||
kwargs = {
|
||||
'plex_id': old['plex_id'],
|
||||
'plex_type': old['plex_type'],
|
||||
|
@ -261,8 +254,8 @@ class KodiMonitor(xbmc.Monitor):
|
|||
"""
|
||||
if 'id' not in data['item']:
|
||||
return
|
||||
old = state.OLD_PLAYER_STATES[data['playlistid']]
|
||||
if (not state.DIRECT_PATHS and
|
||||
old = app.PLAYSTATE.old_player_states[data['playlistid']]
|
||||
if (not app.SYNC.direct_paths and
|
||||
data['position'] == 0 and data['playlistid'] == 1 and
|
||||
not PQ.PLAYQUEUES[data['playlistid']].items and
|
||||
data['item']['type'] == old['kodi_type'] and
|
||||
|
@ -399,7 +392,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
else:
|
||||
pos = info['position'] if info['position'] != -1 else 0
|
||||
LOG.debug('Detected position %s for %s', pos, playqueue)
|
||||
status = state.PLAYER_STATES[playerid]
|
||||
status = app.PLAYSTATE.player_states[playerid]
|
||||
try:
|
||||
item = playqueue.items[pos]
|
||||
except IndexError:
|
||||
|
@ -431,7 +424,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
plex_id, plex_type = self._get_ids(kodi_id, kodi_type, path)
|
||||
if not plex_id:
|
||||
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
|
||||
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
||||
item.file = path
|
||||
|
@ -455,7 +448,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
else:
|
||||
container_key = '/library/metadata/%s' % plex_id
|
||||
# Remember that this player has been active
|
||||
state.ACTIVE_PLAYERS.add(playerid)
|
||||
app.PLAYSTATE.active_players.add(playerid)
|
||||
status.update(info)
|
||||
LOG.debug('Set the Plex container_key to: %s', container_key)
|
||||
status['container_key'] = container_key
|
||||
|
@ -469,8 +462,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
LOG.debug('Set the player state: %s', status)
|
||||
|
||||
|
||||
@utils.thread_methods
|
||||
class SpecialMonitor(Thread):
|
||||
class SpecialMonitor(backgroundthread.KillableThread):
|
||||
"""
|
||||
Detect the resume dialog for widgets.
|
||||
Could also be used to detect external players (see Emby implementation)
|
||||
|
@ -480,15 +472,15 @@ class SpecialMonitor(Thread):
|
|||
# "Start from beginning", "Play from beginning"
|
||||
strings = (utils.try_encode(utils.lang(12021)),
|
||||
utils.try_encode(utils.lang(12023)))
|
||||
while not self.stopped():
|
||||
while not self.isCanceled():
|
||||
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
|
||||
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
|
||||
# Remember that the item IS indeed resumable
|
||||
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:
|
||||
# Different context menu is displayed
|
||||
state.RESUME_PLAYBACK = False
|
||||
app.PLAYSTATE.resume_playback = False
|
||||
if xbmc.getCondVisibility('Window.IsVisible(MyVideoNav.xml)'):
|
||||
path = xbmc.getInfoLabel('container.folderpath')
|
||||
if (isinstance(path, str) and
|
||||
|
@ -507,14 +499,14 @@ def _playback_cleanup(ended=False):
|
|||
timing data otherwise)
|
||||
"""
|
||||
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
|
||||
# Companion (if we could not use the playqueue to store the token)
|
||||
state.PLEX_TRANSIENT_TOKEN = None
|
||||
for playerid in state.ACTIVE_PLAYERS:
|
||||
status = state.PLAYER_STATES[playerid]
|
||||
app.CONN.plex_transient_token = None
|
||||
for playerid in app.PLAYSTATE.active_players:
|
||||
status = app.PLAYSTATE.player_states[playerid]
|
||||
# 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
|
||||
if status['playmethod'] == 'Transcode':
|
||||
LOG.debug('Tell the PMS to stop transcoding')
|
||||
|
@ -527,9 +519,9 @@ def _playback_cleanup(ended=False):
|
|||
# started playback via PMS
|
||||
_record_playstate(status, ended)
|
||||
# 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
|
||||
state.ACTIVE_PLAYERS = set()
|
||||
app.PLAYSTATE.active_players = set()
|
||||
LOG.info('Finished PKC playback cleanup')
|
||||
|
||||
|
||||
|
@ -543,12 +535,12 @@ def _record_playstate(status, ended):
|
|||
# Item not (yet) in Kodi library
|
||||
LOG.debug('No playstate update due to Plex id not found: %s', status)
|
||||
return
|
||||
totaltime = float(utils.kodi_time_to_millis(status['totaltime'])) / 1000
|
||||
totaltime = float(timing.kodi_time_to_millis(status['totaltime'])) / 1000
|
||||
if ended:
|
||||
progress = 0.99
|
||||
time = v.IGNORE_SECONDS_AT_START + 1
|
||||
else:
|
||||
time = float(utils.kodi_time_to_millis(status['time'])) / 1000
|
||||
time = float(timing.kodi_time_to_millis(status['time'])) / 1000
|
||||
try:
|
||||
progress = time / totaltime
|
||||
except ZeroDivisionError:
|
||||
|
@ -556,7 +548,7 @@ def _record_playstate(status, ended):
|
|||
LOG.debug('Playback progress %s (%s of %s seconds)',
|
||||
progress, time, totaltime)
|
||||
playcount = status['playcount']
|
||||
last_played = utils.unix_date_to_kodi(utils.unix_timestamp())
|
||||
last_played = timing.now()
|
||||
if playcount is None:
|
||||
LOG.debug('playcount not found, looking it up in the Kodi DB')
|
||||
with kodi_db.KodiVideoDB() as kodidb:
|
||||
|
@ -582,7 +574,7 @@ def _record_playstate(status, ended):
|
|||
last_played,
|
||||
status['plex_type'])
|
||||
# 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)')):
|
||||
LOG.debug('Refreshing skin to update widgets')
|
||||
xbmc.executebuiltin('ReloadSkin()')
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
import xbmc
|
||||
|
||||
from .. import state
|
||||
from .. import app
|
||||
|
||||
|
||||
class libsync_mixin(object):
|
||||
def isCanceled(self):
|
||||
return (self._canceled or state.STOP_PKC or state.STOP_SYNC or
|
||||
state.SUSPEND_LIBRARY_THREAD or state.SUSPEND_SYNC)
|
||||
return (self._canceled or app.APP.stop_pkc or app.SYNC.stop_sync or
|
||||
app.SYNC.suspend_library_thread or app.SYNC.suspend_sync)
|
||||
|
||||
|
||||
def update_kodi_library(video=True, music=True):
|
||||
|
|
|
@ -8,7 +8,7 @@ from ..plex_api import API
|
|||
from ..plex_db import PlexDB
|
||||
from ..kodi_db import KodiVideoDB
|
||||
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')
|
||||
|
@ -27,14 +27,11 @@ class FanartThread(backgroundthread.KillableThread):
|
|||
self.refresh = refresh
|
||||
super(FanartThread, self).__init__()
|
||||
|
||||
def isCanceled(self):
|
||||
return state.STOP_PKC
|
||||
|
||||
def isSuspended(self):
|
||||
return (state.SUSPEND_LIBRARY_THREAD or
|
||||
state.STOP_SYNC or
|
||||
state.DB_SCAN or
|
||||
state.SUSPEND_SYNC)
|
||||
return (app.SYNC.suspend_library_thread or
|
||||
app.SYNC.stop_sync or
|
||||
app.SYNC.db_scan or
|
||||
app.SYNC.suspend_sync)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
|
@ -80,8 +77,6 @@ class FanartTask(backgroundthread.Task, common.libsync_mixin):
|
|||
self.refresh = refresh
|
||||
|
||||
def run(self):
|
||||
if self.isCanceled():
|
||||
return
|
||||
process_fanart(self.plex_id, self.plex_type, self.refresh)
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import xbmc
|
|||
from .get_metadata import GetMetadataTask, reset_collections
|
||||
from .process_metadata import InitNewSection, UpdateLastSync, ProcessMetadata
|
||||
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 ..plex_db import PlexDB
|
||||
|
||||
|
@ -97,7 +97,7 @@ class FullSync(common.libsync_mixin):
|
|||
if self.isCanceled():
|
||||
return False
|
||||
if not self.install_sync_done:
|
||||
state.PATH_VERIFIED = False
|
||||
app.SYNC.path_verified = False
|
||||
try:
|
||||
# Sync new, updated and deleted items
|
||||
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_EPISODE, v.PLEX_TYPE_SHOW, itemtypes.Episode, False)
|
||||
]
|
||||
if state.ENABLE_MUSIC:
|
||||
if app.SYNC.enable_music:
|
||||
kinds.extend([
|
||||
(v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
|
||||
(v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ARTIST, itemtypes.Album, True),
|
||||
|
@ -181,7 +181,7 @@ class FullSync(common.libsync_mixin):
|
|||
if self.isCanceled():
|
||||
return
|
||||
successful = False
|
||||
self.current_sync = utils.unix_timestamp()
|
||||
self.current_sync = timing.unix_timestamp()
|
||||
# Delete playlist and video node files from Kodi
|
||||
utils.delete_playlists()
|
||||
utils.delete_nodes()
|
||||
|
|
|
@ -102,8 +102,8 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
|
|||
while not self.isCanceled():
|
||||
if section is None:
|
||||
break
|
||||
LOG.debug('Start processing section %s: %s',
|
||||
section.plex_type, section.name)
|
||||
LOG.debug('Start processing section %s (%ss)',
|
||||
section.name, section.plex_type)
|
||||
self.current = 1
|
||||
self.processed = 0
|
||||
self.total = section.total
|
||||
|
|
|
@ -9,7 +9,7 @@ from ..utils import cast
|
|||
from ..plex_db import PlexDB
|
||||
from .. import kodi_db
|
||||
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')
|
||||
|
||||
|
@ -29,7 +29,7 @@ def sync_from_pms():
|
|||
except AttributeError:
|
||||
LOG.error("Error download PMS sections, abort")
|
||||
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
|
||||
music.excludefromscan_music_folders(xml=sections)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from logging import getLogger
|
|||
|
||||
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')
|
||||
|
||||
|
@ -84,7 +84,7 @@ def sync_pms_time():
|
|||
LOG.debug('No timestamp; using 0')
|
||||
timestamp = utils.cast(int, timestamp)
|
||||
# Set the timer
|
||||
koditime = utils.unix_timestamp()
|
||||
koditime = timing.unix_timestamp()
|
||||
# Toggle watched state
|
||||
PF.scrobble(plex_id, 'watched')
|
||||
# Let the PMS process this first!
|
||||
|
@ -100,9 +100,9 @@ def sync_pms_time():
|
|||
return False
|
||||
|
||||
# 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',
|
||||
value=str(state.KODI_PLEX_TIME_OFFSET))
|
||||
value=str(timing.KODI_PLEX_TIME_OFFSET))
|
||||
LOG.info("Time offset Koditime - Plextime in seconds: %s",
|
||||
state.KODI_PLEX_TIME_OFFSET)
|
||||
timing.KODI_PLEX_TIME_OFFSET)
|
||||
return True
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from logging import getLogger
|
||||
|
||||
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):
|
||||
# Plex: reassign mediatype due to Kodi inner workings
|
||||
# How many items do we get at most?
|
||||
limit = state.FETCH_PMS_ITEM_NUMBER
|
||||
limit = unicode(app.APP.fetch_pms_item_number)
|
||||
mediatypes = {
|
||||
'movie': 'movies',
|
||||
'show': 'tvshows',
|
||||
|
|
|
@ -10,7 +10,7 @@ from ..plex_api import API
|
|||
from ..plex_db import PlexDB
|
||||
from .. import kodi_db
|
||||
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')
|
||||
|
||||
|
@ -22,7 +22,7 @@ PLAYSTATE_SESSIONS = {}
|
|||
|
||||
|
||||
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):
|
||||
|
@ -75,7 +75,7 @@ def process_websocket_messages():
|
|||
9: 'deleted'
|
||||
"""
|
||||
global WEBSOCKET_MESSAGES
|
||||
now = utils.unix_timestamp()
|
||||
now = timing.unix_timestamp()
|
||||
update_kodi_video_library, update_kodi_music_library = False, False
|
||||
delete_list = []
|
||||
for i, message in enumerate(WEBSOCKET_MESSAGES):
|
||||
|
@ -84,7 +84,7 @@ def process_websocket_messages():
|
|||
break
|
||||
if message['state'] == 9:
|
||||
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
|
||||
# item. Do it later (excepting deletions)
|
||||
continue
|
||||
|
@ -127,7 +127,7 @@ def process_new_item_message(message):
|
|||
LOG.error('Could not download metadata for %s', message['plex_id'])
|
||||
return False, False, False
|
||||
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],
|
||||
section_name=xml.get('librarySectionTitle'),
|
||||
section_id=xml.get('librarySectionID'))
|
||||
|
@ -169,7 +169,7 @@ def store_timeline_message(data):
|
|||
'state': status,
|
||||
'plex_type': typus,
|
||||
'plex_id': utils.cast(int, message['itemID']),
|
||||
'timestamp': utils.unix_timestamp(),
|
||||
'timestamp': timing.unix_timestamp(),
|
||||
'attempt': 0
|
||||
})
|
||||
elif typus in (v.PLEX_TYPE_MOVIE,
|
||||
|
@ -186,7 +186,7 @@ def store_timeline_message(data):
|
|||
'state': status,
|
||||
'plex_type': typus,
|
||||
'plex_id': plex_id,
|
||||
'timestamp': utils.unix_timestamp(),
|
||||
'timestamp': timing.unix_timestamp(),
|
||||
'attempt': 0
|
||||
})
|
||||
|
||||
|
@ -227,7 +227,7 @@ def store_activity_message(data):
|
|||
'state': None, # Don't need a state here
|
||||
'plex_type': typus['plex_type'],
|
||||
'plex_id': plex_id,
|
||||
'timestamp': utils.unix_timestamp(),
|
||||
'timestamp': timing.unix_timestamp(),
|
||||
'attempt': 0
|
||||
})
|
||||
|
||||
|
@ -246,7 +246,7 @@ def process_playing(data):
|
|||
plex_id = utils.cast(int, message['ratingKey'])
|
||||
skip = False
|
||||
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
|
||||
skip = True
|
||||
if skip:
|
||||
|
@ -265,7 +265,7 @@ def process_playing(data):
|
|||
PLAYSTATE_SESSIONS[session_key] = {}
|
||||
else:
|
||||
# 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',
|
||||
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
|
||||
# update if neither session's username nor userid match
|
||||
# (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
|
||||
# playing (the '1').
|
||||
# Hence must be us (since several users require plex.tv
|
||||
# token for PKC)
|
||||
pass
|
||||
elif not (session['userId'] == state.PLEX_USER_ID or
|
||||
session['username'] == state.PLEX_USERNAME):
|
||||
elif not (session['userId'] == app.CONN.plex_user_id or
|
||||
session['username'] == app.CONN.plex_username):
|
||||
LOG.debug('Our username %s, userid %s did not match '
|
||||
'the session username %s with userid %s',
|
||||
state.PLEX_USERNAME,
|
||||
state.PLEX_USER_ID,
|
||||
app.CONN.plex_username,
|
||||
app.CONN.plex_user_id,
|
||||
session['username'],
|
||||
session['userId'])
|
||||
continue
|
||||
|
@ -334,7 +334,7 @@ def process_playing(data):
|
|||
mark_played = False
|
||||
LOG.debug('Update playstate for user %s for %s with plex id %s to '
|
||||
'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)
|
||||
func = itemtypes.ITEMTYPE_FROM_KODITYPE[session['kodi_type']]
|
||||
with func(None) as fkt:
|
||||
|
@ -343,7 +343,7 @@ def process_playing(data):
|
|||
resume,
|
||||
session['duration'],
|
||||
session['file_id'],
|
||||
utils.unix_timestamp(),
|
||||
timing.unix_timestamp(),
|
||||
v.PLEX_TYPE_FROM_KODI_TYPE[session['kodi_type']])
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from . import pickler
|
|||
from .playutils import PlayUtils
|
||||
from .pkc_listitem import PKCListItem
|
||||
from . import variables as v
|
||||
from . import state
|
||||
from . import app
|
||||
|
||||
###############################################################################
|
||||
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)
|
||||
global RESOLVE
|
||||
# If started via Kodi context menu, we never resolve
|
||||
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False
|
||||
if not state.AUTHENTICATED:
|
||||
RESOLVE = resolve if not app.PLAYSTATE.context_menu_play else False
|
||||
if not app.CONN.authenticated:
|
||||
LOG.error('Not yet authenticated for PMS, abort starting playback')
|
||||
# "Unauthorized for PMS"
|
||||
utils.dialog('notification', utils.lang(29999), utils.lang(30017))
|
||||
_ensure_resolve(abort=True)
|
||||
return
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
|
||||
try:
|
||||
|
@ -208,7 +208,7 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
|||
if RESOLVE:
|
||||
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
|
||||
sleep_timer = 0
|
||||
while not state.PKC_CAUSED_STOP_DONE:
|
||||
while not app.PLAYSTATE.pkc_caused_stop_done:
|
||||
sleep(50)
|
||||
sleep_timer += 1
|
||||
if sleep_timer > 100:
|
||||
|
@ -229,19 +229,19 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
|||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
# Do NOT use _ensure_resolve() because we resolved above already
|
||||
state.CONTEXT_MENU_PLAY = False
|
||||
state.FORCE_TRANSCODE = False
|
||||
state.RESUME_PLAYBACK = False
|
||||
app.PLAYSTATE.context_menu_play = False
|
||||
app.PLAYSTATE.force_transcode = False
|
||||
app.PLAYSTATE.resume_playback = False
|
||||
return
|
||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||
stack = _prep_playlist_stack(xml)
|
||||
_process_stack(playqueue, stack)
|
||||
# Always resume if playback initiated via PMS and there IS a resume
|
||||
# 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
|
||||
state.CONTEXT_MENU_PLAY = False
|
||||
state.FORCE_TRANSCODE = False
|
||||
app.PLAYSTATE.context_menu_play = False
|
||||
app.PLAYSTATE.force_transcode = False
|
||||
# New thread to release this one sooner (e.g. harddisk spinning up)
|
||||
thread = Thread(target=threaded_playback,
|
||||
args=(playqueue.kodi_pl, pos, offset))
|
||||
|
@ -272,8 +272,8 @@ def _ensure_resolve(abort=False):
|
|||
LOG.debug('Passing dummy path to Kodi')
|
||||
# if not state.CONTEXT_MENU_PLAY:
|
||||
# Because playback won't start with context menu play
|
||||
state.PKC_CAUSED_STOP = True
|
||||
state.PKC_CAUSED_STOP_DONE = False
|
||||
app.PLAYSTATE.pkc_caused_stop = True
|
||||
app.PLAYSTATE.pkc_caused_stop_done = False
|
||||
if not abort:
|
||||
result = pickler.Playback_Successful()
|
||||
result.listitem = PKCListItem(path=v.NULL_VIDEO)
|
||||
|
@ -283,9 +283,9 @@ def _ensure_resolve(abort=False):
|
|||
pickler.pickle_me(None)
|
||||
if abort:
|
||||
# Reset some playback variables
|
||||
state.CONTEXT_MENU_PLAY = False
|
||||
state.FORCE_TRANSCODE = False
|
||||
state.RESUME_PLAYBACK = False
|
||||
app.PLAYSTATE.context_menu_play = False
|
||||
app.PLAYSTATE.force_transcode = False
|
||||
app.PLAYSTATE.resume_playback = False
|
||||
|
||||
|
||||
def _init_existing_kodi_playlist(playqueue, pos):
|
||||
|
@ -299,7 +299,7 @@ def _init_existing_kodi_playlist(playqueue, pos):
|
|||
LOG.error('No Kodi items returned')
|
||||
raise PL.PlaylistError('No Kodi items returned')
|
||||
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
|
||||
# a LOT of strain if the following Kodi setting is enabled:
|
||||
# Settings -> Player -> Videos -> Play next video automatically
|
||||
|
@ -310,7 +310,7 @@ def _prep_playlist_stack(xml):
|
|||
stack = []
|
||||
for item in xml:
|
||||
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)):
|
||||
# If user chose to play via PMS or force transcode, do not
|
||||
# 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.part = item['part']
|
||||
playlist_item.id = item['id']
|
||||
playlist_item.force_transcode = state.FORCE_TRANSCODE
|
||||
playlist_item.force_transcode = app.PLAYSTATE.force_transcode
|
||||
pos += 1
|
||||
|
||||
|
||||
|
@ -413,7 +413,7 @@ def _conclude_playback(playqueue, pos):
|
|||
playurl = item.file
|
||||
if not playurl:
|
||||
LOG.info('Did not get a playurl, aborting playback silently')
|
||||
state.RESUME_PLAYBACK = False
|
||||
app.PLAYSTATE.resume_playback = False
|
||||
pickler.pickle_me(result)
|
||||
return
|
||||
listitem.setPath(utils.try_encode(playurl))
|
||||
|
@ -422,8 +422,8 @@ def _conclude_playback(playqueue, pos):
|
|||
elif item.playmethod == 'Transcode':
|
||||
playutils.audio_subtitle_prefs(listitem)
|
||||
|
||||
if state.RESUME_PLAYBACK is True:
|
||||
state.RESUME_PLAYBACK = False
|
||||
if app.PLAYSTATE.resume_playback is True:
|
||||
app.PLAYSTATE.resume_playback = False
|
||||
if (item.offset is None and
|
||||
item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP)):
|
||||
with PlexDB() as plexdb:
|
||||
|
|
|
@ -10,7 +10,7 @@ from . import playback
|
|||
from . import context_entry
|
||||
from . import json_rpc as js
|
||||
from . import pickler
|
||||
from . import state
|
||||
from . import app
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -61,7 +61,7 @@ class PlaybackStarter(Thread):
|
|||
kodi_type=params.get('kodi_type'))
|
||||
|
||||
def run(self):
|
||||
queue = state.COMMAND_PIPELINE_QUEUE
|
||||
queue = app.APP.command_pipeline_queue
|
||||
LOG.info("----===## Starting PlaybackStarter ##===----")
|
||||
while True:
|
||||
item = queue.get()
|
||||
|
|
|
@ -16,7 +16,7 @@ from .downloadutils import DownloadUtils as DU
|
|||
from . import utils
|
||||
from . import json_rpc as js
|
||||
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
|
||||
return kodi_item
|
||||
# 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['file'].startswith('http')):
|
||||
kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'],
|
||||
|
|
|
@ -19,7 +19,7 @@ from . import pms, db, kodi_pl, plex_pl
|
|||
|
||||
from ..watchdog import events
|
||||
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')
|
||||
|
@ -92,7 +92,7 @@ def websocket(plex_id, status):
|
|||
* 9: 'deleted'
|
||||
"""
|
||||
create = False
|
||||
with state.LOCK_PLAYLISTS:
|
||||
with app.APP.lock_playlists:
|
||||
playlist = db.get_playlist(plex_id=plex_id)
|
||||
if plex_id in IGNORE_PLEX_PLAYLIST_CHANGE:
|
||||
LOG.debug('Ignoring detected Plex playlist change for %s',
|
||||
|
@ -155,7 +155,7 @@ def full_sync():
|
|||
fetch the PMS playlists)
|
||||
"""
|
||||
LOG.info('Starting playlist full sync')
|
||||
with state.LOCK_PLAYLISTS:
|
||||
with app.APP.lock_playlists:
|
||||
# Need to lock because we're messing with playlists
|
||||
return _full_sync()
|
||||
|
||||
|
@ -283,7 +283,7 @@ def sync_kodi_playlist(path):
|
|||
return False
|
||||
if extension not in SUPPORTED_FILETYPES:
|
||||
return False
|
||||
if not state.SYNC_SPECIFIC_KODI_PLAYLISTS:
|
||||
if not app.SYNC.sync_specific_kodi_playlists:
|
||||
return True
|
||||
playlist = Playlist()
|
||||
playlist.kodi_path = path
|
||||
|
@ -341,10 +341,10 @@ def sync_plex_playlist(playlist=None, xml=None, plex_id=None):
|
|||
return False
|
||||
name = api.title()
|
||||
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')
|
||||
return False
|
||||
if not state.SYNC_SPECIFIC_PLEX_PLAYLISTS:
|
||||
if not app.SYNC.sync_specific_plex_playlists:
|
||||
return True
|
||||
prefix = utils.settings('syncSpecificPlexPlaylistsPrefix').lower()
|
||||
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_DELETED: self.on_deleted,
|
||||
}
|
||||
with state.LOCK_PLAYLISTS:
|
||||
with app.APP.lock_playlists:
|
||||
_method_map[event.event_type](event)
|
||||
|
||||
def on_created(self, event):
|
||||
|
|
|
@ -5,16 +5,11 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
|
|||
"""
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from threading import Thread
|
||||
import xbmc
|
||||
|
||||
from . import utils
|
||||
from . import playlist_func as PL
|
||||
from . import plex_functions as PF
|
||||
from .plex_api import API
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
from . import state
|
||||
from . import playlist_func as PL, plex_functions as PF
|
||||
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger('PLEX.playqueue')
|
||||
|
@ -35,7 +30,7 @@ def init_playqueues():
|
|||
LOG.debug('Playqueues have already been initialized')
|
||||
return
|
||||
# Initialize Kodi playqueues
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
for i in (0, 1, 2):
|
||||
# Just in case the Kodi response is not sorted correctly
|
||||
for queue in js.get_playlists():
|
||||
|
@ -96,13 +91,18 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
|||
return playqueue
|
||||
|
||||
|
||||
@utils.thread_methods(add_suspends=['PMS_STATUS'])
|
||||
class PlayqueueMonitor(Thread):
|
||||
class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||
"""
|
||||
Unfortunately, Kodi does not tell if items within a Kodi playqueue
|
||||
(playlist) are swapped. This is what this monitor is for. Don't replace
|
||||
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):
|
||||
"""
|
||||
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
|
||||
continue
|
||||
for j, old_item in enumerate(old):
|
||||
if self.stopped():
|
||||
if self.isCanceled():
|
||||
# Chances are that we got an empty Kodi playlist due to
|
||||
# Kodi exit
|
||||
return
|
||||
|
@ -178,7 +178,7 @@ class PlayqueueMonitor(Thread):
|
|||
for j in range(i, len(index)):
|
||||
index[j] += 1
|
||||
for i in reversed(index):
|
||||
if self.stopped():
|
||||
if self.isCanceled():
|
||||
# Chances are that we got an empty Kodi playlist due to
|
||||
# Kodi exit
|
||||
return
|
||||
|
@ -192,20 +192,18 @@ class PlayqueueMonitor(Thread):
|
|||
LOG.debug('Done comparing playqueues')
|
||||
|
||||
def run(self):
|
||||
stopped = self.stopped
|
||||
suspended = self.suspended
|
||||
LOG.info("----===## Starting PlayqueueMonitor ##===----")
|
||||
while not stopped():
|
||||
while suspended():
|
||||
if stopped():
|
||||
while not self.isCanceled():
|
||||
while self.isSuspended():
|
||||
if self.isCanceled():
|
||||
break
|
||||
xbmc.sleep(1000)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
for playqueue in PLAYQUEUES:
|
||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||
if playqueue.old_kodi_pl != kodi_pl:
|
||||
if playqueue.id is None and (not state.DIRECT_PATHS or
|
||||
state.CONTEXT_MENU_PLAY):
|
||||
if playqueue.id is None and (not app.PLAYSTATE.direct_paths or
|
||||
app.PLAYSTATE.context_menu_play):
|
||||
# Only initialize if directly fired up using direct
|
||||
# paths. Otherwise let default.py do its magic
|
||||
LOG.debug('Not yet initiating playback')
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from logging import getLogger
|
||||
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from . import utils, app
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
@ -304,7 +304,7 @@ class PlayUtils():
|
|||
# We don't know the language - no need to download
|
||||
else:
|
||||
path = self.api.attach_plex_token_to_url(
|
||||
"%s%s" % (utils.window('pms_server'),
|
||||
"%s%s" % (app.CONN.server,
|
||||
stream.attrib['key']))
|
||||
downloadable_streams.append(index)
|
||||
download_subs.append(utils.try_encode(path))
|
||||
|
|
|
@ -42,11 +42,11 @@ from .kodi_db import KodiVideoDB, KodiMusicDB
|
|||
from .utils import cast
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import clientinfo
|
||||
from . import utils
|
||||
from . import utils, timing
|
||||
from . import path_ops
|
||||
from . import plex_functions as PF
|
||||
from . import variables as v
|
||||
from . import state
|
||||
from . import app
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger('PLEX.plex_api')
|
||||
|
@ -121,7 +121,7 @@ class API(object):
|
|||
Pass direct_path=True if you're calling from another Plex python
|
||||
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)
|
||||
if (not direct_paths or force_addon or
|
||||
self.plex_type() == v.PLEX_TYPE_CLIP):
|
||||
|
@ -219,15 +219,15 @@ class API(object):
|
|||
extension not in v.KODI_SUPPORTED_IMAGES):
|
||||
# Let Plex transcode
|
||||
# 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'),
|
||||
state.PMS_TOKEN,
|
||||
"%s%s" % (state.PMS_SERVER, self.item[0][0].get('key')),
|
||||
app.CONN.pms_token,
|
||||
"%s%s" % (app.CONN.server, self.item[0][0].get('key')),
|
||||
1920,
|
||||
1080)
|
||||
else:
|
||||
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
|
||||
# later
|
||||
return utils.try_encode('%s&plex_id=%s' % (path, self.plex_id()))
|
||||
|
@ -263,10 +263,9 @@ class API(object):
|
|||
"""
|
||||
res = self.item.get('addedAt')
|
||||
if res is not None:
|
||||
res = utils.unix_date_to_kodi(res)
|
||||
return timing.plex_date_to_kodi(res)
|
||||
else:
|
||||
res = '2000-01-01 10:00:00'
|
||||
return res
|
||||
return '2000-01-01 10:00:00'
|
||||
|
||||
def viewcount(self):
|
||||
"""
|
||||
|
@ -300,11 +299,11 @@ class API(object):
|
|||
played = True if playcount else False
|
||||
|
||||
try:
|
||||
last_played = utils.unix_date_to_kodi(int(item['lastViewedAt']))
|
||||
last_played = utils.plex_date_to_kodi(int(item['lastViewedAt']))
|
||||
except (KeyError, ValueError):
|
||||
last_played = None
|
||||
|
||||
if state.INDICATE_MEDIA_VERSIONS is True:
|
||||
if app.SYNC.indicate_media_versions is True:
|
||||
userrating = 0
|
||||
for _ in self.item.findall('./Media'):
|
||||
userrating += 1
|
||||
|
@ -685,12 +684,12 @@ class API(object):
|
|||
|
||||
url may or may not already contain a '?'
|
||||
"""
|
||||
if not state.PMS_TOKEN:
|
||||
if not app.CONN.pms_token:
|
||||
return 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:
|
||||
url = "%s&X-Plex-Token=%s" % (url, state.PMS_TOKEN)
|
||||
url = "%s&X-Plex-Token=%s" % (url, app.CONN.pms_token)
|
||||
return url
|
||||
|
||||
def item_id(self):
|
||||
|
@ -770,7 +769,7 @@ class API(object):
|
|||
for extras in self.item.iterfind('Extras'):
|
||||
# There will always be only 1 extras element
|
||||
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'
|
||||
% (v.ADDON_ID, self.plex_id()))
|
||||
for extra in extras:
|
||||
|
@ -888,7 +887,7 @@ class API(object):
|
|||
artwork = '%s?width=%s&height=%s' % (artwork, width, height)
|
||||
artwork = ('%s/photo/:/transcode?width=3840&height=3840&'
|
||||
'minSize=1&upscale=0&url=%s'
|
||||
% (state.PMS_SERVER, quote(artwork)))
|
||||
% (app.CONN.server, quote(artwork)))
|
||||
artwork = self.attach_plex_token_to_url(artwork)
|
||||
return artwork
|
||||
|
||||
|
@ -1406,7 +1405,7 @@ class API(object):
|
|||
# trailers are 'clip' with PMS xmls
|
||||
if action == "DirectStream":
|
||||
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 '?'!
|
||||
if '?' in url:
|
||||
url += '&' + urlencode(xargs)
|
||||
|
@ -1423,7 +1422,7 @@ class API(object):
|
|||
}
|
||||
# Path/key to VIDEO item of xml PMS response is needed, not part
|
||||
path = self.item.attrib['key']
|
||||
transcode_path = state.PMS_SERVER + \
|
||||
transcode_path = app.CONN.server + \
|
||||
'/video/:/transcode/universal/start.m3u8?'
|
||||
args = {
|
||||
'audioBoost': utils.settings('audioBoost'),
|
||||
|
@ -1481,7 +1480,7 @@ class API(object):
|
|||
# We don't know the language - no need to download
|
||||
else:
|
||||
path = self.attach_plex_token_to_url(
|
||||
"%s%s" % (state.PMS_SERVER, key))
|
||||
"%s%s" % (app.CONN.server, key))
|
||||
externalsubs.append(path)
|
||||
kodiindex += 1
|
||||
LOG.info('Found external subs: %s', externalsubs)
|
||||
|
@ -1735,16 +1734,16 @@ class API(object):
|
|||
if path is None:
|
||||
return
|
||||
typus = v.REMAP_TYPE_FROM_PLEXTYPE[typus]
|
||||
if state.REMAP_PATH is True:
|
||||
path = path.replace(getattr(state, 'remapSMB%sOrg' % typus),
|
||||
getattr(state, 'remapSMB%sNew' % typus),
|
||||
if app.SYNC.remap_path is True:
|
||||
path = path.replace(getattr(app.SYNC, 'remapSMB%sOrg' % typus),
|
||||
getattr(app.SYNC, 'remapSMB%sNew' % typus),
|
||||
1)
|
||||
# There might be backslashes left over:
|
||||
path = path.replace('\\', '/')
|
||||
elif state.REPLACE_SMB_PATH is True:
|
||||
elif app.SYNC.replace_smb_path is True:
|
||||
if path.startswith('\\\\'):
|
||||
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):
|
||||
return path
|
||||
|
||||
|
@ -1769,14 +1768,14 @@ class API(object):
|
|||
if force_check is False:
|
||||
# Validate the path is correct with user intervention
|
||||
if self.ask_to_validate(path):
|
||||
state.STOP_SYNC = True
|
||||
app.SYNC.stop_sync = True
|
||||
path = None
|
||||
state.PATH_VERIFIED = True
|
||||
app.SYNC.path_verified = True
|
||||
else:
|
||||
path = None
|
||||
elif force_check is False:
|
||||
# Only set the flag if we were not force-checking the path
|
||||
state.PATH_VERIFIED = True
|
||||
app.SYNC.path_verified = True
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -20,7 +20,8 @@ from . import playback
|
|||
from . import json_rpc as js
|
||||
from . import playqueue as PQ
|
||||
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
|
||||
if transient_token is None:
|
||||
transient_token = playqueue.plex_transient_token
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||
try:
|
||||
xml.attrib
|
||||
|
@ -64,8 +65,7 @@ def update_playqueue_from_PMS(playqueue,
|
|||
playback.play_xml(playqueue, xml, offset)
|
||||
|
||||
|
||||
@utils.thread_methods(add_suspends=['PMS_STATUS'])
|
||||
class PlexCompanion(Thread):
|
||||
class PlexCompanion(backgroundthread.KillableThread):
|
||||
"""
|
||||
Plex Companion monitoring class. Invoke only once
|
||||
"""
|
||||
|
@ -80,7 +80,13 @@ class PlexCompanion(Thread):
|
|||
self.player = Player()
|
||||
self.httpd = False
|
||||
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):
|
||||
xml = PF.GetPlexMetadata(data['key'])
|
||||
|
@ -116,10 +122,9 @@ class PlexCompanion(Thread):
|
|||
offset = None
|
||||
playback.play_xml(playqueue, xml, offset)
|
||||
else:
|
||||
state.PLEX_TRANSIENT_TOKEN = data.get('token')
|
||||
app.CONN.plex_transient_token = data.get('token')
|
||||
if data.get('offset') != '0':
|
||||
state.RESUMABLE = True
|
||||
state.RESUME_PLAYBACK = True
|
||||
app.PLAYSTATE.resume_playback = True
|
||||
playback.playback_triage(api.plex_id(),
|
||||
api.plex_type(),
|
||||
resolve=False)
|
||||
|
@ -129,7 +134,7 @@ class PlexCompanion(Thread):
|
|||
"""
|
||||
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 = {
|
||||
'mode': 'plex_node',
|
||||
'key': '{server}%s' % data.get('key'),
|
||||
|
@ -221,16 +226,16 @@ class PlexCompanion(Thread):
|
|||
LOG.debug('Processing: %s', task)
|
||||
data = task['data']
|
||||
if task['action'] == 'alexa':
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
self._process_alexa(data)
|
||||
elif (task['action'] == 'playlist' and
|
||||
data.get('address') == 'node.plexapp.com'):
|
||||
self._process_node(data)
|
||||
elif task['action'] == 'playlist':
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
self._process_playlist(data)
|
||||
elif task['action'] == 'refreshPlayQueue':
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
self._process_refresh(data)
|
||||
elif task['action'] == 'setStreams':
|
||||
try:
|
||||
|
@ -260,8 +265,6 @@ class PlexCompanion(Thread):
|
|||
httpd = self.httpd
|
||||
# Cache for quicker while loops
|
||||
client = self.client
|
||||
stopped = self.stopped
|
||||
suspended = self.suspended
|
||||
|
||||
# Start up instances
|
||||
request_mgr = httppersist.RequestMgr()
|
||||
|
@ -298,12 +301,12 @@ class PlexCompanion(Thread):
|
|||
if httpd:
|
||||
thread = Thread(target=httpd.handle_request)
|
||||
|
||||
while not stopped():
|
||||
while not self.isCanceled():
|
||||
# If we are not authorized, sleep
|
||||
# Otherwise, we trigger a download which leads to a
|
||||
# re-authorizations
|
||||
while suspended():
|
||||
if stopped():
|
||||
while self.isSuspended():
|
||||
if self.isCanceled():
|
||||
break
|
||||
sleep(1000)
|
||||
try:
|
||||
|
@ -335,13 +338,13 @@ class PlexCompanion(Thread):
|
|||
LOG.warn(traceback.format_exc())
|
||||
# See if there's anything we need to process
|
||||
try:
|
||||
task = state.COMPANION_QUEUE.get(block=False)
|
||||
task = app.APP.companion_queue.get(block=False)
|
||||
except Empty:
|
||||
pass
|
||||
else:
|
||||
# Got instructions, process them
|
||||
self._process_tasks(task)
|
||||
state.COMPANION_QUEUE.task_done()
|
||||
app.APP.companion_queue.task_done()
|
||||
# Don't sleep
|
||||
continue
|
||||
sleep(50)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from .. import variables as v
|
||||
|
||||
|
||||
class Playlists(object):
|
||||
|
|
|
@ -5,10 +5,9 @@ from logging import getLogger
|
|||
import time
|
||||
import threading
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils, variables as v, state
|
||||
from . import utils, app
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger('PLEX.plex_tv')
|
||||
|
@ -87,7 +86,7 @@ def switch_home_user(userid, pin, token, machine_identifier):
|
|||
utils.settings('plex_restricteduser',
|
||||
'true' if xml.get('restricted', '0') == '1'
|
||||
else 'false')
|
||||
state.RESTRICTED_USER = True if \
|
||||
app.CONN.restricted_user = True if \
|
||||
xml.get('restricted', '0') == '1' else False
|
||||
|
||||
# Get final token to the PMS we've chosen
|
||||
|
@ -174,7 +173,7 @@ class PinLogin(object):
|
|||
start = time.time()
|
||||
while (not self._abort and
|
||||
time.time() - start < 300 and
|
||||
not state.STOP_PKC):
|
||||
not app.APP.stop_pkc):
|
||||
xml = DU().downloadUrl(self.POLL.format(self.id),
|
||||
authenticate=False)
|
||||
try:
|
||||
|
|
|
@ -31,7 +31,7 @@ import time
|
|||
from xbmc import sleep
|
||||
|
||||
from ..downloadutils import DownloadUtils as DU
|
||||
from .. import utils
|
||||
from .. import utils, app
|
||||
from .. import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
@ -228,7 +228,7 @@ class plexgdm:
|
|||
return self.server_list
|
||||
|
||||
def discover(self):
|
||||
currServer = utils.window('pms_server')
|
||||
currServer = app.CONN.server
|
||||
if not currServer:
|
||||
return
|
||||
currServerProt, currServerIP, currServerPort = \
|
||||
|
|
|
@ -9,8 +9,8 @@ from logging import getLogger
|
|||
from threading import Thread
|
||||
|
||||
from ..downloadutils import DownloadUtils as DU
|
||||
from .. import utils
|
||||
from .. import state
|
||||
from .. import utils, timing
|
||||
from .. import app
|
||||
from .. import variables as v
|
||||
from .. import json_rpc as js
|
||||
from .. import playqueue as PQ
|
||||
|
@ -101,9 +101,9 @@ def update_player_info(playerid):
|
|||
"""
|
||||
Updates all player info for playerid [int] in state.py.
|
||||
"""
|
||||
state.PLAYER_STATES[playerid].update(js.get_player_props(playerid))
|
||||
state.PLAYER_STATES[playerid]['volume'] = js.get_volume()
|
||||
state.PLAYER_STATES[playerid]['muted'] = js.get_muted()
|
||||
app.PLAYSTATE.playerstates[playerid].update(js.get_player_props(playerid))
|
||||
app.PLAYSTATE.playerstates[playerid]['volume'] = js.get_volume()
|
||||
app.PLAYSTATE.playerstates[playerid]['muted'] = js.get_muted()
|
||||
|
||||
|
||||
class SubscriptionMgr(object):
|
||||
|
@ -189,9 +189,9 @@ class SubscriptionMgr(object):
|
|||
return answ
|
||||
|
||||
def _timeline_dict(self, player, ptype):
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
with app.APP.lock_playqueues:
|
||||
playerid = player['playerid']
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
info = app.PLAYSTATE.player_states[playerid]
|
||||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
try:
|
||||
|
@ -208,12 +208,12 @@ class SubscriptionMgr(object):
|
|||
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO,
|
||||
v.PLEX_PLAYLIST_TYPE_PHOTO):
|
||||
self.location = 'fullScreenVideo'
|
||||
pbmc_server = utils.window('pms_server')
|
||||
pbmc_server = app.CONN.server
|
||||
if pbmc_server:
|
||||
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
||||
self.server = self.server.replace('/', '')
|
||||
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'
|
||||
mute = '1' if info['muted'] is True else '0'
|
||||
answ = {
|
||||
|
@ -225,7 +225,7 @@ class SubscriptionMgr(object):
|
|||
'state': status,
|
||||
'type': ptype,
|
||||
'itemType': ptype,
|
||||
'time': utils.kodi_time_to_millis(info['time']),
|
||||
'time': timing.kodi_time_to_millis(info['time']),
|
||||
'duration': duration,
|
||||
'seekRange': '0-%s' % duration,
|
||||
'shuffle': shuffle,
|
||||
|
@ -254,8 +254,8 @@ class SubscriptionMgr(object):
|
|||
if playqueue.items[position].guid:
|
||||
answ['guid'] = item.guid
|
||||
# Temp. token set?
|
||||
if state.PLEX_TRANSIENT_TOKEN:
|
||||
answ['token'] = state.PLEX_TRANSIENT_TOKEN
|
||||
if app.CONN.plex_transient_token:
|
||||
answ['token'] = app.CONN.plex_transient_token
|
||||
elif playqueue.plex_transient_token:
|
||||
answ['token'] = playqueue.plex_transient_token
|
||||
# Process audio and subtitle streams
|
||||
|
@ -301,7 +301,7 @@ class SubscriptionMgr(object):
|
|||
stream_type: 'video', 'audio', 'subtitle'
|
||||
"""
|
||||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
info = app.PLAYSTATE.player_states[playerid]
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
if info[STREAM_DETAILS[stream_type]] == -1:
|
||||
kodi_stream_index = -1
|
||||
|
@ -315,7 +315,7 @@ class SubscriptionMgr(object):
|
|||
Updates the Plex Companien client with the machine identifier uuid with
|
||||
command_id
|
||||
"""
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
with app.APP.lock_subscriber:
|
||||
if command_id and self.subscribers.get(uuid):
|
||||
self.subscribers[uuid].command_id = int(command_id)
|
||||
|
||||
|
@ -326,7 +326,7 @@ class SubscriptionMgr(object):
|
|||
playqueues.
|
||||
"""
|
||||
for player in players.values():
|
||||
info = state.PLAYER_STATES[player['playerid']]
|
||||
info = app.PLAYSTATE.player_states[player['playerid']]
|
||||
playqueue = PQ.PLAYQUEUES[player['playerid']]
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
try:
|
||||
|
@ -345,7 +345,7 @@ class SubscriptionMgr(object):
|
|||
Causes PKC to tell the PMS and Plex Companion players to receive a
|
||||
notification what's being played.
|
||||
"""
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
with app.APP.lock_subscriber:
|
||||
self._cleanup()
|
||||
# Get all the active/playing Kodi players (video, audio, pictures)
|
||||
players = js.get_players()
|
||||
|
@ -378,7 +378,7 @@ class SubscriptionMgr(object):
|
|||
self._send_pms_notification(player['playerid'], self.last_params)
|
||||
|
||||
def _get_pms_params(self, playerid):
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
info = app.PLAYSTATE.player_states[playerid]
|
||||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
try:
|
||||
|
@ -390,8 +390,8 @@ class SubscriptionMgr(object):
|
|||
'state': status,
|
||||
'ratingKey': item.plex_id,
|
||||
'key': '/library/metadata/%s' % item.plex_id,
|
||||
'time': utils.kodi_time_to_millis(info['time']),
|
||||
'duration': utils.kodi_time_to_millis(info['totaltime'])
|
||||
'time': timing.kodi_time_to_millis(info['time']),
|
||||
'duration': timing.kodi_time_to_millis(info['totaltime'])
|
||||
}
|
||||
if info['container_key'] is not None:
|
||||
# params['containerKey'] = info['container_key']
|
||||
|
@ -407,12 +407,12 @@ class SubscriptionMgr(object):
|
|||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
xargs = params_pms()
|
||||
xargs.update(params)
|
||||
if state.PLEX_TRANSIENT_TOKEN:
|
||||
xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN
|
||||
if app.CONN.plex_transient_token:
|
||||
xargs['X-Plex-Token'] = app.CONN.plex_transient_token
|
||||
elif playqueue.plex_transient_token:
|
||||
xargs['X-Plex-Token'] = playqueue.plex_transient_token
|
||||
elif state.PMS_TOKEN:
|
||||
xargs['X-Plex-Token'] = state.PMS_TOKEN
|
||||
elif app.ACCOUNT.pms_token:
|
||||
xargs['X-Plex-Token'] = app.ACCOUNT.pms_token
|
||||
url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
|
||||
serv.get('server', 'localhost'),
|
||||
serv.get('port', '32400'))
|
||||
|
@ -434,7 +434,7 @@ class SubscriptionMgr(object):
|
|||
command_id,
|
||||
self,
|
||||
self.request_mgr)
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
with app.APP.lock_subscriber:
|
||||
self.subscribers[subscriber.uuid] = subscriber
|
||||
return subscriber
|
||||
|
||||
|
@ -444,7 +444,7 @@ class SubscriptionMgr(object):
|
|||
uuid from PKC notifications.
|
||||
(Calls the cleanup() method of the subscriber)
|
||||
"""
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
with app.APP.lock_subscriber:
|
||||
for subscriber in self.subscribers.values():
|
||||
if subscriber.uuid == uuid or subscriber.host == uuid:
|
||||
subscriber.cleanup()
|
||||
|
|
|
@ -5,38 +5,107 @@ import logging
|
|||
import sys
|
||||
import xbmc
|
||||
|
||||
from . import utils
|
||||
from . import userclient
|
||||
from . import initialsetup
|
||||
from . import utils, clientinfo, timing
|
||||
from . import initialsetup, artwork
|
||||
from . import kodimonitor
|
||||
from . import sync
|
||||
from . import websocket_client
|
||||
from . import plex_companion
|
||||
from . import plex_functions as PF
|
||||
from . import command_pipeline
|
||||
from . import plex_functions as PF, playqueue as PQ
|
||||
from . import playback_starter
|
||||
from . import playqueue
|
||||
from . import variables as v
|
||||
from . import state
|
||||
from . import app
|
||||
from . import loghandler
|
||||
from .windows import userselect
|
||||
|
||||
###############################################################################
|
||||
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():
|
||||
|
||||
server_online = True
|
||||
warn_auth = True
|
||||
|
||||
user = None
|
||||
ws = None
|
||||
sync = None
|
||||
plexcompanion = None
|
||||
|
||||
user_running = False
|
||||
ws_running = False
|
||||
alexa_running = False
|
||||
sync_running = False
|
||||
|
@ -67,28 +136,110 @@ class Service():
|
|||
utils.settings('syncSpecificPlexPlaylistsPrefix'))
|
||||
LOG.info('XML decoding being used: %s', utils.ETREE)
|
||||
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()
|
||||
# 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):
|
||||
return xbmc.abortRequested or state.STOP_PKC
|
||||
def isCanceled(self):
|
||||
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):
|
||||
# Important: Threads depending on abortRequest will not trigger
|
||||
# if profile switch happens more than once.
|
||||
_stop_pkc = self._stop_pkc
|
||||
monitor = self.monitor
|
||||
app.init()
|
||||
# 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
|
||||
initialsetup.InitialSetup().setup()
|
||||
self.setup = initialsetup.InitialSetup()
|
||||
self.setup.setup()
|
||||
|
||||
# Detect playback start early on
|
||||
self.command_pipeline = command_pipeline.Monitor_Window()
|
||||
self.command_pipeline.start()
|
||||
|
||||
# Initialize important threads, handing over self for callback purposes
|
||||
self.user = userclient.UserClient()
|
||||
# Initialize important threads
|
||||
self.ws = websocket_client.PMS_Websocket()
|
||||
self.alexa = websocket_client.Alexa_Websocket()
|
||||
self.sync = sync.Sync()
|
||||
|
@ -97,9 +248,10 @@ class Service():
|
|||
self.playback_starter = playback_starter.PlaybackStarter()
|
||||
self.playqueue = playqueue.PlayqueueMonitor()
|
||||
|
||||
server_online = True
|
||||
welcome_msg = True
|
||||
counter = 0
|
||||
while not _stop_pkc():
|
||||
while not self.isCanceled():
|
||||
|
||||
if utils.window('plex_kodiProfile') != v.KODI_PROFILE:
|
||||
# Profile change happened, terminate this thread and others
|
||||
|
@ -108,149 +260,167 @@ class Service():
|
|||
v.KODI_PROFILE, utils.window('plex_kodiProfile'))
|
||||
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:
|
||||
# 1. Server is online
|
||||
# 2. User is set
|
||||
# 3. User has access to the server
|
||||
|
||||
if utils.window('plex_online') == "true":
|
||||
# Plex server is online
|
||||
# Verify if user is set and has access to the server
|
||||
if (self.user.user is not None) and self.user.has_access:
|
||||
if app.CONN.pms_status == 'Stop':
|
||||
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:
|
||||
# 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.specialmonitor.start()
|
||||
# Start the Websocket Client
|
||||
if not self.ws_running:
|
||||
self.ws_running = True
|
||||
self.ws.start()
|
||||
# Start the Alexa thread
|
||||
if (not self.alexa_running and
|
||||
utils.settings('enable_alexa') == 'true'):
|
||||
self.alexa_running = True
|
||||
self.alexa.start()
|
||||
# Start the syncing thread
|
||||
if not self.sync_running:
|
||||
self.sync_running = True
|
||||
self.sync.start()
|
||||
# Start the Plex Companion thread
|
||||
if not self.plexcompanion_running:
|
||||
self.plexcompanion_running = True
|
||||
self.plexcompanion.start()
|
||||
if not self.playback_starter_running:
|
||||
self.playback_starter_running = True
|
||||
self.playback_starter.start()
|
||||
# Start the Websocket Client
|
||||
if not self.ws_running:
|
||||
self.ws_running = True
|
||||
self.ws.start()
|
||||
# Start the Alexa thread
|
||||
if (not self.alexa_running and
|
||||
utils.settings('enable_alexa') == 'true'):
|
||||
self.alexa_running = True
|
||||
self.alexa.start()
|
||||
# Start the syncing thread
|
||||
if not self.sync_running:
|
||||
self.sync_running = True
|
||||
self.sync.start()
|
||||
# Start the Plex Companion thread
|
||||
if not self.plexcompanion_running:
|
||||
self.plexcompanion_running = True
|
||||
self.plexcompanion.start()
|
||||
if not self.playback_starter_running:
|
||||
self.playback_starter_running = True
|
||||
self.playback_starter.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:
|
||||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not self._stop_pkc():
|
||||
server = self.user.get_server()
|
||||
if server is False:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
elif PF.check_connection(server, verifySSL=True) is False:
|
||||
# Server is offline or cannot be reached
|
||||
# Alert the user and suppress future warning
|
||||
if self.server_online:
|
||||
self.server_online = False
|
||||
utils.window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
state.SUSPEND_LIBRARY_THREAD = True
|
||||
LOG.error("Plex Media Server went offline")
|
||||
if utils.settings('show_pms_offline') == 'true':
|
||||
utils.dialog('notification',
|
||||
utils.lang(33001),
|
||||
"%s %s" % (utils.lang(29999),
|
||||
utils.lang(33002)),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
counter += 1
|
||||
# Periodically check if the IP changed, e.g. per minute
|
||||
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
|
||||
server = app.CONN.server
|
||||
if not server:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
elif PF.check_connection(server, verifySSL=True) is False:
|
||||
# Server is offline or cannot be reached
|
||||
# Alert the user and suppress future warning
|
||||
if server_online:
|
||||
server_online = False
|
||||
utils.window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
app.SYNC.suspend_library_thread = True
|
||||
LOG.warn("Plex Media Server went offline")
|
||||
if utils.settings('show_pms_offline') == 'true':
|
||||
utils.dialog('notification',
|
||||
utils.lang(33001),
|
||||
"%s %s" % (utils.lang(29999),
|
||||
utils.lang(33002)),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
counter += 1
|
||||
# Periodically check if the IP changed, e.g. per minute
|
||||
if counter > 20:
|
||||
counter = 0
|
||||
if not self.server_online:
|
||||
# Server was offline when Kodi started.
|
||||
# Wait for server to be fully established.
|
||||
if monitor.waitForAbort(5):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
self.server_online = True
|
||||
# Alert the user that server is online.
|
||||
if (welcome_msg is False and
|
||||
utils.settings('show_pms_offline') == 'true'):
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(33003),
|
||||
icon='{plex}',
|
||||
time=5000,
|
||||
sound=False)
|
||||
LOG.info("Server %s is online and ready.", server)
|
||||
utils.window('plex_online', value="true")
|
||||
if state.AUTHENTICATED:
|
||||
# Server got offline when we were authenticated.
|
||||
# Hence resume threads
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
setup = initialsetup.InitialSetup()
|
||||
tmp = setup.pick_pms()
|
||||
if tmp:
|
||||
setup.write_pms_to_settings(tmp)
|
||||
app.CONN.load()
|
||||
else:
|
||||
# Server is online
|
||||
counter = 0
|
||||
if not server_online:
|
||||
# Server was offline when Kodi started.
|
||||
server_online = True
|
||||
# Alert the user that server is online.
|
||||
if (welcome_msg is False and
|
||||
utils.settings('show_pms_offline') == 'true'):
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(33003),
|
||||
icon='{plex}',
|
||||
time=5000,
|
||||
sound=False)
|
||||
LOG.info("Server %s is online and ready.", server)
|
||||
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 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):
|
||||
if self.monitor.waitForAbort(0.05):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
# Terminating PlexKodiConnect
|
||||
|
||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||
state.STOP_PKC = True
|
||||
app.APP.stop_pkc = True
|
||||
utils.window('plex_service_started', clear=True)
|
||||
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
||||
|
||||
|
|
|
@ -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'
|
|
@ -5,8 +5,8 @@ from logging import getLogger
|
|||
import xbmc
|
||||
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import library_sync
|
||||
from . import backgroundthread, utils, path_ops, artwork, variables as v, state
|
||||
from . import library_sync, timing
|
||||
from . import backgroundthread, utils, path_ops, artwork, variables as v, app
|
||||
from . import plex_db, kodi_db
|
||||
|
||||
LOG = getLogger('PLEX.sync')
|
||||
|
@ -18,10 +18,10 @@ def set_library_scan_toggle(boolean=True):
|
|||
"""
|
||||
if not boolean:
|
||||
# Deactivate
|
||||
state.DB_SCAN = False
|
||||
app.SYNC.db_scan = False
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
else:
|
||||
state.DB_SCAN = True
|
||||
app.SYNC.db_scan = True
|
||||
utils.window('plex_dbScan', value="true")
|
||||
|
||||
|
||||
|
@ -40,11 +40,8 @@ class Sync(backgroundthread.KillableThread):
|
|||
# self.lock = backgroundthread.threading.Lock()
|
||||
super(Sync, self).__init__()
|
||||
|
||||
def isCanceled(self):
|
||||
return state.STOP_PKC
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -54,7 +51,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
icon: "plex": shows Plex 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
|
||||
if icon == "plex":
|
||||
utils.dialog('notification',
|
||||
|
@ -70,14 +67,14 @@ class Sync(backgroundthread.KillableThread):
|
|||
|
||||
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
|
||||
"""
|
||||
if state.RUN_LIB_SCAN in ("full", "repair"):
|
||||
if app.SYNC.run_lib_scan in ("full", "repair"):
|
||||
set_library_scan_toggle()
|
||||
LOG.info('Full library scan requested, starting')
|
||||
self.start_library_sync(show_dialog=True,
|
||||
repair=state.RUN_LIB_SCAN == 'repair',
|
||||
repair=app.SYNC.run_lib_scan == 'repair',
|
||||
block=True)
|
||||
if self.sync_successful:
|
||||
# Full library sync finished
|
||||
|
@ -85,7 +82,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
elif not self.isSuspended() and not self.isCanceled():
|
||||
# ERROR in library sync
|
||||
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)
|
||||
from .windows import optionsdialog
|
||||
refresh = optionsdialog.show(utils.lang(29999),
|
||||
|
@ -99,7 +96,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
message=utils.lang(30015),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
elif state.RUN_LIB_SCAN == 'textures':
|
||||
elif app.SYNC.run_lib_scan == 'textures':
|
||||
LOG.info("Caching of images requested")
|
||||
if not utils.yesno_dialog("Image Texture Cache", utils.lang(39250)):
|
||||
return
|
||||
|
@ -113,7 +110,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
Hit this after the full sync has finished
|
||||
"""
|
||||
self.sync_successful = successful
|
||||
self.last_full_sync = utils.unix_timestamp()
|
||||
self.last_full_sync = timing.unix_timestamp()
|
||||
set_library_scan_toggle(boolean=False)
|
||||
if successful:
|
||||
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):
|
||||
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)
|
||||
# if block:
|
||||
# self.lock.acquire()
|
||||
|
@ -153,7 +150,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
|
||||
def on_fanart_download_finished(self, successful):
|
||||
# FanartTV lookup completed
|
||||
if successful and state.SYNC_DIALOG:
|
||||
if successful and app.SYNC.sync_dialog:
|
||||
utils.dialog('notification',
|
||||
heading='{plex}',
|
||||
message=utils.lang(30019),
|
||||
|
@ -174,7 +171,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
try:
|
||||
self._run_internal()
|
||||
except:
|
||||
state.DB_SCAN = False
|
||||
app.SYNC.db_scan = False
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
utils.ERROR(txt='sync.py crashed', notify=True)
|
||||
raise
|
||||
|
@ -188,12 +185,12 @@ class Sync(backgroundthread.KillableThread):
|
|||
last_time_sync = 0
|
||||
one_day_in_seconds = 60 * 60 * 24
|
||||
# Link to Websocket queue
|
||||
queue = state.WEBSOCKET_QUEUE
|
||||
queue = app.APP.websocket_queue
|
||||
|
||||
# Kodi Version supported by PKC?
|
||||
if (not path_ops.exists(v.DB_VIDEO_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
|
||||
LOG.error('The current Kodi version is incompatible')
|
||||
LOG.error('Current Kodi version: %s', utils.try_decode(
|
||||
|
@ -242,7 +239,7 @@ class Sync(backgroundthread.KillableThread):
|
|||
self.force_dialog = True
|
||||
# Initialize time offset Kodi - PMS
|
||||
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')
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(true)')
|
||||
# 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
|
||||
# PKC installation has been completed
|
||||
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')
|
||||
state.SUSPEND_SYNC = False
|
||||
app.SYNC.suspend_sync = False
|
||||
self.start_library_sync(block=True)
|
||||
if self.sync_successful:
|
||||
initial_sync_done = True
|
||||
|
@ -285,27 +282,27 @@ class Sync(backgroundthread.KillableThread):
|
|||
xbmc.sleep(1000)
|
||||
|
||||
# Currently no db scan, so we could start a new scan
|
||||
elif state.DB_SCAN is False:
|
||||
# Full scan was requested from somewhere else, e.g. userclient
|
||||
if state.RUN_LIB_SCAN is not None:
|
||||
elif app.SYNC.db_scan is False:
|
||||
# Full scan was requested from somewhere else
|
||||
if app.SYNC.run_lib_scan is not None:
|
||||
# Force-show dialogs since they are user-initiated
|
||||
self.force_dialog = True
|
||||
self.triage_lib_scans()
|
||||
self.force_dialog = False
|
||||
# Reset the flag
|
||||
state.RUN_LIB_SCAN = None
|
||||
app.SYNC.run_lib_scan = None
|
||||
continue
|
||||
|
||||
# Standard syncs - don't force-show dialogs
|
||||
now = utils.unix_timestamp()
|
||||
if (now - self.last_full_sync > state.FULL_SYNC_INTERVALL):
|
||||
now = timing.unix_timestamp()
|
||||
if (now - self.last_full_sync > app.SYNC.full_sync_intervall):
|
||||
LOG.info('Doing scheduled full library scan')
|
||||
self.start_library_sync()
|
||||
elif now - last_time_sync > one_day_in_seconds:
|
||||
LOG.info('Starting daily time sync')
|
||||
library_sync.sync_pms_time()
|
||||
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
|
||||
# this once a while (otherwise, potentially many screen
|
||||
# refreshes lead to flickering)
|
||||
|
|
94
resources/lib/timing.py
Normal file
94
resources/lib/timing.py
Normal 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
|
|
@ -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 ----===##")
|
|
@ -6,8 +6,7 @@ Various functions and decorators for PKC
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from sqlite3 import connect, OperationalError
|
||||
from datetime import datetime, timedelta
|
||||
from time import localtime, strftime
|
||||
from datetime import datetime
|
||||
from unicodedata import normalize
|
||||
try:
|
||||
import xml.etree.cElementTree as etree
|
||||
|
@ -19,7 +18,7 @@ except ImportError:
|
|||
import defusedxml.ElementTree as defused_etree # etree parse unsafe
|
||||
from xml.etree.ElementTree import ParseError
|
||||
ETREE = 'ElementTree'
|
||||
from functools import wraps, partial
|
||||
from functools import wraps
|
||||
from urllib import quote_plus
|
||||
import hashlib
|
||||
import re
|
||||
|
@ -28,7 +27,7 @@ import xbmc
|
|||
import xbmcaddon
|
||||
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)
|
||||
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
EPOCH = datetime.utcfromtimestamp(0)
|
||||
|
||||
# Grab Plex id from '...plex_id=XXXX....'
|
||||
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))
|
||||
|
||||
|
||||
def plex_command(key, value):
|
||||
def plex_command(value):
|
||||
"""
|
||||
Used to funnel states between different Python instances. NOT really thread
|
||||
safe - let's hope the Kodi user can't click fast enough
|
||||
|
||||
key: state.py variable
|
||||
value: either 'True' or 'False'
|
||||
"""
|
||||
while window('plex_command'):
|
||||
xbmc.sleep(20)
|
||||
window('plex_command', value='%s-%s' % (key, value))
|
||||
window('plex_command', value=value)
|
||||
|
||||
|
||||
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])
|
||||
LOG.error('Error encountered: %s - %s', txt, short)
|
||||
if cancel_sync:
|
||||
state.STOP_SYNC = True
|
||||
import app
|
||||
app.SYNC.stop_sync = True
|
||||
if hide_tb:
|
||||
return short
|
||||
|
||||
|
@ -275,47 +271,6 @@ class AttributeDict(dict):
|
|||
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):
|
||||
"""
|
||||
Cast the specified value to the specified type (returned by func). Currently this
|
||||
|
@ -434,47 +389,6 @@ def escape_html(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):
|
||||
"""
|
||||
Open a connection to the Kodi database.
|
||||
|
@ -1135,95 +1049,3 @@ def log_time(func):
|
|||
elapsedtotal, func.__name__)
|
||||
return result
|
||||
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
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from logging import getLogger
|
||||
from json import loads
|
||||
from threading import Thread
|
||||
from ssl import CERT_NONE
|
||||
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)
|
||||
|
||||
def __init__(self):
|
||||
|
@ -48,18 +47,15 @@ class WebSocket(Thread):
|
|||
|
||||
def run(self):
|
||||
LOG.info("----===## Starting %s ##===----", self.__class__.__name__)
|
||||
|
||||
counter = 0
|
||||
stopped = self.stopped
|
||||
suspended = self.suspended
|
||||
while not stopped():
|
||||
while not self.isCanceled():
|
||||
# In the event the server goes offline
|
||||
while suspended():
|
||||
while self.isSuspended():
|
||||
# Set in service.py
|
||||
if self.ws is not None:
|
||||
self.ws.close()
|
||||
self.ws = None
|
||||
if stopped():
|
||||
if self.isCanceled():
|
||||
# Abort was requested while waiting. We should exit
|
||||
LOG.info("##===---- %s Stopped ----===##",
|
||||
self.__class__.__name__)
|
||||
|
@ -133,14 +129,20 @@ class WebSocket(Thread):
|
|||
LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
|
||||
|
||||
|
||||
@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'BACKGROUND_SYNC_DISABLED'])
|
||||
class PMS_Websocket(WebSocket):
|
||||
"""
|
||||
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):
|
||||
server = utils.window('pms_server')
|
||||
server = app.CONN.server
|
||||
# Get the appropriate prefix for the websocket
|
||||
if server.startswith('https'):
|
||||
server = "wss%s" % server[5:]
|
||||
|
@ -148,8 +150,8 @@ class PMS_Websocket(WebSocket):
|
|||
server = "ws%s" % server[4:]
|
||||
uri = "%s/:/websockets/notifications" % server
|
||||
# Need to use plex.tv token, if any. NOT user token
|
||||
if state.PLEX_TOKEN:
|
||||
uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN
|
||||
if app.ACCOUNT.plex_token:
|
||||
uri += '?X-Plex-Token=%s' % app.ACCOUNT.plex_token
|
||||
sslopt = {}
|
||||
if utils.settings('sslverify') == "false":
|
||||
sslopt["cert_reqs"] = CERT_NONE
|
||||
|
@ -185,30 +187,33 @@ class PMS_Websocket(WebSocket):
|
|||
# Drop everything we're not interested in
|
||||
if typus not in ('playing', 'timeline', 'activity'):
|
||||
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
|
||||
# possibly causing these reprocessing messages en mass)
|
||||
LOG.debug('%s: Dropping message as PKC is currently synching',
|
||||
self.__class__.__name__)
|
||||
else:
|
||||
# Put PMS message on queue and let libsync take care of it
|
||||
state.WEBSOCKET_QUEUE.put(message)
|
||||
app.APP.websocket_queue.put(message)
|
||||
|
||||
|
||||
class Alexa_Websocket(WebSocket):
|
||||
"""
|
||||
Websocket connection to talk to Amazon Alexa.
|
||||
|
||||
Can't use utils.thread_methods!
|
||||
"""
|
||||
thread_stopped = False
|
||||
thread_suspended = False
|
||||
def isSuspended(self):
|
||||
"""
|
||||
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):
|
||||
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,
|
||||
state.PLEX_TOKEN))
|
||||
app.ACCOUNT.plex_token))
|
||||
sslopt = {}
|
||||
LOG.debug("%s: Uri: %s, sslopt: %s",
|
||||
self.__class__.__name__, uri, sslopt)
|
||||
|
@ -238,33 +243,3 @@ class Alexa_Websocket(WebSocket):
|
|||
self.__class__.__name__)
|
||||
return
|
||||
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
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
import xbmcgui
|
||||
|
||||
from . import kodigui
|
||||
from .. import utils, variables as v
|
||||
from .. import variables as v
|
||||
|
||||
|
||||
class Background(kodigui.BaseWindow):
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
<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="30505" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=resetauth)" option="close" /><!-- reset connection attempts -->
|
||||
<setting id="accessToken" type="text" visible="false" default="" />
|
||||
</category>
|
||||
|
||||
|
|
Loading…
Reference in a new issue