Attempt to fix locking mechanisms

- Wraper to lock entire function was NOT working
This commit is contained in:
Croneter 2018-06-21 20:43:39 +02:00
parent c440dc7779
commit 9b76795ea4
7 changed files with 183 additions and 204 deletions

View file

@ -138,6 +138,7 @@ class KodiMonitor(xbmc.Monitor):
if method == "Player.OnPlay":
state.SUSPEND_SYNC = True
with state.LOCK_PLAYQUEUES:
self.PlayBackStart(data)
elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck
@ -146,22 +147,27 @@ class KodiMonitor(xbmc.Monitor):
self.hack_replay == data['item']):
# Hack for add-on paths
self.hack_replay = None
with state.LOCK_PLAYQUEUES:
self._hack_addon_paths_replay_video()
elif data.get('end'):
if state.PKC_CAUSED_STOP is True:
state.PKC_CAUSED_STOP = False
LOG.debug('PKC caused this playback stop - ignoring')
else:
with state.LOCK_PLAYQUEUES:
_playback_cleanup(ended=True)
else:
with state.LOCK_PLAYQUEUES:
_playback_cleanup()
state.PKC_CAUSED_STOP_DONE = True
state.SUSPEND_SYNC = False
elif method == 'Playlist.OnAdd':
with state.LOCK_PLAYQUEUES:
self._playlist_onadd(data)
elif method == 'Playlist.OnRemove':
self._playlist_onremove(data)
elif method == 'Playlist.OnClear':
with state.LOCK_PLAYQUEUES:
self._playlist_onclear(data)
elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched
@ -208,7 +214,6 @@ class KodiMonitor(xbmc.Monitor):
state.STOP_PKC = True
@staticmethod
@state.LOCKER_SUBSCRIBER.lockthis
def _hack_addon_paths_replay_video():
"""
Hack we need for RESUMABLE items because Kodi lost the path of the
@ -239,7 +244,6 @@ class KodiMonitor(xbmc.Monitor):
thread = Thread(target=playback.playback_triage, kwargs=kwargs)
thread.start()
@state.LOCKER_SUBSCRIBER.lockthis
def _playlist_onadd(self, data):
"""
Called if an item is added to a Kodi playlist. Example data dict:
@ -272,7 +276,6 @@ class KodiMonitor(xbmc.Monitor):
"""
pass
@state.LOCKER_SUBSCRIBER.lockthis
def _playlist_onclear(self, data):
"""
Called if a Kodi playlist is cleared. Example data dict:
@ -348,7 +351,6 @@ class KodiMonitor(xbmc.Monitor):
json_item.get('type'),
json_item.get('file'))
@state.LOCKER_SUBSCRIBER.lockthis
def PlayBackStart(self, data):
"""
Called whenever playback is started. Example data:
@ -486,7 +488,6 @@ class SpecialMonitor(Thread):
LOG.info("#====---- Special Monitor Stopped ----====#")
@state.LOCKER_SUBSCRIBER.lockthis
def _playback_cleanup(ended=False):
"""
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi

View file

