From 87606f8fac7398c7d498b723b220c0c1a0777921 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Fri, 26 Aug 2022 14:21:46 -0400 Subject: [PATCH] db: Give Albums knowledge about their Media We create a filter on the Media table for each Album object, but only match Media that have a name set. I also adjust filtering to display Albums that have a matching Medium. Signed-off-by: Anna Schumaker --- emmental/db/albums.py | 28 +++++++++++++++++++++-- emmental/db/emmental.sql | 16 +++++++------ tests/db/test_albums.py | 49 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/emmental/db/albums.py b/emmental/db/albums.py index ed20f84..24ac5a7 100644 --- a/emmental/db/albums.py +++ b/emmental/db/albums.py @@ -3,6 +3,8 @@ import pathlib import sqlite3 from gi.repository import GObject +from gi.repository import Gtk +from .media import Medium from .. import format from . import playlist @@ -16,10 +18,23 @@ class Album(playlist.Playlist): mbid = GObject.Property(type=str) cover = GObject.Property(type=GObject.TYPE_PYOBJECT) + def __init__(self, **kwargs): + """Initialize an Album object.""" + super().__init__(**kwargs) + self.add_children(self.table.sql.media, + Gtk.CustomFilter.new(self.__match_medium)) + + def __match_medium(self, medium: Medium) -> bool: + return medium.albumid == self.albumid and len(medium.name) > 0 + def get_artists(self) -> list[playlist.Playlist]: """Get a list of artists for this album.""" return self.table.get_artists(self) + def get_media(self) -> list[Medium]: + """Get a list of media for this album.""" + return self.table.get_media(self) + @property def primary_key(self) -> int: """Get the Album primary key.""" @@ -51,12 +66,15 @@ class Table(playlist.Table): """Delete an album.""" for artist in album.get_artists(): artist.remove_album(album) + for medium in album.get_media(): + medium.delete() return self.sql("DELETE FROM albums WHERE albumid=?", album.albumid) def do_sql_glob(self, glob: str) -> sqlite3.Cursor: """Search for albums matching the search text.""" - return self.sql("""SELECT albumid FROM albums - WHERE CASEFOLD(name) GLOB ?""", glob) + return self.sql("""SELECT albumid FROM album_artist_view + WHERE CASEFOLD(album) GLOB :glob + OR CASEFOLD(medium) GLOB :glob""", glob=glob) def do_sql_insert(self, name: str, artist: str, release: str, *, mbid: str = "", @@ -99,3 +117,9 @@ class Table(playlist.Table): WHERE albumid=?""", album.albumid).fetchall() artists = [self.sql.artists.rows.get(row["artistid"]) for row in rows] return list(filter(None, artists)) + + def get_media(self, album: Album) -> list[Medium]: + """Get the list of media for this album.""" + rows = self.sql("SELECT mediumid FROM media WHERE albumid=?", + album.albumid) + return [self.sql.media.rows.get(row["mediumid"]) for row in rows] diff --git a/emmental/db/emmental.sql b/emmental/db/emmental.sql index aeb57bd..da018fa 100644 --- a/emmental/db/emmental.sql +++ b/emmental/db/emmental.sql @@ -184,11 +184,11 @@ CREATE TRIGGER media_delete_trigger AFTER DELETE ON media END; -/******************************************* - * * - * Artist <--> Album Linking * - * * - *******************************************/ +/******************************************************* + * * + * Artist <--> Album <--> Medium Linking * + * * + *******************************************************/ CREATE TABLE album_artist_link ( artistid INTEGER NOT NULL REFERENCES artists (artistid) @@ -202,10 +202,12 @@ CREATE TABLE album_artist_link ( CREATE VIEW album_artist_view AS SELECT artistid, artists.name as artist, - albumid, COALESCE(albums.name, "") as album + albumid, COALESCE(albums.name, "") as album, + media.mediumid, COALESCE(media.name, "") as medium FROM artists LEFT JOIN album_artist_link USING (artistid) - LEFT JOIN albums USING (albumid); + LEFT JOIN albums USING (albumid) + LEFT JOIN media USING (albumid); /****************************************** diff --git a/tests/db/test_albums.py b/tests/db/test_albums.py index bbb541b..dd35847 100644 --- a/tests/db/test_albums.py +++ b/tests/db/test_albums.py @@ -4,6 +4,7 @@ import pathlib import unittest.mock import emmental.db import tests.util +from gi.repository import Gtk class TestAlbumObject(tests.util.TestCase): @@ -48,6 +49,30 @@ class TestAlbumObject(tests.util.TestCase): mock.assert_called_with(self.album) self.assertEqual(self.album.parent, 1) + def test_get_media(self): + """Test getting the list of album media.""" + with unittest.mock.patch.object(self.table, "get_media", + return_value=[1, 2, 3]) as mock: + self.assertListEqual(self.album.get_media(), [1, 2, 3]) + mock.assert_called_with(self.album) + + def test_media_model(self): + """Test getting a Gio.ListModel representing this Album's media.""" + self.assertIsInstance(self.album.children, Gtk.FilterListModel) + self.assertIsInstance(self.album.children.get_filter(), + Gtk.CustomFilter) + self.assertEqual(self.album.children.get_model(), self.sql.media) + + album = self.table.create("Test Album", "Album Artist", "2023-03") + medium = self.sql.media.create(album, "Test Medium", number=1) + self.assertTrue(album.children.get_filter().match(medium)) + + medium.albumid = album.albumid + 1 + self.assertFalse(album.children.get_filter().match(medium)) + + medium = self.sql.media.create(album, "", number=2) + self.assertFalse(album.children.get_filter().match(medium)) + class TestAlbumTable(tests.util.TestCase): """Tests our album table.""" @@ -117,11 +142,13 @@ class TestAlbumTable(tests.util.TestCase): """Test deleting an album playlist.""" artist = self.sql.artists.create("Test Artist") album = self.table.create("Test Album", "Album Artist", "2023-03") + medium = self.sql.media.create(album, "Test Medium", number=1) artist.add_album(album) self.assertTrue(album.delete()) self.assertIsNone(self.table.index(album)) self.assertFalse(artist.has_album(album)) + self.assertIsNone(self.sql.media.index(medium)) cur = self.sql("SELECT COUNT(name) FROM albums") self.assertEqual(cur.fetchone()["COUNT(name)"], 0) @@ -139,14 +166,24 @@ class TestAlbumTable(tests.util.TestCase): def test_filter(self): """Test filtering an album playlist.""" - self.table.create("Album 1", "Album Artist", "2023-03") - self.table.create("Album 2", "Album Artist", "2023-03") + artist = self.sql.artists.create("Test Artist") + + album1 = self.table.create("Album 1", "Album Artist", "2023-03") + album2 = self.table.create("Album 2", "Album Artist", "2023-03") + artist.add_album(album1) + artist.add_album(album2) + + self.sql.media.create(album2, "Medium 3", number=3) + self.sql.media.create(album2, "", number=4) self.table.filter("*1", now=True) self.assertSetEqual(self.table.get_filter().keys, {1}) self.table.filter("album*", now=True) self.assertSetEqual(self.table.get_filter().keys, {1, 2}) + self.table.filter("*3", now=True) + self.assertSetEqual(self.table.get_filter().keys, {2}) + def test_get_sort_key(self): """Test the get_sort_key() function.""" album1 = self.table.create("Album 1", "Album Artist", "2023-02") @@ -233,3 +270,11 @@ class TestAlbumTable(tests.util.TestCase): del self.sql.artists.rows[artist1.artistid] self.assertListEqual(self.table.get_artists(album), [artist2]) + + def test_get_media(self): + """Test getting the list of media for an album.""" + album = self.table.create("Test Album", "Album Artist", "2023-03") + medium1 = self.sql.media.create(album, "", number=1) + medium2 = self.sql.media.create(album, "", number=2) + + self.assertListEqual(self.table.get_media(album), [medium1, medium2])