From fdfc12fbd291f1053dd30598dd0cb4f7dcca0b12 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Sat, 15 Apr 2023 13:31:15 -0400 Subject: [PATCH] playlist: Create a Playlist Factory object The playlist factory has properties for the currently selected, currently active, and previous playlists. It will eventually create Gio.ListModel instances representing the tracks in each of these playlists, in sorted order. Signed-off-by: Anna Schumaker --- emmental/__init__.py | 11 ++++++ emmental/playlist/__init__.py | 27 +++++++++++++++ tests/playlist/__init__.py | 2 ++ tests/playlist/test_factory.py | 63 ++++++++++++++++++++++++++++++++++ tests/test_emmental.py | 22 ++++++++++++ 5 files changed, 125 insertions(+) create mode 100644 emmental/playlist/__init__.py create mode 100644 tests/playlist/__init__.py create mode 100644 tests/playlist/test_factory.py diff --git a/emmental/__init__.py b/emmental/__init__.py index 5c4d3a5..61b4902 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -9,6 +9,7 @@ from . import header from . import mpris2 from . import nowplaying from . import options +from . import playlist from . import sidebar from . import window from gi.repository import GObject @@ -25,6 +26,7 @@ class Application(Adw.Application): """Our custom Adw.Application.""" db = GObject.Property(type=db.Connection) + factory = GObject.Property(type=playlist.Factory) mpris = GObject.Property(type=mpris2.Connection) player = GObject.Property(type=audio.Player) win = GObject.Property(type=window.Window) @@ -162,6 +164,13 @@ class Application(Adw.Application): self.mpris.player.connect("SetPosition", self.__set_position) self.mpris.player.connect("Stop", self.player.stop) + def connect_playlist_factory(self) -> None: + """Connect the playlist factory properties.""" + self.db.playlists.bind_property("previous", + self.factory, "db-previous") + self.win.sidebar.bind_property("selected-playlist", + self.factory, "db-visible") + def do_handle_local_options(self, opts: GLib.VariantDict) -> int: """Handle any command line options.""" if opts.contains("version"): @@ -175,6 +184,7 @@ class Application(Adw.Application): Adw.Application.do_startup(self) self.db = db.Connection() self.mpris = mpris2.Connection() + self.factory = playlist.Factory(self.db) self.player = audio.Player() gsetup.add_style() @@ -186,6 +196,7 @@ class Application(Adw.Application): self.win = self.build_window() self.add_window(self.win) self.connect_mpris2() + self.connect_playlist_factory() def do_activate(self) -> None: """Handle the Adw.Application::activate signal.""" diff --git a/emmental/playlist/__init__.py b/emmental/playlist/__init__.py new file mode 100644 index 0000000..11f7642 --- /dev/null +++ b/emmental/playlist/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2023 (c) Anna Schumaker. +"""An object for managing the visible and active playlists.""" +from gi.repository import GObject +from .. import db + + +class Factory(GObject.GObject): + """Our playlist model factory.""" + + sql = GObject.Property(type=db.Connection) + + db_active = GObject.Property(type=db.playlist.Playlist) + db_previous = GObject.Property(type=db.playlist.Playlist) + db_visible = GObject.Property(type=db.playlist.Playlist) + + def __init__(self, sql: db.Connection): + """Initialize the Playlist Factory.""" + super().__init__(sql=sql) + self.sql.bind_property("active-playlist", self, "db-active") + self.connect("notify", self.__notify_db_playlists) + + def __notify_db_playlists(self, factory: GObject.GObject, param) -> None: + match param.name: + case "db-active" | "db-previous" | "db-visible": + plist = self.get_property(param.name) + name = "" if plist is None else plist.name + print(f"factory: {param.name[3:]} playlist is:", name) diff --git a/tests/playlist/__init__.py b/tests/playlist/__init__.py new file mode 100644 index 0000000..d645978 --- /dev/null +++ b/tests/playlist/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Needed to fix the unique basename problem with pytest.""" diff --git a/tests/playlist/test_factory.py b/tests/playlist/test_factory.py new file mode 100644 index 0000000..0b7e67d --- /dev/null +++ b/tests/playlist/test_factory.py @@ -0,0 +1,63 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Test our Playlist Manager object.""" +import io +import unittest.mock +import emmental.playlist +import tests.util +from gi.repository import GObject + + +@unittest.mock.patch("sys.stdout", new_callable=io.StringIO) +class TestFactory(tests.util.TestCase): + """Test the Playlist Factory class.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.sql.playlists.load(now=True) + self.user_plist = self.sql.playlists.create("User Playlist") + self.factory = emmental.playlist.Factory(self.sql) + + def test_init(self, mock_stdout: io.StringIO): + """Check that the Playlist Factory is set up properly.""" + self.assertIsInstance(self.factory, GObject.GObject) + self.assertEqual(self.factory.sql, self.sql) + + def test_active(self, mock_stdout: io.StringIO): + """Test the active playlist property.""" + self.assertIsNone(self.factory.db_active) + + self.sql.set_active_playlist(self.user_plist) + self.assertEqual(self.factory.db_active, self.user_plist) + self.assertRegex(mock_stdout.getvalue(), + "factory: active playlist is: User Playlist") + + self.sql.set_active_playlist(None) + self.assertIsNone(self.factory.db_active) + self.assertRegex(mock_stdout.getvalue(), "active playlist is: ") + + def test_previous(self, mock_stdout: io.StringIO): + """Test the previous playlist property.""" + self.assertIsNone(self.factory.db_previous) + + self.factory.db_previous = self.sql.playlists.previous + self.assertEqual(self.factory.db_previous, self.sql.playlists.previous) + self.assertRegex(mock_stdout.getvalue(), + r"factory: previous playlist is: Previous Tracks") + + self.factory.db_previous = None + self.assertRegex(mock_stdout.getvalue(), + "factory: previous playlist is: ") + + def test_visible(self, mock_stdout: io.StringIO): + """Test the visible playlist property.""" + self.assertIsNone(self.factory.db_visible) + + self.factory.db_visible = self.user_plist + self.assertEqual(self.factory.db_visible, self.user_plist) + self.assertRegex(mock_stdout.getvalue(), + "factory: visible playlist is: User Playlist") + + self.factory.db_visible = None + self.assertRegex(mock_stdout.getvalue(), + "factory: visible playlist is: ") diff --git a/tests/test_emmental.py b/tests/test_emmental.py index 57c8221..53e1c0c 100644 --- a/tests/test_emmental.py +++ b/tests/test_emmental.py @@ -45,6 +45,7 @@ class TestEmmental(unittest.TestCase): """Test that the startup signal works as expected.""" self.assertIsNone(self.application.db) self.assertIsNone(self.application.mpris) + self.assertIsNone(self.application.factory) self.assertIsNone(self.application.player) self.assertIsNone(self.application.win) @@ -53,6 +54,8 @@ class TestEmmental(unittest.TestCase): self.assertIsInstance(self.application.mpris, emmental.mpris2.Connection) self.assertIsInstance(self.application.player, emmental.audio.Player) + self.assertIsInstance(self.application.factory, + emmental.playlist.Factory) self.assertIsInstance(self.application.win, emmental.window.Window) mock_startup.assert_called() @@ -154,6 +157,25 @@ class TestEmmental(unittest.TestCase): self.assertEqual(win.sidebar.sql, self.application.db) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) + def test_playlist_factory(self, mock_stdout: io.StringIO): + """Test that the Playlist Factory is wired up properly.""" + self.application.db = emmental.db.Connection() + self.application.player = emmental.audio.Player() + self.application.factory = emmental.playlist.Factory( + self.application.db) + self.application.win = self.application.build_window() + self.application.connect_playlist_factory() + + self.application.db.playlists.load(now=True) + playlist = self.application.db.playlists.create("Test Playlist") + + self.application.win.sidebar.selected_playlist = playlist + self.assertEqual(self.application.factory.db_visible, playlist) + + self.assertEqual(self.application.factory.db_previous, + self.application.db.playlists.previous) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) def test_replaygain(self, mock_stdout: io.StringIO): """Test setting replaygain modes."""