from typing import List, Optional, Union
from peewee import (
AutoField,
BooleanField,
ForeignKeyField,
IntegerField,
Model,
Query,
SqliteDatabase,
TextField,
prefetch,
)
from .sqlite_extensions import (
CacheConstantsField,
DurationField,
SortedManyToManyField,
TzDateTimeField,
)
database = SqliteDatabase(None)
# Models
# =============================================================================
[docs]class BaseModel(Model):
class Meta:
database = database
[docs]class CacheInfo(BaseModel):
id = AutoField()
valid = BooleanField(default=False)
cache_key = CacheConstantsField()
parameter = TextField(null=True, default="")
# TODO (#2) actually use this for cache expiry.
last_ingestion_time = TzDateTimeField(null=False)
class Meta:
indexes = ((("cache_key", "parameter"), True),)
# Used for cached files.
file_id = TextField(null=True)
file_hash = TextField(null=True)
size = IntegerField(null=True)
path = TextField(null=True)
cache_permanently = BooleanField(null=True)
[docs]class Genre(BaseModel):
name = TextField(unique=True, primary_key=True)
song_count = IntegerField(null=True)
album_count = IntegerField(null=True)
[docs]class Artist(BaseModel):
id = TextField(unique=True, primary_key=True)
name = TextField(null=True)
album_count = IntegerField(null=True)
starred = TzDateTimeField(null=True)
biography = TextField(null=True)
music_brainz_id = TextField(null=True)
last_fm_url = TextField(null=True)
_artist_image_url = ForeignKeyField(CacheInfo, null=True)
@property
def artist_image_url(self) -> Optional[str]:
try:
return self._artist_image_url.file_id
except Exception:
return None
@property
def similar_artists(self) -> Query:
return (
Artist.select()
.join(SimilarArtist, on=(SimilarArtist.similar_artist == Artist.id))
.where(SimilarArtist.artist == self.id)
.order_by(SimilarArtist.order)
)
[docs]class SimilarArtist(BaseModel):
artist = ForeignKeyField(Artist)
similar_artist = ForeignKeyField(Artist)
order = IntegerField()
class Meta:
# The whole thing is unique.
indexes = ((("artist", "similar_artist", "order"), True),)
[docs]class Album(BaseModel):
id = TextField(unique=True, primary_key=True)
created = TzDateTimeField(null=True)
duration = DurationField(null=True)
name = TextField(null=True)
play_count = IntegerField(null=True)
song_count = IntegerField(null=True)
starred = TzDateTimeField(null=True)
year = IntegerField(null=True)
artist = ForeignKeyField(Artist, null=True, backref="albums")
genre = ForeignKeyField(Genre, null=True, backref="albums")
_cover_art = ForeignKeyField(CacheInfo, null=True)
@property
def cover_art(self) -> Optional[str]:
try:
return self._cover_art.file_id
except Exception:
return None
@property
def songs(self) -> List["Song"]:
albums = Album.select()
artists = Album.select()
return sorted(
prefetch(
# _songs is a backref from Song
self._songs, # type: ignore
albums,
artists,
),
key=lambda s: (s.disc_number or 1, s.track),
)
[docs]class AlbumQueryResult(BaseModel):
query_hash = TextField(primary_key=True)
albums = SortedManyToManyField(Album)
[docs]class IgnoredArticle(BaseModel):
name = TextField(unique=True, primary_key=True)
[docs]class Directory(BaseModel):
id = TextField(unique=True, primary_key=True)
name = TextField(null=True)
parent_id = TextField(null=True)
_children: Optional[List[Union["Directory", "Song"]]] = None
@property
def children(self) -> List[Union["Directory", "Song"]]:
if not self._children:
self._children = list(Directory.select().where(Directory.parent_id == self.id)) + list(
Song.select().where(Song.parent_id == self.id)
)
return self._children
@children.setter
def children(self, value: List[Union["Directory", "Song"]]):
self._children = value
[docs]class Song(BaseModel):
id = TextField(unique=True, primary_key=True)
title = TextField()
duration = DurationField(null=True)
parent_id = TextField(null=True)
album = ForeignKeyField(Album, null=True, backref="_songs")
artist = ForeignKeyField(Artist, null=True)
genre = ForeignKeyField(Genre, null=True, backref="songs")
# figure out how to deal with different transcodings, etc.
file = ForeignKeyField(CacheInfo, null=True)
@property
def size(self) -> Optional[int]:
try:
return self.file.size
except Exception:
return None
@property
def path(self) -> Optional[str]:
try:
return self.file.path
except Exception:
return None
_cover_art = ForeignKeyField(CacheInfo, null=True)
@property
def cover_art(self) -> Optional[str]:
try:
return self._cover_art.file_id
except Exception:
return None
track = IntegerField(null=True)
disc_number = IntegerField(null=True)
year = IntegerField(null=True)
user_rating = IntegerField(null=True)
starred = TzDateTimeField(null=True)
[docs]class Playlist(BaseModel):
id = TextField(unique=True, primary_key=True)
name = TextField()
comment = TextField(null=True)
owner = TextField(null=True)
song_count = IntegerField(null=True)
duration = DurationField(null=True)
created = TzDateTimeField(null=True)
changed = TzDateTimeField(null=True)
public = BooleanField(null=True)
_songs = SortedManyToManyField(Song, backref="playlists")
@property
def songs(self) -> List[Song]:
albums = Album.select()
artists = Album.select()
return prefetch(self._songs, albums, artists)
_cover_art = ForeignKeyField(CacheInfo, null=True)
@property
def cover_art(self) -> Optional[str]:
try:
return self._cover_art.file_id
except Exception:
return None
[docs]class Version(BaseModel):
id = IntegerField(unique=True, primary_key=True)
major = IntegerField()
minor = IntegerField()
patch = IntegerField()
[docs] @staticmethod
def is_less_than(semver: str) -> bool:
major, minor, patch = map(int, semver.split("."))
version, created = Version.get_or_create(
id=0, defaults={"major": major, "minor": minor, "patch": patch}
)
if created:
# There was no version before, definitely out-of-date
return True
return version.major < major or version.minor < minor or version.patch < patch
[docs] @staticmethod
def update_version(semver: str):
major, minor, patch = map(int, semver.split("."))
Version.update(major=major, minor=minor, patch=patch)
ALL_TABLES = (
Album,
AlbumQueryResult,
AlbumQueryResult.albums.get_through_model(),
Artist,
CacheInfo,
Directory,
Genre,
IgnoredArticle,
Playlist,
Playlist._songs.get_through_model(),
SimilarArtist,
Song,
Version,
)