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 <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-04-16 21:28:01 -04:00
parent 298b58a54e
commit edaa275ba5
2 changed files with 125 additions and 3 deletions

View File

@ -4,6 +4,8 @@ from gi.repository import GObject
from . import model from . import model
from .. import db from .. import db
FLAGS = GObject.ParamFlags.READWRITE | GObject.ParamFlags.EXPLICIT_NOTIFY
class Playlist(model.TrackidModel): class Playlist(model.TrackidModel):
"""A TrackidModel with extra Playlist features.""" """A TrackidModel with extra Playlist features."""
@ -12,11 +14,42 @@ class Playlist(model.TrackidModel):
playlist: db.playlist.Playlist = None): playlist: db.playlist.Playlist = None):
"""Initialize the Playlist instance.""" """Initialize the Playlist instance."""
super().__init__(sql=sql) super().__init__(sql=sql)
self.__sort_keys = {}
self.__playlist = None self.__playlist = None
self.__sort_order = None
if playlist is not None: if playlist is not None:
self.playlist = playlist 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: def add_track(self, track: db.tracks.Track) -> None:
"""Add a track to the playlist.""" """Add a track to the playlist."""
if self.__playlist is not None: if self.__playlist is not None:
@ -40,5 +73,27 @@ class Playlist(model.TrackidModel):
@playlist.setter @playlist.setter
def playlist(self, new: db.playlist.Playlist | None) -> None: def playlist(self, new: db.playlist.Playlist | None) -> None:
"""Set a new db playlist to the playlist.""" """Set a new db playlist to the playlist."""
if self.__playlist:
self.__playlist.disconnect_by_func(self.__playlist_notify)
self.__playlist = new 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

View File

@ -1,8 +1,10 @@
# Copyright 2023 (c) Anna Schumaker. # Copyright 2023 (c) Anna Schumaker.
"""Tests our PlaylistModel.""" """Tests our PlaylistModel."""
import pathlib import pathlib
import unittest.mock
import tests.util import tests.util
import emmental.playlist.playlist import emmental.playlist.playlist
from gi.repository import GObject
class TestPlaylist(tests.util.TestCase): class TestPlaylist(tests.util.TestCase):
@ -21,17 +23,32 @@ class TestPlaylist(tests.util.TestCase):
self.track1 = self.sql.tracks.create(self.library, self.track1 = self.sql.tracks.create(self.library,
pathlib.Path("/a/b/1.ogg"), 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, self.track2 = self.sql.tracks.create(self.library,
pathlib.Path("/a/b/2.ogg"), 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): def test_init(self):
"""Test that the Playlist was set up correctly.""" """Test that the Playlist was set up correctly."""
self.assertIsInstance(self.playlist, self.assertIsInstance(self.playlist,
emmental.playlist.model.TrackidModel) emmental.playlist.model.TrackidModel)
self.assertEqual(self.playlist.sql, self.sql) 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__playlist)
self.assertIsNone(self.playlist._Playlist__sort_order)
plist2 = emmental.playlist.playlist.Playlist(self.sql, self.db_plist) plist2 = emmental.playlist.playlist.Playlist(self.sql, self.db_plist)
self.assertEqual(plist2.playlist, 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.assertEqual(self.playlist.index(self.track1), 0)
self.assertIsNone(self.playlist.index(self.track2)) self.assertIsNone(self.playlist.index(self.track2))
self.assertIsNone(super(type(self.playlist), self.playlist).index(0))
self.assertIsNone(self.playlist.index(None)) self.assertIsNone(self.playlist.index(None))
def test_remove_track(self): 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__playlist, self.db_plist)
self.assertEqual(self.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.trackid_set, self.db_plist.tracks)
self.assertEqual(self.playlist.sort_order, self.db_plist.sort_order)
self.playlist.playlist = None self.playlist.playlist = None
self.assertIsNone(self.playlist.playlist) self.assertIsNone(self.playlist.playlist)
self.assertIsNone(self.playlist.trackid_set) 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()