@ -30,7 +30,6 @@ NULL_VIDEO = join(v.ADDON_FOLDER, 'addons', v.ADDON_ID, 'empty_video.mp4')
###############################################################################
@state.LOCKER_SUBSCRIBER.lockthis
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
"""
Hit this function for addon path playback, Plex trailers, etc.
@ -107,6 +106,7 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
initiate = True
else:
initiate = False
with state.LOCK_PLAYQUEUES:
if initiate:
_playback_init(plex_id, plex_type, playqueue, pos)
else:

View file

@ -267,13 +267,13 @@ def _kodi_playlist_identical(xml_element):
pass
@state.LOCKER_PLAYLISTS.lockthis
def process_websocket(plex_id, updated_at, state):
"""
Hit by librarysync to process websocket messages concerning playlists
"""
create = False
playlist = playlist_object_from_db(plex_id=plex_id)
with state.LOCK_PLAYLISTS:
try:
if playlist and state == 9:
LOG.debug('Plex deletion of playlist detected: %s', playlist)
@ -286,7 +286,8 @@ def process_websocket(plex_id, updated_at, state):
delete_kodi_playlist(playlist)
create = True
elif not playlist and not state == 9:
LOG.debug('Creation of new Plex playlist detected: %s', plex_id)
LOG.debug('Creation of new Plex playlist detected: %s',
plex_id)
create = True
# To the actual work
if create:
@ -295,13 +296,20 @@ def process_websocket(plex_id, updated_at, state):
pass
@state.LOCKER_PLAYLISTS.lockthis
def full_sync():
"""
Full sync of playlists between Kodi and Plex. Returns True is successful,
False otherwise
"""
LOG.info('Starting playlist full sync')
with state.LOCK_PLAYLISTS:
return _full_sync()
def _full_sync():
"""
Need to lock because we're messing with playlists
"""
# Get all Plex playlists
xml = PL.get_all_playlists()
if xml is None:

View file

@ -34,7 +34,7 @@ def init_playqueues():
LOG.debug('Playqueues have already been initialized')
return
# Initialize Kodi playqueues
with state.LOCK_SUBSCRIBER:
with state.LOCK_PLAYQUEUES:
for i in (0, 1, 2):
# Just in case the Kodi response is not sorted correctly
for queue in js.get_playlists():
@ -62,7 +62,6 @@ def get_playqueue_from_type(kodi_playlist_type):
Returns the playqueue according to the kodi_playlist_type ('video',
'audio', 'picture') passed in
"""
with state.LOCK_SUBSCRIBER:
for playqueue in PLAYQUEUES:
if playqueue.type == kodi_playlist_type:
break
@ -190,7 +189,7 @@ class PlayqueueMonitor(Thread):
if stopped():
break
xbmc.sleep(1000)
with state.LOCK_SUBSCRIBER:
with state.LOCK_PLAYQUEUES:
for playqueue in PLAYQUEUES:
kodi_pl = js.playlist_get_items(playqueue.playlistid)
if playqueue.old_kodi_pl != kodi_pl:

View file

@ -43,7 +43,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_SUBSCRIBER:
with state.LOCK_PLAYQUEUES:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear()
try:
@ -74,7 +74,6 @@ class PlexCompanion(Thread):
self.subscription_manager = None
Thread.__init__(self)
@state.LOCKER_SUBSCRIBER.lockthis
def _process_alexa(self, data):
xml = PF.GetPlexMetadata(data['key'])
try:
@ -131,7 +130,6 @@ class PlexCompanion(Thread):
executebuiltin('RunPlugin(plugin://%s?%s)'
% (v.ADDON_ID, urlencode(params)))
@state.LOCKER_SUBSCRIBER.lockthis
def _process_playlist(self, data):
# Get the playqueue ID
_, container_key, query = PF.ParseContainerKey(data['containerKey'])
@ -156,7 +154,6 @@ class PlexCompanion(Thread):
offset=data.get('offset'),
transient_token=data.get('token'))
@state.LOCKER_SUBSCRIBER.lockthis
def _process_streams(self, data):
"""
Plex Companion client adjusted audio or subtitle stream
@ -178,7 +175,6 @@ class PlexCompanion(Thread):
else:
LOG.error('Unknown setStreams command: %s', data)
@state.LOCKER_SUBSCRIBER.lockthis
def _process_refresh(self, data):
"""
example data: {'playQueueID': '8475', 'commandID': '11'}
@ -217,13 +213,16 @@ class PlexCompanion(Thread):
LOG.debug('Processing: %s', task)
data = task['data']
if task['action'] == 'alexa':
with state.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:
self._process_playlist(data)
elif task['action'] == 'refreshPlayQueue':
with state.LOCK_PLAYQUEUES:
self._process_refresh(data)
elif task['action'] == 'setStreams':
try:

View file

