db: Add a Genre Table

This table allows us to work with Genre playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-08-30 14:43:46 -04:00
parent aeeee1417a
commit 2dc5d9ed0a
5 changed files with 223 additions and 2 deletions

View File

@ -6,6 +6,7 @@ from typing import Generator
from . import albums
from . import artists
from . import connection
from . import genres
from . import playlist
from . import media
from . import playlists
@ -35,6 +36,7 @@ class Connection(connection.Connection):
self.artists = artists.Table(self)
self.albums = albums.Table(self, queue=self.artists.queue)
self.media = media.Table(self, queue=self.artists.queue)
self.genres = genres.Table(self)
def close(self) -> None:
"""Close the database connection."""
@ -57,7 +59,8 @@ class Connection(connection.Connection):
def playlist_tables(self) -> Generator[playlist.Table, None, None]:
"""Iterate over each playlist table."""
for tbl in [self.playlists, self.artists, self.albums, self.media]:
for tbl in [self.playlists, self.artists, self.albums, self.media,
self.genres]:
yield tbl
def set_active_playlist(self, plist: playlist.Playlist) -> None:

View File

@ -210,6 +210,38 @@ CREATE VIEW album_artist_view AS
LEFT JOIN media USING (albumid);
/************************
* *
* Genres *
* *
************************/
CREATE TABLE genres (
genreid INTEGER PRIMARY KEY,
propertyid INTEGER REFERENCES playlist_properties (propertyid)
ON DELETE CASCADE
ON UPDATE CASCADE,
name TEXT NOT NULL UNIQUE COLLATE NOCASE
);
CREATE VIEW genres_view AS
SELECT genreid, propertyid, name, active
FROM genres
JOIN playlist_properties USING (propertyid);
CREATE TRIGGER genres_insert_trigger AFTER INSERT ON genres
BEGIN
INSERT INTO playlist_properties (active) VALUES (False);
UPDATE genres SET propertyid = last_insert_rowid()
WHERE genreid = NEW.genreid;
END;
CREATE TRIGGER genres_delete_trigger AFTER DELETE ON genres
BEGIN
DELETE FROM playlist_properties WHERE propertyid = OLD.propertyid;
END;
/******************************************
* *
* Create Default Playlists *

59
emmental/db/genres.py Normal file
View File

@ -0,0 +1,59 @@
# Copyright 2022 (c) Anna Schumaker
"""A custom Gio.ListModel for genres."""
import sqlite3
from gi.repository import GObject
from .. import format
from . import playlist
class Genre(playlist.Playlist):
"""Our custom Genre object representing a single genre."""
genreid = GObject.Property(type=int)
@property
def primary_key(self) -> int:
"""Get this Gener's primary key."""
return self.genreid
class Table(playlist.Table):
"""Our Genre Table."""
def do_construct(self, **kwargs) -> Genre:
"""Construct a new Genre."""
return Genre(**kwargs)
def do_get_sort_key(self, genre: Genre) -> tuple[tuple[str], int]:
"""Get a sort key for the Genre."""
return (format.sort_key(genre.name), genre.genreid)
def do_sql_delete(self, genre: Genre) -> sqlite3.Cursor:
"""Delete a genre."""
return self.sql("DELETE FROM genres WHERE genreid=?", genre.genreid)
def do_sql_glob(self, glob: str) -> sqlite3.Cursor:
"""Search for genres matching the search text."""
return self.sql("""SELECT genreid FROM genres
WHERE CASEFOLD(name) GLOB ?""", glob)
def do_sql_insert(self, name: str) -> sqlite3.Cursor | None:
"""Create a new genre."""
if cur := self.sql("INSERT INTO genres (name) VALUES (?)", name):
return self.sql("SELECT * FROM genres_view WHERE genreid=?",
cur.lastrowid)
def do_sql_select_all(self) -> sqlite3.Cursor:
"""Load genres from the database."""
return self.sql("SELECT * FROM genres_view")
def do_sql_select_one(self, name: str) -> sqlite3.Cursor:
"""Look up a genre by name."""
return self.sql("SELECT genreid FROM genres WHERE CASEFOLD(name)=?",
name.casefold())
def do_sql_update(self, genre: playlist.Playlist,
column: str, newval) -> sqlite3.Cursor:
"""Update a genre."""
return self.sql(f"UPDATE genres SET {column}=? WHERE genreid=?",
newval, genre.genreid)

View File

@ -44,13 +44,15 @@ class TestConnection(tests.util.TestCase):
self.assertIsInstance(self.sql.artists, emmental.db.artists.Table)
self.assertIsInstance(self.sql.albums, emmental.db.albums.Table)
self.assertIsInstance(self.sql.media, emmental.db.media.Table)
self.assertIsInstance(self.sql.genres, emmental.db.genres.Table)
self.assertEqual(self.sql.albums.queue, self.sql.artists.queue)
self.assertEqual(self.sql.media.queue, self.sql.artists.queue)
self.assertListEqual([tbl for tbl in self.sql.playlist_tables()],
[self.sql.playlists, self.sql.artists,
self.sql.albums, self.sql.media])
self.sql.albums, self.sql.media,
self.sql.genres])
def test_load(self):
"""Check that calling load() loads the tables."""

125
tests/db/test_genres.py Normal file
View File

@ -0,0 +1,125 @@
# Copyright 2022 (c) Anna Schumaker
"""Tests our genre Gio.ListModel."""
import emmental.db
import tests.util
class TestGenreObject(tests.util.TestCase):
"""Tests our genre object."""
def setUp(self):
"""Set up common variables."""
tests.util.TestCase.setUp(self)
self.table = self.sql.genres
self.genre = emmental.db.genres.Genre(table=self.table, genreid=123,
propertyid=456, name="Genre")
def test_init(self):
"""Test that the Genre is set up properly."""
self.assertIsInstance(self.genre, emmental.db.playlist.Playlist)
self.assertEqual(self.genre.table, self.table)
self.assertEqual(self.genre.propertyid, 456)
self.assertEqual(self.genre.genreid, 123)
self.assertEqual(self.genre.primary_key, 123)
self.assertIsNone(self.genre.parent)
class TestGenreTable(tests.util.TestCase):
"""Tests our genre table."""
def setUp(self):
"""Set up common variables."""
tests.util.TestCase.setUp(self)
self.table = self.sql.genres
def test_init(self):
"""Test that the genre model is configured correctly."""
self.assertIsInstance(self.table, emmental.db.playlist.Table)
self.assertEqual(len(self.table), 0)
def test_construct(self):
"""Test constructing a new genre playlist."""
genre = self.table.construct(genreid=1, propertyid=1, name="Genre")
self.assertIsInstance(genre, emmental.db.genres.Genre)
self.assertEqual(genre.table, self.table)
self.assertEqual(genre.propertyid, 1)
self.assertEqual(genre.genreid, 1)
self.assertEqual(genre.name, "Genre")
self.assertFalse(genre.active)
def test_create(self):
"""Test creating a genre playlist."""
genre = self.table.create("Test Genre")
self.assertIsInstance(genre, emmental.db.genres.Genre)
self.assertEqual(genre.name, "Test Genre")
cur = self.sql("SELECT COUNT(name) FROM genres")
self.assertEqual(cur.fetchone()["COUNT(name)"], 1)
row = self.sql("""SELECT COUNT(*) FROM playlist_properties
WHERE propertyid=?""", genre.propertyid).fetchone()
self.assertEqual(row["COUNT(*)"], 1)
self.assertIsNone(self.table.create("Test Genre"))
def test_delete(self):
"""Test deleting a genre playlist."""
genre = self.table.create("Test Genre")
self.assertTrue(genre.delete())
self.assertIsNone(self.table.index(genre))
cur = self.sql("SELECT COUNT(name) FROM genres")
self.assertEqual(cur.fetchone()["COUNT(name)"], 0)
self.assertEqual(len(self.table), 0)
self.assertIsNone(self.table.get_item(0))
row = self.sql("""SELECT COUNT(*) FROM playlist_properties
WHERE propertyid=?""", genre.propertyid).fetchone()
self.assertEqual(row["COUNT(*)"], 0)
self.assertFalse(genre.delete())
def test_filter(self):
"""Test filtering a genre playlist."""
self.table.create("Genre 1")
self.table.create("Genre 2")
self.table.filter("*1", now=True)
self.assertSetEqual(self.table.get_filter().keys, {1})
self.table.filter("genre*", now=True)
self.assertSetEqual(self.table.get_filter().keys, {1, 2})
def test_get_sort_key(self):
"""Test the get_sort_key() function."""
genre = self.table.create("Genre 1")
self.assertTupleEqual(self.table.get_sort_key(genre),
(("genre", "1"), genre.genreid))
def test_load(self):
"""Test loading genres from the database."""
self.table.create("Genre 1")
self.table.create("Genre 2")
genres2 = emmental.db.genres.Table(self.sql)
self.assertEqual(len(genres2), 0)
genres2.load(now=True)
self.assertEqual(len(genres2), 2)
self.assertEqual(genres2.get_item(0).name, "Genre 1")
self.assertEqual(genres2.get_item(1).name, "Genre 2")
def test_lookup(self):
"""Test looking up genre playlists."""
genre = self.table.create("Test Genre")
self.assertEqual(self.table.lookup("Test Genre"), genre)
self.assertEqual(self.table.lookup("test genre"), genre)
self.assertIsNone(self.table.lookup("No Genre"))
def test_update(self):
"""Test updating genre attributes."""
genre = self.table.create("Test Genre")
genre.active = True
row = self.sql("""SELECT active FROM playlist_properties
WHERE propertyid=?""", genre.propertyid).fetchone()
self.assertEqual(row["active"], True)