diff --git a/emmental/db/playlist.py b/emmental/db/playlist.py index bc6e7e2..7515069 100644 --- a/emmental/db/playlist.py +++ b/emmental/db/playlist.py @@ -1,6 +1,7 @@ # Copyright 2022 (c) Anna Schumaker """A customized Gio.ListStore for tracking Playlist GObjects.""" import sqlite3 +import typing from gi.repository import GObject from gi.repository import Gio from gi.repository import Gtk @@ -28,6 +29,7 @@ class Playlist(table.Row): tracks_movable = GObject.Property(type=bool, default=False) current_trackid = GObject.Property(type=int) + child_set = GObject.Property(type=table.TableSubset) children = GObject.Property(type=Gtk.FilterListModel) def __init__(self, table: Gio.ListModel, propertyid: int, @@ -49,19 +51,27 @@ class Playlist(table.Row): return True def add_children(self, child_table: table.Table, - child_filter: Gtk.Filter) -> None: + child_filter: Gtk.Filter, + child_keys: set | None = None) -> None: """Create a FilterListModel for this playlist's children.""" + child_keys = set() if child_keys is None else child_keys + self.child_set = table.TableSubset(child_table, keys=child_keys) self.children = Gtk.FilterListModel.new(child_table, child_filter) self.children.set_incremental(True) def do_update(self, column: str) -> bool: """Update a Playlist object.""" match column: - case "propertyid" | "name" | "n-tracks" | "children" | \ - "user-tracks" | "tracks-loaded" | "tracks-movable": pass + case "propertyid" | "name" | "n-tracks" | "child-set" | \ + "children" | "user-tracks" | "tracks-loaded" | \ + "tracks-movable": pass case _: return super().do_update(column) return True + def add_child(self, child: typing.Self) -> None: + """Add a child Playlist to this Playlist.""" + self.child_set.add_row(child) + def add_track(self, track: Track, *, idle: bool = False) -> None: """Add a Track to this Playlist.""" if self.table.add_track(self, track): @@ -71,6 +81,10 @@ class Playlist(table.Row): """Get a dictionary mapping for trackid -> sorted position.""" return self.table.get_track_order(self) + def has_child(self, child: typing.Self) -> bool: + """Check if this Playlist has a specific child Playlist.""" + return child in self.child_set + def has_track(self, track: Track) -> bool: """Check if a Track is on this Playlist.""" return track in self.tracks @@ -95,6 +109,10 @@ class Playlist(table.Row): self.tracks_loaded = False self.table.queue.push(self.load_tracks, now=not idle) + def remove_child(self, child: typing.Self) -> None: + """Remove a child Playlist from this Playlist.""" + self.child_set.remove_row(child) + def remove_track(self, track: table.Row, *, idle: bool = False) -> None: """Remove a Track from this Playlist.""" self.table.queue.push(self.__remove_track, track, now=not idle) diff --git a/tests/db/test_playlist.py b/tests/db/test_playlist.py index 8c8b712..2c1a5c0 100644 --- a/tests/db/test_playlist.py +++ b/tests/db/test_playlist.py @@ -65,15 +65,28 @@ class TestPlaylistRow(unittest.TestCase): def test_children(self): """Test the child playlist properties.""" + self.assertIsNone(self.playlist.child_set) self.assertIsNone(self.playlist.children) filter = Gtk.Filter() - self.playlist.add_children(self.table, filter) + table = emmental.db.table.Table(None) + self.playlist.add_children(table, filter) + + self.assertIsInstance(self.playlist.child_set, + emmental.db.table.TableSubset) + self.assertEqual(self.playlist.child_set.table, table) + self.assertSetEqual(self.playlist.child_set.keyset.keys, set()) + self.assertIsInstance(self.playlist.children, Gtk.FilterListModel) self.assertEqual(self.playlist.children.get_filter(), filter) - self.assertEqual(self.playlist.children.get_model(), self.table) + self.assertEqual(self.playlist.children.get_model(), table) self.assertTrue(self.playlist.children.get_incremental()) + playlist2 = emmental.db.playlist.Playlist(table=self.table, + propertyid=2, name="Plist2") + playlist2.add_children(table, filter, {1, 2, 3}) + self.assertSetEqual(playlist2.child_set.keyset.keys, {1, 2, 3}) + def test_parent(self): """Test the parent playlist property.""" self.assertIsNone(self.playlist.parent) @@ -97,6 +110,15 @@ class TestPlaylistRow(unittest.TestCase): self.table.update.assert_called_with(self.playlist, prop, value) + def test_add_child(self): + """Test adding a child playlist to the playlist.""" + table = emmental.db.table.Table(None) + child = tests.util.table.MockRow(table=table, number=1) + self.playlist.add_children(table, Gtk.Filter()) + + self.playlist.add_child(child) + self.assertIn(child, self.playlist.child_set) + def test_add_track(self): """Test adding a track to the playlist.""" self.playlist.add_track(self.track, idle=True) @@ -122,6 +144,18 @@ class TestPlaylistRow(unittest.TestCase): {1: 3, 2: 2, 3: 1}) self.table.get_track_order.assert_called_with(self.playlist) + def test_has_child(self): + """Test the playlist has_child() function.""" + table = emmental.db.table.Table(None) + child = tests.util.table.MockRow(table=table, number=1) + self.playlist.add_children(table, Gtk.Filter()) + + self.assertFalse(self.playlist.has_child(child)) + self.playlist.add_child(child) + self.assertTrue(self.playlist.has_child(child)) + self.playlist.remove_child(child) + self.assertFalse(self.playlist.has_child(child)) + def test_has_track(self): """Test the playlist has_track() function.""" self.assertFalse(self.playlist.has_track(self.track)) @@ -141,6 +175,16 @@ class TestPlaylistRow(unittest.TestCase): self.assertTrue(self.playlist.move_track_up(self.track)) self.table.move_track_up.assert_called_with(self.playlist, self.track) + def test_remove_child(self): + """Test removing a child playlist from the playlist.""" + table = emmental.db.table.Table(None) + child = tests.util.table.MockRow(table=table, number=1) + self.playlist.add_children(table, Gtk.Filter()) + + self.playlist.add_child(child) + self.playlist.remove_child(child) + self.assertFalse(child in self.playlist.child_set) + def test_remove_track(self): """Test removing a track from the playlist.""" self.playlist.tracks.trackids.add(self.track.trackid)