From 820eda4c460f241968527a052a6399d22737e6f8 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Thu, 20 Apr 2023 15:51:26 -0400 Subject: [PATCH] playlist: Have the Factory create an active Playlist And add some special handling for setting the active playlist and visible playlist to the same db playlist. I also add active-loop and active-shuffle properties that are wired up to the MPRIS2 "Shuffle" and "LoopStatus" player properties. Implements: #7 (Add MPRIS2 Support) Signed-off-by: Anna Schumaker --- emmental/__init__.py | 4 ++ emmental/playlist/__init__.py | 59 ++++++++++++++-- tests/playlist/test_factory.py | 122 +++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 5 deletions(-) diff --git a/emmental/__init__.py b/emmental/__init__.py index 61b4902..aa8e587 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -155,6 +155,10 @@ class Application(Adw.Application): ("status", "PlaybackStatus"), ("position", "Position")]: self.player.bind_property(prop, self.mpris.player, mpris_prop) + for (prop, mpris_prop) in [("active-shuffle", "Shuffle"), + ("active-loop", "LoopStatus")]: + self.factory.bind_property(prop, self.mpris.player, mpris_prop, + GObject.BindingFlags.BIDIRECTIONAL) self.mpris.player.link_property("Volume", self.win.header, "volume") self.mpris.player.connect("OpenPath", self.__load_path) self.mpris.player.connect("Pause", self.player.pause) diff --git a/emmental/playlist/__init__.py b/emmental/playlist/__init__.py index f45a6c0..42e7583 100644 --- a/emmental/playlist/__init__.py +++ b/emmental/playlist/__init__.py @@ -14,6 +14,7 @@ class Factory(GObject.GObject): db_previous = GObject.Property(type=db.playlist.Playlist) db_visible = GObject.Property(type=db.playlist.Playlist) + active_playlist = GObject.Property(type=playlist.Playlist) visible_playlist = GObject.Property(type=playlist.Playlist) def __init__(self, sql: db.Connection): @@ -22,6 +23,16 @@ class Factory(GObject.GObject): self.sql.bind_property("active-playlist", self, "db-active") self.connect("notify", self.__notify_db_playlists) + def __get_playlists(self) -> list[playlist.Playlist]: + plists = [self.active_playlist, self.visible_playlist] + return [p for p in plists if p is not None] + + def __search_playlists(self, db_plist: db.playlist.Playlist) \ + -> playlist.Playlist: + for plist in self.__get_playlists(): + if plist.playlist == db_plist: + return plist + def __run_factory(self, label: str) -> None: db_plist = self.get_property(f"db-{label}") plist = self.get_property(f"{label}-playlist") @@ -30,21 +41,59 @@ class Factory(GObject.GObject): "" if db_plist is None else db_plist.name) if db_plist is None: - plist.playlist = None + if self.__get_playlists().count(plist) == 1: + plist.playlist = None new = None - elif plist is None: - new = playlist.Playlist(self.sql, db_plist) - else: + elif plist is None or self.__get_playlists().count(plist) > 1: + if (new := self.__search_playlists(db_plist)) is None: + new = playlist.Playlist(self.sql, db_plist) + elif (new := self.__search_playlists(db_plist)) is None: plist.playlist = db_plist return + else: + plist.playlist = None self.set_property(f"{label}-playlist", new) def __notify_db_playlists(self, factory: GObject.GObject, param) -> None: match param.name: - case "db-active" | "db-previous": + case "db-active": + self.__run_factory("active") + self.notify("active-loop") + self.notify("active-shuffle") + case "db-previous": plist = self.get_property(param.name) name = "" if plist is None else plist.name print(f"factory: {param.name[3:]} playlist is:", name) case "db-visible": self.__run_factory("visible") + + @GObject.Property(type=str, flags=playlist.FLAGS) + def active_loop(self) -> str: + """Get the loop state of the active playlist.""" + if self.active_playlist is not None: + return self.active_playlist.loop + return "None" + + @active_loop.setter + def active_loop(self, newval: str) -> None: + """Set the loop state of the active playlist.""" + if self.active_playlist is not None: + if self.active_playlist.loop != newval: + self.active_playlist.loop = newval + self.notify("active-loop") + + @GObject.Property(type=bool, default=False, flags=playlist.FLAGS) + def active_shuffle(self) -> bool: + """Get the shuffle state of the active playlist.""" + if self.active_playlist is not None: + return self.active_playlist.shuffle + return False + + @active_shuffle.setter + def active_shuffle(self, newval: bool) -> None: + """Set the shuffle state of the active playlist.""" + if self.active_playlist is not None: + if self.active_playlist.shuffle != newval: + self.active_playlist.shuffle = newval + self.notify("active-shuffle") diff --git a/tests/playlist/test_factory.py b/tests/playlist/test_factory.py index b8d4a52..66b5b95 100644 --- a/tests/playlist/test_factory.py +++ b/tests/playlist/test_factory.py @@ -36,6 +36,102 @@ class TestFactory(tests.util.TestCase): self.assertIsNone(self.factory.db_active) self.assertRegex(mock_stdout.getvalue(), "active playlist is: ") + def test_active_playlist(self, mock_stdout: io.StringIO): + """Test creating a Playlist when setting the dv_visible property.""" + self.assertIsNone(self.factory.active_playlist) + + self.factory.db_active = self.user_plist + self.assertIsInstance(self.factory.active_playlist, + emmental.playlist.playlist.Playlist) + self.assertEqual(self.factory.active_playlist.sql, self.sql) + self.assertEqual(self.factory.active_playlist.playlist, + self.user_plist) + + orig_id = id(self.factory.active_playlist) + self.factory.db_active = self.sql.playlists.collection + self.assertEqual(id(self.factory.active_playlist), orig_id) + self.assertEqual(self.factory.active_playlist.playlist, + self.sql.playlists.collection) + + active = self.factory.active_playlist + self.factory.db_active = None + self.assertIsNone(self.factory.active_playlist) + self.assertIsNone(active.playlist) + + def test_active_visible_playlist(self, mock_stdout: io.StringIO): + """Test setting the active playlist to the visible playlist.""" + self.factory.db_visible = self.user_plist + self.factory.db_active = self.user_plist + self.assertEqual(id(self.factory.active_playlist), + id(self.factory.visible_playlist)) + + self.factory.db_active = self.user_plist + self.assertEqual(id(self.factory.active_playlist), + id(self.factory.visible_playlist)) + + self.factory.db_active = self.sql.playlists.collection + self.assertNotEqual(id(self.factory.active_playlist), + id(self.factory.visible_playlist)) + + active = self.factory.active_playlist + self.factory.db_active = self.user_plist + self.assertEqual(id(self.factory.active_playlist), + id(self.factory.visible_playlist)) + self.assertIsNone(active.playlist) + + active = self.factory.active_playlist + self.factory.db_active = None + self.assertEqual(active.playlist, self.user_plist) + self.assertIsNone(self.factory.active_playlist) + + def test_active_loop(self, mock_stdout: io.StringIO): + """Test changing the loop property of the active playlist.""" + notify = unittest.mock.Mock() + self.factory.connect("notify::active-loop", notify) + + self.assertEqual(self.factory.active_loop, "None") + self.factory.active_loop = "Playlist" + self.assertEqual(self.factory.active_loop, "None") + notify.assert_not_called() + + self.factory.db_active = self.user_plist + self.assertEqual(self.factory.active_loop, "None") + notify.assert_called() + + notify.reset_mock() + self.factory.active_loop = "Playlist" + self.assertEqual(self.user_plist.loop, "Playlist") + self.assertEqual(self.factory.active_loop, "Playlist") + notify.assert_called() + + notify.reset_mock() + self.factory.active_loop = "Playlist" + notify.assert_not_called() + + def test_active_shuffle(self, mock_stdout: io.StringIO): + """Test changing the shuffle property of the active playlist.""" + notify = unittest.mock.Mock() + self.factory.connect("notify::active-shuffle", notify) + + self.assertFalse(self.factory.active_shuffle) + self.factory.active_shuffle = True + self.assertFalse(self.factory.active_shuffle) + notify.assert_not_called() + + self.factory.db_active = self.user_plist + self.assertFalse(self.factory.active_shuffle) + notify.assert_called() + + notify.reset_mock() + self.factory.active_shuffle = True + self.assertTrue(self.user_plist.shuffle) + self.assertTrue(self.factory.active_shuffle) + notify.assert_called() + + notify.reset_mock() + self.factory.active_shuffle = True + notify.assert_not_called() + def test_previous(self, mock_stdout: io.StringIO): """Test the previous playlist property.""" self.assertIsNone(self.factory.db_previous) @@ -83,3 +179,29 @@ class TestFactory(tests.util.TestCase): self.factory.db_visible = None self.assertIsNone(self.factory.visible_playlist) self.assertIsNone(visible.playlist) + + def test_visible_active_playlist(self, mock_stdout: io.StringIO): + """Test setting the visible playlist to the active playlist.""" + self.factory.db_active = self.user_plist + self.factory.db_visible = self.user_plist + self.assertEqual(id(self.factory.visible_playlist), + id(self.factory.active_playlist)) + + self.factory.db_visible = self.user_plist + self.assertEqual(id(self.factory.visible_playlist), + id(self.factory.active_playlist)) + + self.factory.db_visible = self.sql.playlists.collection + self.assertNotEqual(id(self.factory.visible_playlist), + id(self.factory.active_playlist)) + + visible = self.factory.visible_playlist + self.factory.db_visible = self.user_plist + self.assertEqual(id(self.factory.visible_playlist), + id(self.factory.active_playlist)) + self.assertIsNone(visible.playlist) + + visible = self.factory.visible_playlist + self.factory.db_visible = None + self.assertEqual(visible.playlist, self.user_plist) + self.assertIsNone(self.factory.visible_playlist)