From 0220dd40f6f5ab160b531297cdaea96059dbd758 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Sun, 3 Mar 2024 13:59:20 +0100 Subject: [PATCH] do not render home feeds on separate threads + increase thumbnail worker count --- melon/background.py | 2 +- melon/home/history.py | 33 +++++++------------ melon/home/new.py | 69 +++++++++++++++++++++------------------ melon/home/playlists.py | 29 +++++++--------- melon/home/subs.py | 27 +++++++-------- melon/models/__init__.py | 6 ++-- melon/widgets/feeditem.py | 23 ++++++++----- 7 files changed, 91 insertions(+), 98 deletions(-) diff --git a/melon/background.py b/melon/background.py index ed131dd..f387a28 100644 --- a/melon/background.py +++ b/melon/background.py @@ -22,4 +22,4 @@ class Queue(): def add(self, func, *args): self.tasks.append((func, args)) -queue = Queue(1) +queue = Queue(5) diff --git a/melon/home/history.py b/melon/home/history.py index a14679a..38e8b3d 100644 --- a/melon/home/history.py +++ b/melon/home/history.py @@ -2,7 +2,7 @@ import sys import gi gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -from gi.repository import Gtk, Adw +from gi.repository import Gtk, Adw, GLib import threading from gettext import gettext as _ @@ -16,13 +16,9 @@ class History(ViewStackPage): # datecount displayed page_size = 7 - def update(self): - app_settings = get_app_settings() - # make sure only videos from available servers are shown - hist = filter_resources(get_history(), app_settings, access=lambda x:x[0]) - hist.reverse() - self.data = list(group_by_date(hist).items()) - if len(hist) == 0: + def update(self, ls): + self.data = ls + if not ls: status = Adw.StatusPage() status.set_title(_("*crickets chirping*")) status.set_description(_("You haven't watched any videos yet")) @@ -45,27 +41,22 @@ class History(ViewStackPage): def load_page(self, page=0): app_settings = get_app_settings() for date, content in self.data[page*self.page_size:(page+1)*self.page_size]: - # faster thread breakout - if self.stop_update: - break group = Adw.PreferencesGroup() group.set_title(date) self.results.add(group) for resource in content: - # faster thread breakout - if self.stop_update: - break group.add(AdaptiveFeedItem(resource, app_settings["show_images_in_feed"])) # TODO somehow add "load more" button - stop_update = False + def schedule(self): + app_settings = get_app_settings() + # make sure only videos from available servers are shown + hist = filter_resources(get_history(), app_settings, access=lambda x:x[0]) + hist.reverse() + GLib.idle_add(self.update, list(group_by_date(hist).items())) + thread = None def do_update(self): - if not self.thread is None: - self.stop_update = True - # wait for old thread to finish - self.thread.join() - self.stop_update = False # show spinner spinner = Gtk.Spinner() spinner.set_size_request(50, 50) @@ -74,7 +65,7 @@ class History(ViewStackPage): cb.set_center_widget(spinner) self.inner.set_child(cb) # start thread - self.thread = threading.Thread(target=self.update) + self.thread = threading.Thread(target=self.schedule) self.thread.daemon = True self.thread.start() diff --git a/melon/home/new.py b/melon/home/new.py index 30886ba..de44591 100644 --- a/melon/home/new.py +++ b/melon/home/new.py @@ -16,7 +16,8 @@ from melon.models import get_subscribed_channels, get_app_settings from melon.models import get_cached_feed, clear_cached_feed, update_cached_feed, get_last_feed_refresh class NewFeed(ViewStackPage): - def update(self, news): + def update(self): + news = get_cached_feed(100) # both subscreens need the refresh button refresh_btn = IconButton(_("Refresh"), "view-refresh-symbolic") refresh_btn.connect("clicked", lambda a: self.reload()) @@ -57,37 +58,45 @@ class NewFeed(ViewStackPage): title.set_header_suffix(refresh_btn) results.add(title) self.inner.set_child(results) - for date, content in news: - group = Adw.PreferencesGroup() - group.set_title(date) - results.add(group) - for resource in content: - group.add(AdaptiveFeedItem(resource, app_settings["show_images_in_feed"])) + tracker = {} + for entry in news: + vid = entry[0] + uts = entry[1] + date = datetime.fromtimestamp(uts).date().strftime("%c").replace("00:00:00","").replace(" ", " ") + group = None + if date in tracker: + group = tracker[date] + else: + group = Adw.PreferencesGroup() + results.add(group) + group.set_title(date) + tracker[date] = group + group.add(AdaptiveFeedItem(vid, app_settings["show_images_in_feed"])) # return false, because otherwise ide_add would run multiple times return False - def schedule(self): - news = get_cached_feed() - if not news: - # clear the feed again - # in case this wasn't calles from reload - # because we do NOT want duplicate entries - clear_cached_feed() - # long task: - # NOTE: fetch_home_feed automatically dismisses channels - # if the server isn't available (i.e because it has been disabled) - news = fetch_home_feed() - update_cached_feed(news) - # only show last 100 videos - news = news[-100:] - # reverse the feed, because the latest (highest uts) should be on top - news.reverse() - # group the data by date - group = group_by_date(news) - GLib.idle_add(self.update, group.items()) - thread = None def do_update(self): + # show spinner + spinner = Gtk.Spinner() + spinner.set_size_request(50, 50) + spinner.start() + cb = Gtk.CenterBox() + cb.set_center_widget(spinner) + self.inner.set_child(cb) + GLib.idle_add(self.update) + + def do_reload(self): + # clear the feed + clear_cached_feed() + # long task: + # NOTE: fetch_home_feed automatically dismisses channels + # if the server isn't available (i.e because it has been disabled) + news = fetch_home_feed() + update_cached_feed(news) + GLib.idle_add(self.update) + + def reload(self): # show spinner spinner = Gtk.Spinner() spinner.set_size_request(50, 50) @@ -96,14 +105,10 @@ class NewFeed(ViewStackPage): cb.set_center_widget(spinner) self.inner.set_child(cb) # start thread - self.thread = threading.Thread(target=self.schedule) + self.thread = threading.Thread(target=self.do_reload) self.thread.daemon = True self.thread.start() - def reload(self): - clear_cached_feed() - self.do_update() - def __init__(self): super().__init__("feed-new", _("What's new"), "user-home-symbolic") self.widget = Adw.Clamp() diff --git a/melon/home/playlists.py b/melon/home/playlists.py index 2186243..b59a184 100644 --- a/melon/home/playlists.py +++ b/melon/home/playlists.py @@ -2,7 +2,7 @@ import sys import gi gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -from gi.repository import Gtk, Adw +from gi.repository import Gtk, Adw, GLib import threading from gettext import gettext as _ @@ -14,13 +14,9 @@ from melon.models import PlaylistWrapperType from melon.servers.utils import server_is_allowed class Playlists(ViewStackPage): - def update(self): + def update(self, playlists): app_conf = get_app_settings() - # filter external playlists if server is disabled - playlists = [ playlist for playlist in get_playlists() if playlist.type == PlaylistWrapperType.LOCAL or server_is_allowed(playlist.inner.server, app_conf) ] - # local and external playlists have the inner.title attribute - playlists.sort(key=lambda x:x.inner.title) - if len(playlists) == 0: + if not playlists: status = Adw.StatusPage() status.set_title(_("*crickets chirping*")) status.set_description(_("You don't have any playlists yet")) @@ -40,19 +36,18 @@ class Playlists(ViewStackPage): results.set_header_suffix(icon_button) self.inner.set_child(results) for playlist in playlists: - # faster thread breakout - if self.stop_update: - break results.add(AdaptivePlaylistFeedItem(playlist, app_conf["show_images_in_feed"])) - stop_update = False + def schedule(self): + app_conf = get_app_settings() + # filter external playlists if server is disabled + playlists = [ playlist for playlist in get_playlists() if playlist.type == PlaylistWrapperType.LOCAL or server_is_allowed(playlist.inner.server, app_conf) ] + # local and external playlists have the inner.title attribute + playlists.sort(key=lambda x:x.inner.title) + GLib.idle_add(self.update, playlists) + thread = None def do_update(self): - if not self.thread is None: - self.stop_update = True - # wait for old thread to finish - self.thread.join() - self.stop_update = False # show spinner spinner = Gtk.Spinner() spinner.set_size_request(50, 50) @@ -61,7 +56,7 @@ class Playlists(ViewStackPage): cb.set_center_widget(spinner) self.inner.set_child(cb) # start thread - self.thread = threading.Thread(target=self.update) + self.thread = threading.Thread(target=self.schedule) self.thread.daemon = True self.thread.start() diff --git a/melon/home/subs.py b/melon/home/subs.py index 26fce3b..f54eec6 100644 --- a/melon/home/subs.py +++ b/melon/home/subs.py @@ -2,7 +2,7 @@ import sys import gi gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -from gi.repository import Gtk, Adw +from gi.repository import Gtk, Adw, GLib import threading from gettext import gettext as _ @@ -13,11 +13,9 @@ from melon.models import get_subscribed_channels, register_callback, get_app_set from melon.servers.utils import filter_resources class Subscriptions(ViewStackPage): - def update(self): + def update(self, subs): app_settings = get_app_settings() - # make sure only channles from available servers are included - subs = filter_resources(get_subscribed_channels(), app_settings) - if len(subs) == 0: + if not subs: status = Adw.StatusPage() status.set_title(_("*crickets chirping*")) status.set_description(_("You aren't yet subscribed to channels")) @@ -32,26 +30,23 @@ class Subscriptions(ViewStackPage): results = Adw.PreferencesGroup() results.set_title(_("Subscriptions")) results.set_description(_("You are subscribed to the following channels")) - subs.sort(key=lambda c:c.name) # append first, so subscriptions are added as we go # not all at once # reduces time spent on loading animation # NOTE: might seem confusing to unknowing users self.inner.set_child(results) for channel in subs: - # faster thread breakout - if self.stop_update: - break results.add(AdaptiveFeedItem(channel, app_settings["show_images_in_feed"])) - stop_update = False + def schedule(self): + app_settings = get_app_settings() + # make sure only channles from available servers are included + subs = filter_resources(get_subscribed_channels(), app_settings) + subs.sort(key=lambda c:c.name) + GLib.idle_add(self.update, subs) + thread = None def do_update(self): - if not self.thread is None: - self.stop_update = True - # wait for old thread to finish - self.thread.join() - self.stop_update = False # show spinner spinner = Gtk.Spinner() spinner.set_size_request(50, 50) @@ -60,7 +55,7 @@ class Subscriptions(ViewStackPage): cb.set_center_widget(spinner) self.inner.set_child(cb) # start thread - self.thread = threading.Thread(target=self.update) + self.thread = threading.Thread(target=self.schedule) self.thread.daemon = True self.thread.start() diff --git a/melon/models/__init__.py b/melon/models/__init__.py index 7caaeee..d32272f 100644 --- a/melon/models/__init__.py +++ b/melon/models/__init__.py @@ -638,7 +638,7 @@ def get_history(): """).fetchall() return [ (Video(d[1], d[3], d[2], d[4], (d[8], d[7]), d[5], d[6]), d[0]) for d in results ] -def get_cached_feed(): +def get_cached_feed(limit=100): """ Returns a list of (Video, uts) tuples """ @@ -646,7 +646,9 @@ def get_cached_feed(): results = conn.execute(""" SELECT DISTINCT timestamp, server, id, url, title, description, thumbnail, channel_id, channel_name FROM news join videos on news.video_id = videos.id AND news.video_server = videos.server - """).fetchall() + ORDER BY timestamp DESC + LIMIT ? + """, (limit,)).fetchall() return [ (Video(d[1], d[3], d[2], d[4], (d[8], d[7]), d[5], d[6]), d[0]) for d in results ] def clear_cached_feed(): conn = connect_to_db() diff --git a/melon/widgets/feeditem.py b/melon/widgets/feeditem.py index aa01dab..0fba985 100644 --- a/melon/widgets/feeditem.py +++ b/melon/widgets/feeditem.py @@ -83,9 +83,6 @@ class AdaptiveFeedItem(Adw.ActionRow): class AdaptivePlaylistFeedItem(Adw.ActionRow): def __init__(self, playlist:PlaylistWrapper, show_preview=True, *args, **kwargs): super().__init__(*args, **kwargs) - pixbuf = None - if show_preview: - pixbuf = pixbuf_from_url(playlist.inner.thumbnail) self.set_title(unidecode(playlist.inner.title).replace("&","&")) self.set_subtitle(unidecode(playlist.inner.description).replace("&","&")) if playlist.type == PlaylistWrapperType.EXTERNAL: @@ -97,11 +94,19 @@ class AdaptivePlaylistFeedItem(Adw.ActionRow): self.set_action_target_value(GLib.Variant("u", playlist.inner.id)) self.preview = Adw.Avatar() self.preview.set_size(48) - if not pixbuf is None: - texture = Gdk.Texture.new_for_pixbuf(pixbuf) - self.preview.set_custom_image(texture) - else: - self.preview.set_show_initials(True) - self.preview.set_text(self.get_title()) + self.preview.set_show_initials(True) + self.preview.set_text(self.get_title()) self.add_prefix(self.preview) self.set_activatable(True) + if show_preview: + queue.add(self.update_avatar, playlist.inner.thumbnail) + + def update_avatar(self, url): + pixbuf = pixbuf_from_url(url) + if not pixbuf is None: + texture = Gdk.Texture.new_for_pixbuf(pixbuf) + GLib.idle_add(self.complete_avatar, texture) + def complete_avatar(self, texture): + self.preview.set_custom_image(texture) + # return false to cancel the idle job + return False -- 2.38.5