From edaa275ba5bb98af1d1c67e50315bf1423cd7d86 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Sun, 16 Apr 2023 21:28:01 -0400 Subject: [PATCH] playlist: Give the Playlist model a sort-order property And make sure we re-sort the tracks when it changes to match the new order. Signed-off-by: Anna Schumaker --- emmental/playlist/playlist.py | 57 +++++++++++++++++++++++++- tests/playlist/test_playlist.py | 71 ++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/emmental/playlist/playlist.py b/emmental/playlist/playlist.py index 80d85f5..84625bf 100644 --- a/emmental/playlist/playlist.py +++ b/emmental/playlist/playlist.py @@ -4,6 +4,8 @@ from gi.repository import GObject from . import model from .. import db +FLAGS = GObject.ParamFlags.READWRITE | GObject.ParamFlags.EXPLICIT_NOTIFY + class Playlist(model.TrackidModel): """A TrackidModel with extra Playlist features.""" @@ -12,11 +14,42 @@ class Playlist(model.TrackidModel): playlist: db.playlist.Playlist = None): """Initialize the Playlist instance.""" super().__init__(sql=sql) + self.__sort_keys = {} + self.__playlist = None + self.__sort_order = None if playlist is not None: self.playlist = playlist + def __playlist_notify(self, plist: db.playlist.Playlist, param) -> None: + match param.name: + case "sort-order": + self.__sort_order = plist.sort_order + self.on_trackids_reset(plist.tracks) + self.notify("sort-order") + + def do_get_sort_key(self, trackid: int) -> int: + """Get a sort key for the given trackid.""" + if (key := self.__sort_keys.get(trackid)) is None: + if self.__playlist is not None: + self.__sort_keys = self.__playlist.get_track_order() + key = self.__sort_keys.get(trackid, 0) + else: + return trackid + return key + + def on_trackid_removed(self, set: db.tracks.TrackidSet, + trackid: int) -> None: + """Handle the TrackidSet::trackid-removed signal.""" + super().on_trackid_removed(set, trackid) + self.__sort_keys.pop(trackid, None) + + def on_trackids_reset(self, set: db.tracks.TrackidSet) -> None: + """Handle the TrackidSet::trackids-reset signal.""" + self.__sort_keys.clear() + super().on_trackids_reset(set) + def add_track(self, track: db.tracks.Track) -> None: """Add a track to the playlist.""" if self.__playlist is not None: @@ -40,5 +73,27 @@ class Playlist(model.TrackidModel): @playlist.setter def playlist(self, new: db.playlist.Playlist | None) -> None: """Set a new db playlist to the playlist.""" + if self.__playlist: + self.__playlist.disconnect_by_func(self.__playlist_notify) + self.__playlist = new - self.trackid_set = None if new is None else new.tracks + + if new is not None: + self.__playlist.connect("notify", self.__playlist_notify) + self.__sort_order = new.sort_order + self.trackid_set = new.tracks + else: + self.__sort_order = None + self.trackid_set = None + + self.notify("sort-order") + + @GObject.Property(type=str, flags=FLAGS) + def sort_order(self) -> str: + """Get the current sort order.""" + return self.__sort_order + + @sort_order.setter + def sort_order(self, new_order: str) -> None: + if self.__playlist is not None: + self.__playlist.sort_order = new_order diff --git a/tests/playlist/test_playlist.py b/tests/playlist/test_playlist.py index 573ca9e..6783d28 100644 --- a/tests/playlist/test_playlist.py +++ b/tests/playlist/test_playlist.py @@ -1,8 +1,10 @@ # Copyright 2023 (c) Anna Schumaker. """Tests our PlaylistModel.""" import pathlib +import unittest.mock import tests.util import emmental.playlist.playlist +from gi.repository import GObject class TestPlaylist(tests.util.TestCase): @@ -21,17 +23,32 @@ class TestPlaylist(tests.util.TestCase): self.track1 = self.sql.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), - self.medium, self.year, number=1) + self.medium, self.year, + number=1, length=10) self.track2 = self.sql.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), - self.medium, self.year, number=2) + self.medium, self.year, + number=2, length=20) + self.track3 = self.sql.tracks.create(self.library, + pathlib.Path("/a/b/3.ogg"), + self.medium, self.year, + number=3, length=30) + + def test_property_flags(self): + """Test the FLAGS constant.""" + self.assertEqual(emmental.playlist.playlist.FLAGS, + GObject.ParamFlags.READWRITE | + GObject.ParamFlags.EXPLICIT_NOTIFY) def test_init(self): """Test that the Playlist was set up correctly.""" self.assertIsInstance(self.playlist, emmental.playlist.model.TrackidModel) self.assertEqual(self.playlist.sql, self.sql) + self.assertDictEqual(self.playlist._Playlist__sort_keys, {}) + self.assertIsNone(self.playlist._Playlist__playlist) + self.assertIsNone(self.playlist._Playlist__sort_order) plist2 = emmental.playlist.playlist.Playlist(self.sql, self.db_plist) self.assertEqual(plist2.playlist, self.db_plist) @@ -53,6 +70,7 @@ class TestPlaylist(tests.util.TestCase): self.assertEqual(self.playlist.index(self.track1), 0) self.assertIsNone(self.playlist.index(self.track2)) + self.assertIsNone(super(type(self.playlist), self.playlist).index(0)) self.assertIsNone(self.playlist.index(None)) def test_remove_track(self): @@ -72,7 +90,56 @@ class TestPlaylist(tests.util.TestCase): self.assertEqual(self.playlist._Playlist__playlist, self.db_plist) self.assertEqual(self.playlist.playlist, self.db_plist) self.assertEqual(self.playlist.trackid_set, self.db_plist.tracks) + self.assertEqual(self.playlist.sort_order, self.db_plist.sort_order) self.playlist.playlist = None self.assertIsNone(self.playlist.playlist) self.assertIsNone(self.playlist.trackid_set) + + def test_sort_order(self): + """Test the sort-order property.""" + notify = unittest.mock.Mock() + self.playlist.connect("notify::sort-order", notify) + + self.assertIsNone(self.playlist.sort_order) + self.db_plist.add_track(self.track1) + self.db_plist.add_track(self.track2) + self.db_plist.add_track(self.track3) + + self.playlist.playlist = self.db_plist + self.assertEqual(self.playlist.sort_order, self.db_plist.sort_order) + notify.assert_called() + + self.playlist.sort_order = "length DESC" + self.assertEqual(self.db_plist.sort_order, "length DESC") + self.assertDictEqual(self.playlist._Playlist__sort_keys, + {self.track3.trackid: 0, + self.track2.trackid: 1, + self.track1.trackid: 2}) + self.assertListEqual(self.playlist.trackids, + [self.track3.trackid, + self.track2.trackid, + self.track1.trackid]) + + self.db_plist.remove_track(self.track2) + self.assertDictEqual(self.playlist._Playlist__sort_keys, + {self.track3.trackid: 0, + self.track1.trackid: 2}) + + notify.reset_mock() + self.db_plist.sort_order = "user" + self.assertEqual(self.playlist.sort_order, "user") + notify.assert_called() + + notify.reset_mock() + self.playlist.playlist = None + notify.assert_called() + + notify.reset_mock() + self.db_plist.sort_order = "length" + self.assertIsNone(self.playlist.sort_order) + notify.assert_not_called() + + self.playlist.sort_order = "length" + self.assertIsNone(self.playlist.sort_order) + notify.assert_not_called()