diff --git a/emmental/db/tracks.py b/emmental/db/tracks.py new file mode 100644 index 0000000..63549d3 --- /dev/null +++ b/emmental/db/tracks.py @@ -0,0 +1,64 @@ +# Copyright 2022 (c) Anna Schumaker. +"""A custom Gio.ListModel for working with tracks.""" +from gi.repository import GObject +from . import table + + +PLAYED_THRESHOLD = 2 / 3 + + +class Track(table.Row): + """Our custom Track object.""" + + trackid = GObject.Property(type=int) + libraryid = GObject.Property(type=int) + mediumid = GObject.Property(type=int) + year = GObject.Property(type=int) + + active = GObject.Property(type=bool, default=False) + favorite = GObject.Property(type=bool, default=False) + + path = GObject.Property(type=GObject.TYPE_PYOBJECT) + mbid = GObject.Property(type=str) + title = GObject.Property(type=str) + artist = GObject.Property(type=str) + number = GObject.Property(type=int) + length = GObject.Property(type=float) + mtime = GObject.Property(type=float) + playcount = GObject.Property(type=int) + added = GObject.Property(type=GObject.TYPE_PYOBJECT) + + laststarted = GObject.Property(type=GObject.TYPE_PYOBJECT) + lastplayed = GObject.Property(type=GObject.TYPE_PYOBJECT) + restarted = GObject.Property(type=GObject.TYPE_PYOBJECT) + + def do_update(self, column: str) -> bool: + """Update a Track object.""" + match column: + case "trackid" | "libraryid" | "active" | "path" | "playcount" | \ + "laststarted" | "lastplayed" | "restarted": pass + case _: return super().do_update(column) + return True + + def get_library(self) -> table.Row | None: + """Get the Library associated with this Track.""" + return self.table.sql.libraries.rows.get(self.libraryid) + + def get_medium(self) -> table.Row | None: + """Get the Medium associated with this Track.""" + return self.table.sql.media.rows.get(self.mediumid) + + def get_year(self) -> table.Row | None: + """Get the Year associated with this Track.""" + return self.table.sql.years.rows.get(self.year) + + def update_properties(self, **kwargs) -> None: + """Update one or more of this Track's properties.""" + for (property, newval) in kwargs.items(): + if self.get_property(property) != newval: + self.set_property(property, newval) + + @property + def primary_key(self) -> int: + """Get the primary key for this Track.""" + return self.trackid diff --git a/tests/db/test_tracks.py b/tests/db/test_tracks.py new file mode 100644 index 0000000..9dc5f5c --- /dev/null +++ b/tests/db/test_tracks.py @@ -0,0 +1,105 @@ +# Copyright 2022 (c) Anna Schumaker. +"""Tests our track Gio.ListModel.""" +import datetime +import pathlib +import emmental.db.tracks +import tests.util +import unittest.mock +from gi.repository import Gio + + +class TestTrackObject(tests.util.TestCase): + """Tests our track object.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.table = Gio.ListStore() + self.table.sql = self.sql + self.table.update = unittest.mock.Mock() + + self.library = self.sql.libraries.create(pathlib.Path("/a/b")) + self.album = self.sql.albums.create("Test Album", "Test Artist", + release="1988-06") + self.medium = self.sql.media.create(self.album, "", number=1) + self.year = self.sql.years.create(1988) + + self.track = emmental.db.tracks.Track(trackid=12345, table=self.table, + libraryid=self.library.libraryid, + mediumid=self.medium.mediumid, + year=self.year.year, + path=pathlib.Path("/a/b/c.ogg")) + + def test_constants(self): + """Test constant values.""" + self.assertEqual(emmental.db.tracks.PLAYED_THRESHOLD, (2 / 3)) + + def test_init(self): + """Test that the Track is set up properly.""" + self.assertIsInstance(self.track, emmental.db.table.Row) + self.assertEqual(self.track.table, self.table) + self.assertEqual(self.track.trackid, 12345) + self.assertEqual(self.track.primary_key, 12345) + self.assertEqual(self.track.libraryid, self.library.libraryid) + self.assertEqual(self.track.mediumid, self.medium.mediumid) + self.assertEqual(self.track.year, self.year.year) + + self.assertFalse(self.track.active) + self.assertFalse(self.track.favorite) + + self.assertEqual(self.track.path, pathlib.Path("/a/b/c.ogg")) + self.assertEqual(self.track.mbid, "") + self.assertEqual(self.track.title, "") + self.assertEqual(self.track.artist, "") + self.assertEqual(self.track.number, 0) + self.assertEqual(self.track.length, 0.0) + self.assertEqual(self.track.mtime, 0.0) + self.assertEqual(self.track.playcount, 0) + + self.assertIsNone(self.track.added) + self.assertIsNone(self.track.laststarted) + self.assertIsNone(self.track.lastplayed) + self.assertIsNone(self.track.restarted) + + def test_get_library(self): + """Test getting the Library associated with a Track.""" + self.assertEqual(self.track.get_library(), self.library) + + def test_get_medium(self): + """Test getting a Medium playlist.""" + self.assertEqual(self.track.get_medium(), self.medium) + + def test_get_year(self): + """Test getting a Year playlist.""" + self.assertEqual(self.track.get_year(), self.year) + + def test_update_properties(self): + """Test updating track properties.""" + now = datetime.datetime.now() + self.track.update_properties(trackid=1, libraryid=1, active=True, + path=pathlib.Path("/a/b/c.ogg"), + playcount=1, laststarted=now, + lastplayed=now, restarted=now) + self.table.update.assert_not_called() + + self.track.update_properties(mediumid=2, favorite=True, year=1985, + mbid="ab-cd-ef", title="New Title", + artist="New Artist", number=2, + length=12.345, mtime=67.890) + self.table.update.assert_has_calls( + [unittest.mock.call(self.track, "mediumid", 2), + unittest.mock.call(self.track, "favorite", True), + unittest.mock.call(self.track, "year", 1985), + unittest.mock.call(self.track, "mbid", "ab-cd-ef"), + unittest.mock.call(self.track, "title", "New Title"), + unittest.mock.call(self.track, "artist", "New Artist"), + unittest.mock.call(self.track, "number", 2), + unittest.mock.call(self.track, "length", 12.345), + unittest.mock.call(self.track, "mtime", 67.890)]) + + self.table.update.reset_mock() + self.track.update_properties(mediumid=2, favorite=True, year=1985, + mbid="ab-cd-ef", title="New Title", + artist="New Artist", number=2, + length=12.345, mtime=67.890) + self.table.update.assert_not_called()