From ba5c2ac8bb1b1ecded7e272e8ff2324210641bc2 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:17:32 +0700 Subject: [PATCH 001/101] return dummy files on error and update in the background. Notify when background updating --- .../resources/lib/common/utils.py | 35 +++++++++++++++++++ .../resources/lib/refresh.py | 2 ++ 2 files changed, 37 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index a2bcd44b..fa7b884a 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -609,6 +609,38 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) +ERROR_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Error", + "label": "Error", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} + +UPDATING_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Updating", + "label": "Updating", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} + def cache_expiry(hash, widget_id, add=None, no_queue=False): # Currently just caches for 5 min so that the background refresh doesn't go in a loop. @@ -657,10 +689,13 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): else: if not os.path.exists(cache_path): result = "Empty" + contents = UPDATING_FILE + push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" + contents = ERROR_FILE else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 87f1de3e..69436db4 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -327,6 +327,7 @@ def cache_and_update(widget_ids): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ + dialog = xbmcgui.Dialog() assert widget_ids effected_widgets = set() @@ -352,6 +353,7 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless + dialog.notification('AutoWidget', "Updating widget {}".format(_label)) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From cd94cd5e97afa05f66db8a006450c732bcede5a5 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:36:28 +0700 Subject: [PATCH 002/101] handle unicode in notification --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 69436db4..a376dc51 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -353,7 +353,7 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification('AutoWidget', "Updating widget {}".format(_label)) + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From afac5a9c8f4e03345068d66c8cf7da9520fce337 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:39:59 +0700 Subject: [PATCH 003/101] show when widgets have finished updating --- plugin.program.autowidget/resources/lib/refresh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index a376dc51..d98cf2d1 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -98,6 +98,9 @@ def _update_widgets(self): if not widget_def: continue _update_strings(widget_def) + dialog = xbmcgui.Dialog() + dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if self.abortRequested(): break From 89883ad1d649cc99619a4d48d93c40f6939c6b69 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:49:11 +0700 Subject: [PATCH 004/101] notification when backround updating ends --- .../resources/lib/refresh.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index d98cf2d1..d5188690 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -76,15 +76,14 @@ def _update_widgets(self): while not self.abortRequested(): for _ in self.tick(15, 60 * 15): # TODO: somehow delay to all other plugins loaded? + updated = False unrefreshed_widgets = set() for hash, widget_ids in utils.next_cache_queue(): effected_widgets = cache_and_update(widget_ids) - utils.remove_cache_queue( - hash - ) # Just in queued path's widget defintion has changed and it didn't update this path - unrefreshed_widgets = unrefreshed_widgets.union( - effected_widgets - ).difference(set(widget_ids)) + if effected_widgets: + updated = True + utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path + unrefreshed_widgets = unrefreshed_widgets.union(effected_widgets).difference(set(widget_ids)) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update @@ -98,8 +97,9 @@ def _update_widgets(self): if not widget_def: continue _update_strings(widget_def) - dialog = xbmcgui.Dialog() - dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if updated: + dialog = xbmcgui.Dialog() + dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) if self.abortRequested(): From 91503bf55f0850be92e172d9e2ced99e7517e804 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 12:20:09 +0700 Subject: [PATCH 005/101] fix starting up with no cache --- .../resources/lib/common/utils.py | 16 +++++++++++++--- .../resources/lib/refresh.py | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index fa7b884a..c1401d21 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -631,8 +631,8 @@ def cache_files(path, widget_id): "result": { "files": [ { - "title": "Updating", - "label": "Updating", + "title": "Loading Content...", + "label": "Loading Content...", "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art('alert'), "filetype": "file", @@ -674,6 +674,12 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): cache_json = json.dumps(add) if not add or not cache_json.strip(): result = "Invalid Write" + + elif 'error' in add or not add.get('result',{}).get('files'): + # In this case we don't want to cache a bad result + result = "Error" + # TODO: do we schedule a new update? or put dummy content up even if we have + # good cached content? else: write_json(cache_path, add) contents = add @@ -684,9 +690,12 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) - expiry = history[-1][0] + pred_dur / 2.0 + expiry = history[-1][0] + pred_dur * 0.75 # less than prediction to ensure pred keeps up to date result = "Wrote" else: + # write any updated widget_ids so we know what to update when we dequeue + # Also important as wwe use last modified of .history as accessed time + write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" contents = UPDATING_FILE @@ -696,6 +705,7 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): if contents is None: result = "Invalid Read" contents = ERROR_FILE + push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index d5188690..eab534eb 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -269,6 +269,7 @@ def get_files_list(path, widget_id=None): _, files, _ = utils.cache_expiry(hash, widget_id) if files is None: # We had no old content so have to block and get it now + # TODO: will no longer happen because we return dummy file instead utils.log("Blocking cache path read: {}".format(hash[:5]), "info") files, changed = utils.cache_files(path, widget_id) From 6f54fdaf97cb7bd505d8810fdb982276890c92ef Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:10:59 +0700 Subject: [PATCH 006/101] don't do notifications duirng playback --- plugin.program.autowidget/resources/lib/refresh.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index eab534eb..c1af2d78 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -79,7 +79,8 @@ def _update_widgets(self): updated = False unrefreshed_widgets = set() for hash, widget_ids in utils.next_cache_queue(): - effected_widgets = cache_and_update(widget_ids) + notify = self.refresh_enabled == 1 and not self.player.isPlayingVideo() + effected_widgets = cache_and_update(widget_ids, notify=notify) if effected_widgets: updated = True utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path @@ -326,9 +327,9 @@ def is_duplicate(title, titles): return False -def cache_and_update(widget_ids): - """a widget might have many paths. Ensure each path is either queued for an update - or is expired and if so force it to be refreshed. When going through the queue this +def cache_and_update(widget_ids, notify=True): + """ a widget might have many paths. Ensure each path is either queued for an update + or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ dialog = xbmcgui.Dialog() @@ -357,7 +358,8 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify: + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From ce94676f7a512078319f8d71750329a30fdb3c44 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:13:18 +0700 Subject: [PATCH 007/101] and final notify --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index c1af2d78..dead93d7 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -98,7 +98,7 @@ def _update_widgets(self): if not widget_def: continue _update_strings(widget_def) - if updated: + if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) From d140a9f61ee7562fd3c79a9d5d0de456be1bbfcc Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 09:47:48 +0700 Subject: [PATCH 008/101] tidy update dummy path generation --- .../resources/lib/common/utils.py | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index c1401d21..98a52c8e 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -204,6 +204,26 @@ _startup_time = time.time() # TODO: could get reloaded so not accurate? +_startup_time = time.time() #TODO: could get reloaded so not accurate? + + +def make_holding_path(label, art): + return { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": label, + "label": label, + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art(art), + "filetype": "file", + } + ] + } +} + def ft(seconds): return str(datetime.timedelta(seconds=int(seconds))) @@ -609,38 +629,6 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) -ERROR_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Error", - "label": "Error", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} - -UPDATING_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Loading Content...", - "label": "Loading Content...", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} - def cache_expiry(hash, widget_id, add=None, no_queue=False): # Currently just caches for 5 min so that the background refresh doesn't go in a loop. @@ -698,13 +686,13 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" - contents = UPDATING_FILE + contents = make_holding_path(u"Loading Content...", "refresh") push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" - contents = ERROR_FILE + contents = make_holding_path("Error", "error") push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue From 0a406e1b140e365ed6844b7a08f1551d90821876 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 10:40:22 +0700 Subject: [PATCH 009/101] ensure explode and clone get the path in the foreground so they can build the group properly on add --- .../resources/lib/add.py | 2 +- .../resources/lib/common/utils.py | 23 ++++++++++--------- .../resources/lib/refresh.py | 7 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index d0607daa..3f0bb6b3 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,7 +268,7 @@ def _copy_path(path_def): return group_def = manage.get_group_by_id(group_id) - files, hash = refresh.get_files_list(path_def["file"]["file"]) + files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: return diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 98a52c8e..9bbc0653 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -630,12 +630,11 @@ def cache_files(path, widget_id): return (files, changed) -def cache_expiry(hash, widget_id, add=None, no_queue=False): - # Currently just caches for 5 min so that the background refresh doesn't go in a loop. - # In the future it will cache for longer based on the history of how often in changed - # and when it changed in relation to events like events events. - # It should also manage the cache files to remove any too old. - # The cache expiry can also be used later to schedule a future background update. +def cache_expiry(hash, widget_id, add=None, background=True): + # Predict how long to cache for with a min of 5min so updates don't go in a loop + # TODO: find better way to prevents loops so that users trying to manually refresh can do so + # TODO: manage the cache files to remove any too old or no longer used + # TODO: update paths on autowidget refresh based on predicted update frequency cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) @@ -686,14 +685,16 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" - contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(hash) + if background: + contents = make_holding_path(u"Loading Content...", "refresh") + push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" - contents = make_holding_path("Error", "error") - push_cache_queue(hash) + if background: + contents = make_holding_path("Error", "error") + push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time @@ -705,7 +706,7 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): # queue_len = len(list(iter_queue())) if expiry > time.time(): result = "Read" - elif no_queue: + elif not background: result = "Skip already updated" # elif queue_len > 3: # # Try to give system more breathing space by returning empty cache but ensuring refresh diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index dead93d7..511d22f2 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -265,12 +265,11 @@ def refresh_paths(notify=False, force=False): return True, "AutoWidget" -def get_files_list(path, widget_id=None): +def get_files_list(path, widget_id=None, background=True): hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(hash, widget_id) + _, files, _ = utils.cache_expiry(hash, widget_id, background=background) if files is None: - # We had no old content so have to block and get it now - # TODO: will no longer happen because we return dummy file instead + # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") files, changed = utils.cache_files(path, widget_id) From 12d6a510889b714686dd8566ca68950b4504055e Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 01:13:50 +0700 Subject: [PATCH 010/101] use progress to show background updating --- .../resources/lib/common/utils.py | 3 +- .../resources/lib/refresh.py | 47 ++++++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 9bbc0653..8607d129 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -634,7 +634,8 @@ def cache_expiry(hash, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop # TODO: find better way to prevents loops so that users trying to manually refresh can do so # TODO: manage the cache files to remove any too old or no longer used - # TODO: update paths on autowidget refresh based on predicted update frequency + # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should + # update when autowidget updates. cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 511d22f2..32013a93 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -15,11 +15,13 @@ _thread = None + class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" super(RefreshService, self).__init__() - utils.log("+++++ STARTING AUTOWIDGET SERVICE +++++", "info") + utils.log('+++++ STARTING AUTOWIDGET SERVICE +++++', 'info') + self.player = Player() utils.ensure_addon_data() self._update_properties() @@ -70,6 +72,8 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i + + def _update_widgets(self): self._refresh(True) @@ -78,9 +82,27 @@ def _update_widgets(self): # TODO: somehow delay to all other plugins loaded? updated = False unrefreshed_widgets = set() - for hash, widget_ids in utils.next_cache_queue(): - notify = self.refresh_enabled == 1 and not self.player.isPlayingVideo() - effected_widgets = cache_and_update(widget_ids, notify=notify) + queue = list(utils.next_cache_queue()) + class Progress(object): + dialog = None + service = self + done = set() + + def __call__(self, groupname, path): + if self.dialog is None: + self.dialog = xbmcgui.DialogProgressBG() + self.dialog.create(u"Updating Widgets") + if not self.service.player.isPlayingVideo(): + percent = len(self.done)/float(len(queue)+len(self.done)+1) * 100 + self.dialog.update(int(percent), message=groupname) + self.done.add(path) + progress = Progress() + + while queue: + hash, widget_ids = queue.pop(0) + utils.log("Dequeued cache update: {}".format(hash[:5]), 'notice') + + effected_widgets = cache_and_update(widget_ids, notify=progress) if effected_widgets: updated = True utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path @@ -93,14 +115,15 @@ def _update_widgets(self): # utils.log("paused queue until read {:.2} for {}".format(utils.last_read(hash)-before_update, hash[:5]), 'info') if self.abortRequested(): break + queue = list(utils.next_cache_queue()) for widget_id in unrefreshed_widgets: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: continue _update_strings(widget_def) - if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): - dialog = xbmcgui.Dialog() - dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if progress.dialog is not None: + progress.dialog.update(100, "") + progress.dialog.close() if self.abortRequested(): @@ -274,8 +297,8 @@ def get_files_list(path, widget_id=None, background=True): files, changed = utils.cache_files(path, widget_id) new_files = [] - if "error" not in files: - files = files.get("result").get("files") + if 'error' not in files: + files = files.get('result').get('files', []) if not files: utils.log("No items found for {}".format(path)) return @@ -331,8 +354,6 @@ def cache_and_update(widget_ids, notify=True): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ - dialog = xbmcgui.Dialog() - assert widget_ids effected_widgets = set() for widget_id in widget_ids: @@ -357,8 +378,8 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - if notify: - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify is not None: + notify(_label, path) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From f0ba810f7ccccfc3e75d138cd417a78888964832 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 02:39:14 +0700 Subject: [PATCH 011/101] add progress when exploding --- .../resources/lib/add.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index 3f0bb6b3..e51ecda6 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -267,19 +267,27 @@ def _copy_path(path_def): if not group_id: return + progress = xbmcgui.DialogProgressBG() + progress.create(u"Exploding") + group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: + progress.close() return - + done = 0 for file in files: - if file.get("type") in ["movie", "episode", "musicvideo", "song"]: + done += 1 + if file['type'] in ['movie', 'episode', 'musicvideo', 'song']: continue - - labels = build_labels("json", file, path_def["target"]) + progress.update(int(done/float(len(files))*100), file.get('label')) + + labels = build_labels('json', file, path_def['target']) _add_path(group_def, labels, over=True) + progress.close() + del progress dialog = xbmcgui.Dialog() - dialog.notification( - "AutoWidget", utils.get_string(30105).format(len(files), group_def["label"]) - ) + dialog.notification('AutoWidget', utils.get_string(32131) + .format(len(files), group_def['label'])) del dialog + From d9e52fd64f6c7a49245ea1b01244e8786fe81e6d Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 02:42:15 +0700 Subject: [PATCH 012/101] copying instead of exploding --- plugin.program.autowidget/resources/lib/add.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index e51ecda6..6efca83b 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,7 +268,8 @@ def _copy_path(path_def): return progress = xbmcgui.DialogProgressBG() - progress.create(u"Exploding") + progress.create(u"Copying") + progress.update(1, u"Retrieving") group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) From 9090597ecbd6d1da9659e385b26df50c5bb4f033 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:46:53 +0700 Subject: [PATCH 013/101] reduce wait for queue processing to 1s. --- plugin.program.autowidget/resources/lib/menu.py | 3 +++ plugin.program.autowidget/resources/lib/refresh.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 75f23c4b..56bc830e 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -363,6 +363,7 @@ def show_path( "target": "next", } + next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, @@ -372,6 +373,8 @@ def show_path( isFolder=not paged_widgets or merged, props=properties, ) + # Ensure we precache next page for faster access + utils.push_cache_queue(utils.path2hash(next_path)) else: filetype = file.get("type", "") title = { diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 32013a93..e88e3ad7 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -78,8 +78,8 @@ def _update_widgets(self): self._refresh(True) while not self.abortRequested(): - for _ in self.tick(15, 60 * 15): - # TODO: somehow delay to all other plugins loaded? + for _ in self.tick(step=1, max=60 * 15): + # TODO: somehow delay till all other plugins loaded? updated = False unrefreshed_widgets = set() queue = list(utils.next_cache_queue()) From 1e44c9ac99fd3e1e6ed4025c877fe0e049a99993 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:47:50 +0700 Subject: [PATCH 014/101] precaching next page on paged widgets. but doesn't seem to be working yet --- plugin.program.autowidget/resources/lib/common/directory.py | 2 ++ plugin.program.autowidget/resources/lib/menu.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/directory.py b/plugin.program.autowidget/resources/lib/common/directory.py index 3095e4f0..00f4c073 100644 --- a/plugin.program.autowidget/resources/lib/common/directory.py +++ b/plugin.program.autowidget/resources/lib/common/directory.py @@ -246,6 +246,8 @@ def add_menu_item( xbmcplugin.addDirectoryItem( handle=_handle, url=_plugin, listitem=item, isFolder=isFolder ) + + return _plugin def make_library_path(library, type, id): diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 56bc830e..a059fd09 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -367,14 +367,14 @@ def show_path( directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=file["file"] if not paged_widgets or merged else None, + path=next_path, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - utils.push_cache_queue(utils.path2hash(next_path)) + utils.cache_expiry(utils.path2hash(next_path), widget_id) else: filetype = file.get("type", "") title = { From 85aa4c0a97778b9dc595f48bccc7c03de728774a Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:48:50 +0700 Subject: [PATCH 015/101] ensure we have history when queuing updates --- .../resources/lib/common/utils.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 8607d129..ba849680 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -541,6 +541,21 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path +def read_history(hash, create_if_missing=True): + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + if not os.path.exists(history_path): + if create_if_missing: + cache_data = {} + history = cache_data.setdefault('history', []) + widgets = cache_data.setdefault('widgets', []) + write_json(history_path, cache_data) + else: + cache_data = None + else: + cache_data = read_json(history_path) + return cache_data + + def next_cache_queue(): # Simple queue by creating a .queue file @@ -555,15 +570,18 @@ def next_cache_queue(): # TODO: need to workout if a blocking write is happen while it was queued or right now. # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) - path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(path) - if cache_data: - log("Dequeued cache update: {}".format(hash[:5]), "notice") - yield hash, cache_data.get("widgets", []) + cache_data = read_history(hash, create_if_missing=True) + yield hash, cache_data.get('widgets',[]) + + +def push_cache_queue(hash, widget_id=None): + queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history['widgets']: + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history['widgets'].append(widget_id) + write_json(history_path, history) - -def push_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) if os.path.exists(queue_path): pass # Leave original modification date so item is higher priority else: @@ -581,7 +599,10 @@ def remove_cache_queue(hash): def path2hash(path): - return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + if path is not None: + return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + else: + return None def widgets_for_path(path): From 7f0606dedda4fa71b83e3394f3c0e290e3c9231a Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:46:13 -0700 Subject: [PATCH 016/101] :bug: - Fix addon data paths --- plugin.program.autowidget/resources/lib/common/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index ba849680..0ca5df19 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -542,7 +542,7 @@ def iter_queue(): yield path def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} @@ -575,10 +575,10 @@ def next_cache_queue(): def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) history = read_history(hash, create_if_missing=True) # Ensure its created if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) history['widgets'].append(widget_id) write_json(history_path, history) From adad30687481fa7a723726a663af0956c05838e2 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:47:05 -0700 Subject: [PATCH 017/101] :bug: - Try to fix paged widget caching --- plugin.program.autowidget/resources/lib/common/utils.py | 4 ++-- plugin.program.autowidget/resources/lib/menu.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 0ca5df19..3951f573 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -647,7 +647,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files) + _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) return (files, changed) @@ -693,7 +693,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): write_json(cache_path, add) contents = add size = len(cache_json) - content_hash = hashlib.sha1(cache_json.encode("utf8")).hexdigest() + content_hash = path2hash(cache_json) changed = history[-1][1] != content_hash if history else True history.append((time.time(), content_hash)) write_json(history_path, cache_data) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index a059fd09..88dbcb67 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -303,7 +303,8 @@ def show_path( stack = widget_def.get("stack", []) path = widget_path["file"]["file"] if not stack else stack[-1] - files, hash = refresh.get_files_list(path, widget_id) + + files, hash = refresh.get_files_list(path, widget_id, background=False) if not files: return titles, path_label, content @@ -363,18 +364,17 @@ def show_path( "target": "next", } - next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=next_path, + path=file['file'] if not paged_widgets or merged else None, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - utils.cache_expiry(utils.path2hash(next_path), widget_id) + utils.cache_expiry(utils.path2hash(file['file']), widget_id, background=True) else: filetype = file.get("type", "") title = { From dd87fdeea030a899c80ec5bf484fcc662688e77b Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:13:59 -0700 Subject: [PATCH 018/101] :alien: - Add compatibility for old widgets --- .../resources/lib/refresh.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index e88e3ad7..38824154 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -361,17 +361,24 @@ def cache_and_update(widget_ids, notify=True): if not widget_def: continue changed = False - widget_path = widget_def.get("path", {}) + widget_path = widget_def.get("path", "") utils.log( "trying to update {} with widget def {}".format(widget_id, widget_def), "inspect", ) + if type(widget_path) != list: widget_path = [widget_path] - for path in widget_path: - if isinstance(path, dict): - _label = path["label"] - path = path["file"]["file"] + for path_id in widget_path: + # simple compatibility with pre-3.3.0 widgets + if isinstance(path_id, dict): + path_id = path_id.get("id", "") + path = manage.get_path_by_id(path_id) + if not path: + continue + + _label = path["label"] + path = path["file"]["file"] hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. From 32856127c85f205ad9013f07ce02134a1490ac5a Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:37:43 -0700 Subject: [PATCH 019/101] :recycle: - Use last_read --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 3951f573..6867aed4 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -667,7 +667,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): cache_data = {} since_read = 0 else: - since_read = time.time() - os.path.getmtime(history_path) + since_read = time.time() - last_read(hash) history = cache_data.setdefault("history", []) widgets = cache_data.setdefault("widgets", []) From 819247c136af3c9a20ea0d41650fbb50fd877a66 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:07 -0700 Subject: [PATCH 020/101] :lipstick: - Format with black --- .../resources/lib/common/utils.py | 44 ++++++++++--------- .../resources/lib/refresh.py | 41 +++++++++-------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 6867aed4..00de1df5 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -204,13 +204,13 @@ _startup_time = time.time() # TODO: could get reloaded so not accurate? -_startup_time = time.time() #TODO: could get reloaded so not accurate? +_startup_time = time.time() # TODO: could get reloaded so not accurate? def make_holding_path(label, art): return { - "jsonrpc": "2.0", - "id": 1, + "jsonrpc": "2.0", + "id": 1, "result": { "files": [ { @@ -218,11 +218,11 @@ def make_holding_path(label, art): "label": label, "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art(art), - "filetype": "file", + "filetype": "file", } ] - } -} + }, + } def ft(seconds): @@ -541,14 +541,15 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path + def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, "{}.history".format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} - history = cache_data.setdefault('history', []) - widgets = cache_data.setdefault('widgets', []) - write_json(history_path, cache_data) + history = cache_data.setdefault("history", []) + widgets = cache_data.setdefault("widgets", []) + write_json(history_path, cache_data) else: cache_data = None else: @@ -556,7 +557,6 @@ def read_history(hash, create_if_missing=True): return cache_data - def next_cache_queue(): # Simple queue by creating a .queue file # TODO: use watchdog to use less resources @@ -571,15 +571,15 @@ def next_cache_queue(): # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) cache_data = read_history(hash, create_if_missing=True) - yield hash, cache_data.get('widgets',[]) - + yield hash, cache_data.get("widgets", []) + def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created - if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) - history['widgets'].append(widget_id) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history["widgets"]: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + history["widgets"].append(widget_id) write_json(history_path, history) if os.path.exists(queue_path): @@ -684,7 +684,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): if not add or not cache_json.strip(): result = "Invalid Write" - elif 'error' in add or not add.get('result',{}).get('files'): + elif "error" in add or not add.get("result", {}).get("files"): # In this case we don't want to cache a bad result result = "Error" # TODO: do we schedule a new update? or put dummy content up even if we have @@ -699,12 +699,14 @@ def cache_expiry(hash, widget_id, add=None, background=True): write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) - expiry = history[-1][0] + pred_dur * 0.75 # less than prediction to ensure pred keeps up to date + expiry = ( + history[-1][0] + pred_dur * 0.75 + ) # less than prediction to ensure pred keeps up to date result = "Wrote" else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) + write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" if background: diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 38824154..f3f55c93 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -15,12 +15,11 @@ _thread = None - class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" super(RefreshService, self).__init__() - utils.log('+++++ STARTING AUTOWIDGET SERVICE +++++', 'info') + utils.log("+++++ STARTING AUTOWIDGET SERVICE +++++", "info") self.player = Player() utils.ensure_addon_data() @@ -72,8 +71,6 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i - - def _update_widgets(self): self._refresh(True) @@ -83,6 +80,7 @@ def _update_widgets(self): updated = False unrefreshed_widgets = set() queue = list(utils.next_cache_queue()) + class Progress(object): dialog = None service = self @@ -93,20 +91,29 @@ def __call__(self, groupname, path): self.dialog = xbmcgui.DialogProgressBG() self.dialog.create(u"Updating Widgets") if not self.service.player.isPlayingVideo(): - percent = len(self.done)/float(len(queue)+len(self.done)+1) * 100 + percent = ( + len(self.done) + / float(len(queue) + len(self.done) + 1) + * 100 + ) self.dialog.update(int(percent), message=groupname) self.done.add(path) + progress = Progress() while queue: hash, widget_ids = queue.pop(0) - utils.log("Dequeued cache update: {}".format(hash[:5]), 'notice') + utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") effected_widgets = cache_and_update(widget_ids, notify=progress) if effected_widgets: updated = True - utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path - unrefreshed_widgets = unrefreshed_widgets.union(effected_widgets).difference(set(widget_ids)) + utils.remove_cache_queue( + hash + ) # Just in queued path's widget defintion has changed and it didn't update this path + unrefreshed_widgets = unrefreshed_widgets.union( + effected_widgets + ).difference(set(widget_ids)) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update @@ -125,7 +132,6 @@ def __call__(self, groupname, path): progress.dialog.update(100, "") progress.dialog.close() - if self.abortRequested(): break @@ -297,16 +303,14 @@ def get_files_list(path, widget_id=None, background=True): files, changed = utils.cache_files(path, widget_id) new_files = [] - if 'error' not in files: - files = files.get('result').get('files', []) + if "error" not in files: + files = files.get("result").get("files", []) if not files: utils.log("No items found for {}".format(path)) return for file in files: - new_file = { - k: v for k, v in file.items() if v is not None - } + new_file = {k: v for k, v in file.items() if v is not None} if "art" in new_file: for art in new_file["art"]: @@ -320,6 +324,7 @@ def get_files_list(path, widget_id=None, background=True): return new_files, hash + def queue_widget_update(widget_id): global _thread new_thread = False @@ -327,8 +332,8 @@ def queue_widget_update(widget_id): _thread = Worker() _thread.daemon = True new_thread = True - _thread.queue.put(widget_id) - if new_thread: + _thread.queue.put(widget_id) + if new_thread: _thread.start() @@ -350,8 +355,8 @@ def is_duplicate(title, titles): def cache_and_update(widget_ids, notify=True): - """ a widget might have many paths. Ensure each path is either queued for an update - or is expired and if so force it to be refreshed. When going through the queue this + """a widget might have many paths. Ensure each path is either queued for an update + or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids From 0bc55c380fd42bdf3f236fbfe4fb7111977d43c5 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:36 -0700 Subject: [PATCH 021/101] :recycle: - Cache files in background --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 00de1df5..b53552e0 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -647,7 +647,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) + _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) From c6bce55f529cb1d2f54891dd7bb58e50a4f74ce0 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:17:32 +0700 Subject: [PATCH 022/101] return dummy files on error and update in the background. Notify when background updating --- .../resources/lib/common/utils.py | 35 +++++++++++++++++++ .../resources/lib/refresh.py | 2 ++ 2 files changed, 37 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index a2bcd44b..fa7b884a 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -609,6 +609,38 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) +ERROR_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Error", + "label": "Error", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} + +UPDATING_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Updating", + "label": "Updating", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} + def cache_expiry(hash, widget_id, add=None, no_queue=False): # Currently just caches for 5 min so that the background refresh doesn't go in a loop. @@ -657,10 +689,13 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): else: if not os.path.exists(cache_path): result = "Empty" + contents = UPDATING_FILE + push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" + contents = ERROR_FILE else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 4b1f933d..566f4e59 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -326,6 +326,7 @@ def cache_and_update(widget_ids): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ + dialog = xbmcgui.Dialog() assert widget_ids effected_widgets = set() @@ -351,6 +352,7 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless + dialog.notification('AutoWidget', "Updating widget {}".format(_label)) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From f938cc147d2105ab1932919ccf0a8da1c9d7bb57 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:36:28 +0700 Subject: [PATCH 023/101] handle unicode in notification --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 566f4e59..6c145ee5 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -352,7 +352,7 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification('AutoWidget', "Updating widget {}".format(_label)) + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From e104d0f4bbfa9a49505f53d5ebd4bda59532f654 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:39:59 +0700 Subject: [PATCH 024/101] show when widgets have finished updating --- plugin.program.autowidget/resources/lib/refresh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 6c145ee5..e4598b9d 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -98,6 +98,9 @@ def _update_widgets(self): if not widget_def: continue _update_strings(widget_def) + dialog = xbmcgui.Dialog() + dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if self.abortRequested(): break From e64bf9994ab7fadabbecd07815573407e34dab0f Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:49:11 +0700 Subject: [PATCH 025/101] notification when backround updating ends --- .../resources/lib/refresh.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index e4598b9d..4015a890 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -76,15 +76,14 @@ def _update_widgets(self): while not self.abortRequested(): for _ in self.tick(15, 60 * 15): # TODO: somehow delay to all other plugins loaded? + updated = False unrefreshed_widgets = set() for hash, widget_ids in utils.next_cache_queue(): effected_widgets = cache_and_update(widget_ids) - utils.remove_cache_queue( - hash - ) # Just in queued path's widget defintion has changed and it didn't update this path - unrefreshed_widgets = unrefreshed_widgets.union( - effected_widgets - ).difference(set(widget_ids)) + if effected_widgets: + updated = True + utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path + unrefreshed_widgets = unrefreshed_widgets.union(effected_widgets).difference(set(widget_ids)) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update @@ -98,8 +97,9 @@ def _update_widgets(self): if not widget_def: continue _update_strings(widget_def) - dialog = xbmcgui.Dialog() - dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if updated: + dialog = xbmcgui.Dialog() + dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) if self.abortRequested(): From 17120b87fb7b8d57d8a41802da6b83cd7a219c03 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 12:20:09 +0700 Subject: [PATCH 026/101] fix starting up with no cache --- .../resources/lib/common/utils.py | 16 +++++++++++++--- .../resources/lib/refresh.py | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index fa7b884a..c1401d21 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -631,8 +631,8 @@ def cache_files(path, widget_id): "result": { "files": [ { - "title": "Updating", - "label": "Updating", + "title": "Loading Content...", + "label": "Loading Content...", "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art('alert'), "filetype": "file", @@ -674,6 +674,12 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): cache_json = json.dumps(add) if not add or not cache_json.strip(): result = "Invalid Write" + + elif 'error' in add or not add.get('result',{}).get('files'): + # In this case we don't want to cache a bad result + result = "Error" + # TODO: do we schedule a new update? or put dummy content up even if we have + # good cached content? else: write_json(cache_path, add) contents = add @@ -684,9 +690,12 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) - expiry = history[-1][0] + pred_dur / 2.0 + expiry = history[-1][0] + pred_dur * 0.75 # less than prediction to ensure pred keeps up to date result = "Wrote" else: + # write any updated widget_ids so we know what to update when we dequeue + # Also important as wwe use last modified of .history as accessed time + write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" contents = UPDATING_FILE @@ -696,6 +705,7 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): if contents is None: result = "Invalid Read" contents = ERROR_FILE + push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 4015a890..dcb87184 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -269,6 +269,7 @@ def get_files_list(path, widget_id=None): _, files, _ = utils.cache_expiry(hash, widget_id) if files is None: # We had no old content so have to block and get it now + # TODO: will no longer happen because we return dummy file instead utils.log("Blocking cache path read: {}".format(hash[:5]), "info") files, changed = utils.cache_files(path, widget_id) From 87c2cd771e5ff6175d64fcd67def168df57bafe0 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:10:59 +0700 Subject: [PATCH 027/101] don't do notifications duirng playback --- plugin.program.autowidget/resources/lib/refresh.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index dcb87184..8cdb4cef 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -79,7 +79,8 @@ def _update_widgets(self): updated = False unrefreshed_widgets = set() for hash, widget_ids in utils.next_cache_queue(): - effected_widgets = cache_and_update(widget_ids) + notify = self.refresh_enabled == 1 and not self.player.isPlayingVideo() + effected_widgets = cache_and_update(widget_ids, notify=notify) if effected_widgets: updated = True utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path @@ -325,9 +326,9 @@ def is_duplicate(title, titles): return False -def cache_and_update(widget_ids): - """a widget might have many paths. Ensure each path is either queued for an update - or is expired and if so force it to be refreshed. When going through the queue this +def cache_and_update(widget_ids, notify=True): + """ a widget might have many paths. Ensure each path is either queued for an update + or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ dialog = xbmcgui.Dialog() @@ -356,7 +357,8 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify: + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From 2d7dbfa2b97fb70ecd2f117ff1a5805a938abbcd Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:13:18 +0700 Subject: [PATCH 028/101] and final notify --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 8cdb4cef..72426e4c 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -98,7 +98,7 @@ def _update_widgets(self): if not widget_def: continue _update_strings(widget_def) - if updated: + if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) From 09efe123e10a76eb5edc8461db53bdb70e3dcdef Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 09:47:48 +0700 Subject: [PATCH 029/101] tidy update dummy path generation --- .../resources/lib/common/utils.py | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index c1401d21..98a52c8e 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -204,6 +204,26 @@ _startup_time = time.time() # TODO: could get reloaded so not accurate? +_startup_time = time.time() #TODO: could get reloaded so not accurate? + + +def make_holding_path(label, art): + return { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": label, + "label": label, + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art(art), + "filetype": "file", + } + ] + } +} + def ft(seconds): return str(datetime.timedelta(seconds=int(seconds))) @@ -609,38 +629,6 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) -ERROR_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Error", - "label": "Error", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} - -UPDATING_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Loading Content...", - "label": "Loading Content...", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} - def cache_expiry(hash, widget_id, add=None, no_queue=False): # Currently just caches for 5 min so that the background refresh doesn't go in a loop. @@ -698,13 +686,13 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" - contents = UPDATING_FILE + contents = make_holding_path(u"Loading Content...", "refresh") push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" - contents = ERROR_FILE + contents = make_holding_path("Error", "error") push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue From d004aa939b5b57833a8f0d374244d581c0e5d6a6 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 10:40:22 +0700 Subject: [PATCH 030/101] ensure explode and clone get the path in the foreground so they can build the group properly on add --- .../resources/lib/add.py | 2 +- .../resources/lib/common/utils.py | 23 ++++++++++--------- .../resources/lib/refresh.py | 7 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index d0607daa..3f0bb6b3 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,7 +268,7 @@ def _copy_path(path_def): return group_def = manage.get_group_by_id(group_id) - files, hash = refresh.get_files_list(path_def["file"]["file"]) + files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: return diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 98a52c8e..9bbc0653 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -630,12 +630,11 @@ def cache_files(path, widget_id): return (files, changed) -def cache_expiry(hash, widget_id, add=None, no_queue=False): - # Currently just caches for 5 min so that the background refresh doesn't go in a loop. - # In the future it will cache for longer based on the history of how often in changed - # and when it changed in relation to events like events events. - # It should also manage the cache files to remove any too old. - # The cache expiry can also be used later to schedule a future background update. +def cache_expiry(hash, widget_id, add=None, background=True): + # Predict how long to cache for with a min of 5min so updates don't go in a loop + # TODO: find better way to prevents loops so that users trying to manually refresh can do so + # TODO: manage the cache files to remove any too old or no longer used + # TODO: update paths on autowidget refresh based on predicted update frequency cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) @@ -686,14 +685,16 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" - contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(hash) + if background: + contents = make_holding_path(u"Loading Content...", "refresh") + push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" - contents = make_holding_path("Error", "error") - push_cache_queue(hash) + if background: + contents = make_holding_path("Error", "error") + push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time @@ -705,7 +706,7 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): # queue_len = len(list(iter_queue())) if expiry > time.time(): result = "Read" - elif no_queue: + elif not background: result = "Skip already updated" # elif queue_len > 3: # # Try to give system more breathing space by returning empty cache but ensuring refresh diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 72426e4c..a0fcb2cb 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -265,12 +265,11 @@ def refresh_paths(notify=False, force=False): return True, "AutoWidget" -def get_files_list(path, widget_id=None): +def get_files_list(path, widget_id=None, background=True): hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(hash, widget_id) + _, files, _ = utils.cache_expiry(hash, widget_id, background=background) if files is None: - # We had no old content so have to block and get it now - # TODO: will no longer happen because we return dummy file instead + # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") files, changed = utils.cache_files(path, widget_id) From ff7a69f4636900eebb16a657e8717d949db9bf77 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 01:13:50 +0700 Subject: [PATCH 031/101] use progress to show background updating --- .../resources/lib/common/utils.py | 3 +- .../resources/lib/refresh.py | 47 ++++++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 9bbc0653..8607d129 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -634,7 +634,8 @@ def cache_expiry(hash, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop # TODO: find better way to prevents loops so that users trying to manually refresh can do so # TODO: manage the cache files to remove any too old or no longer used - # TODO: update paths on autowidget refresh based on predicted update frequency + # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should + # update when autowidget updates. cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index a0fcb2cb..10342618 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -15,11 +15,13 @@ _thread = None + class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" super(RefreshService, self).__init__() - utils.log("+++++ STARTING AUTOWIDGET SERVICE +++++", "info") + utils.log('+++++ STARTING AUTOWIDGET SERVICE +++++', 'info') + self.player = Player() utils.ensure_addon_data() self._update_properties() @@ -70,6 +72,8 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i + + def _update_widgets(self): self._refresh(True) @@ -78,9 +82,27 @@ def _update_widgets(self): # TODO: somehow delay to all other plugins loaded? updated = False unrefreshed_widgets = set() - for hash, widget_ids in utils.next_cache_queue(): - notify = self.refresh_enabled == 1 and not self.player.isPlayingVideo() - effected_widgets = cache_and_update(widget_ids, notify=notify) + queue = list(utils.next_cache_queue()) + class Progress(object): + dialog = None + service = self + done = set() + + def __call__(self, groupname, path): + if self.dialog is None: + self.dialog = xbmcgui.DialogProgressBG() + self.dialog.create(u"Updating Widgets") + if not self.service.player.isPlayingVideo(): + percent = len(self.done)/float(len(queue)+len(self.done)+1) * 100 + self.dialog.update(int(percent), message=groupname) + self.done.add(path) + progress = Progress() + + while queue: + hash, widget_ids = queue.pop(0) + utils.log("Dequeued cache update: {}".format(hash[:5]), 'notice') + + effected_widgets = cache_and_update(widget_ids, notify=progress) if effected_widgets: updated = True utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path @@ -93,14 +115,15 @@ def _update_widgets(self): # utils.log("paused queue until read {:.2} for {}".format(utils.last_read(hash)-before_update, hash[:5]), 'info') if self.abortRequested(): break + queue = list(utils.next_cache_queue()) for widget_id in unrefreshed_widgets: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: continue _update_strings(widget_def) - if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): - dialog = xbmcgui.Dialog() - dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if progress.dialog is not None: + progress.dialog.update(100, "") + progress.dialog.close() if self.abortRequested(): @@ -274,8 +297,8 @@ def get_files_list(path, widget_id=None, background=True): files, changed = utils.cache_files(path, widget_id) new_files = [] - if "error" not in files: - files = files.get("result").get("files") + if 'error' not in files: + files = files.get('result').get('files', []) if not files: utils.log("No items found for {}".format(path)) return [], hash @@ -330,8 +353,6 @@ def cache_and_update(widget_ids, notify=True): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ - dialog = xbmcgui.Dialog() - assert widget_ids effected_widgets = set() for widget_id in widget_ids: @@ -356,8 +377,8 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - if notify: - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify is not None: + notify(_label, path) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From 29266ec8a1a3e4e115c3e5bfa2bd450c5bb19f6b Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 02:39:14 +0700 Subject: [PATCH 032/101] add progress when exploding --- .../resources/lib/add.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index 3f0bb6b3..e51ecda6 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -267,19 +267,27 @@ def _copy_path(path_def): if not group_id: return + progress = xbmcgui.DialogProgressBG() + progress.create(u"Exploding") + group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: + progress.close() return - + done = 0 for file in files: - if file.get("type") in ["movie", "episode", "musicvideo", "song"]: + done += 1 + if file['type'] in ['movie', 'episode', 'musicvideo', 'song']: continue - - labels = build_labels("json", file, path_def["target"]) + progress.update(int(done/float(len(files))*100), file.get('label')) + + labels = build_labels('json', file, path_def['target']) _add_path(group_def, labels, over=True) + progress.close() + del progress dialog = xbmcgui.Dialog() - dialog.notification( - "AutoWidget", utils.get_string(30105).format(len(files), group_def["label"]) - ) + dialog.notification('AutoWidget', utils.get_string(32131) + .format(len(files), group_def['label'])) del dialog + From b2bbab6ce5d602d7f09722e1d98a7df7514d7be4 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 02:42:15 +0700 Subject: [PATCH 033/101] copying instead of exploding --- plugin.program.autowidget/resources/lib/add.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index e51ecda6..6efca83b 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,7 +268,8 @@ def _copy_path(path_def): return progress = xbmcgui.DialogProgressBG() - progress.create(u"Exploding") + progress.create(u"Copying") + progress.update(1, u"Retrieving") group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) From db5e42c9ca34c5763414bd9fb2650ddee52da6ce Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:46:53 +0700 Subject: [PATCH 034/101] reduce wait for queue processing to 1s. --- plugin.program.autowidget/resources/lib/menu.py | 3 +++ plugin.program.autowidget/resources/lib/refresh.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 804d5e21..cc6cf15a 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -367,6 +367,7 @@ def show_path( "target": "next", } + next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, @@ -376,6 +377,8 @@ def show_path( isFolder=not paged_widgets or merged, props=properties, ) + # Ensure we precache next page for faster access + utils.push_cache_queue(utils.path2hash(next_path)) else: filetype = file.get("type", "") title = { diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 10342618..9ffa9d20 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -78,8 +78,8 @@ def _update_widgets(self): self._refresh(True) while not self.abortRequested(): - for _ in self.tick(15, 60 * 15): - # TODO: somehow delay to all other plugins loaded? + for _ in self.tick(step=1, max=60 * 15): + # TODO: somehow delay till all other plugins loaded? updated = False unrefreshed_widgets = set() queue = list(utils.next_cache_queue()) From 5002f83521600feb29553d6157f170e5124db901 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:47:50 +0700 Subject: [PATCH 035/101] precaching next page on paged widgets. but doesn't seem to be working yet --- plugin.program.autowidget/resources/lib/common/directory.py | 2 ++ plugin.program.autowidget/resources/lib/menu.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/directory.py b/plugin.program.autowidget/resources/lib/common/directory.py index 3095e4f0..00f4c073 100644 --- a/plugin.program.autowidget/resources/lib/common/directory.py +++ b/plugin.program.autowidget/resources/lib/common/directory.py @@ -246,6 +246,8 @@ def add_menu_item( xbmcplugin.addDirectoryItem( handle=_handle, url=_plugin, listitem=item, isFolder=isFolder ) + + return _plugin def make_library_path(library, type, id): diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index cc6cf15a..9b04f459 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -371,14 +371,14 @@ def show_path( directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=file["file"] if not paged_widgets or merged else None, + path=next_path, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - utils.push_cache_queue(utils.path2hash(next_path)) + utils.cache_expiry(utils.path2hash(next_path), widget_id) else: filetype = file.get("type", "") title = { From 1a03220e270ef06cfc5443941d2b8173188776bd Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:48:50 +0700 Subject: [PATCH 036/101] ensure we have history when queuing updates --- .../resources/lib/common/utils.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 8607d129..ba849680 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -541,6 +541,21 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path +def read_history(hash, create_if_missing=True): + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + if not os.path.exists(history_path): + if create_if_missing: + cache_data = {} + history = cache_data.setdefault('history', []) + widgets = cache_data.setdefault('widgets', []) + write_json(history_path, cache_data) + else: + cache_data = None + else: + cache_data = read_json(history_path) + return cache_data + + def next_cache_queue(): # Simple queue by creating a .queue file @@ -555,15 +570,18 @@ def next_cache_queue(): # TODO: need to workout if a blocking write is happen while it was queued or right now. # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) - path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(path) - if cache_data: - log("Dequeued cache update: {}".format(hash[:5]), "notice") - yield hash, cache_data.get("widgets", []) + cache_data = read_history(hash, create_if_missing=True) + yield hash, cache_data.get('widgets',[]) + + +def push_cache_queue(hash, widget_id=None): + queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history['widgets']: + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history['widgets'].append(widget_id) + write_json(history_path, history) - -def push_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) if os.path.exists(queue_path): pass # Leave original modification date so item is higher priority else: @@ -581,7 +599,10 @@ def remove_cache_queue(hash): def path2hash(path): - return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + if path is not None: + return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + else: + return None def widgets_for_path(path): From 5c1acfba870f4f2622d712cbd4031b5a0087e2a3 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:46:13 -0700 Subject: [PATCH 037/101] :bug: - Fix addon data paths --- plugin.program.autowidget/resources/lib/common/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index ba849680..0ca5df19 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -542,7 +542,7 @@ def iter_queue(): yield path def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} @@ -575,10 +575,10 @@ def next_cache_queue(): def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) history = read_history(hash, create_if_missing=True) # Ensure its created if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) history['widgets'].append(widget_id) write_json(history_path, history) From 1752b8410e3ebf12dad8402bd5879f28fc606295 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:47:05 -0700 Subject: [PATCH 038/101] :bug: - Try to fix paged widget caching --- plugin.program.autowidget/resources/lib/common/utils.py | 4 ++-- plugin.program.autowidget/resources/lib/menu.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 0ca5df19..3951f573 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -647,7 +647,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files) + _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) return (files, changed) @@ -693,7 +693,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): write_json(cache_path, add) contents = add size = len(cache_json) - content_hash = hashlib.sha1(cache_json.encode("utf8")).hexdigest() + content_hash = path2hash(cache_json) changed = history[-1][1] != content_hash if history else True history.append((time.time(), content_hash)) write_json(history_path, cache_data) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 9b04f459..54fd1d91 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -306,7 +306,8 @@ def show_path( stack = widget_def.get("stack", []) path = widget_path["file"]["file"] if not stack else stack[-1] - files, hash = refresh.get_files_list(path, widget_id) + + files, hash = refresh.get_files_list(path, widget_id, background=False) if not files: return titles, path_label, content @@ -367,18 +368,17 @@ def show_path( "target": "next", } - next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=next_path, + path=file['file'] if not paged_widgets or merged else None, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - utils.cache_expiry(utils.path2hash(next_path), widget_id) + utils.cache_expiry(utils.path2hash(file['file']), widget_id, background=True) else: filetype = file.get("type", "") title = { From 87c9a437a9f902e34d7d3788c0e9af6dbad96ec8 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:13:59 -0700 Subject: [PATCH 039/101] :alien: - Add compatibility for old widgets --- .../resources/lib/refresh.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 9ffa9d20..ca6315fe 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -360,17 +360,24 @@ def cache_and_update(widget_ids, notify=True): if not widget_def: continue changed = False - widget_path = widget_def.get("path", {}) + widget_path = widget_def.get("path", "") utils.log( "trying to update {} with widget def {}".format(widget_id, widget_def), "inspect", ) + if type(widget_path) != list: widget_path = [widget_path] - for path in widget_path: - if isinstance(path, dict): - _label = path["label"] - path = path["file"]["file"] + for path_id in widget_path: + # simple compatibility with pre-3.3.0 widgets + if isinstance(path_id, dict): + path_id = path_id.get("id", "") + path = manage.get_path_by_id(path_id) + if not path: + continue + + _label = path["label"] + path = path["file"]["file"] hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. From 122ccd9e1ff206b951c53f0e772fd0bd7473af48 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:37:43 -0700 Subject: [PATCH 040/101] :recycle: - Use last_read --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 3951f573..6867aed4 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -667,7 +667,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): cache_data = {} since_read = 0 else: - since_read = time.time() - os.path.getmtime(history_path) + since_read = time.time() - last_read(hash) history = cache_data.setdefault("history", []) widgets = cache_data.setdefault("widgets", []) From 2e20d12b84fec6f16732defb7cab1b1f46c0dd8f Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:07 -0700 Subject: [PATCH 041/101] :lipstick: - Format with black --- .../resources/lib/common/utils.py | 44 ++++++++++--------- .../resources/lib/refresh.py | 32 ++++++++------ 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 6867aed4..00de1df5 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -204,13 +204,13 @@ _startup_time = time.time() # TODO: could get reloaded so not accurate? -_startup_time = time.time() #TODO: could get reloaded so not accurate? +_startup_time = time.time() # TODO: could get reloaded so not accurate? def make_holding_path(label, art): return { - "jsonrpc": "2.0", - "id": 1, + "jsonrpc": "2.0", + "id": 1, "result": { "files": [ { @@ -218,11 +218,11 @@ def make_holding_path(label, art): "label": label, "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art(art), - "filetype": "file", + "filetype": "file", } ] - } -} + }, + } def ft(seconds): @@ -541,14 +541,15 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path + def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, "{}.history".format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} - history = cache_data.setdefault('history', []) - widgets = cache_data.setdefault('widgets', []) - write_json(history_path, cache_data) + history = cache_data.setdefault("history", []) + widgets = cache_data.setdefault("widgets", []) + write_json(history_path, cache_data) else: cache_data = None else: @@ -556,7 +557,6 @@ def read_history(hash, create_if_missing=True): return cache_data - def next_cache_queue(): # Simple queue by creating a .queue file # TODO: use watchdog to use less resources @@ -571,15 +571,15 @@ def next_cache_queue(): # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) cache_data = read_history(hash, create_if_missing=True) - yield hash, cache_data.get('widgets',[]) - + yield hash, cache_data.get("widgets", []) + def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created - if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) - history['widgets'].append(widget_id) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history["widgets"]: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + history["widgets"].append(widget_id) write_json(history_path, history) if os.path.exists(queue_path): @@ -684,7 +684,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): if not add or not cache_json.strip(): result = "Invalid Write" - elif 'error' in add or not add.get('result',{}).get('files'): + elif "error" in add or not add.get("result", {}).get("files"): # In this case we don't want to cache a bad result result = "Error" # TODO: do we schedule a new update? or put dummy content up even if we have @@ -699,12 +699,14 @@ def cache_expiry(hash, widget_id, add=None, background=True): write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) - expiry = history[-1][0] + pred_dur * 0.75 # less than prediction to ensure pred keeps up to date + expiry = ( + history[-1][0] + pred_dur * 0.75 + ) # less than prediction to ensure pred keeps up to date result = "Wrote" else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) + write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" if background: diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index ca6315fe..40a8e57d 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -15,12 +15,11 @@ _thread = None - class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" super(RefreshService, self).__init__() - utils.log('+++++ STARTING AUTOWIDGET SERVICE +++++', 'info') + utils.log("+++++ STARTING AUTOWIDGET SERVICE +++++", "info") self.player = Player() utils.ensure_addon_data() @@ -72,8 +71,6 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i - - def _update_widgets(self): self._refresh(True) @@ -83,6 +80,7 @@ def _update_widgets(self): updated = False unrefreshed_widgets = set() queue = list(utils.next_cache_queue()) + class Progress(object): dialog = None service = self @@ -93,20 +91,29 @@ def __call__(self, groupname, path): self.dialog = xbmcgui.DialogProgressBG() self.dialog.create(u"Updating Widgets") if not self.service.player.isPlayingVideo(): - percent = len(self.done)/float(len(queue)+len(self.done)+1) * 100 + percent = ( + len(self.done) + / float(len(queue) + len(self.done) + 1) + * 100 + ) self.dialog.update(int(percent), message=groupname) self.done.add(path) + progress = Progress() while queue: hash, widget_ids = queue.pop(0) - utils.log("Dequeued cache update: {}".format(hash[:5]), 'notice') + utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") effected_widgets = cache_and_update(widget_ids, notify=progress) if effected_widgets: updated = True - utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path - unrefreshed_widgets = unrefreshed_widgets.union(effected_widgets).difference(set(widget_ids)) + utils.remove_cache_queue( + hash + ) # Just in queued path's widget defintion has changed and it didn't update this path + unrefreshed_widgets = unrefreshed_widgets.union( + effected_widgets + ).difference(set(widget_ids)) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update @@ -125,7 +132,6 @@ def __call__(self, groupname, path): progress.dialog.update(100, "") progress.dialog.close() - if self.abortRequested(): break @@ -297,8 +303,8 @@ def get_files_list(path, widget_id=None, background=True): files, changed = utils.cache_files(path, widget_id) new_files = [] - if 'error' not in files: - files = files.get('result').get('files', []) + if "error" not in files: + files = files.get("result").get("files", []) if not files: utils.log("No items found for {}".format(path)) return [], hash @@ -349,8 +355,8 @@ def is_duplicate(title, titles): def cache_and_update(widget_ids, notify=True): - """ a widget might have many paths. Ensure each path is either queued for an update - or is expired and if so force it to be refreshed. When going through the queue this + """a widget might have many paths. Ensure each path is either queued for an update + or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids From 76789643843d44d5fd147e8fb93e302aa7ea25ad Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:36 -0700 Subject: [PATCH 042/101] :recycle: - Cache files in background --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 00de1df5..b53552e0 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -647,7 +647,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) + _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) From 0be49f8eb842dc54780ed7f8c3f8b35068ea8ef6 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 22 Jul 2021 11:46:02 +0700 Subject: [PATCH 043/101] put path into .history and background refresh only path not whole widget --- .gitignore | 1 + .../resources/lib/common/utils.py | 26 ++++++---- .../resources/lib/refresh.py | 50 ++++++++++--------- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index daf8fba6..7621a03c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ bin .venv .vscode venv/ +lib diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index b53552e0..2db2cb38 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -571,15 +571,22 @@ def next_cache_queue(): # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) cache_data = read_history(hash, create_if_missing=True) - yield hash, cache_data.get("widgets", []) + yield hash, cache_data -def push_cache_queue(hash, widget_id=None): +def push_cache_queue(path, widget_id=None): + hash = path2hash(path) queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) history = read_history(hash, create_if_missing=True) # Ensure its created + changed = False if widget_id is not None and widget_id not in history["widgets"]: - history_path = os.path.join(_addon_data, "{}.history".format(hash)) history["widgets"].append(widget_id) + changed = True + if history.get("path") != "path": + history["path"] = path + changed = True + if changed: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) write_json(history_path, history) if os.path.exists(queue_path): @@ -647,16 +654,17 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files) + _, _, changed = cache_expiry(path, widget_id, add=files) return (files, changed) -def cache_expiry(hash, widget_id, add=None, background=True): +def cache_expiry(path, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop # TODO: find better way to prevents loops so that users trying to manually refresh can do so # TODO: manage the cache files to remove any too old or no longer used # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should # update when autowidget updates. + hash = path2hash(path) cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) @@ -711,14 +719,14 @@ def cache_expiry(hash, widget_id, add=None, background=True): result = "Empty" if background: contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(hash) + push_cache_queue(path) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" if background: contents = make_holding_path("Error", "error") - push_cache_queue(hash) + push_cache_queue(path) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time @@ -740,7 +748,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): # result = "Skip (queue={})".format(queue_len) # contents = dict(result=dict(files=[])) else: - push_cache_queue(hash) + push_cache_queue(path) result = "Read and queue" # TODO: some metric that tells us how long to the first and last widgets becomes visible and then get updated # not how to measure the time delay when when the cache is read until it appears on screen? @@ -859,7 +867,7 @@ def widgets_changed_by_watching(media_type): "chance widget changed after play {}% {}".format(chance, hash[:5]), "notice", ) - yield hash + yield hash, path def chance_playback_updates_widget(history_path, plays, cutoff_time=60 * 5): diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 40a8e57d..33f65e19 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -105,7 +105,7 @@ def __call__(self, groupname, path): hash, widget_ids = queue.pop(0) utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") - effected_widgets = cache_and_update(widget_ids, notify=progress) + effected_widgets = cache_and_update(hash, widget_ids, notify=progress) if effected_widgets: updated = True utils.remove_cache_queue( @@ -354,18 +354,40 @@ def is_duplicate(title, titles): return False -def cache_and_update(widget_ids, notify=True): +def cache_and_update(hash, widget_ids, notify=True): """a widget might have many paths. Ensure each path is either queued for an update or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids effected_widgets = set() + + changed = False + hash = utils.path2hash(path) + # TODO: we might be updating paths used by widgets that weren't initiall queued. + # We need to return those and ensure they get refreshed also. + effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) + if utils.is_cache_queue(hash): + # we need to update this path regardless + if notify is not None: + notify(_label, path) + new_files, files_changed = utils.cache_files(path, widget_id) + changed = changed or files_changed + utils.remove_cache_queue(hash) + # else: + # # double check this hasn't been updated already when updating another widget + # expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + # if expiry <= time.time(): + # utils.cache_files(path, widget_id) + # else: + # pass # Skipping this path because its already been updated + + + # TODO: update every widget? for widget_id in widget_ids: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: continue - changed = False widget_path = widget_def.get("path", "") utils.log( "trying to update {} with widget def {}".format(widget_id, widget_def), @@ -384,24 +406,6 @@ def cache_and_update(widget_ids, notify=True): _label = path["label"] path = path["file"]["file"] - hash = utils.path2hash(path) - # TODO: we might be updating paths used by widgets that weren't initiall queued. - # We need to return those and ensure they get refreshed also. - effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) - if utils.is_cache_queue(hash): - # we need to update this path regardless - if notify is not None: - notify(_label, path) - new_files, files_changed = utils.cache_files(path, widget_id) - changed = changed or files_changed - utils.remove_cache_queue(hash) - # else: - # # double check this hasn't been updated already when updating another widget - # expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) - # if expiry <= time.time(): - # utils.cache_files(path, widget_id) - # else: - # pass # Skipping this path because its already been updated # TODO: only need to do that if a path has changed which we can tell from the history if changed: _update_strings(widget_def) @@ -503,9 +507,9 @@ def onPlayBackEnded(self): # wait for a bit so scrobing can happen time.sleep(5) - for hash in utils.widgets_changed_by_watching(self.type): + for hash, path in utils.widgets_changed_by_watching(self.type): # Queue them for refresh - utils.push_cache_queue(hash) + utils.push_cache_queue(path) utils.log("Queued cache update: {}".format(hash[:5]), "notice") def onPlayBackStopped(self): From df340245028c8c799bda97e006d0c97f750e2c4a Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 14:13:14 -0700 Subject: [PATCH 044/101] :bug::recycle: - Work towards better caching --- .../resources/lib/common/utils.py | 10 +-- .../resources/lib/menu.py | 2 +- .../resources/lib/refresh.py | 86 ++++++++++--------- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 2db2cb38..7e02d8ac 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -592,7 +592,7 @@ def push_cache_queue(path, widget_id=None): if os.path.exists(queue_path): pass # Leave original modification date so item is higher priority else: - touch(queue_path) + write_json(queue_path, {"hash": hash, "path": path}) def is_cache_queue(hash): @@ -933,14 +933,6 @@ def save_playback_history(media_type, playback_percentage): write_json(_playback_history_path, history) -def touch(fname, times=None): - fhandle = open(fname, "a") - try: - os.utime(fname, times) - finally: - fhandle.close() - - def call_builtin(action, delay=0): if delay: xbmc.sleep(delay) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 54fd1d91..b80c218d 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -378,7 +378,7 @@ def show_path( props=properties, ) # Ensure we precache next page for faster access - utils.cache_expiry(utils.path2hash(file['file']), widget_id, background=True) + utils.cache_expiry(file['file'], widget_id, background=True) else: filetype = file.get("type", "") title = { diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 33f65e19..b248f11f 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -360,56 +360,58 @@ def cache_and_update(hash, widget_ids, notify=True): could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids - effected_widgets = set() + affected_widgets = set() changed = False + path = widget_ids.get("path", "") hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. - effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) - if utils.is_cache_queue(hash): - # we need to update this path regardless - if notify is not None: - notify(_label, path) - new_files, files_changed = utils.cache_files(path, widget_id) - changed = changed or files_changed - utils.remove_cache_queue(hash) - # else: - # # double check this hasn't been updated already when updating another widget - # expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) - # if expiry <= time.time(): - # utils.cache_files(path, widget_id) - # else: - # pass # Skipping this path because its already been updated + affected_widgets = affected_widgets.union(utils.widgets_for_path(path)) + for widget_id in affected_widgets: + if utils.is_cache_queue(hash): + # we need to update this path regardless + # if notify is not None: + # notify(_label, path) + new_files, files_changed = utils.cache_files(path, widget_id) + changed = changed or files_changed + utils.remove_cache_queue(hash) + else: + # double check this hasn't been updated already when updating another widget + expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + if expiry <= time.time(): + utils.cache_files(path, widget_id) + else: + pass # Skipping this path because its already been updated # TODO: update every widget? - for widget_id in widget_ids: - widget_def = manage.get_widget_by_id(widget_id) - if not widget_def: - continue - widget_path = widget_def.get("path", "") - utils.log( - "trying to update {} with widget def {}".format(widget_id, widget_def), - "inspect", - ) - - if type(widget_path) != list: - widget_path = [widget_path] - for path_id in widget_path: - # simple compatibility with pre-3.3.0 widgets - if isinstance(path_id, dict): - path_id = path_id.get("id", "") - path = manage.get_path_by_id(path_id) - if not path: - continue - - _label = path["label"] - path = path["file"]["file"] - # TODO: only need to do that if a path has changed which we can tell from the history - if changed: - _update_strings(widget_def) - return effected_widgets + # for widget_id in widget_ids: + # widget_def = manage.get_widget_by_id(widget_id) + # if not widget_def: + # continue + # widget_path = widget_def.get("path", "") + # utils.log( + # "trying to update {} with widget def {}".format(widget_id, widget_def), + # "inspect", + # ) + + # if type(widget_path) != list: + # widget_path = [widget_path] + # for path_id in widget_path: + # # simple compatibility with pre-3.3.0 widgets + # if isinstance(path_id, dict): + # path_id = path_id.get("id", "") + # path = manage.get_path_by_id(path_id) + # if not path: + # continue + + # _label = path["label"] + # path = path["file"]["file"] + # # TODO: only need to do that if a path has changed which we can tell from the history + # if changed: + # _update_strings(widget_def) + return affected_widgets # Get info on whats playing and use it to update the right widgets when playback stopped From 40f8621ea246b91db3daf3d7c6c53d9e3da53bb1 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 18:53:47 -0700 Subject: [PATCH 045/101] :zap: - Remove unneeded hashing --- plugin.program.autowidget/resources/lib/common/utils.py | 2 -- plugin.program.autowidget/resources/lib/refresh.py | 9 +++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 7e02d8ac..772bd130 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -641,8 +641,6 @@ def get_info_keys(): def cache_files(path, widget_id): - hash = path2hash(path) - info_keys = get_info_keys() params = { "jsonrpc": "2.0", diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index b248f11f..d248e79c 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -105,7 +105,9 @@ def __call__(self, groupname, path): hash, widget_ids = queue.pop(0) utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") - effected_widgets = cache_and_update(hash, widget_ids, notify=progress) + effected_widgets = cache_and_update( + hash, widget_ids, notify=progress + ) if effected_widgets: updated = True utils.remove_cache_queue( @@ -364,7 +366,6 @@ def cache_and_update(hash, widget_ids, notify=True): changed = False path = widget_ids.get("path", "") - hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. affected_widgets = affected_widgets.union(utils.widgets_for_path(path)) @@ -378,11 +379,11 @@ def cache_and_update(hash, widget_ids, notify=True): utils.remove_cache_queue(hash) else: # double check this hasn't been updated already when updating another widget - expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) if expiry <= time.time(): utils.cache_files(path, widget_id) else: - pass # Skipping this path because its already been updated + pass # Skipping this path because its already been updated # TODO: update every widget? From 94064db83dd765b1732d3d8877a2d5a01f53c944 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 18:56:07 -0700 Subject: [PATCH 046/101] :bulb: - Fix comment indents --- .../resources/lib/refresh.py | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index d248e79c..ca9fe879 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -373,7 +373,7 @@ def cache_and_update(hash, widget_ids, notify=True): if utils.is_cache_queue(hash): # we need to update this path regardless # if notify is not None: - # notify(_label, path) + # notify(_label, path) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) @@ -385,33 +385,32 @@ def cache_and_update(hash, widget_ids, notify=True): else: pass # Skipping this path because its already been updated - # TODO: update every widget? # for widget_id in widget_ids: - # widget_def = manage.get_widget_by_id(widget_id) - # if not widget_def: - # continue - # widget_path = widget_def.get("path", "") - # utils.log( - # "trying to update {} with widget def {}".format(widget_id, widget_def), - # "inspect", - # ) - - # if type(widget_path) != list: - # widget_path = [widget_path] - # for path_id in widget_path: - # # simple compatibility with pre-3.3.0 widgets - # if isinstance(path_id, dict): - # path_id = path_id.get("id", "") - # path = manage.get_path_by_id(path_id) - # if not path: - # continue - - # _label = path["label"] - # path = path["file"]["file"] - # # TODO: only need to do that if a path has changed which we can tell from the history - # if changed: - # _update_strings(widget_def) + # widget_def = manage.get_widget_by_id(widget_id) + # if not widget_def: + # continue + # widget_path = widget_def.get("path", "") + # utils.log( + # "trying to update {} with widget def {}".format(widget_id, widget_def), + # "inspect", + # ) + + # if type(widget_path) != list: + # widget_path = [widget_path] + # for path_id in widget_path: + # # simple compatibility with pre-3.3.0 widgets + # if isinstance(path_id, dict): + # path_id = path_id.get("id", "") + # path = manage.get_path_by_id(path_id) + # if not path: + # continue + + # _label = path["label"] + # path = path["file"]["file"] + # # TODO: only need to do that if a path has changed which we can tell from the history + # if changed: + # _update_strings(widget_def) return affected_widgets From 4e6d3049f2f1daee90dbd45cec134da1b398730a Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 19:48:58 -0700 Subject: [PATCH 047/101] :bug: - Store path in history files --- .../resources/lib/common/utils.py | 20 +++++++++++-------- .../resources/lib/refresh.py | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 772bd130..50253253 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -539,10 +539,12 @@ def iter_queue(): queued = filter(os.path.isfile, glob.glob(os.path.join(_addon_data, "*.queue"))) # TODO: sort by path instead so load plugins at the same time for path in sorted(queued, key=os.path.getmtime): - yield path + queue_data = read_json(path) + yield queue_data.get("path", "") -def read_history(hash, create_if_missing=True): +def read_history(path, create_if_missing=True): + hash = path2hash(path) history_path = os.path.join(_addon_data, "{}.history".format(hash)) if not os.path.exists(history_path): if create_if_missing: @@ -562,27 +564,28 @@ def next_cache_queue(): # TODO: use watchdog to use less resources for path in iter_queue(): # TODO: sort by path instead so load plugins at the same time - if not os.path.exists(path): + hash = path2hash(path) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + if not os.path.exists(queue_path): # a widget update has already taken care of updating this path continue # We will let the update operation remove the item from the queue # TODO: need to workout if a blocking write is happen while it was queued or right now. # probably need a .lock file to ensure foreground calls can get priority. - hash = hash_from_cache_path(path) - cache_data = read_history(hash, create_if_missing=True) + cache_data = read_history(path, create_if_missing=True) yield hash, cache_data def push_cache_queue(path, widget_id=None): hash = path2hash(path) queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created + history = read_history(path, create_if_missing=True) # Ensure its created changed = False if widget_id is not None and widget_id not in history["widgets"]: history["widgets"].append(widget_id) changed = True - if history.get("path") != "path": + if history.get("path", "") != path: history["path"] = path changed = True if changed: @@ -663,7 +666,6 @@ def cache_expiry(path, widget_id, add=None, background=True): # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should # update when autowidget updates. hash = path2hash(path) - cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) # Read file every time as we might be called from multiple processes @@ -702,6 +704,8 @@ def cache_expiry(path, widget_id, add=None, background=True): content_hash = path2hash(cache_json) changed = history[-1][1] != content_hash if history else True history.append((time.time(), content_hash)) + if cache_data.get("path") != path: + cache_data["path"] = path write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index ca9fe879..16af318e 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -298,7 +298,7 @@ def refresh_paths(notify=False, force=False): def get_files_list(path, widget_id=None, background=True): hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(hash, widget_id, background=background) + _, files, _ = utils.cache_expiry(path, widget_id, background=background) if files is None: # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") From b567e8ac110b1f1aa2b17976984228411cdfd5c7 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 20:03:49 -0700 Subject: [PATCH 048/101] :heavy_plus_sign: - Move cache methods into cache.py --- .../clear_cache_single.py | 3 +- .../resources/lib/common/router.py | 5 +- .../resources/lib/common/utils.py | 422 ------------------ .../resources/lib/refresh.py | 35 +- 4 files changed, 23 insertions(+), 442 deletions(-) diff --git a/plugin.program.autowidget/clear_cache_single.py b/plugin.program.autowidget/clear_cache_single.py index 3a4c6d91..a1a6f300 100644 --- a/plugin.program.autowidget/clear_cache_single.py +++ b/plugin.program.autowidget/clear_cache_single.py @@ -1,7 +1,8 @@ +from resources.lib.common import cache from resources.lib.common import utils if __name__ == "__main__": target = utils.get_infolabel("ListItem.Property(autoCache)") if target: - utils.clear_cache(target) + cache.clear_cache(target) diff --git a/plugin.program.autowidget/resources/lib/common/router.py b/plugin.program.autowidget/resources/lib/common/router.py index ff1b45eb..4410ec42 100644 --- a/plugin.program.autowidget/resources/lib/common/router.py +++ b/plugin.program.autowidget/resources/lib/common/router.py @@ -11,6 +11,7 @@ from resources.lib import menu from resources.lib import manage from resources.lib import refresh +from resources.lib.common import cache from resources.lib.common import directory from resources.lib.common import utils @@ -105,9 +106,9 @@ def dispatch(_plugin, _handle, _params): utils.update_container(True) elif mode == "clear_cache": if not target: - utils.clear_cache() + cache.clear_cache() else: - utils.clear_cache(target) + cache.clear_cache(target) elif mode == "set_color": utils.set_color(setting=True) elif mode == "backup" and action: diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 50253253..acac3294 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -10,9 +10,6 @@ import string import time import unicodedata -import hashlib -import glob -import math import datetime import six @@ -30,15 +27,12 @@ except AttributeError: translate_path = xbmc.translatePath -DEFAULT_CACHE_TIME = 60 * 5 - _addon_id = settings.get_addon_info("id") _addon_data = translate_path(settings.get_addon_info("profile")) _addon_root = translate_path(settings.get_addon_info("path")) _art_path = os.path.join(_addon_root, "resources", "media") _home = translate_path("special://home/") -_playback_history_path = os.path.join(_addon_data, "cache.history") windows = { "programs": ["program", "script"], @@ -202,10 +196,6 @@ "maroon", ] # brown -_startup_time = time.time() # TODO: could get reloaded so not accurate? - -_startup_time = time.time() # TODO: could get reloaded so not accurate? - def make_holding_path(label, art): return { @@ -280,20 +270,6 @@ def wipe(folder=_addon_data): os.rmdir(dir) -def clear_cache(target): - if not target: - dialog = xbmcgui.Dialog() - choice = dialog.yesno("AutoWidget", get_string(30118)) - del dialog - - if choice: - for file in [i for i in os.listdir(_addon_data) if i.endswith(".cache")]: - os.remove(os.path.join(_addon_data, file)) - else: - os.remove(os.path.join(_addon_data, "{}.cache".format(target))) - update_container(True) - - def get_art(filename, color=None): art = {} if not color: @@ -494,17 +470,6 @@ def get_property(property, window=10000): return xbmcgui.Window(window).getProperty(property) -def push_queue(property, value): - set_property(property, ",".join(get_property(property).split(",") + [value])) - - -def pop_queue(property): - queue = get_property(property).split(",") - value = queue.pop() - set_property(property, ",".join(queue)) - return value - - def clear_property(property, window=10000): xbmcgui.Window(window).clearProperty(property) @@ -530,101 +495,6 @@ def clean_artwork_url(url): return url -def hash_from_cache_path(path): - base = os.path.basename(path) - return os.path.splitext(base)[0] - - -def iter_queue(): - queued = filter(os.path.isfile, glob.glob(os.path.join(_addon_data, "*.queue"))) - # TODO: sort by path instead so load plugins at the same time - for path in sorted(queued, key=os.path.getmtime): - queue_data = read_json(path) - yield queue_data.get("path", "") - - -def read_history(path, create_if_missing=True): - hash = path2hash(path) - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - if not os.path.exists(history_path): - if create_if_missing: - cache_data = {} - history = cache_data.setdefault("history", []) - widgets = cache_data.setdefault("widgets", []) - write_json(history_path, cache_data) - else: - cache_data = None - else: - cache_data = read_json(history_path) - return cache_data - - -def next_cache_queue(): - # Simple queue by creating a .queue file - # TODO: use watchdog to use less resources - for path in iter_queue(): - # TODO: sort by path instead so load plugins at the same time - hash = path2hash(path) - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - if not os.path.exists(queue_path): - # a widget update has already taken care of updating this path - continue - # We will let the update operation remove the item from the queue - - # TODO: need to workout if a blocking write is happen while it was queued or right now. - # probably need a .lock file to ensure foreground calls can get priority. - cache_data = read_history(path, create_if_missing=True) - yield hash, cache_data - - -def push_cache_queue(path, widget_id=None): - hash = path2hash(path) - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - history = read_history(path, create_if_missing=True) # Ensure its created - changed = False - if widget_id is not None and widget_id not in history["widgets"]: - history["widgets"].append(widget_id) - changed = True - if history.get("path", "") != path: - history["path"] = path - changed = True - if changed: - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - write_json(history_path, history) - - if os.path.exists(queue_path): - pass # Leave original modification date so item is higher priority - else: - write_json(queue_path, {"hash": hash, "path": path}) - - -def is_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - return os.path.exists(queue_path) - - -def remove_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - remove_file(queue_path) - - -def path2hash(path): - if path is not None: - return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() - else: - return None - - -def widgets_for_path(path): - hash = path2hash(path) - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(history_path) if os.path.exists(history_path) else None - if cache_data is None: - cache_data = {} - widgets = cache_data.setdefault("widgets", []) - return set(widgets) - - def get_info_keys(): params = { "jsonrpc": "2.0", @@ -643,298 +513,6 @@ def get_info_keys(): return info_keys["result"]["types"]["List.Fields.Files"]["items"]["enums"] -def cache_files(path, widget_id): - info_keys = get_info_keys() - params = { - "jsonrpc": "2.0", - "method": "Files.GetDirectory", - "params": { - "properties": info_keys, - "directory": path, - }, - "id": 1, - } - files = call_jsonrpc(params) - _, _, changed = cache_expiry(path, widget_id, add=files) - return (files, changed) - - -def cache_expiry(path, widget_id, add=None, background=True): - # Predict how long to cache for with a min of 5min so updates don't go in a loop - # TODO: find better way to prevents loops so that users trying to manually refresh can do so - # TODO: manage the cache files to remove any too old or no longer used - # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should - # update when autowidget updates. - hash = path2hash(path) - cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) - - # Read file every time as we might be called from multiple processes - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(history_path) if os.path.exists(history_path) else None - if cache_data is None: - cache_data = {} - since_read = 0 - else: - since_read = time.time() - last_read(hash) - - history = cache_data.setdefault("history", []) - widgets = cache_data.setdefault("widgets", []) - if widget_id not in widgets: - widgets.append(widget_id) - - expiry = time.time() - 20 - contents = None - changed = True - size = 0 - - if add is not None: - cache_json = json.dumps(add) - if not add or not cache_json.strip(): - result = "Invalid Write" - - elif "error" in add or not add.get("result", {}).get("files"): - # In this case we don't want to cache a bad result - result = "Error" - # TODO: do we schedule a new update? or put dummy content up even if we have - # good cached content? - else: - write_json(cache_path, add) - contents = add - size = len(cache_json) - content_hash = path2hash(cache_json) - changed = history[-1][1] != content_hash if history else True - history.append((time.time(), content_hash)) - if cache_data.get("path") != path: - cache_data["path"] = path - write_json(history_path, cache_data) - # expiry = history[-1][0] + DEFAULT_CACHE_TIME - pred_dur = predict_update_frequency(history) - expiry = ( - history[-1][0] + pred_dur * 0.75 - ) # less than prediction to ensure pred keeps up to date - result = "Wrote" - else: - # write any updated widget_ids so we know what to update when we dequeue - # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) - if not os.path.exists(cache_path): - result = "Empty" - if background: - contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(path) - else: - contents = read_json(cache_path, log_file=True) - if contents is None: - result = "Invalid Read" - if background: - contents = make_holding_path("Error", "error") - push_cache_queue(path) - else: - # write any updated widget_ids so we know what to update when we dequeue - # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) - size = len(json.dumps(contents)) - if history: - expiry = history[-1][0] + predict_update_frequency(history) - - # queue_len = len(list(iter_queue())) - if expiry > time.time(): - result = "Read" - elif not background: - result = "Skip already updated" - # elif queue_len > 3: - # # Try to give system more breathing space by returning empty cache but ensuring refresh - # # better way is to just do this the first X accessed after startup. - # # or how many accessed in the last 30s? - # push_cache_queue(hash) - # result = "Skip (queue={})".format(queue_len) - # contents = dict(result=dict(files=[])) - else: - push_cache_queue(path) - result = "Read and queue" - # TODO: some metric that tells us how long to the first and last widgets becomes visible and then get updated - # not how to measure the time delay when when the cache is read until it appears on screen? - # Is the first cache read always the top visibible widget? - log( - "{} cache {}B (exp:{}, last:{}): {} {}".format( - result, size, ft(expiry - time.time()), ft(since_read), hash[:5], widgets - ), - "notice", - ) - return expiry, contents, changed - - -def last_read(hash): - # Technically this is last read or updated but we can change it to be last read Later - # if we create another file - path = os.path.join(_addon_data, "{}.history".format(hash)) - return os.path.getmtime(path) - - -def predict_update_frequency(history): - if not history: - return DEFAULT_CACHE_TIME - update_count = 0 - duration = 0 - changes = [] - last_when, last = history[0] - for when, content in history[1:]: - update_count += 1 - if content == last: - duration += when - last_when - else: - duration = ( - +(when - last_when) / 2 - ) # change could have happened any time inbetween - changes.append((duration, update_count)) - duration = 0 - update_count = 0 - last_when = when - last = content - if not changes and duration: - # drop the last part of the history that hasn't changed yet unless we have no other history to work with - # This is an underestimate as we aren't sure when in the future it will change - changes.append((duration, update_count)) - # TODO: the first change is potentially an underestimate too because we don't know how long it was unchanged for - # before we started recording. - - # Now we have changes, we can do some trends on them. - durations = [duration for duration, update_count in changes if update_count > 1] - if not durations: - return DEFAULT_CACHE_TIME - med_dur = sorted(durations)[int(math.floor(len(durations) / 2)) - 1] - avg_dur = sum(durations) / len(durations) - # weighted by how many snapshots we took inbetween. - # TODO: number of snapshots inbetween is really just increasing the confidence on the end time bot the duration as a whole. - # so perhaps a better metric is the error margin of the duration? and not weighting by that completely. - # ie durations with wide margin of error should be less important. e.g. times kodi was never turned on for months/weeks. - weighted = sum([d * c for d, c in changes]) / sum([c for _, c in changes]) - # TODO: also try exponential decay. Older durations are less important than newer ones. - ones = len([c for d, c in changes if c == 1]) / float(len(changes)) - # TODO: if many streaks with lots of counts then its stable and can predict - log( - "avg_dur {:0.0f}s, med_dur {:0.0f}s, weighted {:0.0f}s, ones {:0.2f}, all {}".format( - avg_dur, med_dur, weighted, ones, changes - ), - "debug", - ) - if ones > 0.9: - # too unstable so no point guessing - return DEFAULT_CACHE_TIME - elif DEFAULT_CACHE_TIME > avg_dur / 2.0: - # should not got less than 5min otherwise our updates go in a loop - return DEFAULT_CACHE_TIME - else: - return ( - avg_dur / 2.0 - ) # we want to ensure we check more often than the actual predicted expiry - - -# return DEFAULT_CACHE_TIME - - -def widgets_changed_by_watching(media_type): - # Predict which widgets the skin might have that could have changed based on recently finish - # watching something - - all_cache = filter( - os.path.isfile, glob.glob(os.path.join(_addon_data, "*.history")) - ) - - # Simple version. Anything updated recently (since startup?) - # priority = sorted(all_cache, key=os.path.getmtime) - # Sort by chance of it updating - plays = read_json(_playback_history_path, default={}).setdefault("plays", []) - plays_for_type = [(time, t) for time, t in plays if t == media_type] - priority = sorted( - [ - (chance_playback_updates_widget(path, plays_for_type), path) - for path in all_cache - ], - reverse=True, - ) - - for chance, path in priority: - hash = hash_from_cache_path(path) - last_update = os.path.getmtime(path) - _startup_time - if last_update < 0: - log( - "widget not updated since startup {} {}".format(last_update, hash[:5]), - "notice", - ) - # elif chance < 0.3: - # log("chance widget changed after play {}% {}".format(chance, hash[:5]), 'notice') - else: - log( - "chance widget changed after play {}% {}".format(chance, hash[:5]), - "notice", - ) - yield hash, path - - -def chance_playback_updates_widget(history_path, plays, cutoff_time=60 * 5): - cache_data = read_json(history_path) - history = cache_data.setdefault("history", []) - # Complex version - # - for each widget - # - come up with chance it will update after a playback - # - each pair of updates, is there a playback inbetween and updated with X min after playback - # - num playback with change / num playback with no change - changes, non_changes, unrelated_changes = 0, 0, 0 - update = "" - time_since_play = 0 - for play_time, media_type in plays: - while True: - last_update = update - if not history: - break - update_time, update = history.pop(0) - time_since_play = update_time - play_time - # log("{} {} {} {}".format(update[:5],last_update[:5], unrelated_changes, time_since_play), 'notice') - if time_since_play > 0: - break - elif update != last_update: - unrelated_changes += 1 - - if update == last_update: - non_changes += 1 - elif ( - time_since_play > cutoff_time - ): # update too long after playback to be releated - pass - else: - changes += 1 - # TODO: what if the previous update was a long time before playback? - - # There is probably a more statistically correct way of doing this but the idea is that - # with few datapoints we should tend towards 0.5 probability but as we get more datapoints - # then error goes down and rely on actual changes vs nonchanges - # We will do a simple weighted average with 0.5 to simulate this - # TODO: currently random widgets score higher than recently played widgets. need to score them lower - # as they are less relevent - log( - "changes={}, non_changes={}, unrelated_changes={}".format( - changes, non_changes, unrelated_changes - ), - "debug", - ) - datapoints = float(changes + non_changes) - prob = changes / float(changes + non_changes + unrelated_changes) - unknown_weight = 4 - prob = (prob * datapoints + 0.5 * unknown_weight) / (datapoints + unknown_weight) - return prob - - -def save_playback_history(media_type, playback_percentage): - # Record in json when things got played to help predict which widgets will change after playback - # if playback_percentage < 0.7: - # return - history = read_json(_playback_history_path, default={}) - plays = history.setdefault("plays", []) - plays.append((time.time(), media_type)) - write_json(_playback_history_path, history) - - def call_builtin(action, delay=0): if delay: xbmc.sleep(delay) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 16af318e..9863c287 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -6,6 +6,7 @@ import threading from resources.lib import manage +from resources.lib.common import cache from resources.lib.common import settings from resources.lib.common import utils @@ -79,7 +80,7 @@ def _update_widgets(self): # TODO: somehow delay till all other plugins loaded? updated = False unrefreshed_widgets = set() - queue = list(utils.next_cache_queue()) + queue = list(cache.next_cache_queue()) class Progress(object): dialog = None @@ -110,7 +111,7 @@ def __call__(self, groupname, path): ) if effected_widgets: updated = True - utils.remove_cache_queue( + cache.remove_cache_queue( hash ) # Just in queued path's widget defintion has changed and it didn't update this path unrefreshed_widgets = unrefreshed_widgets.union( @@ -119,12 +120,12 @@ def __call__(self, groupname, path): # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update - # while _ in self.tick(1, 10, lambda: utils.last_read(hash) > before_update): + # while _ in self.tick(1, 10, lambda: cache.last_read(hash) > before_update): # pass - # utils.log("paused queue until read {:.2} for {}".format(utils.last_read(hash)-before_update, hash[:5]), 'info') + # utils.log("paused queue until read {:.2} for {}".format(cache.last_read(hash)-before_update, hash[:5]), 'info') if self.abortRequested(): break - queue = list(utils.next_cache_queue()) + queue = list(cache.next_cache_queue()) for widget_id in unrefreshed_widgets: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: @@ -297,12 +298,12 @@ def refresh_paths(notify=False, force=False): def get_files_list(path, widget_id=None, background=True): - hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(path, widget_id, background=background) + hash = cache.path2hash(path) + _, files, _ = cache.cache_expiry(path, widget_id, background=background) if files is None: # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") - files, changed = utils.cache_files(path, widget_id) + files, changed = cache.cache_files(path, widget_id) new_files = [] if "error" not in files: @@ -368,20 +369,20 @@ def cache_and_update(hash, widget_ids, notify=True): path = widget_ids.get("path", "") # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. - affected_widgets = affected_widgets.union(utils.widgets_for_path(path)) + affected_widgets = affected_widgets.union(cache.widgets_for_path(path)) for widget_id in affected_widgets: - if utils.is_cache_queue(hash): + if cache.is_cache_queue(hash): # we need to update this path regardless # if notify is not None: # notify(_label, path) - new_files, files_changed = utils.cache_files(path, widget_id) + new_files, files_changed = cache.cache_files(path, widget_id) changed = changed or files_changed - utils.remove_cache_queue(hash) + cache.remove_cache_queue(hash) else: # double check this hasn't been updated already when updating another widget - expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + expiry, _ = cache.cache_expiry(hash, widget_id, no_queue=True) if expiry <= time.time(): - utils.cache_files(path, widget_id) + cache.cache_files(path, widget_id) else: pass # Skipping this path because its already been updated @@ -504,14 +505,14 @@ def onPlayBackEnded(self): self.totalTime = -1.0 self.playingTime = 0.0 self.info = {} - utils.save_playback_history(self.type, pp) + cache.save_playback_history(self.type, pp) utils.log("recorded playback of {}% {}".format(pp, self.type), "notice") # wait for a bit so scrobing can happen time.sleep(5) - for hash, path in utils.widgets_changed_by_watching(self.type): + for hash, path in cache.widgets_changed_by_watching(self.type): # Queue them for refresh - utils.push_cache_queue(path) + cache.push_cache_queue(path) utils.log("Queued cache update: {}".format(hash[:5]), "notice") def onPlayBackStopped(self): From 3ff4da811444982e02396243c8dc2bfb6ec241a8 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 20:04:10 -0700 Subject: [PATCH 049/101] :recycle: - Remove unneeded params on routing methods --- plugin.program.autowidget/main.py | 3 +-- plugin.program.autowidget/resources/lib/common/router.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/main.py b/plugin.program.autowidget/main.py index d2e09706..c0c7c295 100644 --- a/plugin.program.autowidget/main.py +++ b/plugin.program.autowidget/main.py @@ -5,9 +5,8 @@ if __name__ == "__main__": - _plugin = sys.argv[0] _handle = int(sys.argv[1]) _params = sys.argv[2][1:] with utils.timing(_params): - router.dispatch(_plugin, _handle, _params) + router.dispatch(_handle, _params) diff --git a/plugin.program.autowidget/resources/lib/common/router.py b/plugin.program.autowidget/resources/lib/common/router.py index 4410ec42..8cf9dd14 100644 --- a/plugin.program.autowidget/resources/lib/common/router.py +++ b/plugin.program.autowidget/resources/lib/common/router.py @@ -16,7 +16,7 @@ from resources.lib.common import utils -def _log_params(_plugin, _handle, _params): +def _log_params(_params): msg = "[{}]" params = dict(parse_qsl(_params)) @@ -29,8 +29,8 @@ def _log_params(_plugin, _handle, _params): return params -def dispatch(_plugin, _handle, _params): - params = _log_params(_plugin, int(_handle), _params) +def dispatch(_handle, _params): + params = _log_params(_params) category = "AutoWidget" is_dir = False is_type = "files" From 08ec4de0dec28c4ad7ee2768feeccf5084155072 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:17:32 +0700 Subject: [PATCH 050/101] return dummy files on error and update in the background. Notify when background updating --- .../resources/lib/common/utils.py | 35 +++++++++++++++++++ .../resources/lib/refresh.py | 2 ++ 2 files changed, 37 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 4f6f264a..fbf41709 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -619,6 +619,38 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) +ERROR_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Error", + "label": "Error", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} + +UPDATING_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Updating", + "label": "Updating", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} + def cache_expiry(hash, widget_id, add=None, no_queue=False): # Currently just caches for 5 min so that the background refresh doesn't go in a loop. @@ -667,10 +699,13 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): else: if not os.path.exists(cache_path): result = "Empty" + contents = UPDATING_FILE + push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" + contents = ERROR_FILE else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index e9e3a253..5e777bb2 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -341,6 +341,7 @@ def cache_and_update(widget_ids): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ + dialog = xbmcgui.Dialog() assert widget_ids effected_widgets = set() @@ -366,6 +367,7 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless + dialog.notification('AutoWidget', "Updating widget {}".format(_label)) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From c8426d832aaebecc1afd29a5a9d24042b88b35ee Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:36:28 +0700 Subject: [PATCH 051/101] handle unicode in notification --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 5e777bb2..9edc56fb 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -367,7 +367,7 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification('AutoWidget', "Updating widget {}".format(_label)) + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From e305243aa61f8315efd38f88398d22502b28cea4 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:39:59 +0700 Subject: [PATCH 052/101] show when widgets have finished updating --- plugin.program.autowidget/resources/lib/refresh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 9edc56fb..9ab66e5b 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -106,6 +106,9 @@ def _update_widgets(self): and utils.get_active_window() == "home" ): utils.update_container(True) + dialog = xbmcgui.Dialog() + dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if self.abortRequested(): break From 8e4b81d8702866a25c333f9e9e5453bc853deacc Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:49:11 +0700 Subject: [PATCH 053/101] notification when backround updating ends --- .../resources/lib/common/cache.py | 426 ++++++++++++++++++ .../resources/lib/refresh.py | 16 +- 2 files changed, 434 insertions(+), 8 deletions(-) create mode 100644 plugin.program.autowidget/resources/lib/common/cache.py diff --git a/plugin.program.autowidget/resources/lib/common/cache.py b/plugin.program.autowidget/resources/lib/common/cache.py new file mode 100644 index 00000000..e0433227 --- /dev/null +++ b/plugin.program.autowidget/resources/lib/common/cache.py @@ -0,0 +1,426 @@ +import xbmcgui + +import glob +import hashlib +import json +import math +import os +import time + +import six + +from resources.lib.common import settings +from resources.lib.common import utils + +_addon_data = utils.translate_path(settings.get_addon_info("profile")) + +_playback_history_path = os.path.join(_addon_data, "cache.history") + +_startup_time = time.time() # TODO: could get reloaded so not accurate? +DEFAULT_CACHE_TIME = 60 * 5 + + +def clear_cache(target): + if not target: + dialog = xbmcgui.Dialog() + choice = dialog.yesno("AutoWidget", utils.get_string(30118)) + del dialog + + if choice: + for file in [i for i in os.listdir(_addon_data) if i.endswith(".cache")]: + os.remove(os.path.join(_addon_data, file)) + else: + os.remove(os.path.join(_addon_data, "{}.cache".format(target))) + utils.update_container(True) + + +def hash_from_cache_path(path): + base = os.path.basename(path) + return os.path.splitext(base)[0] + + +def iter_queue(): + queued = filter(os.path.isfile, glob.glob(os.path.join(_addon_data, "*.queue"))) + # TODO: sort by path instead so load plugins at the same time + for path in sorted(queued, key=os.path.getmtime): + queue_data = utils.read_json(path) + yield queue_data.get("path", "") + + +def read_history(path, create_if_missing=True): + hash = path2hash(path) + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + if not os.path.exists(history_path): + if create_if_missing: + cache_data = {} + history = cache_data.setdefault("history", []) + widgets = cache_data.setdefault("widgets", []) + utils.write_json(history_path, cache_data) + else: + cache_data = None + else: + cache_data = utils.read_json(history_path) + return cache_data + + +def next_cache_queue(): + # Simple queue by creating a .queue file + # TODO: use watchdog to use less resources + for path in iter_queue(): + # TODO: sort by path instead so load plugins at the same time + hash = path2hash(path) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + if not os.path.exists(queue_path): + # a widget update has already taken care of updating this path + continue + # We will let the update operation remove the item from the queue + + # TODO: need to workout if a blocking write is happen while it was queued or right now. + # probably need a .lock file to ensure foreground calls can get priority. + cache_data = read_history(path, create_if_missing=True) + yield hash, cache_data + + +def push_cache_queue(path, widget_id=None): + hash = path2hash(path) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + history = read_history(path, create_if_missing=True) # Ensure its created + changed = False + if widget_id is not None and widget_id not in history["widgets"]: + history["widgets"].append(widget_id) + changed = True + if history.get("path", "") != path: + history["path"] = path + changed = True + if changed: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + utils.write_json(history_path, history) + + if os.path.exists(queue_path): + pass # Leave original modification date so item is higher priority + else: + utils.write_json(queue_path, {"hash": hash, "path": path}) + + +def is_cache_queue(hash): + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + return os.path.exists(queue_path) + + +def remove_cache_queue(hash): + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + utils.remove_file(queue_path) + + +def path2hash(path): + if path is not None: + return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + else: + return None + + +def widgets_for_path(path): + hash = path2hash(path) + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + cache_data = utils.read_json(history_path) if os.path.exists(history_path) else None + if cache_data is None: + cache_data = {} + widgets = cache_data.setdefault("widgets", []) + return set(widgets) + + +def cache_files(path, widget_id): + info_keys = utils.get_info_keys() + params = { + "jsonrpc": "2.0", + "method": "Files.GetDirectory", + "params": { + "properties": info_keys, + "directory": path, + }, + "id": 1, + } + files = utils.call_jsonrpc(params) + _, _, changed = cache_expiry(path, widget_id, add=files) + return (files, changed) + + +def cache_expiry(path, widget_id, add=None, background=True): + # Predict how long to cache for with a min of 5min so updates don't go in a loop + # TODO: find better way to prevents loops so that users trying to manually refresh can do so + # TODO: manage the cache files to remove any too old or no longer used + # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should + # update when autowidget updates. + hash = path2hash(path) + cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) + + # Read file every time as we might be called from multiple processes + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + cache_data = utils.read_json(history_path) if os.path.exists(history_path) else None + if cache_data is None: + cache_data = {} + since_read = 0 + else: + since_read = time.time() - last_read(hash) + + history = cache_data.setdefault("history", []) + widgets = cache_data.setdefault("widgets", []) + if widget_id not in widgets: + widgets.append(widget_id) + + expiry = time.time() - 20 + contents = None + changed = True + size = 0 + + if add is not None: + cache_json = json.dumps(add) + if not add or not cache_json.strip(): + result = "Invalid Write" + + elif "error" in add or not add.get("result", {}).get("files"): + # In this case we don't want to cache a bad result + result = "Error" + # TODO: do we schedule a new update? or put dummy content up even if we have + # good cached content? + else: + utils.write_json(cache_path, add) + contents = add + size = len(cache_json) + content_hash = path2hash(cache_json) + changed = history[-1][1] != content_hash if history else True + history.append((time.time(), content_hash)) + if cache_data.get("path") != path: + cache_data["path"] = path + utils.write_json(history_path, cache_data) + # expiry = history[-1][0] + DEFAULT_CACHE_TIME + pred_dur = predict_update_frequency(history) + expiry = ( + history[-1][0] + pred_dur * 0.75 + ) # less than prediction to ensure pred keeps up to date + result = "Wrote" + else: + # write any updated widget_ids so we know what to update when we dequeue + # Also important as wwe use last modified of .history as accessed time + utils.write_json(history_path, cache_data) + if not os.path.exists(cache_path): + result = "Empty" + if background: + contents = utils.make_holding_path(u"Loading Content...", "refresh") + push_cache_queue(path) + else: + contents = utils.read_json(cache_path, log_file=True) + if contents is None: + result = "Invalid Read" + if background: + contents = utils.make_holding_path("Error", "error") + push_cache_queue(path) + else: + # write any updated widget_ids so we know what to update when we dequeue + # Also important as wwe use last modified of .history as accessed time + utils.write_json(history_path, cache_data) + size = len(json.dumps(contents)) + if history: + expiry = history[-1][0] + predict_update_frequency(history) + + # queue_len = len(list(iter_queue())) + if expiry > time.time(): + result = "Read" + elif not background: + result = "Skip already updated" + # elif queue_len > 3: + # # Try to give system more breathing space by returning empty cache but ensuring refresh + # # better way is to just do this the first X accessed after startup. + # # or how many accessed in the last 30s? + # push_cache_queue(hash) + # result = "Skip (queue={})".format(queue_len) + # contents = dict(result=dict(files=[])) + else: + push_cache_queue(path) + result = "Read and queue" + # TODO: some metric that tells us how long to the first and last widgets becomes visible and then get updated + # not how to measure the time delay when when the cache is read until it appears on screen? + # Is the first cache read always the top visibible widget? + utils.log( + "{} cache {}B (exp:{}, last:{}): {} {}".format( + result, + size, + utils.ft(expiry - time.time()), + utils.ft(since_read), + hash[:5], + widgets, + ), + "notice", + ) + return expiry, contents, changed + + +def last_read(hash): + # Technically this is last read or updated but we can change it to be last read Later + # if we create another file + path = os.path.join(_addon_data, "{}.history".format(hash)) + return os.path.getmtime(path) + + +def predict_update_frequency(history): + if not history: + return DEFAULT_CACHE_TIME + update_count = 0 + duration = 0 + changes = [] + last_when, last = history[0] + for when, content in history[1:]: + update_count += 1 + if content == last: + duration += when - last_when + else: + duration = ( + +(when - last_when) / 2 + ) # change could have happened any time inbetween + changes.append((duration, update_count)) + duration = 0 + update_count = 0 + last_when = when + last = content + if not changes and duration: + # drop the last part of the history that hasn't changed yet unless we have no other history to work with + # This is an underestimate as we aren't sure when in the future it will change + changes.append((duration, update_count)) + # TODO: the first change is potentially an underestimate too because we don't know how long it was unchanged for + # before we started recording. + + # Now we have changes, we can do some trends on them. + durations = [duration for duration, update_count in changes if update_count > 1] + if not durations: + return DEFAULT_CACHE_TIME + med_dur = sorted(durations)[int(math.floor(len(durations) / 2)) - 1] + avg_dur = sum(durations) / len(durations) + # weighted by how many snapshots we took inbetween. + # TODO: number of snapshots inbetween is really just increasing the confidence on the end time bot the duration as a whole. + # so perhaps a better metric is the error margin of the duration? and not weighting by that completely. + # ie durations with wide margin of error should be less important. e.g. times kodi was never turned on for months/weeks. + weighted = sum([d * c for d, c in changes]) / sum([c for _, c in changes]) + # TODO: also try exponential decay. Older durations are less important than newer ones. + ones = len([c for d, c in changes if c == 1]) / float(len(changes)) + # TODO: if many streaks with lots of counts then its stable and can predict + utils.log( + "avg_dur {:0.0f}s, med_dur {:0.0f}s, weighted {:0.0f}s, ones {:0.2f}, all {}".format( + avg_dur, med_dur, weighted, ones, changes + ), + "debug", + ) + if ones > 0.9: + # too unstable so no point guessing + return DEFAULT_CACHE_TIME + elif DEFAULT_CACHE_TIME > avg_dur / 2.0: + # should not got less than 5min otherwise our updates go in a loop + return DEFAULT_CACHE_TIME + else: + return ( + avg_dur / 2.0 + ) # we want to ensure we check more often than the actual predicted expiry + + +# return DEFAULT_CACHE_TIME + + +def widgets_changed_by_watching(media_type): + # Predict which widgets the skin might have that could have changed based on recently finish + # watching something + + all_cache = filter( + os.path.isfile, glob.glob(os.path.join(_addon_data, "*.history")) + ) + + # Simple version. Anything updated recently (since startup?) + # priority = sorted(all_cache, key=os.path.getmtime) + # Sort by chance of it updating + plays = utils.read_json(_playback_history_path, default={}).setdefault("plays", []) + plays_for_type = [(time, t) for time, t in plays if t == media_type] + priority = sorted( + [ + (chance_playback_updates_widget(path, plays_for_type), path) + for path in all_cache + ], + reverse=True, + ) + + for chance, path in priority: + hash = hash_from_cache_path(path) + last_update = os.path.getmtime(path) - _startup_time + if last_update < 0: + utils.log( + "widget not updated since startup {} {}".format(last_update, hash[:5]), + "notice", + ) + # elif chance < 0.3: + # log("chance widget changed after play {}% {}".format(chance, hash[:5]), 'notice') + else: + utils.log( + "chance widget changed after play {}% {}".format(chance, hash[:5]), + "notice", + ) + yield hash, path + + +def chance_playback_updates_widget(history_path, plays, cutoff_time=60 * 5): + cache_data = utils.read_json(history_path) + history = cache_data.setdefault("history", []) + # Complex version + # - for each widget + # - come up with chance it will update after a playback + # - each pair of updates, is there a playback inbetween and updated with X min after playback + # - num playback with change / num playback with no change + changes, non_changes, unrelated_changes = 0, 0, 0 + update = "" + time_since_play = 0 + for play_time, media_type in plays: + while True: + last_update = update + if not history: + break + update_time, update = history.pop(0) + time_since_play = update_time - play_time + # log("{} {} {} {}".format(update[:5],last_update[:5], unrelated_changes, time_since_play), 'notice') + if time_since_play > 0: + break + elif update != last_update: + unrelated_changes += 1 + + if update == last_update: + non_changes += 1 + elif ( + time_since_play > cutoff_time + ): # update too long after playback to be releated + pass + else: + changes += 1 + # TODO: what if the previous update was a long time before playback? + + # There is probably a more statistically correct way of doing this but the idea is that + # with few datapoints we should tend towards 0.5 probability but as we get more datapoints + # then error goes down and rely on actual changes vs nonchanges + # We will do a simple weighted average with 0.5 to simulate this + # TODO: currently random widgets score higher than recently played widgets. need to score them lower + # as they are less relevent + utils.log( + "changes={}, non_changes={}, unrelated_changes={}".format( + changes, non_changes, unrelated_changes + ), + "debug", + ) + datapoints = float(changes + non_changes) + prob = changes / float(changes + non_changes + unrelated_changes) + unknown_weight = 4 + prob = (prob * datapoints + 0.5 * unknown_weight) / (datapoints + unknown_weight) + return prob + + +def save_playback_history(media_type, playback_percentage): + # Record in json when things got played to help predict which widgets will change after playback + # if playback_percentage < 0.7: + # return + history = utils.read_json(_playback_history_path, default={}) + plays = history.setdefault("plays", []) + plays.append((time.time(), media_type)) + utils.write_json(_playback_history_path, history) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 9ab66e5b..8c7e8dea 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -79,15 +79,14 @@ def _update_widgets(self): while not self.abortRequested(): for _ in self.tick(15, 60 * 15): # TODO: somehow delay to all other plugins loaded? + updated = False unrefreshed_widgets = set() for hash, widget_ids in utils.next_cache_queue(): effected_widgets = cache_and_update(widget_ids) - utils.remove_cache_queue( - hash - ) # Just in queued path's widget defintion has changed and it didn't update this path - unrefreshed_widgets = unrefreshed_widgets.union( - effected_widgets - ).difference(set(widget_ids)) + if effected_widgets: + updated = True + utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path + unrefreshed_widgets = unrefreshed_widgets.union(effected_widgets).difference(set(widget_ids)) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update @@ -106,8 +105,9 @@ def _update_widgets(self): and utils.get_active_window() == "home" ): utils.update_container(True) - dialog = xbmcgui.Dialog() - dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if updated: + dialog = xbmcgui.Dialog() + dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) if self.abortRequested(): From e0f3827885de72a17b6ae2220a3454cca0033265 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 12:20:09 +0700 Subject: [PATCH 054/101] fix starting up with no cache --- .../resources/lib/common/utils.py | 16 +++++++++++++--- .../resources/lib/refresh.py | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index fbf41709..19273325 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -641,8 +641,8 @@ def cache_files(path, widget_id): "result": { "files": [ { - "title": "Updating", - "label": "Updating", + "title": "Loading Content...", + "label": "Loading Content...", "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art('alert'), "filetype": "file", @@ -684,6 +684,12 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): cache_json = json.dumps(add) if not add or not cache_json.strip(): result = "Invalid Write" + + elif 'error' in add or not add.get('result',{}).get('files'): + # In this case we don't want to cache a bad result + result = "Error" + # TODO: do we schedule a new update? or put dummy content up even if we have + # good cached content? else: write_json(cache_path, add) contents = add @@ -694,9 +700,12 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) - expiry = history[-1][0] + pred_dur / 2.0 + expiry = history[-1][0] + pred_dur * 0.75 # less than prediction to ensure pred keeps up to date result = "Wrote" else: + # write any updated widget_ids so we know what to update when we dequeue + # Also important as wwe use last modified of .history as accessed time + write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" contents = UPDATING_FILE @@ -706,6 +715,7 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): if contents is None: result = "Invalid Read" contents = ERROR_FILE + push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 8c7e8dea..a3cf3f55 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -277,6 +277,7 @@ def get_files_list(path, widget_id=None): _, files, _ = utils.cache_expiry(hash, widget_id) if files is None: # We had no old content so have to block and get it now + # TODO: will no longer happen because we return dummy file instead utils.log("Blocking cache path read: {}".format(hash[:5]), "info") files, changed = utils.cache_files(path, widget_id) From 20d276dacd9d23d6058d5ac0008b0e305f546466 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:10:59 +0700 Subject: [PATCH 055/101] don't do notifications duirng playback --- plugin.program.autowidget/resources/lib/refresh.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index a3cf3f55..3a754e28 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -82,7 +82,8 @@ def _update_widgets(self): updated = False unrefreshed_widgets = set() for hash, widget_ids in utils.next_cache_queue(): - effected_widgets = cache_and_update(widget_ids) + notify = self.refresh_enabled == 1 and not self.player.isPlayingVideo() + effected_widgets = cache_and_update(widget_ids, notify=notify) if effected_widgets: updated = True utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path @@ -340,9 +341,9 @@ def is_duplicate(title, titles): return False -def cache_and_update(widget_ids): - """a widget might have many paths. Ensure each path is either queued for an update - or is expired and if so force it to be refreshed. When going through the queue this +def cache_and_update(widget_ids, notify=True): + """ a widget might have many paths. Ensure each path is either queued for an update + or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ dialog = xbmcgui.Dialog() @@ -371,7 +372,8 @@ def cache_and_update(widget_ids): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify: + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From fe53e171f6fc07eafbe17003fd5931c2dd15f9b1 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:13:18 +0700 Subject: [PATCH 056/101] and final notify --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 3a754e28..5128e8ad 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -106,7 +106,7 @@ def _update_widgets(self): and utils.get_active_window() == "home" ): utils.update_container(True) - if updated: + if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) From 71384a5083fdf819b91b984b6f8f8764b002cafd Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 09:47:48 +0700 Subject: [PATCH 057/101] tidy update dummy path generation --- .../resources/lib/common/utils.py | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 19273325..ef28fc52 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -204,6 +204,26 @@ _startup_time = time.time() # TODO: could get reloaded so not accurate? +_startup_time = time.time() #TODO: could get reloaded so not accurate? + + +def make_holding_path(label, art): + return { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": label, + "label": label, + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art(art), + "filetype": "file", + } + ] + } +} + def ft(seconds): return str(datetime.timedelta(seconds=int(seconds))) @@ -619,38 +639,6 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) -ERROR_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Error", - "label": "Error", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} - -UPDATING_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Loading Content...", - "label": "Loading Content...", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} - def cache_expiry(hash, widget_id, add=None, no_queue=False): # Currently just caches for 5 min so that the background refresh doesn't go in a loop. @@ -708,13 +696,13 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" - contents = UPDATING_FILE + contents = make_holding_path(u"Loading Content...", "refresh") push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" - contents = ERROR_FILE + contents = make_holding_path("Error", "error") push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue From 244a53707a28000dc3dec9247dca766bd2ff7e2e Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 10:40:22 +0700 Subject: [PATCH 058/101] ensure explode and clone get the path in the foreground so they can build the group properly on add --- .../resources/lib/add.py | 2 +- .../resources/lib/common/utils.py | 23 ++++++++++--------- .../resources/lib/refresh.py | 7 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index d0607daa..3f0bb6b3 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,7 +268,7 @@ def _copy_path(path_def): return group_def = manage.get_group_by_id(group_id) - files, hash = refresh.get_files_list(path_def["file"]["file"]) + files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: return diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index ef28fc52..31d4e5b3 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -640,12 +640,11 @@ def cache_files(path, widget_id): return (files, changed) -def cache_expiry(hash, widget_id, add=None, no_queue=False): - # Currently just caches for 5 min so that the background refresh doesn't go in a loop. - # In the future it will cache for longer based on the history of how often in changed - # and when it changed in relation to events like events events. - # It should also manage the cache files to remove any too old. - # The cache expiry can also be used later to schedule a future background update. +def cache_expiry(hash, widget_id, add=None, background=True): + # Predict how long to cache for with a min of 5min so updates don't go in a loop + # TODO: find better way to prevents loops so that users trying to manually refresh can do so + # TODO: manage the cache files to remove any too old or no longer used + # TODO: update paths on autowidget refresh based on predicted update frequency cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) @@ -696,14 +695,16 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" - contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(hash) + if background: + contents = make_holding_path(u"Loading Content...", "refresh") + push_cache_queue(hash) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" - contents = make_holding_path("Error", "error") - push_cache_queue(hash) + if background: + contents = make_holding_path("Error", "error") + push_cache_queue(hash) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time @@ -715,7 +716,7 @@ def cache_expiry(hash, widget_id, add=None, no_queue=False): # queue_len = len(list(iter_queue())) if expiry > time.time(): result = "Read" - elif no_queue: + elif not background: result = "Skip already updated" # elif queue_len > 3: # # Try to give system more breathing space by returning empty cache but ensuring refresh diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 5128e8ad..5d69f9aa 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -273,12 +273,11 @@ def refresh_paths(notify=False, force=False): return True, "AutoWidget" -def get_files_list(path, widget_id=None): +def get_files_list(path, widget_id=None, background=True): hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(hash, widget_id) + _, files, _ = utils.cache_expiry(hash, widget_id, background=background) if files is None: - # We had no old content so have to block and get it now - # TODO: will no longer happen because we return dummy file instead + # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") files, changed = utils.cache_files(path, widget_id) From 7f146f97fc659fedc67e1b1a9b2f8d203f17f1d5 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 01:13:50 +0700 Subject: [PATCH 059/101] use progress to show background updating --- .../resources/lib/common/utils.py | 3 +- .../resources/lib/refresh.py | 40 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 31d4e5b3..4625a9af 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -644,7 +644,8 @@ def cache_expiry(hash, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop # TODO: find better way to prevents loops so that users trying to manually refresh can do so # TODO: manage the cache files to remove any too old or no longer used - # TODO: update paths on autowidget refresh based on predicted update frequency + # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should + # update when autowidget updates. cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 5d69f9aa..37769542 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -18,11 +18,13 @@ _thread = None + class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" super(RefreshService, self).__init__() - utils.log("+++++ STARTING AUTOWIDGET SERVICE +++++", "info") + utils.log('+++++ STARTING AUTOWIDGET SERVICE +++++', 'info') + self.player = Player() utils.ensure_addon_data() self._update_properties() @@ -73,6 +75,8 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i + + def _update_widgets(self): self._refresh(True) @@ -81,9 +85,27 @@ def _update_widgets(self): # TODO: somehow delay to all other plugins loaded? updated = False unrefreshed_widgets = set() - for hash, widget_ids in utils.next_cache_queue(): - notify = self.refresh_enabled == 1 and not self.player.isPlayingVideo() - effected_widgets = cache_and_update(widget_ids, notify=notify) + queue = list(utils.next_cache_queue()) + class Progress(object): + dialog = None + service = self + done = set() + + def __call__(self, groupname, path): + if self.dialog is None: + self.dialog = xbmcgui.DialogProgressBG() + self.dialog.create(u"Updating Widgets") + if not self.service.player.isPlayingVideo(): + percent = len(self.done)/float(len(queue)+len(self.done)+1) * 100 + self.dialog.update(int(percent), message=groupname) + self.done.add(path) + progress = Progress() + + while queue: + hash, widget_ids = queue.pop(0) + utils.log("Dequeued cache update: {}".format(hash[:5]), 'notice') + + effected_widgets = cache_and_update(widget_ids, notify=progress) if effected_widgets: updated = True utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path @@ -96,6 +118,7 @@ def _update_widgets(self): # utils.log("paused queue until read {:.2} for {}".format(utils.last_read(hash)-before_update, hash[:5]), 'info') if self.abortRequested(): break + queue = list(utils.next_cache_queue()) for widget_id in unrefreshed_widgets: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: @@ -106,6 +129,9 @@ def _update_widgets(self): and utils.get_active_window() == "home" ): utils.update_container(True) + if progress.dialog is not None: + progress.dialog.update(100, "") + progress.dialog.close() if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) @@ -345,8 +371,6 @@ def cache_and_update(widget_ids, notify=True): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ - dialog = xbmcgui.Dialog() - assert widget_ids effected_widgets = set() for widget_id in widget_ids: @@ -371,8 +395,8 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - if notify: - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify is not None: + notify(_label, path) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From 0c534cc65d9cd68b2ab5364b4141e32c68ebe5d1 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 02:39:14 +0700 Subject: [PATCH 060/101] add progress when exploding --- .../resources/lib/add.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index 3f0bb6b3..e51ecda6 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -267,19 +267,27 @@ def _copy_path(path_def): if not group_id: return + progress = xbmcgui.DialogProgressBG() + progress.create(u"Exploding") + group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: + progress.close() return - + done = 0 for file in files: - if file.get("type") in ["movie", "episode", "musicvideo", "song"]: + done += 1 + if file['type'] in ['movie', 'episode', 'musicvideo', 'song']: continue - - labels = build_labels("json", file, path_def["target"]) + progress.update(int(done/float(len(files))*100), file.get('label')) + + labels = build_labels('json', file, path_def['target']) _add_path(group_def, labels, over=True) + progress.close() + del progress dialog = xbmcgui.Dialog() - dialog.notification( - "AutoWidget", utils.get_string(30105).format(len(files), group_def["label"]) - ) + dialog.notification('AutoWidget', utils.get_string(32131) + .format(len(files), group_def['label'])) del dialog + From 4cb1eb64fe96c91da6dc22d2d73106e885d3acb8 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 02:42:15 +0700 Subject: [PATCH 061/101] copying instead of exploding --- plugin.program.autowidget/resources/lib/add.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index e51ecda6..6efca83b 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,7 +268,8 @@ def _copy_path(path_def): return progress = xbmcgui.DialogProgressBG() - progress.create(u"Exploding") + progress.create(u"Copying") + progress.update(1, u"Retrieving") group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) From a6f1c65a64b128d43500b31099e3368d4ab8ebdb Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:46:53 +0700 Subject: [PATCH 062/101] reduce wait for queue processing to 1s. --- plugin.program.autowidget/resources/lib/menu.py | 3 +++ plugin.program.autowidget/resources/lib/refresh.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index c359c444..d8fd92b7 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -379,6 +379,7 @@ def show_path( "target": "next", } + next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, @@ -388,6 +389,8 @@ def show_path( isFolder=not paged_widgets or merged, props=properties, ) + # Ensure we precache next page for faster access + utils.push_cache_queue(utils.path2hash(next_path)) else: filetype = file.get("type", "") title = { diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 37769542..9d196da9 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -81,8 +81,8 @@ def _update_widgets(self): self._refresh(True) while not self.abortRequested(): - for _ in self.tick(15, 60 * 15): - # TODO: somehow delay to all other plugins loaded? + for _ in self.tick(step=1, max=60 * 15): + # TODO: somehow delay till all other plugins loaded? updated = False unrefreshed_widgets = set() queue = list(utils.next_cache_queue()) From 66875df8d5457d8b78f57ca6c4741d7823d7eef0 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:47:50 +0700 Subject: [PATCH 063/101] precaching next page on paged widgets. but doesn't seem to be working yet --- plugin.program.autowidget/resources/lib/common/directory.py | 4 +++- plugin.program.autowidget/resources/lib/menu.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/directory.py b/plugin.program.autowidget/resources/lib/common/directory.py index d5d6dcd8..0d85f596 100644 --- a/plugin.program.autowidget/resources/lib/common/directory.py +++ b/plugin.program.autowidget/resources/lib/common/directory.py @@ -250,7 +250,9 @@ def add_menu_item( handle=_handle, url=_plugin, listitem=item, isFolder=isFolder ) - + return _plugin + + def make_library_path(library, type, id): if not library or not type or id == -1: return "" diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index d8fd92b7..56cdb9dd 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -383,14 +383,14 @@ def show_path( directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=file["file"] if not paged_widgets or merged else None, + path=next_path, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - utils.push_cache_queue(utils.path2hash(next_path)) + utils.cache_expiry(utils.path2hash(next_path), widget_id) else: filetype = file.get("type", "") title = { From c7312f30a21620533846dd5e2a518daeba52abdd Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:48:50 +0700 Subject: [PATCH 064/101] ensure we have history when queuing updates --- .../resources/lib/common/utils.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 4625a9af..afe3a269 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -551,6 +551,21 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path +def read_history(hash, create_if_missing=True): + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + if not os.path.exists(history_path): + if create_if_missing: + cache_data = {} + history = cache_data.setdefault('history', []) + widgets = cache_data.setdefault('widgets', []) + write_json(history_path, cache_data) + else: + cache_data = None + else: + cache_data = read_json(history_path) + return cache_data + + def next_cache_queue(): # Simple queue by creating a .queue file @@ -565,15 +580,18 @@ def next_cache_queue(): # TODO: need to workout if a blocking write is happen while it was queued or right now. # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) - path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(path) - if cache_data: - log("Dequeued cache update: {}".format(hash[:5]), "notice") - yield hash, cache_data.get("widgets", []) + cache_data = read_history(hash, create_if_missing=True) + yield hash, cache_data.get('widgets',[]) + + +def push_cache_queue(hash, widget_id=None): + queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history['widgets']: + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history['widgets'].append(widget_id) + write_json(history_path, history) - -def push_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) if os.path.exists(queue_path): pass # Leave original modification date so item is higher priority else: @@ -591,7 +609,10 @@ def remove_cache_queue(hash): def path2hash(path): - return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + if path is not None: + return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() + else: + return None def widgets_for_path(path): From ec68dffa0229d2f414f37c5ffa6375b5d9ca33ee Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:46:13 -0700 Subject: [PATCH 065/101] :bug: - Fix addon data paths --- plugin.program.autowidget/resources/lib/common/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index afe3a269..7ecaef88 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -552,7 +552,7 @@ def iter_queue(): yield path def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} @@ -585,10 +585,10 @@ def next_cache_queue(): def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) history = read_history(hash, create_if_missing=True) # Ensure its created if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) history['widgets'].append(widget_id) write_json(history_path, history) From bcde8f58a4dce81f0dbeaa28a9b0965817b8d39c Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:47:05 -0700 Subject: [PATCH 066/101] :bug: - Try to fix paged widget caching --- plugin.program.autowidget/resources/lib/common/utils.py | 4 ++-- plugin.program.autowidget/resources/lib/menu.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 7ecaef88..a2842dee 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -657,7 +657,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files) + _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) return (files, changed) @@ -703,7 +703,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): write_json(cache_path, add) contents = add size = len(cache_json) - content_hash = hashlib.sha1(cache_json.encode("utf8")).hexdigest() + content_hash = path2hash(cache_json) changed = history[-1][1] != content_hash if history else True history.append((time.time(), content_hash)) write_json(history_path, cache_data) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 56cdb9dd..ba2373ae 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -308,7 +308,8 @@ def show_path( stack = widget_def.get("stack", []) path = widget_path["file"]["file"] if not stack else stack[-1] - files, hash = refresh.get_files_list(path, widget_id) + + files, hash = refresh.get_files_list(path, widget_id, background=False) if not files: properties = { "autoLabel": path_label, @@ -379,18 +380,17 @@ def show_path( "target": "next", } - next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=next_path, + path=file['file'] if not paged_widgets or merged else None, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - utils.cache_expiry(utils.path2hash(next_path), widget_id) + utils.cache_expiry(utils.path2hash(file['file']), widget_id, background=True) else: filetype = file.get("type", "") title = { From 5ea4ce9a9f3f860527eca1c1e57942491e84a77e Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:13:59 -0700 Subject: [PATCH 067/101] :alien: - Add compatibility for old widgets --- .../resources/lib/refresh.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 9d196da9..e902a059 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -378,17 +378,24 @@ def cache_and_update(widget_ids, notify=True): if not widget_def: continue changed = False - widget_path = widget_def.get("path", {}) + widget_path = widget_def.get("path", "") utils.log( "trying to update {} with widget def {}".format(widget_id, widget_def), "inspect", ) + if type(widget_path) != list: widget_path = [widget_path] - for path in widget_path: - if isinstance(path, dict): - _label = path["label"] - path = path["file"]["file"] + for path_id in widget_path: + # simple compatibility with pre-3.3.0 widgets + if isinstance(path_id, dict): + path_id = path_id.get("id", "") + path = manage.get_path_by_id(path_id) + if not path: + continue + + _label = path["label"] + path = path["file"]["file"] hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. From 4fabc6834c88d363dc0ac8b4e2808ffe11ccb79d Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:37:43 -0700 Subject: [PATCH 068/101] :recycle: - Use last_read --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index a2842dee..426c3464 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -677,7 +677,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): cache_data = {} since_read = 0 else: - since_read = time.time() - os.path.getmtime(history_path) + since_read = time.time() - last_read(hash) history = cache_data.setdefault("history", []) widgets = cache_data.setdefault("widgets", []) From 3f0b440cbc3463b9f5c9e72fddf68908baae8366 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:07 -0700 Subject: [PATCH 069/101] :lipstick: - Format with black --- .../resources/lib/common/utils.py | 44 ++++++++++--------- .../resources/lib/refresh.py | 28 +++++++----- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 426c3464..6457557c 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -204,13 +204,13 @@ _startup_time = time.time() # TODO: could get reloaded so not accurate? -_startup_time = time.time() #TODO: could get reloaded so not accurate? +_startup_time = time.time() # TODO: could get reloaded so not accurate? def make_holding_path(label, art): return { - "jsonrpc": "2.0", - "id": 1, + "jsonrpc": "2.0", + "id": 1, "result": { "files": [ { @@ -218,11 +218,11 @@ def make_holding_path(label, art): "label": label, "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art(art), - "filetype": "file", + "filetype": "file", } ] - } -} + }, + } def ft(seconds): @@ -551,14 +551,15 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path + def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, "{}.history".format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} - history = cache_data.setdefault('history', []) - widgets = cache_data.setdefault('widgets', []) - write_json(history_path, cache_data) + history = cache_data.setdefault("history", []) + widgets = cache_data.setdefault("widgets", []) + write_json(history_path, cache_data) else: cache_data = None else: @@ -566,7 +567,6 @@ def read_history(hash, create_if_missing=True): return cache_data - def next_cache_queue(): # Simple queue by creating a .queue file # TODO: use watchdog to use less resources @@ -581,15 +581,15 @@ def next_cache_queue(): # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) cache_data = read_history(hash, create_if_missing=True) - yield hash, cache_data.get('widgets',[]) - + yield hash, cache_data.get("widgets", []) + def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created - if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) - history['widgets'].append(widget_id) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history["widgets"]: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + history["widgets"].append(widget_id) write_json(history_path, history) if os.path.exists(queue_path): @@ -694,7 +694,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): if not add or not cache_json.strip(): result = "Invalid Write" - elif 'error' in add or not add.get('result',{}).get('files'): + elif "error" in add or not add.get("result", {}).get("files"): # In this case we don't want to cache a bad result result = "Error" # TODO: do we schedule a new update? or put dummy content up even if we have @@ -709,12 +709,14 @@ def cache_expiry(hash, widget_id, add=None, background=True): write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) - expiry = history[-1][0] + pred_dur * 0.75 # less than prediction to ensure pred keeps up to date + expiry = ( + history[-1][0] + pred_dur * 0.75 + ) # less than prediction to ensure pred keeps up to date result = "Wrote" else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) + write_json(history_path, cache_data) if not os.path.exists(cache_path): result = "Empty" if background: diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index e902a059..151a6ae3 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -18,12 +18,11 @@ _thread = None - class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" super(RefreshService, self).__init__() - utils.log('+++++ STARTING AUTOWIDGET SERVICE +++++', 'info') + utils.log("+++++ STARTING AUTOWIDGET SERVICE +++++", "info") self.player = Player() utils.ensure_addon_data() @@ -75,8 +74,6 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i - - def _update_widgets(self): self._refresh(True) @@ -86,6 +83,7 @@ def _update_widgets(self): updated = False unrefreshed_widgets = set() queue = list(utils.next_cache_queue()) + class Progress(object): dialog = None service = self @@ -96,20 +94,29 @@ def __call__(self, groupname, path): self.dialog = xbmcgui.DialogProgressBG() self.dialog.create(u"Updating Widgets") if not self.service.player.isPlayingVideo(): - percent = len(self.done)/float(len(queue)+len(self.done)+1) * 100 + percent = ( + len(self.done) + / float(len(queue) + len(self.done) + 1) + * 100 + ) self.dialog.update(int(percent), message=groupname) self.done.add(path) + progress = Progress() while queue: hash, widget_ids = queue.pop(0) - utils.log("Dequeued cache update: {}".format(hash[:5]), 'notice') + utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") effected_widgets = cache_and_update(widget_ids, notify=progress) if effected_widgets: updated = True - utils.remove_cache_queue(hash) # Just in queued path's widget defintion has changed and it didn't update this path - unrefreshed_widgets = unrefreshed_widgets.union(effected_widgets).difference(set(widget_ids)) + utils.remove_cache_queue( + hash + ) # Just in queued path's widget defintion has changed and it didn't update this path + unrefreshed_widgets = unrefreshed_widgets.union( + effected_widgets + ).difference(set(widget_ids)) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update @@ -136,7 +143,6 @@ def __call__(self, groupname, path): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) - if self.abortRequested(): break @@ -367,8 +373,8 @@ def is_duplicate(title, titles): def cache_and_update(widget_ids, notify=True): - """ a widget might have many paths. Ensure each path is either queued for an update - or is expired and if so force it to be refreshed. When going through the queue this + """a widget might have many paths. Ensure each path is either queued for an update + or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids From 0df6861caede5570970dc81f6c952a9c5b2ea5ba Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:36 -0700 Subject: [PATCH 070/101] :recycle: - Cache files in background --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 6457557c..e13d449e 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -657,7 +657,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) + _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) From ff274376cba14731f3c29e6500bc0e00ff967296 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:17:32 +0700 Subject: [PATCH 071/101] return dummy files on error and update in the background. Notify when background updating --- .../resources/lib/common/utils.py | 15 +++++++++++++++ .../resources/lib/refresh.py | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index e13d449e..496cb3b9 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -660,6 +660,21 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) +ERROR_FILE = { + "jsonrpc": "2.0", + "id": 1, + "result": { + "files": [ + { + "title": "Error", + "label": "Error", + "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "art": get_art('alert'), + "filetype": "file", + } + ] + } +} def cache_expiry(hash, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 151a6ae3..3144002e 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -377,6 +377,8 @@ def cache_and_update(widget_ids, notify=True): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ + dialog = xbmcgui.Dialog() + assert widget_ids effected_widgets = set() for widget_id in widget_ids: @@ -408,8 +410,7 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - if notify is not None: - notify(_label, path) + dialog.notification('AutoWidget', "Updating widget {}".format(_label)) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From f433138f8056608216a138b252828e90637b3df5 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:36:28 +0700 Subject: [PATCH 072/101] handle unicode in notification --- plugin.program.autowidget/resources/lib/refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 3144002e..a951fec4 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -410,7 +410,7 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification('AutoWidget', "Updating widget {}".format(_label)) + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From f118e4e1130512bd0a4ba443110fc6c7b8971586 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 28 Jan 2021 00:39:59 +0700 Subject: [PATCH 073/101] show when widgets have finished updating --- plugin.program.autowidget/resources/lib/refresh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index a951fec4..76533548 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -143,6 +143,7 @@ def __call__(self, groupname, path): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + if self.abortRequested(): break From 12630f0eb625c7a6c28b4f526593dd6a676f0b9a Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Fri, 29 Jan 2021 00:10:59 +0700 Subject: [PATCH 074/101] don't do notifications duirng playback --- plugin.program.autowidget/resources/lib/refresh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 76533548..45b94d10 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -411,7 +411,8 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify: + dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From f88bbd710d08658176ab5b6734d3a717f93cb021 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 1 Feb 2021 09:47:48 +0700 Subject: [PATCH 075/101] tidy update dummy path generation --- .../resources/lib/common/utils.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 496cb3b9..e13d449e 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -660,21 +660,6 @@ def cache_files(path, widget_id): _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) -ERROR_FILE = { - "jsonrpc": "2.0", - "id": 1, - "result": { - "files": [ - { - "title": "Error", - "label": "Error", - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", - "art": get_art('alert'), - "filetype": "file", - } - ] - } -} def cache_expiry(hash, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop From 92fe0ce69f628684a7edd567fb9dfe5033d764bd Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 11 Feb 2021 01:13:50 +0700 Subject: [PATCH 076/101] use progress to show background updating --- plugin.program.autowidget/resources/lib/refresh.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 45b94d10..58c56c2c 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -18,6 +18,7 @@ _thread = None + class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" @@ -74,6 +75,8 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i + + def _update_widgets(self): self._refresh(True) @@ -378,8 +381,6 @@ def cache_and_update(widget_ids, notify=True): or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ - dialog = xbmcgui.Dialog() - assert widget_ids effected_widgets = set() for widget_id in widget_ids: @@ -411,8 +412,8 @@ def cache_and_update(widget_ids, notify=True): effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) if utils.is_cache_queue(hash): # we need to update this path regardless - if notify: - dialog.notification(u'AutoWidget', u"Updating widget {}".format(_label), sound=False) + if notify is not None: + notify(_label, path) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) From 058a154697cf0707080226f8ac95348365a28cdd Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:46:53 +0700 Subject: [PATCH 077/101] reduce wait for queue processing to 1s. --- plugin.program.autowidget/resources/lib/menu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index ba2373ae..c532b290 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -380,6 +380,7 @@ def show_path( "target": "next", } + next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, From 28e18f654269c218c3ab2b536fb3b13b8e70005f Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 22 Feb 2021 14:48:50 +0700 Subject: [PATCH 078/101] ensure we have history when queuing updates --- .../resources/lib/common/utils.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index e13d449e..dc030660 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -551,6 +551,19 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path +def read_history(hash, create_if_missing=True): + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + if not os.path.exists(history_path): + if create_if_missing: + cache_data = {} + history = cache_data.setdefault('history', []) + widgets = cache_data.setdefault('widgets', []) + write_json(history_path, cache_data) + else: + cache_data = None + else: + cache_data = read_json(history_path) + return cache_data def read_history(hash, create_if_missing=True): history_path = os.path.join(_addon_data, "{}.history".format(hash)) @@ -583,6 +596,13 @@ def next_cache_queue(): cache_data = read_history(hash, create_if_missing=True) yield hash, cache_data.get("widgets", []) +def push_cache_queue(hash, widget_id=None): + queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history['widgets']: + history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history['widgets'].append(widget_id) + write_json(history_path, history) def push_cache_queue(hash, widget_id=None): queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) From 9f8e3ba233406fa7f709f7791092a4fbee8598b2 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:46:13 -0700 Subject: [PATCH 079/101] :bug: - Fix addon data paths --- plugin.program.autowidget/resources/lib/common/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index dc030660..6a07279b 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -552,7 +552,7 @@ def iter_queue(): yield path def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} @@ -597,10 +597,10 @@ def next_cache_queue(): yield hash, cache_data.get("widgets", []) def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_path, '{}.queue'.format(hash)) + queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) history = read_history(hash, create_if_missing=True) # Ensure its created if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_path, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, '{}.history'.format(hash)) history['widgets'].append(widget_id) write_json(history_path, history) From 34f9ae1393a70adb74f659730b189369097fdd67 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Sun, 18 Jul 2021 17:47:05 -0700 Subject: [PATCH 080/101] :bug: - Try to fix paged widget caching --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- plugin.program.autowidget/resources/lib/menu.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 6a07279b..71a15122 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -677,7 +677,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files) + _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) return (files, changed) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index c532b290..ba2373ae 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -380,7 +380,6 @@ def show_path( "target": "next", } - next_path = file['file'] if not paged_widgets or merged else None directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, From f8b4b0d3b10a3206c069cf2f2aadf930f9aec77e Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:07 -0700 Subject: [PATCH 081/101] :lipstick: - Format with black --- .../resources/lib/common/utils.py | 21 ++++++++++--------- .../resources/lib/refresh.py | 4 ---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 71a15122..18dc4848 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -551,14 +551,15 @@ def iter_queue(): for path in sorted(queued, key=os.path.getmtime): yield path + def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) + history_path = os.path.join(_addon_data, "{}.history".format(hash)) if not os.path.exists(history_path): if create_if_missing: cache_data = {} - history = cache_data.setdefault('history', []) - widgets = cache_data.setdefault('widgets', []) - write_json(history_path, cache_data) + history = cache_data.setdefault("history", []) + widgets = cache_data.setdefault("widgets", []) + write_json(history_path, cache_data) else: cache_data = None else: @@ -579,7 +580,6 @@ def read_history(hash, create_if_missing=True): cache_data = read_json(history_path) return cache_data - def next_cache_queue(): # Simple queue by creating a .queue file # TODO: use watchdog to use less resources @@ -596,12 +596,13 @@ def next_cache_queue(): cache_data = read_history(hash, create_if_missing=True) yield hash, cache_data.get("widgets", []) + def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_data, '{}.queue'.format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created - if widget_id is not None and widget_id not in history['widgets']: - history_path = os.path.join(_addon_data, '{}.history'.format(hash)) - history['widgets'].append(widget_id) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + history = read_history(hash, create_if_missing=True) # Ensure its created + if widget_id is not None and widget_id not in history["widgets"]: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) + history["widgets"].append(widget_id) write_json(history_path, history) def push_cache_queue(hash, widget_id=None): diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 58c56c2c..151a6ae3 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -18,7 +18,6 @@ _thread = None - class RefreshService(xbmc.Monitor): def __init__(self): """Starts all of the actions of AutoWidget's service.""" @@ -75,8 +74,6 @@ def tick(self, step, max, abort_check=lambda: False): i += step yield i - - def _update_widgets(self): self._refresh(True) @@ -146,7 +143,6 @@ def __call__(self, groupname, path): dialog = xbmcgui.Dialog() dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) - if self.abortRequested(): break From de9d55953e512a1b593d50aee73faa5fa9db6d7b Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Mon, 19 Jul 2021 06:39:36 -0700 Subject: [PATCH 082/101] :recycle: - Cache files in background --- plugin.program.autowidget/resources/lib/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 18dc4848..74c4e0ec 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -678,7 +678,7 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files, background=False) + _, _, changed = cache_expiry(hash, widget_id, add=files) return (files, changed) From 9ed60a462268271e481e3cc97fac8fa5f0f86505 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 22 Jul 2021 11:46:02 +0700 Subject: [PATCH 083/101] put path into .history and background refresh only path not whole widget --- .gitignore | 1 + .../resources/lib/common/utils.py | 26 ++++++---- .../resources/lib/refresh.py | 50 ++++++++++--------- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index daf8fba6..7621a03c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ bin .venv .vscode venv/ +lib diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 74c4e0ec..762eabf9 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -594,15 +594,22 @@ def next_cache_queue(): # probably need a .lock file to ensure foreground calls can get priority. hash = hash_from_cache_path(path) cache_data = read_history(hash, create_if_missing=True) - yield hash, cache_data.get("widgets", []) + yield hash, cache_data -def push_cache_queue(hash, widget_id=None): +def push_cache_queue(path, widget_id=None): + hash = path2hash(path) queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) history = read_history(hash, create_if_missing=True) # Ensure its created + changed = False if widget_id is not None and widget_id not in history["widgets"]: - history_path = os.path.join(_addon_data, "{}.history".format(hash)) history["widgets"].append(widget_id) + changed = True + if history.get("path") != "path": + history["path"] = path + changed = True + if changed: + history_path = os.path.join(_addon_data, "{}.history".format(hash)) write_json(history_path, history) def push_cache_queue(hash, widget_id=None): @@ -678,16 +685,17 @@ def cache_files(path, widget_id): "id": 1, } files = call_jsonrpc(params) - _, _, changed = cache_expiry(hash, widget_id, add=files) + _, _, changed = cache_expiry(path, widget_id, add=files) return (files, changed) -def cache_expiry(hash, widget_id, add=None, background=True): +def cache_expiry(path, widget_id, add=None, background=True): # Predict how long to cache for with a min of 5min so updates don't go in a loop # TODO: find better way to prevents loops so that users trying to manually refresh can do so # TODO: manage the cache files to remove any too old or no longer used # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should # update when autowidget updates. + hash = path2hash(path) cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) @@ -742,14 +750,14 @@ def cache_expiry(hash, widget_id, add=None, background=True): result = "Empty" if background: contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(hash) + push_cache_queue(path) else: contents = read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" if background: contents = make_holding_path("Error", "error") - push_cache_queue(hash) + push_cache_queue(path) else: # write any updated widget_ids so we know what to update when we dequeue # Also important as wwe use last modified of .history as accessed time @@ -771,7 +779,7 @@ def cache_expiry(hash, widget_id, add=None, background=True): # result = "Skip (queue={})".format(queue_len) # contents = dict(result=dict(files=[])) else: - push_cache_queue(hash) + push_cache_queue(path) result = "Read and queue" # TODO: some metric that tells us how long to the first and last widgets becomes visible and then get updated # not how to measure the time delay when when the cache is read until it appears on screen? @@ -890,7 +898,7 @@ def widgets_changed_by_watching(media_type): "chance widget changed after play {}% {}".format(chance, hash[:5]), "notice", ) - yield hash + yield hash, path def chance_playback_updates_widget(history_path, plays, cutoff_time=60 * 5): diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 151a6ae3..1a158ce5 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -108,7 +108,7 @@ def __call__(self, groupname, path): hash, widget_ids = queue.pop(0) utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") - effected_widgets = cache_and_update(widget_ids, notify=progress) + effected_widgets = cache_and_update(hash, widget_ids, notify=progress) if effected_widgets: updated = True utils.remove_cache_queue( @@ -372,18 +372,40 @@ def is_duplicate(title, titles): return False -def cache_and_update(widget_ids, notify=True): +def cache_and_update(hash, widget_ids, notify=True): """a widget might have many paths. Ensure each path is either queued for an update or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids effected_widgets = set() + + changed = False + hash = utils.path2hash(path) + # TODO: we might be updating paths used by widgets that weren't initiall queued. + # We need to return those and ensure they get refreshed also. + effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) + if utils.is_cache_queue(hash): + # we need to update this path regardless + if notify is not None: + notify(_label, path) + new_files, files_changed = utils.cache_files(path, widget_id) + changed = changed or files_changed + utils.remove_cache_queue(hash) + # else: + # # double check this hasn't been updated already when updating another widget + # expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + # if expiry <= time.time(): + # utils.cache_files(path, widget_id) + # else: + # pass # Skipping this path because its already been updated + + + # TODO: update every widget? for widget_id in widget_ids: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: continue - changed = False widget_path = widget_def.get("path", "") utils.log( "trying to update {} with widget def {}".format(widget_id, widget_def), @@ -402,24 +424,6 @@ def cache_and_update(widget_ids, notify=True): _label = path["label"] path = path["file"]["file"] - hash = utils.path2hash(path) - # TODO: we might be updating paths used by widgets that weren't initiall queued. - # We need to return those and ensure they get refreshed also. - effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) - if utils.is_cache_queue(hash): - # we need to update this path regardless - if notify is not None: - notify(_label, path) - new_files, files_changed = utils.cache_files(path, widget_id) - changed = changed or files_changed - utils.remove_cache_queue(hash) - # else: - # # double check this hasn't been updated already when updating another widget - # expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) - # if expiry <= time.time(): - # utils.cache_files(path, widget_id) - # else: - # pass # Skipping this path because its already been updated # TODO: only need to do that if a path has changed which we can tell from the history if changed: _update_strings(widget_def) @@ -521,9 +525,9 @@ def onPlayBackEnded(self): # wait for a bit so scrobing can happen time.sleep(5) - for hash in utils.widgets_changed_by_watching(self.type): + for hash, path in utils.widgets_changed_by_watching(self.type): # Queue them for refresh - utils.push_cache_queue(hash) + utils.push_cache_queue(path) utils.log("Queued cache update: {}".format(hash[:5]), "notice") def onPlayBackStopped(self): From 003c8432a12b1be586fd4306fe6740c9c8f0c460 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 14:13:14 -0700 Subject: [PATCH 084/101] :bug::recycle: - Work towards better caching --- .../resources/lib/common/utils.py | 10 +-- .../resources/lib/menu.py | 2 +- .../resources/lib/refresh.py | 86 ++++++++++--------- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 762eabf9..1bdb818a 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -623,7 +623,7 @@ def push_cache_queue(hash, widget_id=None): if os.path.exists(queue_path): pass # Leave original modification date so item is higher priority else: - touch(queue_path) + write_json(queue_path, {"hash": hash, "path": path}) def is_cache_queue(hash): @@ -964,14 +964,6 @@ def save_playback_history(media_type, playback_percentage): write_json(_playback_history_path, history) -def touch(fname, times=None): - fhandle = open(fname, "a") - try: - os.utime(fname, times) - finally: - fhandle.close() - - def call_builtin(action, delay=0): if delay: xbmc.sleep(delay) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index ba2373ae..50af95a9 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -390,7 +390,7 @@ def show_path( props=properties, ) # Ensure we precache next page for faster access - utils.cache_expiry(utils.path2hash(file['file']), widget_id, background=True) + utils.cache_expiry(file['file'], widget_id, background=True) else: filetype = file.get("type", "") title = { diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 1a158ce5..69dd86a4 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -378,56 +378,58 @@ def cache_and_update(hash, widget_ids, notify=True): could mean we refresh paths that other widgets also use. These will then be skipped. """ assert widget_ids - effected_widgets = set() + affected_widgets = set() changed = False + path = widget_ids.get("path", "") hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. - effected_widgets = effected_widgets.union(utils.widgets_for_path(path)) - if utils.is_cache_queue(hash): - # we need to update this path regardless - if notify is not None: - notify(_label, path) - new_files, files_changed = utils.cache_files(path, widget_id) - changed = changed or files_changed - utils.remove_cache_queue(hash) - # else: - # # double check this hasn't been updated already when updating another widget - # expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) - # if expiry <= time.time(): - # utils.cache_files(path, widget_id) - # else: - # pass # Skipping this path because its already been updated + affected_widgets = affected_widgets.union(utils.widgets_for_path(path)) + for widget_id in affected_widgets: + if utils.is_cache_queue(hash): + # we need to update this path regardless + # if notify is not None: + # notify(_label, path) + new_files, files_changed = utils.cache_files(path, widget_id) + changed = changed or files_changed + utils.remove_cache_queue(hash) + else: + # double check this hasn't been updated already when updating another widget + expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + if expiry <= time.time(): + utils.cache_files(path, widget_id) + else: + pass # Skipping this path because its already been updated # TODO: update every widget? - for widget_id in widget_ids: - widget_def = manage.get_widget_by_id(widget_id) - if not widget_def: - continue - widget_path = widget_def.get("path", "") - utils.log( - "trying to update {} with widget def {}".format(widget_id, widget_def), - "inspect", - ) - - if type(widget_path) != list: - widget_path = [widget_path] - for path_id in widget_path: - # simple compatibility with pre-3.3.0 widgets - if isinstance(path_id, dict): - path_id = path_id.get("id", "") - path = manage.get_path_by_id(path_id) - if not path: - continue - - _label = path["label"] - path = path["file"]["file"] - # TODO: only need to do that if a path has changed which we can tell from the history - if changed: - _update_strings(widget_def) - return effected_widgets + # for widget_id in widget_ids: + # widget_def = manage.get_widget_by_id(widget_id) + # if not widget_def: + # continue + # widget_path = widget_def.get("path", "") + # utils.log( + # "trying to update {} with widget def {}".format(widget_id, widget_def), + # "inspect", + # ) + + # if type(widget_path) != list: + # widget_path = [widget_path] + # for path_id in widget_path: + # # simple compatibility with pre-3.3.0 widgets + # if isinstance(path_id, dict): + # path_id = path_id.get("id", "") + # path = manage.get_path_by_id(path_id) + # if not path: + # continue + + # _label = path["label"] + # path = path["file"]["file"] + # # TODO: only need to do that if a path has changed which we can tell from the history + # if changed: + # _update_strings(widget_def) + return affected_widgets # Get info on whats playing and use it to update the right widgets when playback stopped From adf535d335fe35c64227d766afce5e5c35019585 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 18:53:47 -0700 Subject: [PATCH 085/101] :zap: - Remove unneeded hashing --- plugin.program.autowidget/resources/lib/common/utils.py | 2 -- plugin.program.autowidget/resources/lib/refresh.py | 9 +++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 1bdb818a..81f882c0 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -672,8 +672,6 @@ def get_info_keys(): def cache_files(path, widget_id): - hash = path2hash(path) - info_keys = get_info_keys() params = { "jsonrpc": "2.0", diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 69dd86a4..7bff2555 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -108,7 +108,9 @@ def __call__(self, groupname, path): hash, widget_ids = queue.pop(0) utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") - effected_widgets = cache_and_update(hash, widget_ids, notify=progress) + effected_widgets = cache_and_update( + hash, widget_ids, notify=progress + ) if effected_widgets: updated = True utils.remove_cache_queue( @@ -382,7 +384,6 @@ def cache_and_update(hash, widget_ids, notify=True): changed = False path = widget_ids.get("path", "") - hash = utils.path2hash(path) # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. affected_widgets = affected_widgets.union(utils.widgets_for_path(path)) @@ -396,11 +397,11 @@ def cache_and_update(hash, widget_ids, notify=True): utils.remove_cache_queue(hash) else: # double check this hasn't been updated already when updating another widget - expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) if expiry <= time.time(): utils.cache_files(path, widget_id) else: - pass # Skipping this path because its already been updated + pass # Skipping this path because its already been updated # TODO: update every widget? From 92d6091642e5e7e9e19140a736142e965fdc2a7c Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 18:56:07 -0700 Subject: [PATCH 086/101] :bulb: - Fix comment indents --- .../resources/lib/refresh.py | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 7bff2555..26821650 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -391,7 +391,7 @@ def cache_and_update(hash, widget_ids, notify=True): if utils.is_cache_queue(hash): # we need to update this path regardless # if notify is not None: - # notify(_label, path) + # notify(_label, path) new_files, files_changed = utils.cache_files(path, widget_id) changed = changed or files_changed utils.remove_cache_queue(hash) @@ -403,33 +403,32 @@ def cache_and_update(hash, widget_ids, notify=True): else: pass # Skipping this path because its already been updated - # TODO: update every widget? # for widget_id in widget_ids: - # widget_def = manage.get_widget_by_id(widget_id) - # if not widget_def: - # continue - # widget_path = widget_def.get("path", "") - # utils.log( - # "trying to update {} with widget def {}".format(widget_id, widget_def), - # "inspect", - # ) - - # if type(widget_path) != list: - # widget_path = [widget_path] - # for path_id in widget_path: - # # simple compatibility with pre-3.3.0 widgets - # if isinstance(path_id, dict): - # path_id = path_id.get("id", "") - # path = manage.get_path_by_id(path_id) - # if not path: - # continue - - # _label = path["label"] - # path = path["file"]["file"] - # # TODO: only need to do that if a path has changed which we can tell from the history - # if changed: - # _update_strings(widget_def) + # widget_def = manage.get_widget_by_id(widget_id) + # if not widget_def: + # continue + # widget_path = widget_def.get("path", "") + # utils.log( + # "trying to update {} with widget def {}".format(widget_id, widget_def), + # "inspect", + # ) + + # if type(widget_path) != list: + # widget_path = [widget_path] + # for path_id in widget_path: + # # simple compatibility with pre-3.3.0 widgets + # if isinstance(path_id, dict): + # path_id = path_id.get("id", "") + # path = manage.get_path_by_id(path_id) + # if not path: + # continue + + # _label = path["label"] + # path = path["file"]["file"] + # # TODO: only need to do that if a path has changed which we can tell from the history + # if changed: + # _update_strings(widget_def) return affected_widgets From 8ab6758765cbbb2ad15a13221c2298556a3eef91 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 19:48:58 -0700 Subject: [PATCH 087/101] :bug: - Store path in history files --- .../resources/lib/common/utils.py | 20 +++++++++++-------- .../resources/lib/refresh.py | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 81f882c0..6523b398 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -549,10 +549,12 @@ def iter_queue(): queued = filter(os.path.isfile, glob.glob(os.path.join(_addon_data, "*.queue"))) # TODO: sort by path instead so load plugins at the same time for path in sorted(queued, key=os.path.getmtime): - yield path + queue_data = read_json(path) + yield queue_data.get("path", "") -def read_history(hash, create_if_missing=True): +def read_history(path, create_if_missing=True): + hash = path2hash(path) history_path = os.path.join(_addon_data, "{}.history".format(hash)) if not os.path.exists(history_path): if create_if_missing: @@ -585,27 +587,28 @@ def next_cache_queue(): # TODO: use watchdog to use less resources for path in iter_queue(): # TODO: sort by path instead so load plugins at the same time - if not os.path.exists(path): + hash = path2hash(path) + queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) + if not os.path.exists(queue_path): # a widget update has already taken care of updating this path continue # We will let the update operation remove the item from the queue # TODO: need to workout if a blocking write is happen while it was queued or right now. # probably need a .lock file to ensure foreground calls can get priority. - hash = hash_from_cache_path(path) - cache_data = read_history(hash, create_if_missing=True) + cache_data = read_history(path, create_if_missing=True) yield hash, cache_data def push_cache_queue(path, widget_id=None): hash = path2hash(path) queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created + history = read_history(path, create_if_missing=True) # Ensure its created changed = False if widget_id is not None and widget_id not in history["widgets"]: history["widgets"].append(widget_id) changed = True - if history.get("path") != "path": + if history.get("path", "") != path: history["path"] = path changed = True if changed: @@ -694,7 +697,6 @@ def cache_expiry(path, widget_id, add=None, background=True): # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should # update when autowidget updates. hash = path2hash(path) - cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) # Read file every time as we might be called from multiple processes @@ -733,6 +735,8 @@ def cache_expiry(path, widget_id, add=None, background=True): content_hash = path2hash(cache_json) changed = history[-1][1] != content_hash if history else True history.append((time.time(), content_hash)) + if cache_data.get("path") != path: + cache_data["path"] = path write_json(history_path, cache_data) # expiry = history[-1][0] + DEFAULT_CACHE_TIME pred_dur = predict_update_frequency(history) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 26821650..02d3d640 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -309,7 +309,7 @@ def refresh_paths(notify=False, force=False): def get_files_list(path, widget_id=None, background=True): hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(hash, widget_id, background=background) + _, files, _ = utils.cache_expiry(path, widget_id, background=background) if files is None: # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") From 1220e367d61f0696d7b8a3cd8b0b1b28c6464dd7 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 20:03:49 -0700 Subject: [PATCH 088/101] :heavy_plus_sign: - Move cache methods into cache.py --- .../clear_cache_single.py | 3 +- .../resources/lib/common/router.py | 5 +- .../resources/lib/common/utils.py | 445 ------------------ .../resources/lib/refresh.py | 35 +- 4 files changed, 23 insertions(+), 465 deletions(-) diff --git a/plugin.program.autowidget/clear_cache_single.py b/plugin.program.autowidget/clear_cache_single.py index 3a4c6d91..a1a6f300 100644 --- a/plugin.program.autowidget/clear_cache_single.py +++ b/plugin.program.autowidget/clear_cache_single.py @@ -1,7 +1,8 @@ +from resources.lib.common import cache from resources.lib.common import utils if __name__ == "__main__": target = utils.get_infolabel("ListItem.Property(autoCache)") if target: - utils.clear_cache(target) + cache.clear_cache(target) diff --git a/plugin.program.autowidget/resources/lib/common/router.py b/plugin.program.autowidget/resources/lib/common/router.py index ff1b45eb..4410ec42 100644 --- a/plugin.program.autowidget/resources/lib/common/router.py +++ b/plugin.program.autowidget/resources/lib/common/router.py @@ -11,6 +11,7 @@ from resources.lib import menu from resources.lib import manage from resources.lib import refresh +from resources.lib.common import cache from resources.lib.common import directory from resources.lib.common import utils @@ -105,9 +106,9 @@ def dispatch(_plugin, _handle, _params): utils.update_container(True) elif mode == "clear_cache": if not target: - utils.clear_cache() + cache.clear_cache() else: - utils.clear_cache(target) + cache.clear_cache(target) elif mode == "set_color": utils.set_color(setting=True) elif mode == "backup" and action: diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 6523b398..1847b110 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -10,9 +10,6 @@ import string import time import unicodedata -import hashlib -import glob -import math import datetime import six @@ -30,15 +27,12 @@ except AttributeError: translate_path = xbmc.translatePath -DEFAULT_CACHE_TIME = 60 * 5 - _addon_id = settings.get_addon_info("id") _addon_data = translate_path(settings.get_addon_info("profile")) _addon_root = translate_path(settings.get_addon_info("path")) _art_path = os.path.join(_addon_root, "resources", "media") _home = translate_path("special://home/") -_playback_history_path = os.path.join(_addon_data, "cache.history") windows = { "programs": ["program", "script"], @@ -202,10 +196,6 @@ "maroon", ] # brown -_startup_time = time.time() # TODO: could get reloaded so not accurate? - -_startup_time = time.time() # TODO: could get reloaded so not accurate? - def make_holding_path(label, art): return { @@ -280,22 +270,6 @@ def wipe(folder=_addon_data): os.rmdir(dir) -def clear_cache(target=None): - if target is None: - dialog = xbmcgui.Dialog() - choice = dialog.yesno("AutoWidget", get_string(30118)) - del dialog - - if choice: - for file in [i for i in os.listdir(_addon_data) if i.endswith(".cache")]: - os.remove(os.path.join(_addon_data, file)) - else: - target_path = os.path.join(_addon_data, "{}.cache".format(target)) - if os.path.exists(target_path): - os.remove(target_path) - update_container(True) - - def get_art(filename, color=None): art = {} if not color: @@ -504,17 +478,6 @@ def get_property(property, window=10000): return xbmcgui.Window(window).getProperty(property) -def push_queue(property, value): - set_property(property, ",".join(get_property(property).split(",") + [value])) - - -def pop_queue(property): - queue = get_property(property).split(",") - value = queue.pop() - set_property(property, ",".join(queue)) - return value - - def clear_property(property, window=10000): xbmcgui.Window(window).clearProperty(property) @@ -540,122 +503,6 @@ def clean_artwork_url(url): return url -def hash_from_cache_path(path): - base = os.path.basename(path) - return os.path.splitext(base)[0] - - -def iter_queue(): - queued = filter(os.path.isfile, glob.glob(os.path.join(_addon_data, "*.queue"))) - # TODO: sort by path instead so load plugins at the same time - for path in sorted(queued, key=os.path.getmtime): - queue_data = read_json(path) - yield queue_data.get("path", "") - - -def read_history(path, create_if_missing=True): - hash = path2hash(path) - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - if not os.path.exists(history_path): - if create_if_missing: - cache_data = {} - history = cache_data.setdefault("history", []) - widgets = cache_data.setdefault("widgets", []) - write_json(history_path, cache_data) - else: - cache_data = None - else: - cache_data = read_json(history_path) - return cache_data - -def read_history(hash, create_if_missing=True): - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - if not os.path.exists(history_path): - if create_if_missing: - cache_data = {} - history = cache_data.setdefault("history", []) - widgets = cache_data.setdefault("widgets", []) - write_json(history_path, cache_data) - else: - cache_data = None - else: - cache_data = read_json(history_path) - return cache_data - -def next_cache_queue(): - # Simple queue by creating a .queue file - # TODO: use watchdog to use less resources - for path in iter_queue(): - # TODO: sort by path instead so load plugins at the same time - hash = path2hash(path) - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - if not os.path.exists(queue_path): - # a widget update has already taken care of updating this path - continue - # We will let the update operation remove the item from the queue - - # TODO: need to workout if a blocking write is happen while it was queued or right now. - # probably need a .lock file to ensure foreground calls can get priority. - cache_data = read_history(path, create_if_missing=True) - yield hash, cache_data - - -def push_cache_queue(path, widget_id=None): - hash = path2hash(path) - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - history = read_history(path, create_if_missing=True) # Ensure its created - changed = False - if widget_id is not None and widget_id not in history["widgets"]: - history["widgets"].append(widget_id) - changed = True - if history.get("path", "") != path: - history["path"] = path - changed = True - if changed: - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - write_json(history_path, history) - -def push_cache_queue(hash, widget_id=None): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - history = read_history(hash, create_if_missing=True) # Ensure its created - if widget_id is not None and widget_id not in history["widgets"]: - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - history["widgets"].append(widget_id) - write_json(history_path, history) - - if os.path.exists(queue_path): - pass # Leave original modification date so item is higher priority - else: - write_json(queue_path, {"hash": hash, "path": path}) - - -def is_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - return os.path.exists(queue_path) - - -def remove_cache_queue(hash): - queue_path = os.path.join(_addon_data, "{}.queue".format(hash)) - remove_file(queue_path) - - -def path2hash(path): - if path is not None: - return hashlib.sha1(six.ensure_binary(path, "utf8")).hexdigest() - else: - return None - - -def widgets_for_path(path): - hash = path2hash(path) - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(history_path) if os.path.exists(history_path) else None - if cache_data is None: - cache_data = {} - widgets = cache_data.setdefault("widgets", []) - return set(widgets) - - def get_info_keys(): params = { "jsonrpc": "2.0", @@ -674,298 +521,6 @@ def get_info_keys(): return info_keys["result"]["types"]["List.Fields.Files"]["items"]["enums"] -def cache_files(path, widget_id): - info_keys = get_info_keys() - params = { - "jsonrpc": "2.0", - "method": "Files.GetDirectory", - "params": { - "properties": info_keys, - "directory": path, - }, - "id": 1, - } - files = call_jsonrpc(params) - _, _, changed = cache_expiry(path, widget_id, add=files) - return (files, changed) - - -def cache_expiry(path, widget_id, add=None, background=True): - # Predict how long to cache for with a min of 5min so updates don't go in a loop - # TODO: find better way to prevents loops so that users trying to manually refresh can do so - # TODO: manage the cache files to remove any too old or no longer used - # TODO: update paths on autowidget refresh based on predicted update frequency. e.g. plugins with random paths should - # update when autowidget updates. - hash = path2hash(path) - cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) - - # Read file every time as we might be called from multiple processes - history_path = os.path.join(_addon_data, "{}.history".format(hash)) - cache_data = read_json(history_path) if os.path.exists(history_path) else None - if cache_data is None: - cache_data = {} - since_read = 0 - else: - since_read = time.time() - last_read(hash) - - history = cache_data.setdefault("history", []) - widgets = cache_data.setdefault("widgets", []) - if widget_id not in widgets: - widgets.append(widget_id) - - expiry = time.time() - 20 - contents = None - changed = True - size = 0 - - if add is not None: - cache_json = json.dumps(add) - if not add or not cache_json.strip(): - result = "Invalid Write" - - elif "error" in add or not add.get("result", {}).get("files"): - # In this case we don't want to cache a bad result - result = "Error" - # TODO: do we schedule a new update? or put dummy content up even if we have - # good cached content? - else: - write_json(cache_path, add) - contents = add - size = len(cache_json) - content_hash = path2hash(cache_json) - changed = history[-1][1] != content_hash if history else True - history.append((time.time(), content_hash)) - if cache_data.get("path") != path: - cache_data["path"] = path - write_json(history_path, cache_data) - # expiry = history[-1][0] + DEFAULT_CACHE_TIME - pred_dur = predict_update_frequency(history) - expiry = ( - history[-1][0] + pred_dur * 0.75 - ) # less than prediction to ensure pred keeps up to date - result = "Wrote" - else: - # write any updated widget_ids so we know what to update when we dequeue - # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) - if not os.path.exists(cache_path): - result = "Empty" - if background: - contents = make_holding_path(u"Loading Content...", "refresh") - push_cache_queue(path) - else: - contents = read_json(cache_path, log_file=True) - if contents is None: - result = "Invalid Read" - if background: - contents = make_holding_path("Error", "error") - push_cache_queue(path) - else: - # write any updated widget_ids so we know what to update when we dequeue - # Also important as wwe use last modified of .history as accessed time - write_json(history_path, cache_data) - size = len(json.dumps(contents)) - if history: - expiry = history[-1][0] + predict_update_frequency(history) - - # queue_len = len(list(iter_queue())) - if expiry > time.time(): - result = "Read" - elif not background: - result = "Skip already updated" - # elif queue_len > 3: - # # Try to give system more breathing space by returning empty cache but ensuring refresh - # # better way is to just do this the first X accessed after startup. - # # or how many accessed in the last 30s? - # push_cache_queue(hash) - # result = "Skip (queue={})".format(queue_len) - # contents = dict(result=dict(files=[])) - else: - push_cache_queue(path) - result = "Read and queue" - # TODO: some metric that tells us how long to the first and last widgets becomes visible and then get updated - # not how to measure the time delay when when the cache is read until it appears on screen? - # Is the first cache read always the top visibible widget? - log( - "{} cache {}B (exp:{}, last:{}): {} {}".format( - result, size, ft(expiry - time.time()), ft(since_read), hash[:5], widgets - ), - "notice", - ) - return expiry, contents, changed - - -def last_read(hash): - # Technically this is last read or updated but we can change it to be last read Later - # if we create another file - path = os.path.join(_addon_data, "{}.history".format(hash)) - return os.path.getmtime(path) - - -def predict_update_frequency(history): - if not history: - return DEFAULT_CACHE_TIME - update_count = 0 - duration = 0 - changes = [] - last_when, last = history[0] - for when, content in history[1:]: - update_count += 1 - if content == last: - duration += when - last_when - else: - duration = ( - +(when - last_when) / 2 - ) # change could have happened any time inbetween - changes.append((duration, update_count)) - duration = 0 - update_count = 0 - last_when = when - last = content - if not changes and duration: - # drop the last part of the history that hasn't changed yet unless we have no other history to work with - # This is an underestimate as we aren't sure when in the future it will change - changes.append((duration, update_count)) - # TODO: the first change is potentially an underestimate too because we don't know how long it was unchanged for - # before we started recording. - - # Now we have changes, we can do some trends on them. - durations = [duration for duration, update_count in changes if update_count > 1] - if not durations: - return DEFAULT_CACHE_TIME - med_dur = sorted(durations)[int(math.floor(len(durations) / 2)) - 1] - avg_dur = sum(durations) / len(durations) - # weighted by how many snapshots we took inbetween. - # TODO: number of snapshots inbetween is really just increasing the confidence on the end time bot the duration as a whole. - # so perhaps a better metric is the error margin of the duration? and not weighting by that completely. - # ie durations with wide margin of error should be less important. e.g. times kodi was never turned on for months/weeks. - weighted = sum([d * c for d, c in changes]) / sum([c for _, c in changes]) - # TODO: also try exponential decay. Older durations are less important than newer ones. - ones = len([c for d, c in changes if c == 1]) / float(len(changes)) - # TODO: if many streaks with lots of counts then its stable and can predict - log( - "avg_dur {:0.0f}s, med_dur {:0.0f}s, weighted {:0.0f}s, ones {:0.2f}, all {}".format( - avg_dur, med_dur, weighted, ones, changes - ), - "debug", - ) - if ones > 0.9: - # too unstable so no point guessing - return DEFAULT_CACHE_TIME - elif DEFAULT_CACHE_TIME > avg_dur / 2.0: - # should not got less than 5min otherwise our updates go in a loop - return DEFAULT_CACHE_TIME - else: - return ( - avg_dur / 2.0 - ) # we want to ensure we check more often than the actual predicted expiry - - -# return DEFAULT_CACHE_TIME - - -def widgets_changed_by_watching(media_type): - # Predict which widgets the skin might have that could have changed based on recently finish - # watching something - - all_cache = filter( - os.path.isfile, glob.glob(os.path.join(_addon_data, "*.history")) - ) - - # Simple version. Anything updated recently (since startup?) - # priority = sorted(all_cache, key=os.path.getmtime) - # Sort by chance of it updating - plays = read_json(_playback_history_path, default={}).setdefault("plays", []) - plays_for_type = [(time, t) for time, t in plays if t == media_type] - priority = sorted( - [ - (chance_playback_updates_widget(path, plays_for_type), path) - for path in all_cache - ], - reverse=True, - ) - - for chance, path in priority: - hash = hash_from_cache_path(path) - last_update = os.path.getmtime(path) - _startup_time - if last_update < 0: - log( - "widget not updated since startup {} {}".format(last_update, hash[:5]), - "notice", - ) - # elif chance < 0.3: - # log("chance widget changed after play {}% {}".format(chance, hash[:5]), 'notice') - else: - log( - "chance widget changed after play {}% {}".format(chance, hash[:5]), - "notice", - ) - yield hash, path - - -def chance_playback_updates_widget(history_path, plays, cutoff_time=60 * 5): - cache_data = read_json(history_path) - history = cache_data.setdefault("history", []) - # Complex version - # - for each widget - # - come up with chance it will update after a playback - # - each pair of updates, is there a playback inbetween and updated with X min after playback - # - num playback with change / num playback with no change - changes, non_changes, unrelated_changes = 0, 0, 0 - update = "" - time_since_play = 0 - for play_time, media_type in plays: - while True: - last_update = update - if not history: - break - update_time, update = history.pop(0) - time_since_play = update_time - play_time - # log("{} {} {} {}".format(update[:5],last_update[:5], unrelated_changes, time_since_play), 'notice') - if time_since_play > 0: - break - elif update != last_update: - unrelated_changes += 1 - - if update == last_update: - non_changes += 1 - elif ( - time_since_play > cutoff_time - ): # update too long after playback to be releated - pass - else: - changes += 1 - # TODO: what if the previous update was a long time before playback? - - # There is probably a more statistically correct way of doing this but the idea is that - # with few datapoints we should tend towards 0.5 probability but as we get more datapoints - # then error goes down and rely on actual changes vs nonchanges - # We will do a simple weighted average with 0.5 to simulate this - # TODO: currently random widgets score higher than recently played widgets. need to score them lower - # as they are less relevent - log( - "changes={}, non_changes={}, unrelated_changes={}".format( - changes, non_changes, unrelated_changes - ), - "debug", - ) - datapoints = float(changes + non_changes) - prob = changes / float(changes + non_changes + unrelated_changes) - unknown_weight = 4 - prob = (prob * datapoints + 0.5 * unknown_weight) / (datapoints + unknown_weight) - return prob - - -def save_playback_history(media_type, playback_percentage): - # Record in json when things got played to help predict which widgets will change after playback - # if playback_percentage < 0.7: - # return - history = read_json(_playback_history_path, default={}) - plays = history.setdefault("plays", []) - plays.append((time.time(), media_type)) - write_json(_playback_history_path, history) - - def call_builtin(action, delay=0): if delay: xbmc.sleep(delay) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 02d3d640..d8c40efc 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -7,6 +7,7 @@ import threading from resources.lib import manage +from resources.lib.common import cache from resources.lib.common import settings from resources.lib.common import utils @@ -82,7 +83,7 @@ def _update_widgets(self): # TODO: somehow delay till all other plugins loaded? updated = False unrefreshed_widgets = set() - queue = list(utils.next_cache_queue()) + queue = list(cache.next_cache_queue()) class Progress(object): dialog = None @@ -113,7 +114,7 @@ def __call__(self, groupname, path): ) if effected_widgets: updated = True - utils.remove_cache_queue( + cache.remove_cache_queue( hash ) # Just in queued path's widget defintion has changed and it didn't update this path unrefreshed_widgets = unrefreshed_widgets.union( @@ -122,12 +123,12 @@ def __call__(self, groupname, path): # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update - # while _ in self.tick(1, 10, lambda: utils.last_read(hash) > before_update): + # while _ in self.tick(1, 10, lambda: cache.last_read(hash) > before_update): # pass - # utils.log("paused queue until read {:.2} for {}".format(utils.last_read(hash)-before_update, hash[:5]), 'info') + # utils.log("paused queue until read {:.2} for {}".format(cache.last_read(hash)-before_update, hash[:5]), 'info') if self.abortRequested(): break - queue = list(utils.next_cache_queue()) + queue = list(cache.next_cache_queue()) for widget_id in unrefreshed_widgets: widget_def = manage.get_widget_by_id(widget_id) if not widget_def: @@ -308,12 +309,12 @@ def refresh_paths(notify=False, force=False): def get_files_list(path, widget_id=None, background=True): - hash = utils.path2hash(path) - _, files, _ = utils.cache_expiry(path, widget_id, background=background) + hash = cache.path2hash(path) + _, files, _ = cache.cache_expiry(path, widget_id, background=background) if files is None: # Should only happen now when background is False utils.log("Blocking cache path read: {}".format(hash[:5]), "info") - files, changed = utils.cache_files(path, widget_id) + files, changed = cache.cache_files(path, widget_id) new_files = [] if "result" in files: @@ -386,20 +387,20 @@ def cache_and_update(hash, widget_ids, notify=True): path = widget_ids.get("path", "") # TODO: we might be updating paths used by widgets that weren't initiall queued. # We need to return those and ensure they get refreshed also. - affected_widgets = affected_widgets.union(utils.widgets_for_path(path)) + affected_widgets = affected_widgets.union(cache.widgets_for_path(path)) for widget_id in affected_widgets: - if utils.is_cache_queue(hash): + if cache.is_cache_queue(hash): # we need to update this path regardless # if notify is not None: # notify(_label, path) - new_files, files_changed = utils.cache_files(path, widget_id) + new_files, files_changed = cache.cache_files(path, widget_id) changed = changed or files_changed - utils.remove_cache_queue(hash) + cache.remove_cache_queue(hash) else: # double check this hasn't been updated already when updating another widget - expiry, _ = utils.cache_expiry(hash, widget_id, no_queue=True) + expiry, _ = cache.cache_expiry(hash, widget_id, no_queue=True) if expiry <= time.time(): - utils.cache_files(path, widget_id) + cache.cache_files(path, widget_id) else: pass # Skipping this path because its already been updated @@ -522,14 +523,14 @@ def onPlayBackEnded(self): self.totalTime = -1.0 self.playingTime = 0.0 self.info = {} - utils.save_playback_history(self.type, pp) + cache.save_playback_history(self.type, pp) utils.log("recorded playback of {}% {}".format(pp, self.type), "notice") # wait for a bit so scrobing can happen time.sleep(5) - for hash, path in utils.widgets_changed_by_watching(self.type): + for hash, path in cache.widgets_changed_by_watching(self.type): # Queue them for refresh - utils.push_cache_queue(path) + cache.push_cache_queue(path) utils.log("Queued cache update: {}".format(hash[:5]), "notice") def onPlayBackStopped(self): From 02844c1e897cf42949f98c907005c9327bde3337 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 20:04:10 -0700 Subject: [PATCH 089/101] :recycle: - Remove unneeded params on routing methods --- plugin.program.autowidget/main.py | 3 +-- plugin.program.autowidget/resources/lib/common/router.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/main.py b/plugin.program.autowidget/main.py index d2e09706..c0c7c295 100644 --- a/plugin.program.autowidget/main.py +++ b/plugin.program.autowidget/main.py @@ -5,9 +5,8 @@ if __name__ == "__main__": - _plugin = sys.argv[0] _handle = int(sys.argv[1]) _params = sys.argv[2][1:] with utils.timing(_params): - router.dispatch(_plugin, _handle, _params) + router.dispatch(_handle, _params) diff --git a/plugin.program.autowidget/resources/lib/common/router.py b/plugin.program.autowidget/resources/lib/common/router.py index 4410ec42..8cf9dd14 100644 --- a/plugin.program.autowidget/resources/lib/common/router.py +++ b/plugin.program.autowidget/resources/lib/common/router.py @@ -16,7 +16,7 @@ from resources.lib.common import utils -def _log_params(_plugin, _handle, _params): +def _log_params(_params): msg = "[{}]" params = dict(parse_qsl(_params)) @@ -29,8 +29,8 @@ def _log_params(_plugin, _handle, _params): return params -def dispatch(_plugin, _handle, _params): - params = _log_params(_plugin, int(_handle), _params) +def dispatch(_handle, _params): + params = _log_params(_params) category = "AutoWidget" is_dir = False is_type = "files" From f8e818b084fc1c627f96cac2a5be1e3b1c162581 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 21:08:28 -0700 Subject: [PATCH 090/101] :bug: - Fix cache import --- plugin.program.autowidget/resources/lib/menu.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 50af95a9..986114a3 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -7,6 +7,7 @@ from resources.lib import manage from resources.lib import refresh +from resources.lib.common import cache from resources.lib.common import directory from resources.lib.common import settings from resources.lib.common import utils @@ -390,7 +391,7 @@ def show_path( props=properties, ) # Ensure we precache next page for faster access - utils.cache_expiry(file['file'], widget_id, background=True) + cache.cache_expiry(file['file'], widget_id, background=True) else: filetype = file.get("type", "") title = { From 2837c86aaf7450ab9a230e7f0a054c063927b815 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Thu, 22 Jul 2021 21:09:04 -0700 Subject: [PATCH 091/101] :bug: - Cover case when cache file is already removed --- plugin.program.autowidget/resources/lib/refresh.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index d8c40efc..bf083500 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -338,7 +338,9 @@ def get_files_list(path, widget_id=None, background=True): return new_files, hash elif "error" in files: - os.remove(os.path.join(_addon_data, "{}.cache".format(hash))) + cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) + if os.path.exists(cache_path): + os.remove(cache_path) utils.log("Invalid cache file removed for {}".format(hash)) return None, hash else: From 659e5bf336480aaaae0b83814b38547aa7978127 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Fri, 23 Jul 2021 07:18:07 -0700 Subject: [PATCH 092/101] :recycle: - Cleanup placeholder items --- .../resources/lib/menu.py | 32 ++++++------ .../resources/lib/refresh.py | 49 ++++++++++--------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index 986114a3..c1dc5af8 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -309,20 +309,20 @@ def show_path( stack = widget_def.get("stack", []) path = widget_path["file"]["file"] if not stack else stack[-1] - - files, hash = refresh.get_files_list(path, widget_id, background=False) - if not files: - properties = { - "autoLabel": path_label, - "autoID": widget_id, - "autoAction": action, - "autoCache": hash, - } - if files is None: - show_error(path_label, properties) - elif files == []: - show_empty(path_label, properties) - return titles if titles else True, path_label, content + + files, hash = refresh.get_files_list(path, path_label, widget_id) + # if not files: + # properties = { + # "autoLabel": path_label, + # "autoID": widget_id, + # "autoAction": action, + # "autoCache": hash, + # } + # if files is None: + # show_error(path_label, properties) + # elif files == []: + # show_empty(path_label, properties) + # return titles if titles else True, path_label, content utils.log("Loading items from {}".format(path), "debug") @@ -384,14 +384,14 @@ def show_path( directory.add_menu_item( title=label, params=update_params if paged_widgets and not merged else None, - path=file['file'] if not paged_widgets or merged else None, + path=file["file"] if not paged_widgets or merged else None, art=utils.get_art("next_page", color), info=file, isFolder=not paged_widgets or merged, props=properties, ) # Ensure we precache next page for faster access - cache.cache_expiry(file['file'], widget_id, background=True) + cache.cache_expiry(file["file"], widget_id, background=True) else: filetype = file.get("type", "") title = { diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 9f87001d..ff3611a1 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -308,7 +308,7 @@ def refresh_paths(notify=False, force=False): return True, "AutoWidget" -def get_files_list(path, widget_id=None, background=True): +def get_files_list(path, label=None, widget_id=None, background=True): hash = cache.path2hash(path) _, files, _ = cache.cache_expiry(path, widget_id, background=background) if files is None: @@ -319,33 +319,36 @@ def get_files_list(path, widget_id=None, background=True): new_files = [] if "result" in files: files = files.get("result", {}).get("files", []) - if not files: - utils.log("No items found for {}".format(path)) - return [], hash - - for file in files: - new_file = {k: v for k, v in file.items() if v is not None} - - if "art" in new_file: - for art in new_file["art"]: - new_file["art"][art] = utils.clean_artwork_url(file["art"][art]) - if "cast" in new_file: - for idx, cast in enumerate(new_file["cast"]): - new_file["cast"][idx]["thumbnail"] = utils.clean_artwork_url( - cast.get("thumbnail", "") - ) - new_files.append(new_file) - - return new_files, hash elif "error" in files: + utils.log("Error processing {}".format(hash), "error") + error_tile = utils.make_holding_path("Error loading {}".format(label), "alert") + files = error_tile.get("result", {}).get("files", []) cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) if os.path.exists(cache_path): os.remove(cache_path) utils.log("Invalid cache file removed for {}".format(hash)) - return None, hash - else: - utils.log("Error processing {}".format(hash), "error") - return None, hash + + if not files: + utils.log("No items found for {}".format(hash)) + empty_tile = utils.make_holding_path( + "No items found for {}".format(label), "information-outline" + ) + files = empty_tile.get("result", {}).get("files", []) + + for file in files: + new_file = {k: v for k, v in file.items() if v is not None} + + if "art" in new_file: + for art in new_file["art"]: + new_file["art"][art] = utils.clean_artwork_url(file["art"][art]) + if "cast" in new_file: + for idx, cast in enumerate(new_file["cast"]): + new_file["cast"][idx]["thumbnail"] = utils.clean_artwork_url( + cast.get("thumbnail", "") + ) + new_files.append(new_file) + + return new_files, hash def is_duplicate(title, titles): From 8768d38447f303967204811e6b1615e14c43a378 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Fri, 23 Jul 2021 07:18:24 -0700 Subject: [PATCH 093/101] :bug: - Fix notifications --- plugin.program.autowidget/resources/lib/add.py | 16 ++++++++-------- .../resources/lib/refresh.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index 6efca83b..30afc2d2 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -270,7 +270,7 @@ def _copy_path(path_def): progress = xbmcgui.DialogProgressBG() progress.create(u"Copying") progress.update(1, u"Retrieving") - + group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) if not files: @@ -279,16 +279,16 @@ def _copy_path(path_def): done = 0 for file in files: done += 1 - if file['type'] in ['movie', 'episode', 'musicvideo', 'song']: + if file["type"] in ["movie", "episode", "musicvideo", "song"]: continue - progress.update(int(done/float(len(files))*100), file.get('label')) - - labels = build_labels('json', file, path_def['target']) + progress.update(int(done / float(len(files)) * 100), file.get("label")) + + labels = build_labels("json", file, path_def["target"]) _add_path(group_def, labels, over=True) progress.close() del progress dialog = xbmcgui.Dialog() - dialog.notification('AutoWidget', utils.get_string(32131) - .format(len(files), group_def['label'])) + dialog.notification( + "AutoWidget", utils.get_string(30105).format(len(files), group_def["label"]) + ) del dialog - diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index ff3611a1..3d1a6d81 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -142,9 +142,15 @@ def __call__(self, groupname, path): if progress.dialog is not None: progress.dialog.update(100, "") progress.dialog.close() - if updated and self.refresh_enabled == 1 and not self.player.isPlayingVideo(): + if ( + updated + and self.refresh_enabled == 1 + and not self.player.isPlayingVideo() + ): dialog = xbmcgui.Dialog() - dialog.notification(u'AutoWidget', u"Finished Updating Widgets", sound=False) + dialog.notification( + u"AutoWidget", u"Finished Updating Widgets", sound=False + ) if self.abortRequested(): break From 378cc12427774a194a5aa20c260493490de0decd Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Fri, 23 Jul 2021 07:29:18 -0700 Subject: [PATCH 094/101] :zap: - Don't force reload all widgets from placeholder --- plugin.program.autowidget/resources/lib/common/utils.py | 6 ++++-- plugin.program.autowidget/resources/lib/refresh.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index 1847b110..d6ebdbe7 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -197,7 +197,7 @@ ] # brown -def make_holding_path(label, art): +def make_holding_path(label, art, hash=None): return { "jsonrpc": "2.0", "id": 1, @@ -206,7 +206,9 @@ def make_holding_path(label, art): { "title": label, "label": label, - "file": "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", + "file": "plugin://plugin.program.autowidget/?mode=clear_cache&target={}&refresh=&reload=".format( + hash + ), "art": get_art(art), "filetype": "file", } diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index 3d1a6d81..ebb76e6a 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -327,7 +327,9 @@ def get_files_list(path, label=None, widget_id=None, background=True): files = files.get("result", {}).get("files", []) elif "error" in files: utils.log("Error processing {}".format(hash), "error") - error_tile = utils.make_holding_path("Error loading {}".format(label), "alert") + error_tile = utils.make_holding_path( + "Error loading {}".format(label), "alert", hash=hash + ) files = error_tile.get("result", {}).get("files", []) cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) if os.path.exists(cache_path): @@ -337,7 +339,7 @@ def get_files_list(path, label=None, widget_id=None, background=True): if not files: utils.log("No items found for {}".format(hash)) empty_tile = utils.make_holding_path( - "No items found for {}".format(label), "information-outline" + "No items found for {}".format(label), "information-outline", hash=hash ) files = empty_tile.get("result", {}).get("files", []) From e18f9b093d85554f45f1294ee8394ef4b906e6ca Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Fri, 23 Jul 2021 07:52:28 -0700 Subject: [PATCH 095/101] :globe_with_meridians::art: - Fix notifications and update localizations --- .../resource.language.ar_sa/strings.po | 136 +++++++++++------- .../resource.language.da_dk/strings.po | 136 +++++++++++------- .../resource.language.de_de/strings.po | 136 +++++++++++------- .../resource.language.el_gr/strings.po | 136 +++++++++++------- .../resource.language.en_gb/strings.po | 136 +++++++++++------- .../resource.language.en_us/strings.po | 136 +++++++++++------- .../resource.language.es_es/strings.po | 136 +++++++++++------- .../resource.language.es_mx/strings.po | 136 +++++++++++------- .../resource.language.fr_fr/strings.po | 136 +++++++++++------- .../resource.language.it_it/strings.po | 136 +++++++++++------- .../resource.language.nl_nl/strings.po | 136 +++++++++++------- .../resource.language.pt_br/strings.po | 136 +++++++++++------- .../resources/lib/add.py | 10 +- .../resources/lib/common/cache.py | 14 +- .../resources/lib/common/utils.py | 4 +- .../resources/lib/refresh.py | 14 +- 16 files changed, 999 insertions(+), 675 deletions(-) diff --git a/plugin.program.autowidget/resources/language/resource.language.ar_sa/strings.po b/plugin.program.autowidget/resources/language/resource.language.ar_sa/strings.po index a5bd0d44..3e78c1d0 100644 --- a/plugin.program.autowidget/resources/language/resource.language.ar_sa/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.ar_sa/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "الوقت الافتراضي بين تحديث عنصر واجهة المستخدم (بالساعات)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "فرض أدوات التحديث" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "گروه های من" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "أدوات" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "الأفعال" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "إنشاء مجموعة أدوات جديدة" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "إنشاء مجموعة اختصار جديدة" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "فرض كافة الحاجيات المعرفة لتحديث." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "حذف" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "مسار التحول لأعلى" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "مسار التحول لأسفل" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (ركوب الدراجات)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "اختصارات من {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "لم يتم تعريف أي مسارات لهذه المجموعة." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "يتم الآن تحديث الحاجيات..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removed." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "حرّر الاختصار" @@ -242,17 +242,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[لون لون القانون] [ب] تعطيل [/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "تعديل المجموعة" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "مسح البيانات الإضافية" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -265,53 +265,53 @@ msgstr "" "المستحسن أن يتم ذلك فقط إذا كنت تنوي إعادة توجيه كل هذه الحاجيات. لا يمكن " "التراجع عن هذا [COLOR firebrick][/COLOR]." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "نقطة القطعة هنا لإظهار العناصر." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "لم يتم تعريف أي مجموعات." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "تحديث القطعة" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "تعديل عدد سابق" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "منفرد" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "لم تتم تهيئة أي أدوات." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "القطعة المكسورة" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "الحاجيات النشطة" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "يتم تقسيم هذه القطعة، إعادة توجيه أو إزالته لوقف رؤية هذا." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Static)" @@ -327,18 +327,18 @@ msgid "Add New Artwork" msgstr "إضافة عمل فني جديد" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "مسار عشوائي" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "المسار التالي" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Choose an Action" @@ -375,7 +375,7 @@ msgctxt "#30065" msgid "Never" msgstr "أبدًا" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (مُدمَج)" @@ -474,27 +474,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "ليس على الحاجيات المدمجة" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "العودة إلى الصفحة {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "مسار التحول إلى الأعلى" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "مسار التحول إلى الأسفل" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "أختر Path" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "اختيار المسارات إلى دمج" @@ -561,17 +561,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "تحديث المدة" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "تبديل تصحيح الجلد" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} المسارات المدمجة" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "تنظيف ملفات القطعة القديمة" @@ -581,7 +581,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "لون الأيقونة" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} مسارات تمت إضافتها إلى {}" @@ -626,7 +626,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "تسجيل التصحيح" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "تحرير المسار" @@ -641,12 +641,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "تفضل الحلقة إذا كان العرض متوفرا" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "مسح ذاكرة التخزين المؤقت" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -660,7 +660,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "حدد نوع المحتوى" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "نسخ المجموعة" @@ -675,7 +675,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "اختر المسارات" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "اختر مسارات للدورة" @@ -725,42 +725,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "أيقونات البلد" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "التالي" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "الصفحة التالية" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "السابق" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "الصفحة السابقة" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "العودة" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "خطأ في الصفحة! تحديث الصفحة وحاول مرة أخرى." -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "خطأ في إظهار {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "لم يتم العثور على محتوى لـ {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "تحديث الحاجيات" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "تم الانتهاء من تحديث الأدوات" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "جاري نسخ المسارات ..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "جاري استرداد المسارات ..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "تحميل المحتوى ..." diff --git a/plugin.program.autowidget/resources/language/resource.language.da_dk/strings.po b/plugin.program.autowidget/resources/language/resource.language.da_dk/strings.po index 47bf4899..6e62c514 100644 --- a/plugin.program.autowidget/resources/language/resource.language.da_dk/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.da_dk/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Standardtid mellem opdatering af widget (i timer)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Gennemtving opdaterings widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Mine grupper" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Værktøjer" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Handlinger" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Opret ny widgetgruppe" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Opret ny genvejsgruppe" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Tving alle definerede widgets til at opdatere." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Fjern" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Skift sti op" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Skift kurve nedad" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Cykling)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Genveje fra {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Der er ikke defineret nogen stier for denne gruppe." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Opdaterer widgets..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removed." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Redigér genvej" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR plæneklipp] [B] Deaktiver[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Redigér gruppe" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Slet tilføjelsesdata" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,55 +267,55 @@ msgstr "" "du har til hensigt at repointing alle disse widgets. Denne [COLOR firebrick]" " kan ikke[/COLOR] fortrydes." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Peg en widget her for at vise elementer." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Der er ikke defineret nogen grupper." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Opdater widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Rediger widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Forældreløse" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Ingen widgets er blevet initialiseret." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Ødelagt widget" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Aktive widgets" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Denne widget er brudt, repoint eller fjerne det for at stoppe med at se " "dette." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Statisk)" @@ -331,18 +331,18 @@ msgid "Add New Artwork" msgstr "Tilføj ny illustration" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Tilfældig sti" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Næste sti" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Vælg en handling" @@ -379,7 +379,7 @@ msgctxt "#30065" msgid "Never" msgstr "Aldrig" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Flettet)" @@ -478,27 +478,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Ikke på flettede widgets" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Tilbage til side {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Skift sti til toppen" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Skift sti til bund" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Vælg en sti" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Vælg kurver, der skal flettes" @@ -565,17 +565,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Opdater varighed" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Slå temafejlfinding til/fra" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Flettede stier" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Rens gamle widgetfiler" @@ -585,7 +585,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Ikon farve" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} stier føjet til {}" @@ -630,7 +630,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Logføring af fejlfinding" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Redigér sti" @@ -645,12 +645,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Foretrækker Episode hvis Show Tilgængelig" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Ryd cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -664,7 +664,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Vælg indholdstype" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Kopier gruppe" @@ -679,7 +679,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Vælg stier" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Vælg Stier til at cykle" @@ -729,42 +729,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Landsikoner" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Næste" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Næste side" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Forrig" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Forrige side" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Tilbage" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Side" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Fejl ved visning af {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "Ingen indhold blev fundet for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Opdatering af widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Færdig opdatering af widgets" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Kopier stier ..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Henter stier ..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Indlæser indhold ..." diff --git a/plugin.program.autowidget/resources/language/resource.language.de_de/strings.po b/plugin.program.autowidget/resources/language/resource.language.de_de/strings.po index 8abc2443..2d6a6899 100644 --- a/plugin.program.autowidget/resources/language/resource.language.de_de/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.de_de/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Standardzeit zwischen Widget-Aktualisierungen (in Stunden)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Erzwingen von Aktualisierungs-Widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Meine Gruppen" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Werkzeuge" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Aktionen" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Neue Widget-Gruppe erstellen" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Neue Shortcut-Gruppe erstellen" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Erzwingen Sie, dass alle definierten Widgets aktualisiert werden." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Entfernen" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Shift Path Nach oben" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Shift Path Down" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Agro-Tourismus, Freizeit Häuser)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Verknüpfungen von ." -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Für diese Gruppe wurden keine Pfade definiert." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Widgets werden aktualisiert..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} entfernt." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Tastenkürzel bearbeiten" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR Rasengrün] [B] Deaktivieren[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Gruppe bearbeiten" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Löschen von Add-On-Daten" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,55 +267,55 @@ msgstr "" "zu tun, wenn Sie beabsichtigen, alle diese Widgets neu zu verweisen. Dieser " "[COLOR firebrick]kann nicht[/COLOR] rückgängig gemacht werden." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Zeigen Sie hier ein Widget, um Elemente anzuzeigen." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Es wurden keine Gruppen definiert." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Widget aktualisieren" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Widget bearbeiten" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Waisen" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Es wurden keine Widgets initialisiert." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Gebrochenes Widget" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Aktive Widgets" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Dieses Widget ist defekt, repoint oder entfernen Sie es, um dies nicht mehr " "zu sehen." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Static)" @@ -331,18 +331,18 @@ msgid "Add New Artwork" msgstr "Neues Kunstwerk hinzufügen" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Zufälliger Pfad" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Nächster Pfad" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Option auswählen" @@ -379,7 +379,7 @@ msgctxt "#30065" msgid "Never" msgstr "Nie" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Zusammengeführt)" @@ -481,27 +481,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Nicht auf zusammengeführten Widgets" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Back to Page {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Shift-Pfad nach oben" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Shift-Pfad nach unten" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Wählen Sie einen Pfad" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Wählen Sie Pfade zum Zusammenführen" @@ -568,17 +568,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Aktualisierungsdauer" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Umschalten des Skin-Debuggings" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "• Zusammengeführte Pfade" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Saubere alte Widget-Dateien" @@ -588,7 +588,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Icon" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "Pfade, die zu ." @@ -633,7 +633,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Debug Logging" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Strecke bearbeiten" @@ -648,12 +648,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Bevorzugen Sie Episode, wenn Show verfügbar" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Cache leeren" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -667,7 +667,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Inhaltstyp auswählen" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Gruppe kopieren" @@ -682,7 +682,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Pfade wählen" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Wählen Sie Pfade zum Zyklus" @@ -732,42 +732,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Ländersymbole" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Weiter" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Nächste Seite" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Zurück" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Vorherige Seite" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Zurück" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Seite" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Fehler beim Anzeigen von {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "No content found for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Widgets aktualisieren" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Aktualisierung der Widgets abgeschlossen" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Pfade kopieren..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Pfade abrufen..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Inhalt wird geladen..." diff --git a/plugin.program.autowidget/resources/language/resource.language.el_gr/strings.po b/plugin.program.autowidget/resources/language/resource.language.el_gr/strings.po index c98dbda3..067561f8 100644 --- a/plugin.program.autowidget/resources/language/resource.language.el_gr/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.el_gr/strings.po @@ -48,38 +48,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Προεπιλεγμένος χρόνος μεταξύ ανανεώσεων widget (σε ώρες)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Επιβολή γραφικών στοιχείων ανανέωσης" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Οι ομάδες μου" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Εργαλεία" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Ενέργειες" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Δημιουργία νέας ομάδας widget" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Δημιουργία νέας ομάδας συντομεύσεων" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Εξαναγκάσετε όλα τα καθορισμένα widgets να ανανεωθούν." @@ -94,33 +94,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Κατάργηση" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Μετατόπιση διαδρομής προς τα επάνω" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Μετατόπιση διαδρομής προς τα κάτω" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Ποδηλασία)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Συντομεύσεις από {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Δεν έχουν καθοριστεί διαδρομές για αυτήν την ομάδα." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Ανανέωση γραφικών στοιχείων..." @@ -184,7 +184,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} διαγράφτηκε." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Επεξεργασία συντόμευσης" @@ -245,17 +245,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[ΧΡΩΜΑΤΙΚΟ γκαζόν] [Β] Απενεργοποίηση[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Επεξεργασία ομάδας" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Διαγραφή πρόσθετων δεδομένων" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -269,55 +269,55 @@ msgstr "" "αυτό να γίνει μόνο εάν σκοπεύετε να επαναδιορίσετε όλα αυτά τα widgets. Αυτό" " το [COLOR firebrick] δεν μπορεί[/COLOR] να αναιρεθεί." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Τοποθετήστε ένα widget εδώ για να εμφανίσετε στοιχεία." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Δεν έχουν καθοριστεί ομάδες." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Ανανέωση widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Επεξεργασία Widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "ορφανεμένος" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Δεν έχουν αρχικοποιηθεί γραφικά στοιχεία." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Σπασμένο γραφικό στοιχείο" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Ενεργά γραφικά στοιχεία" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Αυτό το widget είναι σπασμένο, επαναδιορίστε ή αφαιρέστε το για να " "σταματήσετε να το βλέπετε αυτό." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Στατικό)" @@ -333,18 +333,18 @@ msgid "Add New Artwork" msgstr "Προσθήκη Νέου Έργου" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Τυχαία διαδρομή" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Επόμενο μονοπάτι" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Επιλογή ενέργειας" @@ -381,7 +381,7 @@ msgctxt "#30065" msgid "Never" msgstr "Ποτέ" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Συγχωνευμένο)" @@ -482,27 +482,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Όχι σε συγχωνευμένα widgets" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Επιστροφή στη Σελίδα {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Μετατόπιση διαδρομής στην κορυφή" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Μετατόπιση διαδρομής προς τα κάτω" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Επιλογή διαδρομής" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Επιλογή διαδρομών για συγχώνευση" @@ -569,17 +569,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Διάρκεια ανανέωσης" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Εναλλαγή εντοπισμού σφαλμάτων δέρματος" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Συγχωνευμένες διαδρομές" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Καθαρισμός παλαιών αρχείων widget" @@ -589,7 +589,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Χρώμα Εικονιδίου" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} διαδρομές που προστέθηκαν στο {}" @@ -634,7 +634,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Καταγραφή εντοπισμού σφαλμάτων" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Επεξεργασία διαδρομής" @@ -649,12 +649,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Προτιμήστε το επεισόδιο εάν η εκπομπή είναι διαθέσιμη" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Απαλοιφή Προσωρινής Μνήμης" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -669,7 +669,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Επιλέξτε Τύπος περιεχομένου" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Αντιγραφή ομάδας" @@ -684,7 +684,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Επιλέξτε Διαδρομές" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Επιλέξτε διαδρομές για κύκλο" @@ -734,42 +734,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Εικονίδια χώρας" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Επόμενο" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Επόμενη σελίδα" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Προηγούμενο" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Προηγούμενη" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Πίσω" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Σελίδα" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Σφάλμα εμφάνισης {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "Δεν βρέθηκε περιεχόμενο για {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Ενημέρωση Widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Ολοκληρώθηκε η ενημέρωση Widgets" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Αντιγραφή διαδρομών ..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Ανάκτηση διαδρομών ..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Φόρτωση περιεχομένου ..." diff --git a/plugin.program.autowidget/resources/language/resource.language.en_gb/strings.po b/plugin.program.autowidget/resources/language/resource.language.en_gb/strings.po index ba4d3246..41ca9b98 100644 --- a/plugin.program.autowidget/resources/language/resource.language.en_gb/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.en_gb/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Default time between widget refreshes (in hours)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Force Refresh Widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "My Groups" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Tools" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Actions" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Create New Widget Group" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Create New Shortcut Group" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Force all defined widgets to refresh." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Remove" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Shift Path Up" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Shift Path Down" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Cycling)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Shortcuts from {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "No paths have been defined for this group." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Refreshing widgets..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removed." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Edit Shortcut" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR lawngreen][B]Disable[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Edit Group" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Wipe Add-on Data" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -266,53 +266,53 @@ msgstr "" "recommended that this only be done if you intend on repointing all of those " "widgets. This [COLOR firebrick]cannot[/COLOR] be undone." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Point a widget here to show items." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "No groups have been defined." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Refresh Widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Edit Widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Orphaned" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "No widgets have been initialized." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Broken Widget" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Active Widgets" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "This widget is broken, repoint or remove it to stop seeing this." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Static)" @@ -328,18 +328,18 @@ msgid "Add New Artwork" msgstr "Add New Artwork" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Random Path" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Next Path" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Choose an Action" @@ -376,7 +376,7 @@ msgctxt "#30065" msgid "Never" msgstr "Never" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Merged)" @@ -475,27 +475,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Not on Merged Widgets" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Back to Page {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Shift Path to Top" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Shift Path to Bottom" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Choose a Path" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Choose Paths to Merge" @@ -562,17 +562,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Refresh Duration" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Toggle Skin Debugging" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Paths Merged" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Clean Old Widget Files" @@ -582,7 +582,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Icon Color" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} paths added to {}" @@ -627,7 +627,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Debug Logging" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Edit Path" @@ -642,12 +642,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Prefer Episode if Show Available" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Clear Cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -661,7 +661,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Select Content Type" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copy Group" @@ -676,7 +676,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Choose Paths" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Choose Paths to Cycle" @@ -726,42 +726,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Country Icons" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Next" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Next Page" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Previous" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Previous Page" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Back" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Page" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Error showing {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "No content found for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Updating Widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Finished Updating Widgets" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copying paths..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Retrieving paths..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Loading content..." diff --git a/plugin.program.autowidget/resources/language/resource.language.en_us/strings.po b/plugin.program.autowidget/resources/language/resource.language.en_us/strings.po index 2ed2f0c2..74ff919e 100644 --- a/plugin.program.autowidget/resources/language/resource.language.en_us/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.en_us/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Default time between widget refreshes (in hours)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Force Refresh Widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "My Groups" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Tools" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Actions" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Create New Widget Group" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Create New Shortcut Group" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Force all defined widgets to refresh." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Remove" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Shift Path Up" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Shift Path Down" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Cycling)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Shortcuts from {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "No paths have been defined for this group." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Refreshing widgets..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removed." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Edit Shortcut" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR lawngreen][B]Disable[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Edit Group" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Wipe Add-on Data" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -266,53 +266,53 @@ msgstr "" "recommended that this only be done if you intend on repointing all of those " "widgets. This [COLOR firebrick]cannot[/COLOR] be undone." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Point a widget here to show items." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "No groups have been defined." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Refresh Widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Edit Widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Orphaned" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "No widgets have been initialized." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Broken Widget" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Active Widgets" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "This widget is broken, repoint or remove it to stop seeing this." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Static)" @@ -328,18 +328,18 @@ msgid "Add New Artwork" msgstr "Add New Artwork" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Random Path" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Next Path" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Choose an Action" @@ -376,7 +376,7 @@ msgctxt "#30065" msgid "Never" msgstr "Never" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Merged)" @@ -475,27 +475,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Not on Merged Widgets" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Back to Page {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Shift Path to Top" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Shift Path to Bottom" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Choose a Path" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Choose Paths to Merge" @@ -562,17 +562,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Refresh Duration" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Toggle Skin Debugging" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Paths Merged" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Clean Old Widget Files" @@ -582,7 +582,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Icon Color" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} paths added to {}" @@ -627,7 +627,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Debug Logging" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Edit Path" @@ -642,12 +642,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Prefer Episode if Show Available" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Clear Cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -661,7 +661,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Select Content Type" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copy Group" @@ -676,7 +676,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Choose Paths" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Choose Paths to Cycle" @@ -726,42 +726,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Country Icons" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Next" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Next Page" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Previous" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Previous Page" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Back" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Page" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Error showing {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "No content found for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Updating Widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Finished Updating Widgets" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copying paths..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Retrieving paths..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Loading content..." diff --git a/plugin.program.autowidget/resources/language/resource.language.es_es/strings.po b/plugin.program.autowidget/resources/language/resource.language.es_es/strings.po index 8acd149b..8aaa960a 100644 --- a/plugin.program.autowidget/resources/language/resource.language.es_es/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.es_es/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Tiempo entre actualizaciones de widgets (horas)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Forzar actualización de los widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Mis Grupos" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Herramientas" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Acciones" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Crear nuevo grupo de widgets" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Crear nuevo grupo de accesos directos" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Fuerza el refresco de todos los widgets definidos." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Eliminar" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Mover arriba" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Mover abajo" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Ciclica)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Accesos directos de {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "No se han definido rutas para este grupo." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Actualizando widgets..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} eliminado." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Editar acceso directo" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR lawngreen][B]Desactivar[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Editar grupo" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Eliminar datos del complemento" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,55 +267,55 @@ msgstr "" "tiene la intención de volver a nombrar todos esos widgets. Esto [COLOR " "firebrick]no se puede deshacer[/COLOR]." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Apunte un widget aquí para mostrar elementos." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "No se han definido grupos." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Refrescar widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Editar widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Huérfano" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "No se han inicializado widgets." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Widget roto" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Widgets activos" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Este widget está roto, vuelva a definirlo o elimínelo para dejar de ver " "esto." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Estática)" @@ -331,18 +331,18 @@ msgid "Add New Artwork" msgstr "Agregar nueva ilustración" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Ruta aleatoria" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Próxima ruta" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Elige una acción" @@ -379,7 +379,7 @@ msgctxt "#30065" msgid "Never" msgstr "Nunca" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Combinada)" @@ -478,27 +478,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "No en widgets combinados" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Regresar a la página {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Mover al inicio" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Mover al fondo" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Elige una ruta" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Elija rutas para combinar" @@ -565,17 +565,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Duración de actualización" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Activar modo depuración de skin" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} rutas fusionadas" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Limpiar archivos antiguos de widgets" @@ -585,7 +585,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Color del icono" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} rutas agregadas a {}" @@ -630,7 +630,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Registro de depuración" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Editar ruta" @@ -645,12 +645,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Preferir episodio si el programa está disponible" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Limpiar cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -664,7 +664,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Seleccionar tipo de contenido" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copiar Grupo" @@ -679,7 +679,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Elija caminos" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Elija rutas para recorrer en bicicleta" @@ -729,42 +729,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Iconos de país" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Siguiente" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Siguiente página" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Anterior" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Pagina anterior" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Volver" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Página" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Error al mostrar {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "No se ha encontrado contenido for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Actualización de widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Actualización completa de widgets" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copiando rutas ..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Recuperando rutas ..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Cargando el contenido..." diff --git a/plugin.program.autowidget/resources/language/resource.language.es_mx/strings.po b/plugin.program.autowidget/resources/language/resource.language.es_mx/strings.po index 8212d881..7e06d208 100644 --- a/plugin.program.autowidget/resources/language/resource.language.es_mx/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.es_mx/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Tiempo predeterminado entre actualizaciones de widgets (en horas)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Forzar widgets de actualización" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Mis grupos" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Herramientas" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Acciones" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Crear nuevo grupo de widgets" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Crear nuevo grupo de accesos directos" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Fuerce la actualización de todos los widgets definidos." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Retirar" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Desplazar ruta hacia arriba" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Desplazar ruta hacia abajo" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Cycling)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Accesos directos de {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "No se ha definido ninguna ruta de acceso para este grupo." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Actualizando widgets..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removed." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Editar acceso rápido" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR césped verde] [B] Desactivar[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Editar grupo" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Borrar datos del complemento" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,55 +267,55 @@ msgstr "" "haga si tiene la intención de volver a nombrar todos esos widgets. Este " "[COLOR firebrick]no se puede[/COLOR] deshacer." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Apunte un widget aquí para mostrar los elementos." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "No se han definido grupos." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Actualizar widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Editar Widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Orphaned" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "No se ha inicializado ningún widget." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Widget roto" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Widgets activos" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Este widget está roto, vuelva a nombrarlo o elimínelo para dejar de ver " "esto." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Estática)" @@ -331,18 +331,18 @@ msgid "Add New Artwork" msgstr "Agregar nueva ilustración" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Ruta aleatoria" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Siguiente ruta de acceso" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Elegir una acción" @@ -379,7 +379,7 @@ msgctxt "#30065" msgid "Never" msgstr "Nunca" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Mezclado)" @@ -478,27 +478,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "No en widgets combinados" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Volver a la página {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Desplazar ruta a la parte superior" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Cambiar ruta a la parte inferior" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Elija una ruta de acceso" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Elija las rutas de acceso para combinar" @@ -565,17 +565,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Duración de la actualización" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Alternar depuración de máscaras" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Rutas de acceso combinadas" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Limpiar archivos widget antiguos" @@ -585,7 +585,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Color del icono" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} rutas de acceso agregadas a {}" @@ -630,7 +630,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Registro de depuración" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "编辑路径" @@ -645,12 +645,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Preferir episodio si show disponible" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Limpiar cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -664,7 +664,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Seleccionar tipo de contenido" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copiar Grupo" @@ -679,7 +679,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Elija caminos" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Elija rutas para recorrer en bicicleta" @@ -729,42 +729,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Iconos de país" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Siguiente" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Siguiente página" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Anterior" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Pagina anterior" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Volver" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Página" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Error al mostrar {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "No se ha encontrado contenido for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Actualización de widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Actualización completa de widgets" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copiando rutas ..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Recuperando rutas ..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Cargando el contenido..." diff --git a/plugin.program.autowidget/resources/language/resource.language.fr_fr/strings.po b/plugin.program.autowidget/resources/language/resource.language.fr_fr/strings.po index c715db36..c468e5e8 100644 --- a/plugin.program.autowidget/resources/language/resource.language.fr_fr/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.fr_fr/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Temps par défaut entre les actualisations de widgets (en heures)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Forcer l’actualisation des widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Mes groupes " -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Outils" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Actions" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Créer un nouveau groupe de widgets" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Créer un nouveau groupe de raccourcis" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Forcer l’actualisation de tous les widgets définis." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Supprimer" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Déplacer le tracé vers le haut" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Déplacer le tracé vers le bas" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Cyclisme)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Raccourcis à partir de {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Aucun chemin d’accès n’a été défini pour ce groupe." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Rafraîchissement des widgets..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removed." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Modifier le raccourci" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR lawngreen] [B] Désactiver[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Éditer" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Effacer les données du module complémentaire" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,54 +267,54 @@ msgstr "" "faire que si vous avez l’intention de repointer tous ces widgets. Ce [COLOR " "firebrick] ne peut pas [/COLOR] être annulé." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Pointez un widget ici pour afficher les éléments." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Aucun groupe n’a été défini." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Widget d’actualisation" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Modifier le widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Enfants orphelins" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Aucun widget n’a été initialisé." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Widget cassé" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Widgets actifs" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Ce widget est cassé, repointez-le ou supprimez-le pour arrêter de voir cela." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Statique)" @@ -330,18 +330,18 @@ msgid "Add New Artwork" msgstr "Ajouter une nouvelle illustration" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Chemin aléatoire" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Chemin suivant" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Sélectionner une action" @@ -378,7 +378,7 @@ msgctxt "#30065" msgid "Never" msgstr "Jamais" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Fusionné)" @@ -478,27 +478,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Pas sur les widgets fusionnés" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Retour {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Déplacer le chemin vers le haut" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Déplacer le chemin vers le bas" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Choisis un Path" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Choisir les chemins à fusionner" @@ -565,17 +565,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Durée d’actualisation" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Basculer le débogage de l’apparence" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Chemins d’accès fusionnés" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Nettoyer les anciens fichiers widget" @@ -585,7 +585,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Couleur de l’icône" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} chemins d’accès ajoutés à {}" @@ -630,7 +630,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Debug Logging" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Modifier le parcours" @@ -645,12 +645,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Préférez l’épisode si l’émission est disponible" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Vider le cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -664,7 +664,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Sélectionner le type de contenu" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copier le groupe" @@ -679,7 +679,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Choisissez des chemins" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Choisissez des chemins à vélo" @@ -729,42 +729,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Icônes de pays" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Suivant" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Page suivante" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Précédent" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Page précédente" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Retour" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Page" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Erreur lors de l'affichage de {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "Aucun résultat trouvé for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Mise à jour des widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Mise à jour des widgets terminée" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copie des chemins..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Récupération des chemins..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Chargement du contenu..." diff --git a/plugin.program.autowidget/resources/language/resource.language.it_it/strings.po b/plugin.program.autowidget/resources/language/resource.language.it_it/strings.po index 7e5ca770..fff25669 100644 --- a/plugin.program.autowidget/resources/language/resource.language.it_it/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.it_it/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Tempo predefinito tra gli aggiornamenti dei widget (in ore)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Forza widget di aggiornamento" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "I miei gruppi" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Strumenti" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Azioni" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Crea nuovo gruppo di widget" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Creare un nuovo gruppo di collegamenti" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Forzare l'aggiornamento di tutti i widget definiti." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Rimuovi" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Sposta percorso verso l'alto" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Sposta percorso verso il basso" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Alpinismo, Parchi nazionali)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Collegamenti da {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Non sono stati definiti percorsi per questo gruppo." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Widget di aggiornamento..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} rimossa." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Modifica scorciatoia" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR verde prato] [B] Disattiva[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Modifica Gruppo" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Cancellare i dati del componente aggiuntivo" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,55 +267,55 @@ msgstr "" "si intende rinominare tutti questi widget. Questo [COLOR firebrick] non " "può[/COLOR] essere annullato." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Punta un widget qui per mostrare gli elementi." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Nessun gruppo è stato definito." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Widget Aggiorna" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Modifica widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Orfani" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Nessun widget inizializzato." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Widget rotto" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Widget attivi" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Questo widget è rotto, vengono rinominati o rimuoverli per smettere di " "vederlo." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Static)" @@ -331,18 +331,18 @@ msgid "Add New Artwork" msgstr "Aggiungi nuova grafica" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Percorso casuale" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Percorso successivo" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Choose an Action" @@ -379,7 +379,7 @@ msgctxt "#30065" msgid "Never" msgstr "Mai" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Unito/a)" @@ -478,27 +478,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Non su widget uniti" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Back to Page {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Spostarsi tra il percorso verso l'alto" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Sposta percorso verso il basso" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Scegli un Path" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Scegliere Percorsi da unire" @@ -565,17 +565,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Durata aggiornamento" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Attivare o disattivare il debug dell'skin" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Percorsi uniti" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Pulisci vecchi file widget" @@ -585,7 +585,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Colore Icona" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} percorsi aggiunti a {}" @@ -630,7 +630,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Debug Logging" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Modifica percorso" @@ -645,12 +645,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Preferisci episodio se mostra disponibile" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Cancella Cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -664,7 +664,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Seleziona contenuto Type" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copiare gruppo" @@ -679,7 +679,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Scegli percorsi" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Scegli i percorsi da pedalare" @@ -729,42 +729,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Icone del Paese" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Prossimo" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Pagina successiva" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Precedente" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Pagina precedente" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Indietro" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Pag" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Errore durante la visualizzazione di {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "Nessun contenuto trovato per {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Aggiornamento dei widget" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Widget di aggiornamento terminato" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copia dei percorsi..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Recupero percorsi..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Loading content..." diff --git a/plugin.program.autowidget/resources/language/resource.language.nl_nl/strings.po b/plugin.program.autowidget/resources/language/resource.language.nl_nl/strings.po index 045da409..6d9b8b43 100644 --- a/plugin.program.autowidget/resources/language/resource.language.nl_nl/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.nl_nl/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Standaardtijd tussen het vernieuwen van de widget (in uren)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Widgets voor vernieuwen forceren" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "Mijn Groepen" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Gereedschappen" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Acties" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Nieuwe widgetgroep maken" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Nieuwe snelkoppelingsgroep maken" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Dwing alle gedefinieerde widgets te vernieuwen." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Verwijderen" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Pad omhoog verschuiven" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Pad naar beneden verschuiven" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Wielrennen)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Snelkoppelingen van {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Er zijn geen paden gedefinieerd voor deze groep." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Widgets vernieuwen..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} wordt verwijderd." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Bewerk sneltoets" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR gazongroen] [B] Uitschakelen[/B][/COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Bewerk de groep" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Add-on-gegevens wissen" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,54 +267,54 @@ msgstr "" "alleen te doen als u van plan bent al deze widgets opnieuw te lokaliseren. " "Deze [COLOR firebrick]kan niet ongedaan worden gemaakt." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Wijs hier een widget aan om items weer te geven." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Er zijn geen groepen gedefinieerd." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Widget Vernieuwen" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Widget bewerken" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Zwevend" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Er zijn geen widgets geïnitialiseerd." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Gebroken widget" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Actieve widgets" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Deze widget is kapot, herpunt of verwijder deze om dit niet meer te zien." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Static)" @@ -330,18 +330,18 @@ msgid "Add New Artwork" msgstr "Nieuwe illustraties toevoegen" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Willekeurig pad" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Volgende pad" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Kies een actie" @@ -378,7 +378,7 @@ msgctxt "#30065" msgid "Never" msgstr "Nooit" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Samengevoegd)" @@ -477,27 +477,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Niet op samengevoegde widgets" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Back to Page {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Pad verschuiven naar boven" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Pad verschuiven naar onder" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Kies een Path" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Paden kiezen om samen te voegen" @@ -564,17 +564,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Vernieuwingsduur" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Skin Debugging in- en uitschakelen" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Paden samengevoegd" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Oude widgetbestanden opschonen" @@ -584,7 +584,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Pictogram kleur" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} paden toegevoegd aan {}" @@ -629,7 +629,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Logboekregistratie opsporen van fouten" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Naar pad" @@ -644,12 +644,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Geef de voorkeur aan Aflevering als Show beschikbaar is" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Wis cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -663,7 +663,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Selecteer het content-type" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Groep kopiëren" @@ -678,7 +678,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Kies paden" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Kies paden om te fietsen" @@ -728,42 +728,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Landpictogrammen" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Volgende" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Volgende pagina" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Vorige" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Vorige pagina" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Achterkant" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Pagina" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Fout bij het weergeven van {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "Geen content gevonden for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Widgets bijwerken" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Updaten van widgets voltooid" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Paden kopiëren..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Paden ophalen..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Bezig met het laden van informatie..." diff --git a/plugin.program.autowidget/resources/language/resource.language.pt_br/strings.po b/plugin.program.autowidget/resources/language/resource.language.pt_br/strings.po index a4c80e71..85ad7d45 100644 --- a/plugin.program.autowidget/resources/language/resource.language.pt_br/strings.po +++ b/plugin.program.autowidget/resources/language/resource.language.pt_br/strings.po @@ -46,38 +46,38 @@ msgctxt "#30005" msgid "Default time between widget refreshes (in hours)" msgstr "Tempo padrão entre as atualizações do widget (em horas)" -#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:250 +#: addon.xml:20 /resources/settings.xml:38 /resources/lib/menu.py:251 msgctxt "#30006" msgid "Force Refresh Widgets" msgstr "Forçar atualização de widgets" -#: /resources/lib/menu.py:40 /resources/lib/menu.py:99 +#: /resources/lib/menu.py:41 /resources/lib/menu.py:100 msgctxt "#30007" msgid "My Groups" msgstr "As Minhas Turmas" -#: /resources/settings.xml:35 /resources/lib/menu.py:52 -#: /resources/lib/menu.py:281 +#: /resources/settings.xml:35 /resources/lib/menu.py:53 +#: /resources/lib/menu.py:282 msgctxt "#30008" msgid "Tools" msgstr "Ferramentas" -#: /resources/lib/menu.py:677 +#: /resources/lib/menu.py:690 msgctxt "#30009" msgid "Actions" msgstr "Ações" -#: /resources/lib/add.py:160 /resources/lib/menu.py:89 +#: /resources/lib/add.py:160 /resources/lib/menu.py:90 msgctxt "#30010" msgid "Create New Widget Group" msgstr "Criar novo grupo de widgets" -#: /resources/lib/add.py:164 /resources/lib/menu.py:94 +#: /resources/lib/add.py:164 /resources/lib/menu.py:95 msgctxt "#30011" msgid "Create New Shortcut Group" msgstr "Criar Novo Grupo de Atalho" -#: /resources/lib/menu.py:253 +#: /resources/lib/menu.py:254 msgctxt "#30012" msgid "Force all defined widgets to refresh." msgstr "Força a atualização de todos os widgets definidos." @@ -92,33 +92,33 @@ msgctxt "#30014" msgid "Remove" msgstr "Remover" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30015" msgid "Shift Path Up" msgstr "Mudar o caminho para cima" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30016" msgid "Shift Path Down" msgstr "Mudar o caminho para baixo" -#: /resources/lib/menu.py:692 +#: /resources/lib/menu.py:705 msgctxt "#30017" msgid "{} (Cycling)" msgstr "{} (Ciclismo)" -#: /resources/lib/menu.py:192 +#: /resources/lib/menu.py:193 msgctxt "#30018" msgid "Shortcuts from {}" msgstr "Atalhos de {}" -#: /resources/lib/menu.py:146 /resources/lib/menu.py:483 -#: /resources/lib/menu.py:547 +#: /resources/lib/menu.py:147 /resources/lib/menu.py:496 +#: /resources/lib/menu.py:560 msgctxt "#30019" msgid "No paths have been defined for this group." msgstr "Nenhum caminho foi definido para este grupo." -#: /resources/lib/refresh.py:252 +#: /resources/lib/refresh.py:299 msgctxt "#30020" msgid "Refreshing widgets..." msgstr "Atualizando widgets ..." @@ -182,7 +182,7 @@ msgctxt "#30030" msgid "{} removed." msgstr "{} removido." -#: /resources/lib/edit.py:128 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:128 /resources/lib/menu.py:637 msgctxt "#30031" msgid "Edit Shortcut" msgstr "Editar o Atalho" @@ -243,17 +243,17 @@ msgctxt "#30041" msgid "[COLOR lawngreen][B]Disable[/B][/COLOR]" msgstr "[COLOR Lawngreen] [B] Desativar [/ B] [/ COLOR]" -#: /resources/lib/edit.py:133 /resources/lib/menu.py:594 +#: /resources/lib/edit.py:133 /resources/lib/menu.py:607 msgctxt "#30042" msgid "Edit Group" msgstr "Editar grupo" -#: /resources/settings.xml:40 /resources/lib/menu.py:263 +#: /resources/settings.xml:40 /resources/lib/menu.py:264 msgctxt "#30043" msgid "Wipe Add-on Data" msgstr "Limpe os dados do complemento" -#: /resources/lib/common/utils.py:244 +#: /resources/lib/common/utils.py:258 msgctxt "#30044" msgid "" "This will remove [B]all[/B] data for AutoWidget, excluding backups. Any " @@ -267,54 +267,54 @@ msgstr "" "apontar novamente todos esses widgets. Este [tijolo de fogo COLOR] não pode " "[/ COLOR] ser desfeito." -#: /resources/lib/menu.py:536 /resources/lib/menu.py:587 +#: /resources/lib/menu.py:549 /resources/lib/menu.py:600 msgctxt "#30045" msgid "Point a widget here to show items." msgstr "Aponte um widget aqui para mostrar os itens." -#: /resources/lib/backup.py:55 /resources/lib/menu.py:83 +#: /resources/lib/backup.py:55 /resources/lib/menu.py:84 msgctxt "#30046" msgid "No groups have been defined." msgstr "Nenhum grupo foi definido." -#: addon.xml:24 /resources/lib/menu.py:198 +#: addon.xml:24 /resources/lib/menu.py:199 msgctxt "#30047" msgid "Refresh Widget" msgstr "Atualizar widget" #: /resources/lib/edit.py:125 /resources/lib/edit.py:160 -#: /resources/lib/menu.py:214 /resources/lib/menu.py:621 +#: /resources/lib/menu.py:215 /resources/lib/menu.py:634 msgctxt "#30048" msgid "Edit Widget" msgstr "Editar Widget" -#: /resources/lib/menu.py:227 +#: /resources/lib/menu.py:228 msgctxt "#30049" msgid "Orphaned" msgstr "Orfandade" -#: /resources/lib/menu.py:239 +#: /resources/lib/menu.py:240 msgctxt "#30050" msgid "No widgets have been initialized." msgstr "Nenhum widget foi inicializado." -#: /resources/lib/menu.py:473 +#: /resources/lib/menu.py:486 msgctxt "#30051" msgid "Broken Widget" msgstr "Widget Quebrado" -#: /resources/lib/menu.py:46 /resources/lib/menu.py:245 +#: /resources/lib/menu.py:47 /resources/lib/menu.py:246 msgctxt "#30052" msgid "Active Widgets" msgstr "Widgets ativos" -#: /resources/lib/menu.py:474 +#: /resources/lib/menu.py:487 msgctxt "#30053" msgid "This widget is broken, repoint or remove it to stop seeing this." msgstr "" "Este widget está quebrado, reponha ou remova-o para parar de ver isso." -#: /resources/lib/menu.py:679 +#: /resources/lib/menu.py:692 msgctxt "#30054" msgid "{} (Static)" msgstr "{} (Estático)" @@ -330,18 +330,18 @@ msgid "Add New Artwork" msgstr "Adicionar nova arte" #: /resources/lib/edit.py:243 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30057" msgid "Random Path" msgstr "Caminho Aleatório" #: /resources/lib/edit.py:245 /resources/lib/edit.py:395 -#: /resources/lib/menu.py:500 +#: /resources/lib/menu.py:513 msgctxt "#30058" msgid "Next Path" msgstr "Próximo Caminho" -#: /resources/lib/edit.py:396 /resources/lib/menu.py:499 +#: /resources/lib/edit.py:396 /resources/lib/menu.py:512 msgctxt "#30059" msgid "Choose an Action" msgstr "Escolher uma ação" @@ -378,7 +378,7 @@ msgctxt "#30065" msgid "Never" msgstr "Nunca" -#: /resources/lib/menu.py:705 +#: /resources/lib/menu.py:718 msgctxt "#30066" msgid "{} (Merged)" msgstr "{} (Fusão)" @@ -477,27 +477,27 @@ msgctxt "#30084" msgid "Not on Merged Widgets" msgstr "Não em widgets mesclados" -#: /resources/lib/menu.py:321 +#: /resources/lib/menu.py:332 msgctxt "#30085" msgid "Back to Page {}" msgstr "Voltar para a página {}" -#: /resources/lib/menu.py:652 +#: /resources/lib/menu.py:665 msgctxt "#30086" msgid "Shift Path to Top" msgstr "Mudar o caminho para o topo" -#: /resources/lib/menu.py:640 +#: /resources/lib/menu.py:653 msgctxt "#30087" msgid "Shift Path to Bottom" msgstr "Mudar o caminho para o fundo" -#: /resources/lib/edit.py:444 /resources/lib/menu.py:491 +#: /resources/lib/edit.py:444 /resources/lib/menu.py:504 msgctxt "#30088" msgid "Choose a Path" msgstr "Escolha um caminho" -#: /resources/lib/edit.py:437 /resources/lib/menu.py:554 +#: /resources/lib/edit.py:437 /resources/lib/menu.py:567 msgctxt "#30089" msgid "Choose Paths to Merge" msgstr "Escolha os caminhos para fundir" @@ -564,17 +564,17 @@ msgctxt "#30100" msgid "Refresh Duration" msgstr "Duração da atualização" -#: /resources/settings.xml:41 /resources/lib/menu.py:269 +#: /resources/settings.xml:41 /resources/lib/menu.py:270 msgctxt "#30101" msgid "Toggle Skin Debugging" msgstr "Alternar depuração de skin" -#: /resources/lib/menu.py:177 +#: /resources/lib/menu.py:178 msgctxt "#30102" msgid "{} Paths Merged" msgstr "{} Caminhos mesclados" -#: /resources/settings.xml:39 /resources/lib/menu.py:257 +#: /resources/settings.xml:39 /resources/lib/menu.py:258 msgctxt "#30103" msgid "Clean Old Widget Files" msgstr "Limpar arquivos de widget antigos" @@ -584,7 +584,7 @@ msgctxt "#30104" msgid "Icon Color" msgstr "Cor do Ícone" -#: /resources/lib/add.py:282 +#: /resources/lib/add.py:295 msgctxt "#30105" msgid "{} paths added to {}" msgstr "{} caminhos adicionados a {}" @@ -629,7 +629,7 @@ msgctxt "#30113" msgid "Debug Logging" msgstr "Registro de Depuração" -#: /resources/lib/edit.py:130 /resources/lib/menu.py:624 +#: /resources/lib/edit.py:130 /resources/lib/menu.py:637 msgctxt "#30114" msgid "Edit Path" msgstr "Para Caminho" @@ -644,12 +644,12 @@ msgctxt "#30116" msgid "Prefer Episode if Show Available" msgstr "Prefira Episódio se Mostrar Disponível" -#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:275 +#: addon.xml:28 /resources/settings.xml:42 /resources/lib/menu.py:276 msgctxt "#30117" msgid "Clear Cache" msgstr "Limpar Cache" -#: /resources/lib/common/utils.py:265 +#: /resources/lib/common/cache.py:26 msgctxt "#30118" msgid "" "Are you sure you want to clear the cache? This action may make widgets load " @@ -663,7 +663,7 @@ msgctxt "#30119" msgid "Select Content Type" msgstr "Seleccione o tipo de conteúdo" -#: /resources/lib/menu.py:604 +#: /resources/lib/menu.py:617 msgctxt "#30120" msgid "Copy Group" msgstr "Copiar Grupo" @@ -678,7 +678,7 @@ msgctxt "#30122" msgid "Choose Paths" msgstr "Escolha os caminhos" -#: /resources/lib/menu.py:509 +#: /resources/lib/menu.py:522 msgctxt "#30123" msgid "Choose Paths to Cycle" msgstr "Escolha os caminhos para pedalar" @@ -728,42 +728,68 @@ msgctxt "#30132" msgid "Country Icons" msgstr "Ícones Country" -#: /resources/lib/menu.py:30 +#: /resources/lib/menu.py:31 msgctxt "#30133" msgid "Next" msgstr "Seguinte" -#: /resources/lib/menu.py:31 +#: /resources/lib/menu.py:32 msgctxt "#30134" msgid "Next Page" msgstr "Página Seguinte" -#: /resources/lib/menu.py:32 +#: /resources/lib/menu.py:33 msgctxt "#30135" msgid "Previous" msgstr "Anterior" -#: /resources/lib/menu.py:33 +#: /resources/lib/menu.py:34 msgctxt "#30136" msgid "Previous Page" msgstr "Página Anterior" -#: /resources/lib/menu.py:34 +#: /resources/lib/menu.py:35 msgctxt "#30137" msgid "Back" msgstr "Voltar" -#: /resources/lib/menu.py:35 +#: /resources/lib/menu.py:36 msgctxt "#30138" msgid "Page" msgstr "Página" -#: /resources/lib/menu.py:740 +#: /resources/lib/menu.py:753 /resources/lib/refresh.py:332 +#: /resources/lib/common/cache.py:276 msgctxt "#30139" msgid "Error showing {}" msgstr "Erro ao mostrar {}" -#: /resources/lib/menu.py:750 +#: /resources/lib/menu.py:764 /resources/lib/refresh.py:343 msgctxt "#30140" msgid "No content found for {}" msgstr "Nenhum conteúdo encontrado for {}" + +#: /resources/lib/refresh.py:95 +msgctxt "#30141" +msgid "Updating Widgets" +msgstr "Atualizando Widgets" + +#: /resources/lib/refresh.py:153 +msgctxt "#30142" +msgid "Finished Updating Widgets" +msgstr "Widgets de atualização concluídos" + +#: /resources/lib/add.py:270 /resources/lib/add.py:285 +msgctxt "#30143" +msgid "Copying paths..." +msgstr "Copiando caminhos ..." + +#: /resources/lib/add.py:271 +msgctxt "#30144" +msgid "Retrieving paths..." +msgstr "Recuperando caminhos ..." + +#: /resources/lib/common/cache.py:268 +msgctxt "#30145" +msgid "Loading content..." +msgstr "Carregando conteúdo..." diff --git a/plugin.program.autowidget/resources/lib/add.py b/plugin.program.autowidget/resources/lib/add.py index 30afc2d2..93087b08 100644 --- a/plugin.program.autowidget/resources/lib/add.py +++ b/plugin.program.autowidget/resources/lib/add.py @@ -268,8 +268,8 @@ def _copy_path(path_def): return progress = xbmcgui.DialogProgressBG() - progress.create(u"Copying") - progress.update(1, u"Retrieving") + progress.create("AutoWidget", utils.get_string(30143)) + progress.update(1, "AutoWidget", utils.get_string(30144)) group_def = manage.get_group_by_id(group_id) files, hash = refresh.get_files_list(path_def["file"]["file"], background=False) @@ -281,7 +281,11 @@ def _copy_path(path_def): done += 1 if file["type"] in ["movie", "episode", "musicvideo", "song"]: continue - progress.update(int(done / float(len(files)) * 100), file.get("label")) + progress.update( + int(done / float(len(files)) * 100), + heading=utils.get_string(30143), + message=file.get("label"), + ) labels = build_labels("json", file, path_def["target"]) _add_path(group_def, labels, over=True) diff --git a/plugin.program.autowidget/resources/lib/common/cache.py b/plugin.program.autowidget/resources/lib/common/cache.py index 48f410d0..609fa2a1 100644 --- a/plugin.program.autowidget/resources/lib/common/cache.py +++ b/plugin.program.autowidget/resources/lib/common/cache.py @@ -9,6 +9,7 @@ import six +from resources.lib import manage from resources.lib.common import settings from resources.lib.common import utils @@ -129,7 +130,7 @@ def widgets_for_path(path): return set(widgets) -def cache_and_update(hash, widget_ids, notify=True): +def cache_and_update(hash, widget_ids, notify=None): """a widget might have many paths. Ensure each path is either queued for an update or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. @@ -145,8 +146,9 @@ def cache_and_update(hash, widget_ids, notify=True): for widget_id in affected_widgets: if is_cache_queue(hash): # we need to update this path regardless - # if notify is not None: - # notify(_label, path) + if notify is not None: + widget_def = manage.get_widget_by_id(widget_id) + notify(widget_def.get("label", ""), path) new_files, files_changed = cache_files(path, widget_id) changed = changed or files_changed remove_cache_queue(hash) @@ -264,14 +266,16 @@ def cache_expiry(path, widget_id, add=None, background=True): if not os.path.exists(cache_path): result = "Empty" if background: - contents = utils.make_holding_path(u"Loading Content...", "refresh") + contents = utils.make_holding_path(utils.get_string(30145), "refresh") push_cache_queue(path) else: contents = utils.read_json(cache_path, log_file=True) if contents is None: result = "Invalid Read" if background: - contents = utils.make_holding_path("Error", "error") + contents = utils.make_holding_path( + utils.get_string(30139).format(hash), "alert" + ) push_cache_queue(path) else: # write any updated widget_ids so we know what to update when we dequeue diff --git a/plugin.program.autowidget/resources/lib/common/utils.py b/plugin.program.autowidget/resources/lib/common/utils.py index d6ebdbe7..3685e8b5 100644 --- a/plugin.program.autowidget/resources/lib/common/utils.py +++ b/plugin.program.autowidget/resources/lib/common/utils.py @@ -208,7 +208,9 @@ def make_holding_path(label, art, hash=None): "label": label, "file": "plugin://plugin.program.autowidget/?mode=clear_cache&target={}&refresh=&reload=".format( hash - ), + ) + if hash + else "plugin://plugin.program.autowidget/?mode=force&refresh=&reload=", "art": get_art(art), "filetype": "file", } diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index ebb76e6a..ecac83a1 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -93,14 +93,16 @@ class Progress(object): def __call__(self, groupname, path): if self.dialog is None: self.dialog = xbmcgui.DialogProgressBG() - self.dialog.create(u"Updating Widgets") + self.dialog.create("AutoWidget", utils.get_string(30141)) if not self.service.player.isPlayingVideo(): percent = ( len(self.done) / float(len(queue) + len(self.done) + 1) * 100 ) - self.dialog.update(int(percent), message=groupname) + self.dialog.update( + int(percent), "AutoWidget", message=groupname + ) self.done.add(path) progress = Progress() @@ -140,7 +142,7 @@ def __call__(self, groupname, path): ): utils.update_container(True) if progress.dialog is not None: - progress.dialog.update(100, "") + progress.dialog.update(100) progress.dialog.close() if ( updated @@ -149,7 +151,7 @@ def __call__(self, groupname, path): ): dialog = xbmcgui.Dialog() dialog.notification( - u"AutoWidget", u"Finished Updating Widgets", sound=False + u"AutoWidget", utils.get_string(30142), sound=False ) if self.abortRequested(): @@ -328,7 +330,7 @@ def get_files_list(path, label=None, widget_id=None, background=True): elif "error" in files: utils.log("Error processing {}".format(hash), "error") error_tile = utils.make_holding_path( - "Error loading {}".format(label), "alert", hash=hash + utils.get_string(30139).format(label), "alert", hash=hash ) files = error_tile.get("result", {}).get("files", []) cache_path = os.path.join(_addon_data, "{}.cache".format(hash)) @@ -339,7 +341,7 @@ def get_files_list(path, label=None, widget_id=None, background=True): if not files: utils.log("No items found for {}".format(hash)) empty_tile = utils.make_holding_path( - "No items found for {}".format(label), "information-outline", hash=hash + utils.get_string(30140).format(label), "information-outline", hash=hash ) files = empty_tile.get("result", {}).get("files", []) From 1c56fc3e7d9be7020278965e63ef5e5ba774f221 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Fri, 23 Jul 2021 08:05:24 -0700 Subject: [PATCH 096/101] :lipstick: - Remove some old commented out code --- plugin.program.autowidget/resources/lib/menu.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index c1dc5af8..e55e2140 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -310,21 +310,8 @@ def show_path( stack = widget_def.get("stack", []) path = widget_path["file"]["file"] if not stack else stack[-1] - files, hash = refresh.get_files_list(path, path_label, widget_id) - # if not files: - # properties = { - # "autoLabel": path_label, - # "autoID": widget_id, - # "autoAction": action, - # "autoCache": hash, - # } - # if files is None: - # show_error(path_label, properties) - # elif files == []: - # show_empty(path_label, properties) - # return titles if titles else True, path_label, content - utils.log("Loading items from {}".format(path), "debug") + files, hash = refresh.get_files_list(path, path_label, widget_id) color = widget_path.get("color", default_color) From ac24a021c67c061767abb837941179a1c490458f Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Fri, 23 Jul 2021 09:22:32 -0700 Subject: [PATCH 097/101] :recycle: - No need to specify background --- plugin.program.autowidget/resources/lib/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index e55e2140..08620309 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -378,7 +378,7 @@ def show_path( props=properties, ) # Ensure we precache next page for faster access - cache.cache_expiry(file["file"], widget_id, background=True) + cache.cache_expiry(file["file"], widget_id) else: filetype = file.get("type", "") title = { From b9c5e5f30b39fb8f3649584a2c0ef248111c6895 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 26 Jul 2021 22:10:43 +0700 Subject: [PATCH 098/101] only update requested path and with updating missing widget --- .../resources/lib/common/cache.py | 76 ++++++------------- .../resources/lib/refresh.py | 14 ++-- 2 files changed, 28 insertions(+), 62 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/cache.py b/plugin.program.autowidget/resources/lib/common/cache.py index 609fa2a1..5dbd7971 100644 --- a/plugin.program.autowidget/resources/lib/common/cache.py +++ b/plugin.program.autowidget/resources/lib/common/cache.py @@ -79,7 +79,8 @@ def next_cache_queue(): # TODO: need to workout if a blocking write is happen while it was queued or right now. # probably need a .lock file to ensure foreground calls can get priority. cache_data = read_history(path, create_if_missing=True) - yield hash, cache_data + widget_id = utils.read_json(queue_path).get("widget_id", None) + yield path, cache_data, widget_id def push_cache_queue(path, widget_id=None): @@ -100,7 +101,7 @@ def push_cache_queue(path, widget_id=None): if os.path.exists(queue_path): pass # Leave original modification date so item is higher priority else: - utils.write_json(queue_path, {"hash": hash, "path": path}) + utils.write_json(queue_path, {"hash": hash, "path": path, "widget_id": widget_id}) def is_cache_queue(hash): @@ -130,63 +131,30 @@ def widgets_for_path(path): return set(widgets) -def cache_and_update(hash, widget_ids, notify=None): +def cache_and_update(path, widget_id, cache_data, notify=None): """a widget might have many paths. Ensure each path is either queued for an update or is expired and if so force it to be refreshed. When going through the queue this could mean we refresh paths that other widgets also use. These will then be skipped. """ - assert widget_ids - affected_widgets = set() + assert widget_id + assert cache_data.get("path") == path + assert widget_id in cache_data["widgets"] - changed = False - path = widget_ids.get("path", "") - # TODO: we might be updating paths used by widgets that weren't initiall queued. - # We need to return those and ensure they get refreshed also. - affected_widgets = affected_widgets.union(widgets_for_path(path)) - for widget_id in affected_widgets: - if is_cache_queue(hash): - # we need to update this path regardless - if notify is not None: - widget_def = manage.get_widget_by_id(widget_id) - notify(widget_def.get("label", ""), path) - new_files, files_changed = cache_files(path, widget_id) - changed = changed or files_changed - remove_cache_queue(hash) - # else: This bit is broken down below - # # double check this hasn't been updated already when updating another widget - # expiry, _ = cache.cache_expiry(hash, widget_id, no_queue=True) - # if expiry <= time.time(): - # cache.cache_files(path, widget_id) - # else: - # pass # Skipping this path because its already been updated - - # TODO: update every widget? - # for widget_id in widget_ids: - # widget_def = manage.get_widget_by_id(widget_id) - # if not widget_def: - # continue - # widget_path = widget_def.get("path", "") - # utils.log( - # "trying to update {} with widget def {}".format(widget_id, widget_def), - # "inspect", - # ) - - # if type(widget_path) != list: - # widget_path = [widget_path] - # for path_id in widget_path: - # # simple compatibility with pre-3.3.0 widgets - # if isinstance(path_id, dict): - # path_id = path_id.get("id", "") - # path = manage.get_path_by_id(path_id) - # if not path: - # continue - - # _label = path["label"] - # path = path["file"]["file"] - # # TODO: only need to do that if a path has changed which we can tell from the history - # if changed: - # _update_strings(widget_def) - return affected_widgets + hash = path2hash(path) + if not is_cache_queue(hash): + return [] + + if notify is not None: + widget_def = manage.get_widget_by_id(widget_id) + if widget_def is not None: + notify(widget_def.get("label", ""), path) + + new_files, files_changed = cache_files(path, widget_id) + remove_cache_queue(hash) + + # TODO: this is all widgets that ever requested this path. do we + # need to update all of them? + return cache_data["widgets"] if files_changed else [] def cache_files(path, widget_id): diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index ecac83a1..b521cd98 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -108,20 +108,18 @@ def __call__(self, groupname, path): progress = Progress() while queue: - hash, widget_ids = queue.pop(0) + path, cache_data, widget_id = queue.pop(0) + hash = cache.path2hash(path) utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") - affected_widgets = cache.cache_and_update( - hash, widget_ids, notify=progress - ) + affected_widgets = set(cache.cache_and_update( + path, widget_id, cache_data, notify=progress + )) if affected_widgets: updated = True - cache.remove_cache_queue( - hash - ) # Just in queued path's widget defintion has changed and it didn't update this path unrefreshed_widgets = unrefreshed_widgets.union( affected_widgets - ).difference(set(widget_ids)) + ) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update From 40dbccb94776206e9dc79edbe44b0ad638248369 Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Tue, 10 Aug 2021 21:44:12 -0700 Subject: [PATCH 099/101] :bug: - Fix refresh after playback --- .../resources/lib/common/cache.py | 10 +++++----- plugin.program.autowidget/resources/lib/menu.py | 1 + plugin.program.autowidget/resources/lib/refresh.py | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/common/cache.py b/plugin.program.autowidget/resources/lib/common/cache.py index 5dbd7971..2311d1d4 100644 --- a/plugin.program.autowidget/resources/lib/common/cache.py +++ b/plugin.program.autowidget/resources/lib/common/cache.py @@ -369,15 +369,15 @@ def widgets_changed_by_watching(media_type): plays_for_type = [(time, t) for time, t in plays if t == media_type] priority = sorted( [ - (chance_playback_updates_widget(path, plays_for_type), path) + (chance_playback_updates_widget(path, plays_for_type), utils.read_json(path).get("path", ""), path) for path in all_cache ], reverse=True, ) - - for chance, path in priority: - hash = hash_from_cache_path(path) - last_update = os.path.getmtime(path) - _startup_time + + for chance, path, history_path in priority: + hash = path2hash(path) + last_update = os.path.getmtime(history_path) - _startup_time if last_update < 0: utils.log( "widget not updated since startup {} {}".format(last_update, hash[:5]), diff --git a/plugin.program.autowidget/resources/lib/menu.py b/plugin.program.autowidget/resources/lib/menu.py index c7257612..eea70ba7 100644 --- a/plugin.program.autowidget/resources/lib/menu.py +++ b/plugin.program.autowidget/resources/lib/menu.py @@ -710,6 +710,7 @@ def _create_action_items(group_def, _id): "action": "merged", "group": group_id, "id": six.text_type(_id), + "refresh": refresh, }, art=utils.get_art("merge"), isFolder=True, diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index d45941fc..f9197f04 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -475,6 +475,7 @@ def onPlayBackEnded(self): # Queue them for refresh cache.push_cache_queue(path) utils.log("Queued cache update: {}".format(hash[:5]), "notice") + utils.update_container(reload=True) def onPlayBackStopped(self): self.onPlayBackEnded() From a2ed968de20abcc527e99948c9d82cfc64c9e1cd Mon Sep 17 00:00:00 2001 From: drinfernoo Date: Wed, 11 Aug 2021 20:10:31 -0700 Subject: [PATCH 100/101] :zap: - Don't process cache queue during video playback --- .../resources/lib/refresh.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index f9197f04..c1cf7d9c 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -80,6 +80,10 @@ def _update_widgets(self): while not self.abortRequested(): for _ in self.tick(step=1, max=60 * 15): + # don't process cache queue during video playback + if self.player.isPlayingVideo(): + continue + # TODO: somehow delay till all other plugins loaded? updated = False unrefreshed_widgets = set() @@ -112,14 +116,14 @@ def __call__(self, groupname, path): hash = cache.path2hash(path) utils.log("Dequeued cache update: {}".format(hash[:5]), "notice") - affected_widgets = set(cache.cache_and_update( - path, widget_id, cache_data, notify=progress - )) + affected_widgets = set( + cache.cache_and_update( + path, widget_id, cache_data, notify=progress + ) + ) if affected_widgets: updated = True - unrefreshed_widgets = unrefreshed_widgets.union( - affected_widgets - ) + unrefreshed_widgets = unrefreshed_widgets.union(affected_widgets) # # wait 5s or for the skin to reload the widget # # this should reduce churn at startup where widgets take too long too long show up # before_update = time.time() # TODO: have .access file so we can put above update From f706b98b63238f3ca38cbc924557dec2807348db Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Sat, 21 Aug 2021 13:51:10 +0700 Subject: [PATCH 101/101] stop processing queue during playback --- plugin.program.autowidget/resources/lib/refresh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin.program.autowidget/resources/lib/refresh.py b/plugin.program.autowidget/resources/lib/refresh.py index c1cf7d9c..30b022f6 100644 --- a/plugin.program.autowidget/resources/lib/refresh.py +++ b/plugin.program.autowidget/resources/lib/refresh.py @@ -132,6 +132,9 @@ def __call__(self, groupname, path): # utils.log("paused queue until read {:.2} for {}".format(cache.last_read(hash)-before_update, hash[:5]), 'info') if self.abortRequested(): break + if self.player.isPlayingVideo(): + # Video stop will cause another refresh anyway. + break queue = list(cache.next_cache_queue()) for widget_id in unrefreshed_widgets: widget_def = manage.get_widget_by_id(widget_id)