@ -145,7 +145,6 @@ class SubscriptionMgr(object):
position = info['position']
return position
@state.LOCKER_SUBSCRIBER.lockthis
def msg(self, players):
"""
Returns a timeline xml as str
@ -185,6 +184,7 @@ class SubscriptionMgr(object):
return answ
def _timeline_dict(self, player, ptype):
with state.LOCK_PLAYQUEUES:
playerid = player['playerid']
info = state.PLAYER_STATES[playerid]
playqueue = PQ.PLAYQUEUES[playerid]
@ -198,7 +198,8 @@ class SubscriptionMgr(object):
'type': ptype,
'state': 'stopped'
}
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO, v.PLEX_PLAYLIST_TYPE_PHOTO):
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO,
v.PLEX_PLAYLIST_TYPE_PHOTO):
self.location = 'fullScreenVideo'
self.stop_sent_to_web = False
pbmc_server = utils.window('pms_server')
@ -230,8 +231,9 @@ class SubscriptionMgr(object):
'partCount': 1,
'providerIdentifier': 'com.plexapp.plugins.library',
}
# Get the plex id from the PKC playqueue not info, as Kodi jumps to next
# playqueue element way BEFORE kodi monitor onplayback is called
# Get the plex id from the PKC playqueue not info, as Kodi jumps to
# next playqueue element way BEFORE kodi monitor onplayback is
# called
if item.plex_id:
answ['key'] = '/library/metadata/%s' % item.plex_id
answ['ratingKey'] = item.plex_id
@ -270,7 +272,8 @@ class SubscriptionMgr(object):
# still be {}
strm_id = None
if strm_id is not None:
# If None, then the subtitle is only present on Kodi side
# If None, then the subtitle is only present on Kodi
# side
answ['subtitleStreamID'] = strm_id
return answ
@ -297,12 +300,12 @@ class SubscriptionMgr(object):
return playqueue.items[position].plex_stream_index(
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
@state.LOCKER_SUBSCRIBER.lockthis
def update_command_id(self, uuid, command_id):
"""
Updates the Plex Companien client with the machine identifier uuid with
command_id
"""
with state.LOCK_SUBSCRIBER:
if command_id and self.subscribers.get(uuid):
self.subscribers[uuid].command_id = int(command_id)
@ -327,12 +330,12 @@ class SubscriptionMgr(object):
return False
return True
@state.LOCKER_SUBSCRIBER.lockthis
def notify(self):
"""
Causes PKC to tell the PMS and Plex Companion players to receive a
notification what's being played.
"""
with state.LOCK_SUBSCRIBER:
self._cleanup()
# Get all the active/playing Kodi players (video, audio, pictures)
players = js.get_players()
@ -342,7 +345,7 @@ class SubscriptionMgr(object):
# Check whether we can use the CURRENT info or whether PKC is still
# initializing
if self._playqueue_init_done(players) is False:
LOG.debug('PKC playqueue is still initializing - skipping update')
LOG.debug('PKC playqueue is still initializing - skip update')
return
self._notify_server(players)
if self.subscribers:
@ -410,7 +413,6 @@ class SubscriptionMgr(object):
LOG.debug("Sent server notification with parameters: %s to %s",
xargs, url)
@state.LOCKER_SUBSCRIBER.lockthis
def add_subscriber(self, protocol, host, port, uuid, command_id):
"""
Adds a new Plex Companion subscriber to PKC.
@ -422,16 +424,17 @@ class SubscriptionMgr(object):
command_id,
self,
self.request_mgr)
with state.LOCK_SUBSCRIBER:
self.subscribers[subscriber.uuid] = subscriber
return subscriber
@state.LOCKER_SUBSCRIBER.lockthis
def remove_subscriber(self, uuid):
"""
Removes a connected Plex Companion subscriber with machine identifier
uuid from PKC notifications.
(Calls the cleanup() method of the subscriber)
"""
with state.LOCK_SUBSCRIBER:
for subscriber in self.subscribers.values():
if subscriber.uuid == uuid or subscriber.host == uuid:
subscriber.cleanup()

View file

@ -1,48 +1,17 @@
# -*- coding: utf-8 -*-
# THREAD SAFE
from threading import Lock, RLock
from functools import wraps
class LockFunction(object):
"""
Decorator for class methods and functions to lock them with lock.
Initialize this class first
lockfunction = LockFunction(lock), where lock is a threading.Lock() object
To then lock a function or method:
@lockfunction.lockthis
def some_function(args, kwargs)
"""
def __init__(self, lock):
self.lock = lock
def lockthis(self, func):
"""
Use this method to actually lock a function or method
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""
Wrapper construct
"""
with self.lock:
result = func(*args, **kwargs)
return result
return wrapper
# LOCKS
####################
# Need to lock all methods and functions messing with subscribers
# Need to lock all methods and functions messing with Plex Companion subscribers
LOCK_SUBSCRIBER = RLock()
LOCKER_SUBSCRIBER = LockFunction(LOCK_SUBSCRIBER)
# 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()
LOCKER_PLAYLISTS = LockFunction(LOCK_PLAYLISTS)
# Quit PKC
STOP_PKC = False