Hopefully fix Kodi and Plex playlists getting out of sync
- Implement a special Watchdog observer that will wait for <timeout> AFTER a filesystem event has been received
This commit is contained in:
parent
4fe95fdf12
commit
44bbcddbdf
1 changed files with 71 additions and 1 deletions
|
@ -1,8 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
import Queue
|
||||||
|
import xbmc
|
||||||
|
|
||||||
from .watchdog import events
|
from .watchdog import events
|
||||||
from .watchdog.observers import Observer
|
from .watchdog.observers import Observer
|
||||||
|
from .watchdog.utils.bricks import OrderedSetQueue
|
||||||
from . import playlist_func as PL
|
from . import playlist_func as PL
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from . import kodidb_functions as kodidb
|
from . import kodidb_functions as kodidb
|
||||||
|
@ -16,6 +19,11 @@ from . import state
|
||||||
|
|
||||||
LOG = getLogger('PLEX.playlists')
|
LOG = getLogger('PLEX.playlists')
|
||||||
|
|
||||||
|
# Safety margin for playlist filesystem operations
|
||||||
|
FILESYSTEM_TIMEOUT = 3
|
||||||
|
# These filesystem events are considered similar
|
||||||
|
SIMILAR_EVENTS = (events.EVENT_TYPE_CREATED, events.EVENT_TYPE_MODIFIED)
|
||||||
|
|
||||||
# Which playlist formates are supported by PKC?
|
# Which playlist formates are supported by PKC?
|
||||||
SUPPORTED_FILETYPES = (
|
SUPPORTED_FILETYPES = (
|
||||||
'm3u',
|
'm3u',
|
||||||
|
@ -563,6 +571,68 @@ class PlaylistEventhandler(events.FileSystemEventHandler):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistObserver(Observer):
|
||||||
|
"""
|
||||||
|
PKC implementation, overriding the dispatcher. PKC will wait for the
|
||||||
|
duration timeout (in seconds) before dispatching. A new event will reset
|
||||||
|
the timer.
|
||||||
|
Creating and modifying will be regarded as equal.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PlaylistObserver, self).__init__(*args, **kwargs)
|
||||||
|
# Drop the same events that get into the queue even if there are other
|
||||||
|
# events in between these similar events
|
||||||
|
self._event_queue = OrderedSetQueue()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _pkc_similar_events(event1, event2):
|
||||||
|
if event1 == event2:
|
||||||
|
return True
|
||||||
|
elif (event1.src_path == event2.src_path and
|
||||||
|
event1.event_type in SIMILAR_EVENTS and
|
||||||
|
event2.event_type in SIMILAR_EVENTS):
|
||||||
|
# Ignore a consecutive firing of created and modified events
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _dispatch_iterator(self, event_queue, timeout):
|
||||||
|
"""
|
||||||
|
This iterator will block for timeout (seconds) until an event is
|
||||||
|
received or raise Queue.Empty.
|
||||||
|
"""
|
||||||
|
event, watch = event_queue.get(block=True, timeout=timeout)
|
||||||
|
event_queue.task_done()
|
||||||
|
start = utils.unix_timestamp()
|
||||||
|
while utils.unix_timestamp() - start < timeout:
|
||||||
|
if state.STOP_PKC:
|
||||||
|
raise Queue.Empty
|
||||||
|
try:
|
||||||
|
new_event, new_watch = event_queue.get(block=False)
|
||||||
|
except Queue.Empty:
|
||||||
|
xbmc.sleep(200)
|
||||||
|
else:
|
||||||
|
event_queue.task_done()
|
||||||
|
start = utils.unix_timestamp()
|
||||||
|
if self._pkc_similar_events(new_event, event):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# At least on Windows, a dir modified event will be
|
||||||
|
# triggered once the writing process is done. Fine though
|
||||||
|
yield event, watch
|
||||||
|
event, watch = new_event, new_watch
|
||||||
|
yield event, watch
|
||||||
|
|
||||||
|
def dispatch_events(self, event_queue, timeout):
|
||||||
|
for event, watch in self._dispatch_iterator(event_queue, timeout):
|
||||||
|
with self._lock:
|
||||||
|
# To allow unschedule/stop and safe removal of event handlers
|
||||||
|
# within event handlers itself, check if the handler is still
|
||||||
|
# registered after every dispatch.
|
||||||
|
for handler in list(self._handlers.get(watch, [])):
|
||||||
|
if handler in self._handlers.get(watch, []):
|
||||||
|
handler.dispatch(event)
|
||||||
|
|
||||||
|
|
||||||
def kodi_playlist_monitor():
|
def kodi_playlist_monitor():
|
||||||
"""
|
"""
|
||||||
Monitors the Kodi playlist folder special://profile/playlist for the user.
|
Monitors the Kodi playlist folder special://profile/playlist for the user.
|
||||||
|
@ -572,7 +642,7 @@ def kodi_playlist_monitor():
|
||||||
observer.stop() (and maybe observer.join()) to shut down properly
|
observer.stop() (and maybe observer.join()) to shut down properly
|
||||||
"""
|
"""
|
||||||
event_handler = PlaylistEventhandler()
|
event_handler = PlaylistEventhandler()
|
||||||
observer = Observer()
|
observer = PlaylistObserver(timeout=FILESYSTEM_TIMEOUT)
|
||||||
observer.schedule(event_handler, v.PLAYLIST_PATH, recursive=True)
|
observer.schedule(event_handler, v.PLAYLIST_PATH, recursive=True)
|
||||||
observer.start()
|
observer.start()
|
||||||
return observer
|
return observer
|
||||||
|
|
Loading…
Reference in a new issue