From f67ff2f136845f37fdfbf474aff3e647232a9a79 Mon Sep 17 00:00:00 2001 From: croneter Date: Wed, 13 Nov 2019 17:45:42 +0100 Subject: [PATCH] Fix recently added albums sort order (you will have to reset the Kodi database manually) --- resources/lib/backgroundthread.py | 61 ++++++++++++++++++++++ resources/lib/library_sync/full_sync.py | 37 ++++++++----- resources/lib/library_sync/get_metadata.py | 6 ++- 3 files changed, 89 insertions(+), 15 deletions(-) diff --git a/resources/lib/backgroundthread.py b/resources/lib/backgroundthread.py index 80f099a0..7823fffd 100644 --- a/resources/lib/backgroundthread.py +++ b/resources/lib/backgroundthread.py @@ -140,6 +140,67 @@ class KillableThread(threading.Thread): return self._suspended +class OrderedQueue(Queue.PriorityQueue, object): + """ + Queue that enforces an order on the items it returns. An item you push + onto the queue must be a tuple + (index, item) + where index=-1 is the item that will be returned first. The Queue will block + until index=-1, 0, 1, 2, 3, ... is then made available + """ + def __init__(self, maxsize=0): + super(OrderedQueue, self).__init__(maxsize) + self.smallest = -1 + self.not_next_item = threading.Condition(self.mutex) + + def _put(self, item, heappush=heapq.heappush): + heappush(self.queue, item) + if item[0] == self.smallest: + self.not_next_item.notify() + + def get(self, block=True, timeout=None): + """Remove and return an item from the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until an item is available. If 'timeout' is + a non-negative number, it blocks at most 'timeout' seconds and raises + the Empty exception if no item was available within that time. + Otherwise ('block' is false), return an item if one is immediately + available, else raise the Empty exception ('timeout' is ignored + in that case). + """ + self.not_empty.acquire() + try: + if not block: + if not self._qsize() or self.queue[0][0] != self.smallest: + raise Queue.Empty + elif timeout is None: + while not self._qsize(): + self.not_empty.wait() + while self.queue[0][0] != self.smallest: + self.not_next_item.wait() + elif timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + else: + endtime = Queue._time() + timeout + while not self._qsize(): + remaining = endtime - Queue._time() + if remaining <= 0.0: + raise Queue.Empty + self.not_empty.wait(remaining) + while self.queue[0][0] != self.smallest: + remaining = endtime - Queue._time() + if remaining <= 0.0: + raise Queue.Empty + self.not_next_item.wait(remaining) + item = self._get() + self.smallest += 1 + self.not_full.notify() + return item + finally: + self.not_empty.release() + + class Tasks(list): def add(self, task): for t in self: diff --git a/resources/lib/library_sync/full_sync.py b/resources/lib/library_sync/full_sync.py index b0fe1f3f..382cf761 100644 --- a/resources/lib/library_sync/full_sync.py +++ b/resources/lib/library_sync/full_sync.py @@ -102,14 +102,15 @@ class FullSync(common.fullsync_mixin): self.threader.addTask(GetMetadataTask(self.queue, plex_id, self.plex_type, - self.get_children)) + self.get_children, + self.item_count)) self.item_count += 1 def update_library(self): LOG.debug('Writing changes to Kodi library now') i = 0 if not self.section: - self.section = self.queue.get() + _, self.section = self.queue.get() self.queue.task_done() while not self.isCanceled() and self.item_count > 0: section = self.section @@ -125,7 +126,7 @@ class FullSync(common.fullsync_mixin): with section.context(self.current_sync) as context: while not self.isCanceled() and self.item_count > 0: try: - item = self.queue.get(block=False) + _, item = self.queue.get(block=False) except backgroundthread.Queue.Empty: if self.threader.threader.working(): app.APP.monitor.waitForAbort(0.02) @@ -174,7 +175,7 @@ class FullSync(common.fullsync_mixin): iterator.get('title1')), section.section_id, section.plex_type) - self.queue.put(queue_info) + self.queue.put((-1, queue_info)) last = True # To keep track of the item-number in order to kill while loops self.item_count = 0 @@ -216,7 +217,7 @@ class FullSync(common.fullsync_mixin): section.name, section.section_id, section.plex_type) - self.queue.put(queue_info) + self.queue.put((-1, queue_info)) self.total = iterator.total self.section_name = section.name self.section_type_text = utils.lang( @@ -268,6 +269,7 @@ class FullSync(common.fullsync_mixin): element.section_type = element.plex_type element.context = kind[2] element.get_children = kind[3] + element.Queue = kind[4] if self.repair or all_items: updated_at = None else: @@ -292,16 +294,22 @@ class FullSync(common.fullsync_mixin): def full_library_sync(self): """ """ + # structure: + # (plex_type, + # section_type, + # context for itemtype, + # download children items, e.g. songs for a specific album?, + # Queue) kinds = [ - (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_MOVIE, itemtypes.Movie, False), - (v.PLEX_TYPE_SHOW, v.PLEX_TYPE_SHOW, itemtypes.Show, False), - (v.PLEX_TYPE_SEASON, v.PLEX_TYPE_SHOW, itemtypes.Season, False), - (v.PLEX_TYPE_EPISODE, v.PLEX_TYPE_SHOW, itemtypes.Episode, False) + (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_MOVIE, itemtypes.Movie, False, Queue.Queue), + (v.PLEX_TYPE_SHOW, v.PLEX_TYPE_SHOW, itemtypes.Show, False, Queue.Queue), + (v.PLEX_TYPE_SEASON, v.PLEX_TYPE_SHOW, itemtypes.Season, False, Queue.Queue), + (v.PLEX_TYPE_EPISODE, v.PLEX_TYPE_SHOW, itemtypes.Episode, False, Queue.Queue) ] 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), + (v.PLEX_TYPE_ARTIST, v.PLEX_TYPE_ARTIST, itemtypes.Artist, False, Queue.Queue), + (v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ARTIST, itemtypes.Album, True, backgroundthread.OrderedQueue), ]) # ADD NEW ITEMS # Already start setting up the iterators. We need to enforce @@ -323,6 +331,7 @@ class FullSync(common.fullsync_mixin): self.section_type = section.section_type self.context = section.context self.get_children = section.get_children + self.queue = section.Queue() # Now do the heavy lifting if self.isCanceled() or not self.addupdate_section(section): return False @@ -352,8 +361,11 @@ class FullSync(common.fullsync_mixin): LOG.info('Start synching playstate and userdata for every item') # In order to not delete all your songs again if app.SYNC.enable_music: + # We don't need to enforce the album order now + kinds.pop(5) kinds.extend([ - (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ARTIST, itemtypes.Song, True), + (v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ARTIST, itemtypes.Album, True, Queue.Queue), + (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ARTIST, itemtypes.Song, True, Queue.Queue), ]) # Make sure we're not showing an item's title in the sync dialog self.title = '' @@ -429,7 +441,6 @@ class FullSync(common.fullsync_mixin): return self.successful = True try: - self.queue = backgroundthread.Queue.Queue() if self.show_dialog: self.dialog = xbmcgui.DialogProgressBG() self.dialog.create(utils.lang(39714)) diff --git a/resources/lib/library_sync/get_metadata.py b/resources/lib/library_sync/get_metadata.py index 5e623de9..6cd653c2 100644 --- a/resources/lib/library_sync/get_metadata.py +++ b/resources/lib/library_sync/get_metadata.py @@ -36,11 +36,13 @@ class GetMetadataTask(common.fullsync_mixin, backgroundthread.Task): queue Queue.Queue() object where this thread will store the downloaded metadata XMLs as etree objects """ - def __init__(self, queue, plex_id, plex_type, get_children=False): + def __init__(self, queue, plex_id, plex_type, get_children=False, + count=None): self.queue = queue self.plex_id = plex_id self.plex_type = plex_type self.get_children = get_children + self.count = count super(GetMetadataTask, self).__init__() def _collections(self, item): @@ -120,4 +122,4 @@ class GetMetadataTask(common.fullsync_mixin, backgroundthread.Task): else: item['children'] = children_xml if not self.isCanceled(): - self.queue.put(item) + self.queue.put((self.count, item))