curds: Improvements to the notifications system
- Clean up the code - Kick off a GLib idle handler when events are added - If we are already running in the main thread, then we don't need to use the idle handler. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
7433154494
commit
6db713a993
|
@ -19,7 +19,7 @@ Track = tags.track.Track
|
|||
|
||||
|
||||
def reset():
|
||||
notify.queued.clear()
|
||||
notify.clear()
|
||||
tags.map.clear()
|
||||
playlist.reset()
|
||||
|
||||
|
|
|
@ -1,36 +1,46 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
import threading
|
||||
from gi.repository import GLib
|
||||
|
||||
registered = { }
|
||||
queued = [ ]
|
||||
lock = threading.Lock()
|
||||
events = { }
|
||||
main_queue = [ ]
|
||||
event_lock = threading.Lock()
|
||||
|
||||
def cancel(name, func):
|
||||
cb = events.get(name, [])
|
||||
for event in cb:
|
||||
if event[0] == func:
|
||||
cb.remove(event)
|
||||
if len(cb) == 0:
|
||||
events.pop(name)
|
||||
|
||||
def clear():
|
||||
events.clear()
|
||||
main_queue.clear()
|
||||
|
||||
def notify(name, *args):
|
||||
for (func, queue) in registered.get(name, []):
|
||||
if queue == True:
|
||||
with lock:
|
||||
if (func, args) not in queued:
|
||||
queued.append((func, args))
|
||||
notify("task-queued")
|
||||
is_main = threading.current_thread() == threading.main_thread()
|
||||
for (func, main) in events.get(name, []):
|
||||
if main == True and is_main == False:
|
||||
with event_lock:
|
||||
if (func, args) not in main_queue:
|
||||
main_queue.append((func, args))
|
||||
if len(main_queue) == 1:
|
||||
GLib.idle_add(notify_idle)
|
||||
else:
|
||||
func(*args)
|
||||
|
||||
def cancel(name, func, queue=False):
|
||||
cb = registered.get(name, [])
|
||||
if (func, queue) in cb:
|
||||
cb.remove((func, queue))
|
||||
if len(cb) == 0:
|
||||
registered.pop(name)
|
||||
def notify_idle():
|
||||
with event_lock:
|
||||
if len(main_queue) == 0:
|
||||
return False
|
||||
(func, args) = main_queue.pop(0)
|
||||
more = len(main_queue) > 0
|
||||
func(*args)
|
||||
return more
|
||||
|
||||
def register(name, func, queue=False):
|
||||
cb = registered.setdefault(name, [])
|
||||
def register(name, func, main=False):
|
||||
cb = events.setdefault(name, [])
|
||||
if func in [ n[0] for n in cb ]:
|
||||
return
|
||||
cb.append((func, queue))
|
||||
|
||||
def run_queued():
|
||||
with lock:
|
||||
if len(queued) > 0:
|
||||
(func, args) = queued.pop(0)
|
||||
func(*args)
|
||||
return len(queued) > 0
|
||||
cb.append((func, main))
|
||||
|
|
|
@ -4,10 +4,12 @@ from . import playlist
|
|||
from . import root
|
||||
from .. import data
|
||||
from .. import notify
|
||||
import threading
|
||||
|
||||
Library = library.LibraryPlaylist
|
||||
Node = node.PlaylistNode
|
||||
Root = None
|
||||
Lock = threading.Lock()
|
||||
|
||||
|
||||
def current():
|
||||
|
@ -17,8 +19,9 @@ def lookup(name):
|
|||
return Root.lookup(name)
|
||||
|
||||
def save():
|
||||
with data.DataFile("playlists.pickle", data.WRITE) as f:
|
||||
f.pickle([ Root ])
|
||||
with Lock:
|
||||
with data.DataFile("playlists.pickle", data.WRITE) as f:
|
||||
f.pickle([ Root ])
|
||||
|
||||
def load():
|
||||
global Root
|
||||
|
@ -39,4 +42,5 @@ def init():
|
|||
global UpNext
|
||||
Starred = Root.lookup("Playlists").lookup("Starred")
|
||||
UpNext = Root.lookup("Up Next")
|
||||
notify.register("save-playlists", save, queue=True)
|
||||
notify.register("save-playlists", save, main=True)
|
||||
notify.register("save-data", save)
|
||||
|
|
|
@ -22,6 +22,7 @@ class LibraryPlaylist(playlist.Playlist):
|
|||
|
||||
def thread_save(self):
|
||||
tags.map.save()
|
||||
notify.notify("save-data")
|
||||
|
||||
def thread_scan(self):
|
||||
for dirname, subdirs, files in os.walk(self.name):
|
||||
|
|
|
@ -11,7 +11,7 @@ test_album = os.path.join("./trier/Test Library/Test Artist 01/Test Album 1")
|
|||
|
||||
class TestArtistPlaylist(unittest.TestCase):
|
||||
def setUp(self):
|
||||
notify.registered.clear()
|
||||
notify.clear()
|
||||
tags.map.clear()
|
||||
|
||||
def test_artist_node(self):
|
||||
|
@ -46,7 +46,7 @@ class TestArtistPlaylist(unittest.TestCase):
|
|||
|
||||
anode.reset()
|
||||
self.assertEqual(len(anode.children), 0)
|
||||
self.assertIn((anode.new_track, False), notify.registered["new-track"])
|
||||
self.assertIn((anode.new_track, False), notify.events["new-track"])
|
||||
|
||||
def test_artist_playlist_alloc(self):
|
||||
anode = artist.ArtistPlaylist("Test Playlist", artist.ARTIST_ICON)
|
||||
|
|
|
@ -38,7 +38,7 @@ class TestCollectionPlaylist(unittest.TestCase):
|
|||
self.assertEqual(len(plist), 0)
|
||||
self.assertFalse(plist.random)
|
||||
self.assertTrue(plist.loop)
|
||||
self.assertIn((plist.add, False), notify.registered["new-track"])
|
||||
self.assertIn((plist.add, False), notify.events["new-track"])
|
||||
|
||||
def test_collection_loop(self):
|
||||
plist = collection.CollectionPlaylist()
|
||||
|
|
|
@ -14,7 +14,7 @@ test_album3 = os.path.join("Test Album 4", "02 - Test Track 02.ogg")
|
|||
|
||||
class TestDecadePlaylist(unittest.TestCase):
|
||||
def setUp(self):
|
||||
notify.registered.clear()
|
||||
notify.clear()
|
||||
tags.map.clear()
|
||||
|
||||
def test_decade(self):
|
||||
|
@ -55,7 +55,7 @@ class TestDecadePlaylist(unittest.TestCase):
|
|||
|
||||
dnode.reset()
|
||||
self.assertEqual(len(dnode.children), 0)
|
||||
self.assertIn((dnode.new_track, False), notify.registered["new-track"])
|
||||
self.assertIn((dnode.new_track, False), notify.events["new-track"])
|
||||
|
||||
def test_decade_playlist_alloc(self):
|
||||
dnode = decade.DecadePlaylist("Test Decade", decade.DECADE_ICON)
|
||||
|
|
|
@ -11,7 +11,7 @@ test_library = os.path.abspath("./trier/Test Library/Test Artist 01")
|
|||
|
||||
class TestGenrePlaylist(unittest.TestCase):
|
||||
def setUp(self):
|
||||
notify.registered.clear()
|
||||
notify.clear()
|
||||
tags.map.clear()
|
||||
|
||||
def test_genre_node(self):
|
||||
|
@ -69,4 +69,4 @@ class TestGenrePlaylist(unittest.TestCase):
|
|||
|
||||
gnode.reset()
|
||||
self.assertEqual(gnode.n_children(), 0)
|
||||
self.assertIn((gnode.new_track, False), notify.registered["new-track"])
|
||||
self.assertIn((gnode.new_track, False), notify.events["new-track"])
|
||||
|
|
|
@ -17,7 +17,7 @@ class TestLibraryPlaylist(unittest.TestCase):
|
|||
library.reset()
|
||||
tags.map.clear()
|
||||
tags.map.remove()
|
||||
notify.registered.clear()
|
||||
notify.clear()
|
||||
|
||||
def tearDownClass():
|
||||
library.stop()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from .. import data
|
||||
from .. import notify
|
||||
import os
|
||||
import threading
|
||||
|
||||
|
@ -38,5 +39,6 @@ def save():
|
|||
with data.DataFile(tag_file, data.WRITE) as f:
|
||||
f.pickle(tag_map)
|
||||
|
||||
notify.register("save-data", save)
|
||||
|
||||
if os.environ.get("EMMENTAL_TESTING"): remove()
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from . import notify
|
||||
import gi
|
||||
import threading
|
||||
import unittest
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
class TestNotify(unittest.TestCase):
|
||||
def setUp(self):
|
||||
notify.registered.clear()
|
||||
notify.queued.clear()
|
||||
notify.clear()
|
||||
self.test_count = 0
|
||||
self.test_arg1 = None
|
||||
self.test_arg2 = None
|
||||
self.task_queued = False
|
||||
|
||||
def tearDown(self):
|
||||
notify.queued.clear()
|
||||
notify.registered.clear()
|
||||
notify.clear()
|
||||
|
||||
def notify_thread(self, event, *args):
|
||||
thread = threading.Thread(target=notify.notify, args=(event, *args))
|
||||
thread.start()
|
||||
thread.join()
|
||||
|
||||
def on_test1(self, arg1, arg2): self.test_count += 1
|
||||
def on_test2(self, arg1, arg2): self.test_arg1 = arg1
|
||||
|
@ -22,69 +29,65 @@ class TestNotify(unittest.TestCase):
|
|||
def on_task_queued(self): self.task_queued = True
|
||||
def on_test_queue(self, arg1): self.test_count += 1; self.test_arg1 = arg1
|
||||
|
||||
def test_notify(self):
|
||||
self.assertEqual(notify.registered, {})
|
||||
self.assertEqual(notify.queued, [])
|
||||
def test_notify_init(self):
|
||||
self.assertEqual(notify.events, {})
|
||||
self.assertEqual(notify.main_queue, [])
|
||||
self.assertFalse(notify.event_lock.locked())
|
||||
|
||||
def test_notify_register(self):
|
||||
notify.register("on-test", self.on_test1)
|
||||
self.assertEqual(notify.registered, {"on-test" : [ (self.on_test1, False) ]})
|
||||
self.assertEqual(notify.events, {"on-test" : [ (self.on_test1, False) ]})
|
||||
notify.register("on-test", self.on_test1, True)
|
||||
notify.register("on-test", self.on_test2)
|
||||
notify.register("on-test", self.on_test3, True)
|
||||
self.assertEqual(notify.registered, {"on-test" : [ (self.on_test1, False),
|
||||
(self.on_test2, False),
|
||||
(self.on_test3, True ) ]})
|
||||
self.assertEqual(notify.events, {"on-test" : [ (self.on_test1, False),
|
||||
(self.on_test2, False),
|
||||
(self.on_test3, True ) ]})
|
||||
|
||||
def test_notify_clear(self):
|
||||
notify.events = { "abc" : [ (self.on_test1, False) ]}
|
||||
notify.main_q = [ 1, 2, 3 ]
|
||||
notify.clear()
|
||||
self.assertEqual(notify.events, {})
|
||||
self.assertEqual(notify.main_queue, [])
|
||||
|
||||
def test_notify_cancel(self):
|
||||
notify.events = { "on-test" : [ (self.on_test1, False),
|
||||
(self.on_test2, False),
|
||||
(self.on_test3, True) ]}
|
||||
|
||||
notify.cancel("on-test", self.on_test2)
|
||||
self.assertEqual(notify.events, { "on-test" : [ (self.on_test1, False),
|
||||
(self.on_test3, True) ]})
|
||||
notify.cancel("on-test", self.on_test1)
|
||||
self.assertEqual(notify.events, { "on-test" : [ (self.on_test3, True) ] })
|
||||
notify.cancel("on-test", self.on_test3)
|
||||
self.assertEqual(notify.events, { })
|
||||
|
||||
def test_notify_main_thread(self):
|
||||
notify.register("on-test", self.on_test1)
|
||||
notify.register("on-test", self.on_test2)
|
||||
notify.register("on-test", self.on_test3, True)
|
||||
|
||||
notify.notify("on-test", "It Worked", "CoolCoolCool")
|
||||
self.assertEqual(self.test_count, 1)
|
||||
self.assertEqual(self.test_arg1, "It Worked")
|
||||
self.assertEqual(self.test_arg2, None)
|
||||
|
||||
self.assertEqual(notify.queued, [ (self.on_test3, ("It Worked", "CoolCoolCool")) ])
|
||||
self.assertFalse(notify.run_queued())
|
||||
self.assertEqual(self.test_arg2, "CoolCoolCool")
|
||||
self.assertEqual(notify.queued, [ ])
|
||||
|
||||
notify.notify("no-cb", "Please", "Don't", "Crash")
|
||||
self.assertFalse(notify.run_queued())
|
||||
def test_notify_bg_thread(self):
|
||||
notify.register("on-test", self.on_test1, True)
|
||||
notify.register("on-test", self.on_test2)
|
||||
|
||||
notify.cancel("on-test", self.on_test2)
|
||||
self.assertEqual(notify.registered, {"on-test" : [ (self.on_test1, False),
|
||||
(self.on_test3, True) ]})
|
||||
notify.cancel("on-test", self.on_test1)
|
||||
self.assertEqual(notify.registered, {"on-test" : [ (self.on_test3, True) ]})
|
||||
notify.cancel("on-test", self.on_test3, True)
|
||||
self.assertEqual(notify.registered, { })
|
||||
notify.cancel("on-test", self.on_test1)
|
||||
self.assertEqual(notify.registered, { })
|
||||
self.notify_thread("on-test", "It Worked", "CoolCoolCool")
|
||||
self.assertEqual(self.test_count, 0)
|
||||
self.assertEqual(self.test_arg1, "It Worked")
|
||||
self.assertEqual(notify.main_queue, [ (self.on_test1, ("It Worked", "CoolCoolCool")) ])
|
||||
|
||||
def test_queue(self):
|
||||
notify.register("task-queued", self.on_task_queued)
|
||||
notify.register("on-test", self.on_test_queue, True)
|
||||
self.notify_thread("on-test", "It Worked", "CoolCoolCool")
|
||||
self.assertEqual(self.test_count, 0)
|
||||
self.assertEqual(notify.main_queue, [ (self.on_test1, ("It Worked", "CoolCoolCool")) ])
|
||||
|
||||
self.assertEqual(notify.registered, {"on-test" : [ (self.on_test_queue, True) ],
|
||||
"task-queued" : [ (self.on_task_queued, False) ]})
|
||||
|
||||
notify.notify("on-test", "Test Bundle")
|
||||
self.assertTrue(self.task_queued)
|
||||
self.assertEqual(notify.queued, [ (self.on_test_queue, ("Test Bundle",)) ])
|
||||
|
||||
self.task_queued = False
|
||||
notify.notify("on-test", "Test Bundle")
|
||||
self.assertFalse(self.task_queued)
|
||||
self.assertEqual(notify.queued, [ (self.on_test_queue, ("Test Bundle",)) ])
|
||||
|
||||
notify.notify("on-test", "Test Bundle 2")
|
||||
self.assertTrue(self.task_queued)
|
||||
self.assertEqual(notify.queued, [ (self.on_test_queue, ("Test Bundle",)),
|
||||
(self.on_test_queue, ("Test Bundle 2",)) ])
|
||||
|
||||
self.assertTrue(notify.run_queued())
|
||||
self.assertEqual(self.test_count, 1)
|
||||
self.assertEqual(self.test_arg1, "Test Bundle")
|
||||
self.assertEqual(notify.queued, [ (self.on_test_queue, ("Test Bundle 2",)) ])
|
||||
|
||||
self.assertFalse(notify.run_queued())
|
||||
self.assertEqual(self.test_count, 2)
|
||||
self.assertEqual(self.test_arg1, "Test Bundle 2")
|
||||
self.assertEqual(notify.queued, [ ])
|
||||
while Gtk.events_pending(): Gtk.main_iteration_do(True)
|
||||
self.assertEqual(self.test_count, 1)
|
||||
self.assertEqual(notify.main_queue, [ ])
|
||||
self.assertFalse(notify.notify_idle())
|
||||
|
|
|
@ -11,7 +11,7 @@ class TestPlaylist(unittest.TestCase):
|
|||
self.orig = playlist.Root
|
||||
|
||||
def tearDown(self):
|
||||
notify.registered.clear()
|
||||
notify.clear()
|
||||
playlist.node.nodes.clear()
|
||||
playlist.Root = self.orig
|
||||
playlist.Root.reset()
|
||||
|
@ -52,7 +52,7 @@ class TestPlaylist(unittest.TestCase):
|
|||
|
||||
self.assertFalse(dfile.exists())
|
||||
playlist.Starred.changed()
|
||||
notify.run_queued()
|
||||
notify.notify_idle()
|
||||
self.assertTrue(dfile.exists())
|
||||
|
||||
playlist.load()
|
||||
|
|
|
@ -110,7 +110,7 @@ Entry.connect("activate", on_activate_entry)
|
|||
def on_show_more(visible):
|
||||
Box.set_visible(visible)
|
||||
|
||||
curds.notify.register("show-more", on_show_more)
|
||||
curds.notify.register("show-more", on_show_more, main=True)
|
||||
|
||||
|
||||
def state_changed(state):
|
||||
|
|
14
rind/gtk.py
14
rind/gtk.py
|
@ -18,7 +18,6 @@ class EmmentalApplication(Gtk.Application):
|
|||
super().__init__(*args, application_id="org.gtk.emmental", **kwargs)
|
||||
self.window = None
|
||||
self.idle_id = None
|
||||
curds.notify.register("task-queued", self.task_queued)
|
||||
|
||||
def do_activate(self):
|
||||
if self.window == None:
|
||||
|
@ -36,14 +35,6 @@ class EmmentalApplication(Gtk.Application):
|
|||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
def on_idle(self):
|
||||
if curds.notify.run_queued() == False:
|
||||
self.idle_id = None
|
||||
return GLib.SOURCE_CONTINUE if self.idle_id else GLib.SOURCE_REMOVE
|
||||
|
||||
def task_queued(self):
|
||||
if self.idle_id == None:
|
||||
self.idle_id = GLib.idle_add(self.on_idle)
|
||||
|
||||
def can_activate_accel(widget, signal):
|
||||
widget.stop_emission_by_name("can-activate-accel")
|
||||
|
@ -63,10 +54,7 @@ def main_loop(delay=0.0, iteration_delay=0.0):
|
|||
Gtk.main_iteration_do(True)
|
||||
|
||||
def notify_loop(delay=0.0, iteration_delay=0.0):
|
||||
time.sleep(delay)
|
||||
while curds.notify.run_queued():
|
||||
time.sleep(iteration_delay)
|
||||
main_loop()
|
||||
main_loop(delay, iteration_delay)
|
||||
|
||||
def timeout_loop(timeout, iteration_delay=0.0):
|
||||
timeout = time.time() + timeout
|
||||
|
|
|
@ -114,5 +114,5 @@ class NodeTreeModel(GObject.GObject, Gtk.TreeModel):
|
|||
return iter
|
||||
|
||||
def reset(self):
|
||||
curds.notify.register("node-inserted", self.on_node_inserted)
|
||||
curds.notify.register("playlist-changed", self.on_playlist_changed, queue=True)
|
||||
curds.notify.register("node-inserted", self.on_node_inserted, main=True)
|
||||
curds.notify.register("playlist-changed", self.on_playlist_changed, main=True)
|
||||
|
|
|
@ -80,14 +80,14 @@ def on_node_inserted(plist, index):
|
|||
path = Filter.convert_child_path_to_path(child)
|
||||
TreeView.expand_to_path(path)
|
||||
|
||||
curds.notify.register("node-inserted", on_node_inserted)
|
||||
curds.notify.register("node-inserted", on_node_inserted, main=True)
|
||||
|
||||
|
||||
def on_show_more(visible):
|
||||
Entry.set_visible(visible)
|
||||
Separator.set_visible(visible)
|
||||
|
||||
curds.notify.register("show-more", on_show_more)
|
||||
curds.notify.register("show-more", on_show_more, main=True)
|
||||
|
||||
|
||||
def reset():
|
||||
|
@ -99,5 +99,5 @@ def reset():
|
|||
Filter.clear_cache()
|
||||
on_show_more(False)
|
||||
TreeView.set_model(Filter)
|
||||
curds.notify.register("show-more", on_show_more)
|
||||
curds.notify.register("node-inserted", on_node_inserted)
|
||||
curds.notify.register("show-more", on_show_more, main=True)
|
||||
curds.notify.register("node-inserted", on_node_inserted, main=True)
|
||||
|
|
|
@ -122,7 +122,7 @@ def on_add_track(plist, track, index):
|
|||
Model.row_inserted(Gtk.TreePath(NextPos), iter)
|
||||
NextPos += 1
|
||||
|
||||
curds.notify.register("add-track", on_add_track, queue=True)
|
||||
curds.notify.register("add-track", on_add_track, main=True)
|
||||
|
||||
|
||||
def on_remove_track(plist, track, index):
|
||||
|
@ -145,7 +145,7 @@ def on_playlist_changed(plist):
|
|||
if plist == Model.playlist:
|
||||
set_runtime(plist.runtime())
|
||||
|
||||
curds.notify.register("playlist-changed", on_playlist_changed, queue=True)
|
||||
curds.notify.register("playlist-changed", on_playlist_changed, main=True)
|
||||
|
||||
|
||||
def scroll_to(index):
|
||||
|
@ -194,8 +194,8 @@ switch(curds.playlist.lookup("Collection"))
|
|||
|
||||
def reset():
|
||||
switch(curds.playlist.lookup("Collection"))
|
||||
curds.notify.register("add-track", on_add_track, queue=True)
|
||||
curds.notify.register("add-track", on_add_track, main=True)
|
||||
curds.notify.register("remove-track", on_remove_track)
|
||||
curds.notify.register("playlist-changed", on_playlist_changed, queue=True)
|
||||
curds.notify.register("playlist-changed", on_playlist_changed, main=True)
|
||||
curds.notify.register("stream-start", on_stream_start)
|
||||
curds.notify.register("show-more", on_show_more)
|
||||
|
|
|
@ -28,15 +28,6 @@ class TestGtk(unittest.TestCase):
|
|||
gtk.main_loop(delay=0.1)
|
||||
self.assertTrue(window.is_visible())
|
||||
|
||||
self.assertIsNone(app.idle_id)
|
||||
app.task_queued()
|
||||
self.assertGreater(app.idle_id, 0)
|
||||
idle_id = app.idle_id
|
||||
app.task_queued()
|
||||
self.assertEqual(app.idle_id, idle_id)
|
||||
gtk.main_loop(delay=0.1)
|
||||
self.assertIsNone(app.idle_id)
|
||||
|
||||
app.idle_id = GLib.idle_add(self.fake_idle)
|
||||
app.quit()
|
||||
gtk.main_loop(delay=0.1)
|
||||
|
|
Loading…
Reference in New Issue