1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-22 08:56:17 +00:00

Redefined models using peewee

Obviously untested, and breaks everything 🙃
This commit is contained in:
Alban Féron 2022-11-27 16:37:56 +01:00
parent 9db3549734
commit 0b6891a5c4
No known key found for this signature in database
GPG Key ID: 8CE0313646D16165
2 changed files with 126 additions and 193 deletions

View File

@ -53,7 +53,7 @@ python_requires = >=3.6,<3.11
install_requires =
click
flask >=0.11
pony >=0.7.6
peewee
Pillow
requests >=1.0.0
mediafile

View File

@ -1,7 +1,7 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2013-2020 Alban 'spl0k' Féron
# Copyright (C) 2013-2022 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
@ -13,11 +13,20 @@ import time
from datetime import datetime
from hashlib import sha1
from pony.orm import Database, Required, Optional, Set, PrimaryKey, LongStr
from pony.orm import ObjectNotFound, DatabaseError
from pony.orm import buffer
from pony.orm import min, avg, sum, count, exists
from pony.orm import db_session
from peewee import (
AutoField,
BinaryUUIDField,
BlobField,
BooleanField,
CharField,
DateTimeField,
FixedCharField,
ForeignKeyField,
IntegerField,
TextField,
)
from peewee import CompositeKey
from playhouse.flask_utils import FlaskDB
from urllib.parse import urlparse, parse_qsl
from uuid import UUID, uuid4
@ -28,22 +37,16 @@ def now():
return datetime.now().replace(microsecond=0)
metadb = Database()
def PrimaryKeyField(**kwargs):
return BinaryUUIDField(primary_key=True, default=uuid4, **kwargs)
class Meta(metadb.Entity):
_table_ = "meta"
key = PrimaryKey(str, 32)
value = Required(str, 256)
db = FlaskDB()
db = Database()
@db.on_connect(provider="sqlite")
def sqlite_case_insensitive_like(db, connection):
cursor = connection.cursor()
cursor.execute("PRAGMA case_sensitive_like = OFF")
class Meta(db.Model):
key = CharField(32, primary_key=True)
value = CharField(256)
class PathMixin:
@ -53,43 +56,32 @@ class PathMixin:
path = kwargs.pop("path", None)
if path:
kwargs["_path_hash"] = sha1(path.encode("utf-8")).digest()
return db.Entity.get.__func__(cls, *args, **kwargs)
return db.Model.get.__func__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
path = kwargs["path"]
kwargs["_path_hash"] = sha1(path.encode("utf-8")).digest()
db.Entity.__init__(self, *args, **kwargs)
db.Model.__init__(self, *args, **kwargs)
def __setattr__(self, attr, value):
db.Entity.__setattr__(self, attr, value)
db.Model.__setattr__(self, attr, value)
if attr == "path":
db.Entity.__setattr__(
db.Model.__setattr__(
self, "_path_hash", sha1(value.encode("utf-8")).digest()
)
class Folder(PathMixin, db.Entity):
_table_ = "folder"
class Folder(PathMixin, db.Model):
id = AutoField()
root = BooleanField()
name = CharField()
path = CharField(4096) # unique
_path_hash = BlobField(column_name="path_hash", unique=True)
created = DateTimeField(default=now)
cover_art = CharField(null=True)
last_scan = IntegerField(default=0)
id = PrimaryKey(int, auto=True)
root = Required(bool, default=False)
name = Required(str, autostrip=False)
path = Required(str, 4096, autostrip=False) # unique
_path_hash = Required(buffer, column="path_hash")
created = Required(datetime, precision=0, default=now)
cover_art = Optional(str, nullable=True, autostrip=False)
last_scan = Required(int, default=0)
parent = Optional(lambda: Folder, reverse="children", column="parent_id")
children = Set(lambda: Folder, reverse="parent")
__alltracks = Set(
lambda: Track, lazy=True, reverse="root_folder"
) # Never used, hide it. Could be huge, lazy load
tracks = Set(lambda: Track, reverse="folder")
stars = Set(lambda: StarredFolder)
ratings = Set(lambda: RatingFolder)
parent = ForeignKeyField("self", null=True, backref="children")
def as_subsonic_child(self, user):
info = {
@ -172,15 +164,9 @@ class Folder(PathMixin, db.Entity):
return total
class Artist(db.Entity):
_table_ = "artist"
id = PrimaryKey(UUID, default=uuid4)
name = Required(str) # unique
albums = Set(lambda: Album)
tracks = Set(lambda: Track)
stars = Set(lambda: StarredArtist)
class Artist(db.Model):
id = PrimaryKeyField()
name = CharField()
def as_subsonic_artist(self, user):
info = {
@ -206,15 +192,10 @@ class Artist(db.Entity):
).delete()
class Album(db.Entity):
_table_ = "album"
id = PrimaryKey(UUID, default=uuid4)
name = Required(str)
artist = Required(Artist, column="artist_id")
tracks = Set(lambda: Track)
stars = Set(lambda: StarredAlbum)
class Album(db.Model):
id = PrimaryKeyField()
name = CharField()
artist = ForeignKeyField(Artist, backref="albums")
def as_subsonic_album(self, user): # "AlbumID3" type in XSD
info = {
@ -263,38 +244,31 @@ class Album(db.Entity):
).delete()
class Track(PathMixin, db.Entity):
_table_ = "track"
class Track(PathMixin, db.Model):
id = PrimaryKeyField()
disc = IntegerField()
number = IntegerField()
title = CharField()
year = IntegerField(null=True)
genre = CharField(null=True)
duration = IntegerField()
has_art = BooleanField(default=False)
id = PrimaryKey(UUID, default=uuid4)
disc = Required(int)
number = Required(int)
title = Required(str)
year = Optional(int)
genre = Optional(str, nullable=True)
duration = Required(int)
has_art = Required(bool, default=False)
album = ForeignKeyField(Album, backref="tracks")
artist = ForeignKeyField(Artist, backref="tracks")
album = Required(Album, column="album_id")
artist = Required(Artist, column="artist_id")
bitrate = IntegerField()
bitrate = Required(int)
path = CharField(4096) # unique
_path_hash = BlobField(column_name="path_hash", unique=True)
created = DateTimeField(default=now)
last_modification = IntegerField()
path = Required(str, 4096, autostrip=False) # unique
_path_hash = Required(buffer, column="path_hash")
created = Required(datetime, precision=0, default=now)
last_modification = Required(int)
play_count = IntegerField(default=0)
last_play = DateTimeField(null=True)
play_count = Required(int, default=0)
last_play = Optional(datetime, precision=0)
root_folder = Required(Folder, column="root_folder_id")
folder = Required(Folder, column="folder_id")
__lastly_played_by = Set(lambda: User) # Never used, hide it
stars = Set(lambda: StarredTrack)
ratings = Set(lambda: RatingTrack)
root_folder = ForeignKeyField(Folder, backref="+")
folder = ForeignKeyField(Folder, backref="tracks")
def as_subsonic_child(self, user, prefs):
info = {
@ -374,36 +348,23 @@ class Track(PathMixin, db.Entity):
return f"{self.album.artist.name}{self.album.name}{self.disc:02}{self.number:02}{self.title}".lower()
class User(db.Entity):
_table_ = "user"
class User(db.Model):
id = PrimaryKeyField()
name = CharField(64, unique=True)
mail = CharField(null=True)
password = FixedCharField(40)
salt = FixedCharField(6)
id = PrimaryKey(UUID, default=uuid4)
name = Required(str, 64) # unique
mail = Optional(str)
password = Required(str, 40)
salt = Required(str, 6)
admin = BooleanField(default=False)
jukebox = BooleanField(default=False)
admin = Required(bool, default=False)
jukebox = Required(bool, default=False)
lastfm_session = Optional(str, 32, nullable=True)
lastfm_status = Required(
bool, default=True
lastfm_session = FixedCharField(32, null=True)
lastfm_status = BooleanField(
default=True
) # True: ok/unlinked, False: invalid session
last_play = Optional(Track, column="last_play_id")
last_play_date = Optional(datetime, precision=0)
clients = Set(lambda: ClientPrefs)
playlists = Set(lambda: Playlist)
__messages = Set(lambda: ChatMessage, lazy=True) # Never used, hide it
starred_folders = Set(lambda: StarredFolder, lazy=True)
starred_artists = Set(lambda: StarredArtist, lazy=True)
starred_albums = Set(lambda: StarredAlbum, lazy=True)
starred_tracks = Set(lambda: StarredTrack, lazy=True)
folder_ratings = Set(lambda: RatingFolder, lazy=True)
track_ratings = Set(lambda: RatingTrack, lazy=True)
last_play = ForeignKeyField(Track, null=True, backref="+")
last_play_date = DateTimeField(null=True)
def as_subsonic_user(self):
return {
@ -424,81 +385,57 @@ class User(db.Entity):
}
class ClientPrefs(db.Entity):
_table_ = "client_prefs"
class ClientPrefs(db.Model):
user = ForeignKeyField(User, backref="clients")
client_name = CharField(32)
format = CharField(8, null=True)
bitrate = IntegerField(null=True)
user = Required(User, column="user_id")
client_name = Required(str, 32)
PrimaryKey(user, client_name)
format = Optional(str, 8, nullable=True)
bitrate = Optional(int)
class Meta:
primary_key = CompositeKey("user", "client_name")
class StarredFolder(db.Entity):
_table_ = "starred_folder"
def _make_starred_model(target_model):
class Starred(db.Model):
user = ForeignKeyField(User, backref="+")
starred = ForeignKeyField(target_model, backref="+")
date = DateTimeField(default=now)
user = Required(User, column="user_id")
starred = Required(Folder, column="starred_id")
date = Required(datetime, precision=0, default=now)
class Meta:
primary_key = CompositeKey("user", "starred")
table_name = "starred_" + target_model._meta.table_name
PrimaryKey(user, starred)
return Starred
class StarredArtist(db.Entity):
_table_ = "starred_artist"
user = Required(User, column="user_id")
starred = Required(Artist, column="starred_id")
date = Required(datetime, precision=0, default=now)
PrimaryKey(user, starred)
StarredFolder = _make_starred_model(Folder)
StarredArtist = _make_starred_model(Artist)
StarredAlbum = _make_starred_model(Album)
StarredTrack = _make_starred_model(Track)
class StarredAlbum(db.Entity):
_table_ = "starred_album"
def _make_rating_model(target_model):
class Rating(db.Model):
user = ForeignKeyField(User, backref="+")
rated = ForeignKeyField(target_model, backref="+")
rating = IntegerField() # min=1, max=5
user = Required(User, column="user_id")
starred = Required(Album, column="starred_id")
date = Required(datetime, precision=0, default=now)
class Meta:
primary_key = CompositeKey("user", "rated")
table_name = "rating_" + target_model._meta.table_name
PrimaryKey(user, starred)
return Rating
class StarredTrack(db.Entity):
_table_ = "starred_track"
user = Required(User, column="user_id")
starred = Required(Track, column="starred_id")
date = Required(datetime, precision=0, default=now)
PrimaryKey(user, starred)
RatingFolder = _make_rating_model(Folder)
RatingTrack = _make_rating_model(Track)
class RatingFolder(db.Entity):
_table_ = "rating_folder"
user = Required(User, column="user_id")
rated = Required(Folder, column="rated_id")
rating = Required(int, min=1, max=5)
PrimaryKey(user, rated)
class RatingTrack(db.Entity):
_table_ = "rating_track"
user = Required(User, column="user_id")
rated = Required(Track, column="rated_id")
rating = Required(int, min=1, max=5)
PrimaryKey(user, rated)
class ChatMessage(db.Entity):
_table_ = "chat_message"
id = PrimaryKey(UUID, default=uuid4)
user = Required(User, column="user_id")
time = Required(int, default=lambda: int(time.time()))
message = Required(str, 512)
class ChatMessage(db.Model):
id = PrimaryKeyField()
user = ForeignKeyField(User, backref="+")
time = IntegerField(default=lambda: int(time.time()))
message = CharField(512)
def responsize(self):
return {
@ -508,16 +445,14 @@ class ChatMessage(db.Entity):
}
class Playlist(db.Entity):
_table_ = "playlist"
id = PrimaryKey(UUID, default=uuid4)
user = Required(User, column="user_id")
name = Required(str)
comment = Optional(str)
public = Required(bool, default=False)
created = Required(datetime, precision=0, default=now)
tracks = Optional(LongStr)
class Playlist(db.Model):
id = PrimaryKeyField()
user = ForeignKeyField(User, backref="playlists")
name = CharField()
comment = CharField(null=True)
public = BooleanField(default=False)
created = DateTimeField(default=now)
tracks = TextField(null=True)
def as_subsonic_playlist(self, user):
tracks = self.get_tracks()
@ -583,14 +518,12 @@ class Playlist(db.Entity):
self.tracks = ",".join(t for t in tracks if t)
class RadioStation(db.Entity):
_table_ = "radio_station"
id = PrimaryKey(UUID, default=uuid4)
stream_url = Required(str)
name = Required(str)
homepage_url = Optional(str, nullable=True)
created = Required(datetime, precision=0, default=now)
class RadioStation(db.Model):
id = PrimaryKeyField()
stream_url = CharField()
name = CharField()
homepage_url = CharField(null=True)
created = DateTimeField(default=now)
def as_subsonic_station(self):
info = {