M melon/background.py => melon/background.py +1 -1
@@ 22,4 22,4 @@ class Queue():
def add(self, func, *args):
self.tasks.append((func, args))
-queue = Queue(1)
+queue = Queue(5)
M melon/home/history.py => melon/home/history.py +12 -21
@@ 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()
M melon/home/new.py => melon/home/new.py +37 -32
@@ 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,35 58,23 @@ 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
@@ 95,15 84,31 @@ class NewFeed(ViewStackPage):
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)
+ spinner.start()
+ cb = Gtk.CenterBox()
+ 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()
M melon/home/playlists.py => melon/home/playlists.py +12 -17
@@ 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()
M melon/home/subs.py => melon/home/subs.py +11 -16
@@ 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()
M melon/models/__init__.py => melon/models/__init__.py +4 -2
@@ 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()
M melon/widgets/feeditem.py => melon/widgets/feeditem.py +14 -9
@@ 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