db: Add Track support to Playlist Objects
Playlists use a tracks.TrackidSet to manage a set of trackids representing the Tracks in this Playlist. I have two functions for loading tracks: load_tracks() and reload_tracks(). Calling load_tracks() checks if the tracks have been loaded first before doing any work, but calling reload_tracks() will force the Playlist to go to the database to load the latest tracks. Finally, I add a have-next-track property to the main database connection. This is set to True whenever the active playlist has one or more tracks. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
11560d781e
commit
1b9458c278
|
@ -3,6 +3,7 @@
|
|||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from .tracks import Track, TrackidSet
|
||||
from .. import format
|
||||
from . import table
|
||||
|
||||
|
@ -15,15 +16,28 @@ class Playlist(table.Row):
|
|||
name = GObject.Property(type=str)
|
||||
active = GObject.Property(type=bool, default=False)
|
||||
|
||||
tracks = GObject.Property(type=TrackidSet)
|
||||
n_tracks = GObject.Property(type=int)
|
||||
tracks_loaded = GObject.Property(type=bool, default=False)
|
||||
tracks_movable = GObject.Property(type=bool, default=False)
|
||||
|
||||
children = GObject.Property(type=Gtk.FilterListModel)
|
||||
|
||||
def __init__(self, table: Gio.ListModel, propertyid: int,
|
||||
name: str, **kwargs):
|
||||
"""Initialize a Playlist object."""
|
||||
super().__init__(table=table, propertyid=propertyid,
|
||||
name=name, **kwargs)
|
||||
super().__init__(table=table, propertyid=propertyid, name=name,
|
||||
tracks=TrackidSet(), **kwargs)
|
||||
self.tracks.bind_property("n-trackids", self, "n-tracks")
|
||||
|
||||
def __add_track(self, track: Track) -> bool:
|
||||
self.tracks.add_track(track)
|
||||
return True
|
||||
|
||||
def __remove_track(self, track: Track) -> bool:
|
||||
self.tracks.remove_track(track)
|
||||
self.table.remove_track(self, track)
|
||||
return True
|
||||
|
||||
def add_children(self, child_table: table.Table,
|
||||
child_filter: Gtk.Filter) -> None:
|
||||
|
@ -34,10 +48,48 @@ class Playlist(table.Row):
|
|||
def do_update(self, column: str) -> bool:
|
||||
"""Update a Playlist object."""
|
||||
match column:
|
||||
case "propertyid" | "name" | "n-tracks" | "children": pass
|
||||
case "propertyid" | "name" | "n-tracks" | "children" | \
|
||||
"tracks-loaded" | "tracks-movable": pass
|
||||
case _: return super().do_update(column)
|
||||
return True
|
||||
|
||||
def add_track(self, track: Track, *, idle: bool = False) -> None:
|
||||
"""Add a Track to this Playlist."""
|
||||
if self.table.add_track(self, track):
|
||||
self.table.queue.push(self.__add_track, track, now=not idle)
|
||||
|
||||
def has_track(self, track: Track) -> bool:
|
||||
"""Check if a Track is on this Playlist."""
|
||||
return track in self.tracks
|
||||
|
||||
def load_tracks(self) -> bool:
|
||||
"""Load this Playlist's Tracks (if they haven't been loaded yet)."""
|
||||
if not self.tracks_loaded:
|
||||
self.tracks.trackids = self.table.get_trackids(self)
|
||||
self.tracks_loaded = True
|
||||
return True
|
||||
|
||||
def move_track_down(self, track: Track) -> bool:
|
||||
"""Move a track down in the sort order."""
|
||||
return self.table.move_track_down(self, track)
|
||||
|
||||
def move_track_up(self, track: Track) -> bool:
|
||||
"""Move a track up in the sort order."""
|
||||
return self.table.move_track_up(self, track)
|
||||
|
||||
def reload_tracks(self, *, idle: bool = False) -> None:
|
||||
"""Load this Playlist's Tracks."""
|
||||
self.tracks_loaded = False
|
||||
self.table.queue.push(self.load_tracks, now=not idle)
|
||||
|
||||
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)
|
||||
|
||||
def rename(self, new_name: str) -> bool:
|
||||
"""Rename this playlist."""
|
||||
return self.table.rename(self, new_name)
|
||||
|
||||
@GObject.Property(type=table.Row)
|
||||
def parent(self) -> table.Row | None:
|
||||
"""Get this playlist's parent playlist."""
|
||||
|
|
|
@ -14,20 +14,33 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.table = Gio.ListStore()
|
||||
self.table.add_track = unittest.mock.Mock(return_value=True)
|
||||
self.table.remove_track = unittest.mock.Mock(return_value=True)
|
||||
self.table.move_track_down = unittest.mock.Mock(return_value=True)
|
||||
self.table.move_track_up = unittest.mock.Mock(return_value=True)
|
||||
self.table.get_trackids = unittest.mock.Mock(return_value={1, 2, 3})
|
||||
self.table.queue = emmental.db.idle.Queue()
|
||||
self.table.update = unittest.mock.Mock(return_value=True)
|
||||
|
||||
self.playlist = emmental.db.playlist.Playlist(table=self.table,
|
||||
propertyid=0,
|
||||
name="Test Playlist")
|
||||
self.track = emmental.db.tracks.Track(table=None, trackid=12345)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the Playlist object is configured correctly."""
|
||||
self.assertIsInstance(self.playlist, emmental.db.table.Row)
|
||||
self.assertIsInstance(self.playlist.tracks,
|
||||
emmental.db.tracks.TrackidSet)
|
||||
self.assertEqual(self.playlist.table, self.table)
|
||||
|
||||
self.assertEqual(self.playlist.propertyid, 0)
|
||||
self.assertEqual(self.playlist.name, "Test Playlist")
|
||||
self.assertEqual(self.playlist.n_tracks, 0)
|
||||
self.assertFalse(self.playlist.active)
|
||||
|
||||
self.assertEqual(self.playlist.n_tracks, 0)
|
||||
self.assertFalse(self.playlist.tracks_loaded)
|
||||
|
||||
playlist2 = emmental.db.playlist.Playlist(table=self.table,
|
||||
propertyid=1,
|
||||
name="Test Name", active=1)
|
||||
|
@ -54,7 +67,8 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
"""Test the do_update() function."""
|
||||
for (prop, value) in [("name", "New Name"), ("propertyid", 12345),
|
||||
("children", Gtk.FilterListModel()),
|
||||
("n-tracks", 42)]:
|
||||
("n-tracks", 42), ("tracks-loaded", True),
|
||||
("tracks-movable", True)]:
|
||||
with self.subTest(property=prop):
|
||||
self.playlist.set_property(prop, value)
|
||||
self.table.update.assert_not_called()
|
||||
|
@ -62,6 +76,89 @@ class TestPlaylistRow(unittest.TestCase):
|
|||
self.playlist.active = True
|
||||
self.table.update.assert_called_with(self.playlist, "active", True)
|
||||
|
||||
def test_add_track(self):
|
||||
"""Test adding a track to the playlist."""
|
||||
self.playlist.add_track(self.track, idle=True)
|
||||
self.table.add_track.assert_called_with(self.playlist, self.track)
|
||||
self.assertNotIn(self.track, self.playlist.tracks)
|
||||
self.assertEqual(self.table.queue[0],
|
||||
(self.playlist._Playlist__add_track, self.track))
|
||||
|
||||
self.playlist.add_track(self.track)
|
||||
self.table.add_track.assert_called_with(self.playlist, self.track)
|
||||
self.assertIn(self.track, self.playlist.tracks)
|
||||
self.assertEqual(self.playlist.n_tracks, 1)
|
||||
|
||||
self.playlist.tracks.trackids.clear()
|
||||
self.table.add_track.return_value = False
|
||||
self.playlist.add_track(self.track)
|
||||
self.assertNotIn(self.track, self.playlist.tracks)
|
||||
|
||||
def test_has_track(self):
|
||||
"""Test the playlist has_track() function."""
|
||||
self.assertFalse(self.playlist.has_track(self.track))
|
||||
self.playlist.add_track(self.track)
|
||||
self.assertTrue(self.playlist.has_track(self.track))
|
||||
self.playlist.remove_track(self.track)
|
||||
self.assertFalse(self.playlist.has_track(self.track))
|
||||
|
||||
def test_move_tracks(self):
|
||||
"""Test the move_track_up() and move_track_down() functions."""
|
||||
self.assertFalse(self.playlist.tracks_movable)
|
||||
|
||||
self.assertTrue(self.playlist.move_track_down(self.track))
|
||||
self.table.move_track_down.assert_called_with(self.playlist,
|
||||
self.track)
|
||||
|
||||
self.assertTrue(self.playlist.move_track_up(self.track))
|
||||
self.table.move_track_up.assert_called_with(self.playlist, self.track)
|
||||
|
||||
def test_remove_track(self):
|
||||
"""Test removing a track from the playlist."""
|
||||
self.playlist.tracks.trackids.add(self.track.trackid)
|
||||
self.playlist.remove_track(self.track, idle=True)
|
||||
self.table.remove_track.assert_not_called()
|
||||
self.assertIn(self.track, self.playlist.tracks)
|
||||
self.assertEqual(self.table.queue[0],
|
||||
(self.playlist._Playlist__remove_track, self.track))
|
||||
|
||||
self.playlist.remove_track(self.track)
|
||||
self.table.remove_track.assert_called_with(self.playlist, self.track)
|
||||
self.assertNotIn(self.track, self.playlist.tracks)
|
||||
self.assertEqual(self.playlist.n_tracks, 0)
|
||||
|
||||
self.playlist.tracks.trackids.add(self.track.trackid)
|
||||
self.table.remove_track.return_value = False
|
||||
self.playlist.remove_track(self.track)
|
||||
self.table.remove_track.assert_called_with(self.playlist, self.track)
|
||||
self.assertNotIn(self.track, self.playlist.tracks)
|
||||
|
||||
def test_load_tracks(self):
|
||||
"""Test loading a Playlist's Tracks."""
|
||||
self.assertEqual(self.playlist.n_tracks, 0)
|
||||
self.assertFalse(self.playlist.tracks_loaded)
|
||||
|
||||
self.assertTrue(self.playlist.load_tracks())
|
||||
self.table.get_trackids.assert_called_with(self.playlist)
|
||||
self.assertSetEqual(self.playlist.tracks.trackids, {1, 2, 3})
|
||||
self.assertTrue(self.playlist.tracks_loaded)
|
||||
|
||||
self.table.get_trackids.reset_mock()
|
||||
self.assertTrue(self.playlist.load_tracks())
|
||||
self.table.get_trackids.assert_not_called()
|
||||
|
||||
def test_reload_tracks(self):
|
||||
"""Test reloading a Playlist's Tracks."""
|
||||
self.playlist.tracks_loaded = True
|
||||
self.playlist.reload_tracks(idle=True)
|
||||
self.table.get_trackids.assert_not_called()
|
||||
self.assertEqual(self.table.queue[0], (self.playlist.load_tracks,))
|
||||
|
||||
self.playlist.tracks_loaded = True
|
||||
self.playlist.reload_tracks()
|
||||
self.table.get_trackids.assert_called_with(self.playlist)
|
||||
self.assertSetEqual(self.playlist.tracks.trackids, {1, 2, 3})
|
||||
|
||||
|
||||
class TestPlaylistTable(tests.util.TestCase):
|
||||
"""Tests our Playlist Table."""
|
||||
|
|
Loading…
Reference in New Issue