mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-04 12:33:03 +00:00
combine userdata and swing db into one
+ port populate to new db interface
+ add genrehashes and hash info to tracks
+ properly structure new db table files
+ move helpers to dedicated utils file
+ move settings from db to config file
+ move artists, albums, auth and favorites endpoint to new db interface
+ use folder store to index filepaths
+ paginate favorite pages
+ 56 moretiny changes 😅
This commit is contained in:
+5
-3
@@ -11,9 +11,9 @@ from flask_openapi3 import OpenAPI
|
||||
from flask_jwt_extended import JWTManager
|
||||
from app.config import UserConfig
|
||||
|
||||
from app.db.userdata import UserTable
|
||||
from app.settings import Info as AppInfo
|
||||
from .plugins import lyrics as lyrics_plugin
|
||||
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
|
||||
from app.api import (
|
||||
album,
|
||||
artist,
|
||||
@@ -92,8 +92,10 @@ def create_api():
|
||||
def user_lookup_callback(_jwt_header, jwt_data):
|
||||
identity = jwt_data["sub"]
|
||||
userid = identity["id"]
|
||||
user = authdb.get_user_by_id(userid)
|
||||
return user.todict()
|
||||
user = UserTable.get_by_id(userid)
|
||||
|
||||
if user:
|
||||
return user.todict()
|
||||
|
||||
# Register all the API blueprints
|
||||
with app.app_context():
|
||||
|
||||
+27
-40
@@ -12,15 +12,14 @@ from flask_openapi3 import APIBlueprint
|
||||
from app.api.apischemas import AlbumHashSchema, AlbumLimitSchema, ArtistHashSchema
|
||||
|
||||
from app.config import UserConfig
|
||||
from app.db import AlbumTable as AlbumDb, TrackTable as TrackDb
|
||||
from app.db.libdata import ArtistTable
|
||||
from app.db.libdata import AlbumTable as AlbumDb, TrackTable as TrackDb
|
||||
from app.db.userdata import SimilarArtistTable
|
||||
from app.settings import Defaults
|
||||
from app.models import FavType, Track
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.tracks import TrackStore
|
||||
from app.utils.hashing import create_hash
|
||||
from app.lib.albumslib import sort_by_track_no
|
||||
from app.serializers.album import serialize_for_card, serialize_for_card_many
|
||||
from app.serializers.track import serialize_track
|
||||
from app.serializers.track import serialize_track, serialize_tracks
|
||||
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as adb
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as lastfmdb
|
||||
@@ -52,9 +51,22 @@ def get_album_tracks_and_info(body: AlbumHashSchema):
|
||||
album.type = album.check_type(
|
||||
tracks=tracks, singleTrackAsSingle=UserConfig().showAlbumsAsSingles
|
||||
)
|
||||
album.populate_versions()
|
||||
|
||||
return {"info": album, "tracks": tracks}
|
||||
track_total = sum({int(t.extra.get("track_total", 1) or 1) for t in tracks})
|
||||
|
||||
return {
|
||||
"info": album,
|
||||
"extra": {
|
||||
# INFO: track_total is the sum of a set of track_total values from each track
|
||||
# ASSUMPTIONS
|
||||
# 1. All the tracks have the correct track totals
|
||||
# 2. Tracks with the same track total are from the same disc
|
||||
"track_total": track_total,
|
||||
"avg_bitrate": sum(t.bitrate for t in tracks) // len(tracks),
|
||||
},
|
||||
"copyright": tracks[0].copyright,
|
||||
"tracks": serialize_tracks(tracks, remove_disc=False),
|
||||
}
|
||||
|
||||
|
||||
@api.get("/<albumhash>/tracks")
|
||||
@@ -68,7 +80,7 @@ def get_album_tracks(path: AlbumHashSchema):
|
||||
tracks = TrackDb.get_tracks_by_albumhash(path.albumhash)
|
||||
tracks = sort_by_track_no(tracks)
|
||||
|
||||
return tracks
|
||||
return serialize_tracks(tracks)
|
||||
|
||||
|
||||
class GetMoreFromArtistsBody(AlbumLimitSchema):
|
||||
@@ -138,30 +150,14 @@ def get_album_versions(body: GetAlbumVersionsBody):
|
||||
artisthash = body.artisthash
|
||||
|
||||
albums = AlbumDb.get_albums_by_base_title(base_title)
|
||||
print(albums)
|
||||
albums = [
|
||||
a
|
||||
for a in albums
|
||||
if a.og_title != og_album_title
|
||||
and artisthash in {a["artisthash"] for a in a.albumartists}
|
||||
]
|
||||
|
||||
print(albums)
|
||||
|
||||
# albums = AlbumStore.get_albums_by_artisthash(artisthash)
|
||||
|
||||
# albums = [
|
||||
# a
|
||||
# for a in albums
|
||||
# if create_hash(a.base_title) == create_hash(base_title)
|
||||
# and create_hash(og_album_title) != create_hash(a.og_title)
|
||||
# ]
|
||||
|
||||
# for a in albums:
|
||||
# tracks = TrackStore.get_tracks_by_albumhash(a.albumhash)
|
||||
# a.get_date_from_tracks(tracks)
|
||||
|
||||
return albums
|
||||
return serialize_for_card_many(albums)
|
||||
|
||||
|
||||
class GetSimilarAlbumsQuery(ArtistHashSchema, AlbumLimitSchema):
|
||||
@@ -178,24 +174,15 @@ def get_similar_albums(query: GetSimilarAlbumsQuery):
|
||||
artisthash = query.artisthash
|
||||
limit = query.limit
|
||||
|
||||
similar_artists = lastfmdb.get_similar_artists_for(artisthash)
|
||||
similar_artists = SimilarArtistTable.get_by_hash(artisthash)
|
||||
|
||||
if similar_artists is None:
|
||||
return {"albums": []}
|
||||
return []
|
||||
|
||||
artisthashes = similar_artists.get_artist_hash_set()
|
||||
artists = ArtistTable.get_artists_by_artisthashes(artisthashes)
|
||||
|
||||
if len(artisthashes) == 0:
|
||||
return {"albums": []}
|
||||
albums = AlbumDb.get_albums_by_artisthashes([a.artisthash for a in artists])
|
||||
sample = random.sample(albums, min(len(albums), limit))
|
||||
|
||||
albums = [AlbumStore.get_albums_by_artisthash(a) for a in artisthashes]
|
||||
|
||||
albums = [a for sublist in albums for a in sublist]
|
||||
albums = list({a.albumhash: a for a in albums}.values())
|
||||
|
||||
try:
|
||||
albums = random.sample(albums, min(len(albums), limit))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return [serialize_for_card(a) for a in albums[:limit]]
|
||||
return serialize_for_card_many(sample[:limit])
|
||||
|
||||
+12
-91
@@ -2,12 +2,11 @@
|
||||
Contains all the artist(s) routes.
|
||||
"""
|
||||
|
||||
from itertools import groupby
|
||||
import math
|
||||
import random
|
||||
from datetime import datetime
|
||||
from itertools import groupby
|
||||
|
||||
from flask_jwt_extended import current_user
|
||||
from flask_openapi3 import APIBlueprint, Tag
|
||||
from pydantic import Field
|
||||
from app.api.apischemas import (
|
||||
@@ -18,18 +17,13 @@ from app.api.apischemas import (
|
||||
)
|
||||
|
||||
from app.config import UserConfig
|
||||
from app.db import AlbumTable, ArtistTable, TrackTable
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as fmdb
|
||||
from app.models import Album, FavType
|
||||
from app.db.libdata import ArtistTable
|
||||
from app.db.libdata import AlbumTable, TrackTable
|
||||
from app.db.userdata import SimilarArtistTable
|
||||
|
||||
from app.serializers.album import serialize_for_card_many
|
||||
from app.serializers.track import serialize_tracks
|
||||
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.artists import ArtistStore
|
||||
from app.store.tracks import TrackStore
|
||||
|
||||
|
||||
bp_tag = Tag(name="Artist", description="Single artist")
|
||||
api = APIBlueprint("artist", __name__, url_prefix="/artist", abp_tags=[bp_tag])
|
||||
|
||||
@@ -45,8 +39,6 @@ def get_artist(path: ArtistHashSchema, query: TrackLimitSchema):
|
||||
limit = query.limit
|
||||
|
||||
artist = ArtistTable.get_artist_by_hash(artisthash)
|
||||
print(artist)
|
||||
|
||||
if artist is None:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
@@ -56,8 +48,6 @@ def get_artist(path: ArtistHashSchema, query: TrackLimitSchema):
|
||||
if artist.albumcount == 0 and tcount < 10:
|
||||
limit = tcount
|
||||
|
||||
# artist.is_favorite = favdb.check_is_favorite(artisthash, FavType.artist)
|
||||
|
||||
try:
|
||||
year = datetime.fromtimestamp(artist.date).year
|
||||
except ValueError:
|
||||
@@ -106,7 +96,7 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
|
||||
t.albumhash for t in tracks if t.albumhash not in {a.albumhash for a in albums}
|
||||
}
|
||||
|
||||
albums.extend(AlbumTable.get_albums_by_hash(missing_albumhashes))
|
||||
albums.extend(AlbumTable.get_albums_by_albumhashes(missing_albumhashes))
|
||||
albumdict = {a.albumhash: a for a in albums}
|
||||
|
||||
config = UserConfig()
|
||||
@@ -117,43 +107,6 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
|
||||
if album:
|
||||
album.check_type(list(tracks), config.showAlbumsAsSingles)
|
||||
|
||||
# all_albums = AlbumStore.get_albums_by_artisthash(artisthash)
|
||||
# start: check for missing albums. ie. compilations and features
|
||||
# all_tracks = TrackStore.get_tracks_by_artisthash(artisthash)
|
||||
|
||||
# track_albums = set(t.albumhash for t in all_tracks)
|
||||
# missing_album_hashes = track_albums.difference(set(a.albumhash for a in all_albums))
|
||||
|
||||
# if len(missing_album_hashes) > 0:
|
||||
# missing_albums = AlbumStore.get_albums_by_hashes(list(missing_album_hashes))
|
||||
# all_albums.extend(missing_albums)
|
||||
|
||||
# end check
|
||||
|
||||
# def get_album_tracks(albumhash: str):
|
||||
# tracks = [t for t in all_tracks if t.albumhash == albumhash]
|
||||
|
||||
# if len(tracks) > 0:
|
||||
# return tracks
|
||||
|
||||
# return TrackStore.get_tracks_by_albumhash(albumhash)
|
||||
|
||||
# for a in all_albums:
|
||||
# a.check_type()
|
||||
|
||||
# album_tracks = get_album_tracks(a.albumhash)
|
||||
|
||||
# if len(album_tracks) == 0:
|
||||
# continue
|
||||
|
||||
# a.get_date_from_tracks(album_tracks)
|
||||
|
||||
# if a.date == 0:
|
||||
# AlbumStore.remove_album_by_hash(a.albumhash)
|
||||
# continue
|
||||
|
||||
# a.is_single(album_tracks)
|
||||
|
||||
albums = [a for a in albumdict.values()]
|
||||
all_albums = sorted(albums, key=lambda a: str(a.date), reverse=True)
|
||||
|
||||
@@ -174,29 +127,6 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
|
||||
else:
|
||||
res["albums"].append(album)
|
||||
|
||||
# def remove_EPs_and_singles(albums_: list[Album]):
|
||||
# albums_ = [a for a in albums_ if not a.type == "single"]
|
||||
# albums_ = [a for a in albums_ if not a.type == "ep"]
|
||||
# return albums_
|
||||
|
||||
# albums = filter(lambda a: artisthash in missing_albumhashes, all_albums)
|
||||
# albums = list(albums)
|
||||
# albums = remove_EPs_and_singles(albums)
|
||||
|
||||
# compilations = [a for a in albums if a.is_compilation]
|
||||
# for c in compilations:
|
||||
# albums.remove(c)
|
||||
|
||||
# appearances = filter(lambda a: artisthash not in a.albumartists_hashes, all_albums)
|
||||
# appearances = list(appearances)
|
||||
|
||||
# appearances = remove_EPs_and_singles(appearances)
|
||||
|
||||
# artist = ArtistStore.get_artist_by_hash(artisthash)
|
||||
|
||||
# if artist is None:
|
||||
# return {"error": "Artist not found"}, 404
|
||||
|
||||
if return_all:
|
||||
limit = len(all_albums)
|
||||
|
||||
@@ -215,8 +145,8 @@ def get_all_artist_tracks(path: ArtistHashSchema):
|
||||
|
||||
Returns all artists by a given artist.
|
||||
"""
|
||||
tracks = TrackStore.get_tracks_by_artisthash(path.artisthash)
|
||||
|
||||
# tracks = TrackStore.get_tracks_by_artisthash(path.artisthash)
|
||||
tracks = TrackTable.get_tracks_by_artisthash(path.artisthash)
|
||||
return serialize_tracks(tracks)
|
||||
|
||||
|
||||
@@ -226,23 +156,14 @@ def get_similar_artists(path: ArtistHashSchema, query: ArtistLimitSchema):
|
||||
Get similar artists.
|
||||
"""
|
||||
limit = query.limit
|
||||
|
||||
artist = ArtistStore.get_artist_by_hash(path.artisthash)
|
||||
|
||||
if artist is None:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
result = fmdb.get_similar_artists_for(artist.artisthash)
|
||||
result = SimilarArtistTable.get_by_hash(path.artisthash)
|
||||
|
||||
if result is None:
|
||||
return {"artists": []}
|
||||
return []
|
||||
|
||||
similar = ArtistStore.get_artists_by_hashes(result.get_artist_hash_set())
|
||||
similar = ArtistTable.get_artists_by_artisthashes(result.get_artist_hash_set())
|
||||
|
||||
if len(similar) > limit:
|
||||
similar = random.sample(similar, limit)
|
||||
similar = random.sample(similar, min(limit, len(similar)))
|
||||
|
||||
return similar[:limit]
|
||||
|
||||
|
||||
# TODO: Rewrite this file using generators where possible
|
||||
|
||||
+53
-43
@@ -14,7 +14,8 @@ from pydantic import BaseModel, Field
|
||||
from flask_openapi3 import Tag
|
||||
from flask_openapi3 import APIBlueprint
|
||||
|
||||
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
|
||||
# from app.db.sqlite.auth import SQLiteAuthMethods as authdb
|
||||
from app.db.userdata import UserTable
|
||||
from app.utils.auth import check_password, hash_password
|
||||
from app.config import UserConfig
|
||||
|
||||
@@ -65,7 +66,7 @@ def login(body: LoginBody):
|
||||
Authenticate using username and password
|
||||
"""
|
||||
|
||||
user = authdb.get_user_by_username(body.username)
|
||||
user = UserTable.get_by_username(body.username)
|
||||
|
||||
if user is None:
|
||||
return {"msg": "User not found"}, 404
|
||||
@@ -87,42 +88,41 @@ def login(body: LoginBody):
|
||||
pair_token = dict()
|
||||
|
||||
|
||||
@api.get("/getpaircode")
|
||||
def get_pair():
|
||||
"""
|
||||
Get a new pair code to log in to thee Swing Music mobile app
|
||||
"""
|
||||
# INFO: if user is already logged in, create a new pair code
|
||||
token = create_new_token(get_jwt_identity())
|
||||
key = token["accesstoken"][-6:]
|
||||
|
||||
global pair_token
|
||||
pair_token = {
|
||||
key: token,
|
||||
}
|
||||
|
||||
return {"code": key}
|
||||
|
||||
|
||||
class PairDeviceQuery(BaseModel):
|
||||
code: str = Field("", description="The code")
|
||||
|
||||
|
||||
@api.get("/pair")
|
||||
@api.post("/pair")
|
||||
@jwt_required(optional=True)
|
||||
def pair_device(query: PairDeviceQuery):
|
||||
def pair_with_code(body: PairDeviceQuery):
|
||||
"""
|
||||
Pair the Swing Music mobile app with this server
|
||||
|
||||
Send a code to get an access token. Send an authenticated request without the code to generate a new token.
|
||||
Get an access token by sending a pair code. NOTE: A code can only be used once!
|
||||
"""
|
||||
# INFO: if user is already logged in, create a new pair code
|
||||
if current_user:
|
||||
token = create_new_token(get_jwt_identity())
|
||||
key = token["accesstoken"][-6:]
|
||||
global pair_token
|
||||
token = pair_token.get(body.code, None)
|
||||
|
||||
global pair_token
|
||||
pair_token = {
|
||||
key: token,
|
||||
}
|
||||
if token:
|
||||
pair_token = {}
|
||||
return token
|
||||
|
||||
return {"code": key}
|
||||
|
||||
# INFO: if there's a pair code, return the token
|
||||
if query.code:
|
||||
token = pair_token.get(query.code, None)
|
||||
|
||||
if token:
|
||||
# INFO: reset pair_token
|
||||
pair_token = {}
|
||||
return token
|
||||
|
||||
return {"msg": "Invalid code"}, 400
|
||||
|
||||
return {"msg": "No code provided"}, 400
|
||||
return {"msg": "Invalid code"}, 400
|
||||
|
||||
|
||||
@api.post("/refresh")
|
||||
@@ -133,6 +133,8 @@ def refresh():
|
||||
|
||||
>>> Headers:
|
||||
>>> Authorization: Bearer <refresh_token>
|
||||
|
||||
Won't work with cookies!!!
|
||||
"""
|
||||
user = get_jwt_identity()
|
||||
return create_new_token(user)
|
||||
@@ -153,7 +155,6 @@ def update_profile(body: UpdateProfileBody):
|
||||
"""
|
||||
user = {
|
||||
"id": body.id,
|
||||
"email": body.email,
|
||||
"username": body.username,
|
||||
"password": body.password,
|
||||
"roles": body.roles,
|
||||
@@ -172,7 +173,8 @@ def update_profile(body: UpdateProfileBody):
|
||||
if "admin" not in current_user["roles"]:
|
||||
return {"msg": "Only admins can update roles"}, 403
|
||||
|
||||
all_users = authdb.get_all_users()
|
||||
# all_users = authdb.get_all_users()
|
||||
all_users = UserTable.get_all()
|
||||
if "admin" not in body.roles:
|
||||
# check if we're removing the last admin
|
||||
admins = [user for user in all_users if "admin" in user.roles]
|
||||
@@ -195,7 +197,9 @@ def update_profile(body: UpdateProfileBody):
|
||||
clean_user = {k: v for k, v in user.items() if v}
|
||||
|
||||
try:
|
||||
return authdb.update_user(clean_user)
|
||||
# return authdb.update_user(clean_user)
|
||||
UserTable.update_one(clean_user)
|
||||
return UserTable.get_by_id(user["id"]).todict()
|
||||
except sqlite3.IntegrityError:
|
||||
return {"msg": "Username already exists"}, 400
|
||||
|
||||
@@ -216,11 +220,18 @@ def create_user(body: UpdateProfileBody):
|
||||
}
|
||||
|
||||
# check if user already exists
|
||||
if authdb.get_user_by_username(user["username"]):
|
||||
if UserTable.get_by_username(user["username"]):
|
||||
return {"msg": "Username already exists"}, 400
|
||||
|
||||
userid = authdb.insert_user(user)
|
||||
return authdb.get_user_by_id(userid).todict()
|
||||
UserTable.insert_one(user)
|
||||
user = UserTable.get_by_username(user["username"])
|
||||
|
||||
if user:
|
||||
return user.todict()
|
||||
|
||||
return {
|
||||
"msg": "Failed to create user",
|
||||
}, 500
|
||||
|
||||
|
||||
@api.post("/profile/guest/create")
|
||||
@@ -230,14 +241,14 @@ def create_guest_user():
|
||||
Create a guest user
|
||||
"""
|
||||
# check if guest user already exists
|
||||
guest_user = authdb.get_user_by_username("guest")
|
||||
guest_user = UserTable.get_by_username("guest")
|
||||
|
||||
if guest_user:
|
||||
return {
|
||||
"msg": "Guest user already exists",
|
||||
}, 400
|
||||
|
||||
userid = authdb.insert_guest_user()
|
||||
userid = UserTable.insert_guest_user()
|
||||
|
||||
if userid:
|
||||
return {
|
||||
@@ -264,12 +275,12 @@ def delete_user(body: DeleteUseBody):
|
||||
return {"msg": "Sorry! you cannot delete yourselfu"}, 400
|
||||
|
||||
# prevent deleting the only admin
|
||||
users = authdb.get_all_users()
|
||||
users = UserTable.get_all()
|
||||
admins = [user for user in users if "admin" in user.roles]
|
||||
if len(admins) == 1 and admins[0].username == body.username:
|
||||
return {"msg": "Cannot delete the only admin"}, 400
|
||||
|
||||
authdb.delete_user_by_username(body.username)
|
||||
UserTable.remove_by_username(body.username)
|
||||
return {"msg": f"User {body.username} deleted"}
|
||||
|
||||
|
||||
@@ -308,8 +319,7 @@ def get_all_users(query: GetAllUsersQuery):
|
||||
"users": [],
|
||||
}
|
||||
|
||||
users = authdb.get_all_users()
|
||||
|
||||
users = UserTable.get_all()
|
||||
is_admin = current_user and "admin" in current_user["roles"]
|
||||
settings["enableGuest"] = [
|
||||
user for user in users if user.username == "guest"
|
||||
@@ -355,8 +365,8 @@ def get_all_users(query: GetAllUsersQuery):
|
||||
|
||||
if query.simplified:
|
||||
res["users"] = [user.todict_simplified() for user in users]
|
||||
|
||||
res["users"] = [user.todict() for user in users]
|
||||
else:
|
||||
res["users"] = [user.todict() for user in users]
|
||||
|
||||
return res
|
||||
|
||||
|
||||
+73
-97
@@ -6,18 +6,19 @@ from flask_openapi3 import APIBlueprint
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.api.apischemas import GenericLimitSchema
|
||||
from app.db.libdata import ArtistTable
|
||||
from app.db.libdata import AlbumTable, TrackTable
|
||||
from app.db.userdata import FavoritesTable
|
||||
from app.models import FavType
|
||||
from app.settings import Defaults
|
||||
from app.utils.bisection import use_bisection
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.serializers.track import serialize_track, serialize_tracks
|
||||
from app.serializers.artist import serialize_for_card as serialize_artist, serialize_for_cards
|
||||
from app.serializers.album import serialize_for_card, serialize_for_card_many
|
||||
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.tracks import TrackStore
|
||||
from app.store.artists import ArtistStore
|
||||
from app.serializers.artist import (
|
||||
serialize_for_card as serialize_artist,
|
||||
serialize_for_cards,
|
||||
)
|
||||
from app.utils.dates import timestamp_to_time_passed
|
||||
from app.serializers.album import serialize_for_card, serialize_for_card_many
|
||||
|
||||
bp_tag = Tag(name="Favorites", description="Your favorite items")
|
||||
api = APIBlueprint("favorites", __name__, url_prefix="/favorites", abp_tags=[bp_tag])
|
||||
@@ -41,17 +42,18 @@ class FavoritesAddBody(BaseModel):
|
||||
|
||||
|
||||
@api.post("/add")
|
||||
def add_favorite(body: FavoritesAddBody):
|
||||
def toggle_favorite(body: FavoritesAddBody):
|
||||
"""
|
||||
Adds a favorite to the database.
|
||||
"""
|
||||
itemhash = body.hash
|
||||
itemtype = body.type
|
||||
FavoritesTable.insert_item({"hash": body.hash, "type": body.type})
|
||||
|
||||
favdb.insert_one_favorite(itemtype, itemhash)
|
||||
|
||||
if itemtype == FavType.track:
|
||||
TrackStore.make_track_fav(itemhash)
|
||||
if body.type == FavType.track:
|
||||
TrackTable.set_is_favorite(body.hash, True)
|
||||
elif body.type == FavType.album:
|
||||
AlbumTable.set_is_favorite(body.hash, True)
|
||||
elif body.type == FavType.artist:
|
||||
ArtistTable.set_is_favorite(body.hash, True)
|
||||
|
||||
return {"msg": "Added to favorites"}
|
||||
|
||||
@@ -61,80 +63,62 @@ def remove_favorite(body: FavoritesAddBody):
|
||||
"""
|
||||
Removes a favorite from the database.
|
||||
"""
|
||||
itemhash = body.hash
|
||||
itemtype = body.type
|
||||
FavoritesTable.remove_item({"hash": body.hash, "type": body.type})
|
||||
|
||||
favdb.delete_favorite(itemtype, itemhash)
|
||||
|
||||
if itemtype == FavType.track:
|
||||
TrackStore.remove_track_from_fav(itemhash)
|
||||
if body.type == FavType.track:
|
||||
TrackTable.set_is_favorite(body.hash, False)
|
||||
elif body.type == FavType.album:
|
||||
AlbumTable.set_is_favorite(body.hash, False)
|
||||
elif body.type == FavType.artist:
|
||||
ArtistTable.set_is_favorite(body.hash, False)
|
||||
|
||||
return {"msg": "Removed from favorites"}
|
||||
|
||||
|
||||
class GetAllOfTypeQuery(GenericLimitSchema):
|
||||
"""
|
||||
Extending this class will give you a model with the `limit` field
|
||||
"""
|
||||
|
||||
start: int = Field(
|
||||
description="Where to start from",
|
||||
example=Defaults.API_CARD_LIMIT,
|
||||
default=Defaults.API_CARD_LIMIT,
|
||||
)
|
||||
|
||||
|
||||
@api.get("/albums")
|
||||
def get_favorite_albums(query: GenericLimitSchema):
|
||||
def get_favorite_albums(query: GetAllOfTypeQuery):
|
||||
"""
|
||||
Get favorite albums
|
||||
"""
|
||||
limit = query.limit
|
||||
albums = favdb.get_fav_albums()
|
||||
albumhashes = [a[1] for a in albums]
|
||||
albumhashes.reverse()
|
||||
fav_albums, total = FavoritesTable.get_fav_albums(query.start, query.limit)
|
||||
fav_albums.reverse()
|
||||
|
||||
src_albums = sorted(AlbumStore.albums, key=lambda x: x.albumhash)
|
||||
|
||||
fav_albums = use_bisection(src_albums, "albumhash", albumhashes)
|
||||
fav_albums = remove_none(fav_albums)
|
||||
|
||||
if limit == 0:
|
||||
limit = len(albums)
|
||||
|
||||
return {"albums": serialize_for_card_many(fav_albums[:limit])}
|
||||
return {"albums": serialize_for_card_many(fav_albums), "total": total}
|
||||
|
||||
|
||||
@api.get("/tracks")
|
||||
def get_favorite_tracks(query: GenericLimitSchema):
|
||||
def get_favorite_tracks(query: GetAllOfTypeQuery):
|
||||
"""
|
||||
Get favorite tracks
|
||||
"""
|
||||
limit = query.limit
|
||||
userid = current_user['id']
|
||||
|
||||
tracks = favdb.get_fav_tracks(userid)
|
||||
trackhashes = [t[1] for t in tracks]
|
||||
trackhashes.reverse()
|
||||
src_tracks = sorted(TrackStore.tracks, key=lambda x: x.trackhash)
|
||||
|
||||
tracks = use_bisection(src_tracks, "trackhash", trackhashes)
|
||||
tracks = remove_none(tracks)
|
||||
|
||||
if limit == 0:
|
||||
limit = len(tracks)
|
||||
|
||||
return {"tracks": serialize_tracks(tracks[:limit])}
|
||||
tracks, total = FavoritesTable.get_fav_tracks(query.start, query.limit)
|
||||
return {"tracks": serialize_tracks(tracks), "total": total}
|
||||
|
||||
|
||||
@api.get("/artists")
|
||||
def get_favorite_artists(query: GenericLimitSchema):
|
||||
def get_favorite_artists(query: GetAllOfTypeQuery):
|
||||
"""
|
||||
Get favorite artists
|
||||
"""
|
||||
limit = query.limit
|
||||
artists, total = FavoritesTable.get_fav_artists(
|
||||
start=query.start,
|
||||
limit=query.limit,
|
||||
)
|
||||
artists.reverse()
|
||||
|
||||
artists = favdb.get_fav_artists()
|
||||
artisthashes = [a[1] for a in artists]
|
||||
artisthashes.reverse()
|
||||
|
||||
src_artists = sorted(ArtistStore.artists, key=lambda x: x.artisthash)
|
||||
|
||||
artists = use_bisection(src_artists, "artisthash", artisthashes)
|
||||
artists = remove_none(artists)
|
||||
|
||||
if limit == 0:
|
||||
limit = len(artists)
|
||||
|
||||
return {"artists": artists[:limit]}
|
||||
return {"artists": [serialize_artist(a) for a in artists], "total": total}
|
||||
|
||||
|
||||
class GetAllFavoritesQuery(BaseModel):
|
||||
@@ -173,27 +157,29 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
# largest is x2 to accound for broken hashes if any
|
||||
largest = max(track_limit, album_limit, artist_limit)
|
||||
|
||||
favs = favdb.get_all()
|
||||
favs = FavoritesTable.get_all()
|
||||
favs.reverse()
|
||||
|
||||
tracks = []
|
||||
albums = []
|
||||
artists = []
|
||||
|
||||
track_master_hash = set(t.trackhash for t in TrackStore.tracks)
|
||||
album_master_hash = set(a.albumhash for a in AlbumStore.albums)
|
||||
artist_master_hash = set(a.artisthash for a in ArtistStore.artists)
|
||||
track_master_hash = TrackTable.get_all_hashes()
|
||||
album_master_hash = AlbumTable.get_all_hashes()
|
||||
artist_master_hash = ArtistTable.get_all_hashes()
|
||||
|
||||
# INFO: Filter out invalid hashes (file not found or tags edited)
|
||||
for fav in favs:
|
||||
# INFO: hash is [1], type is [2], timestamp is [3]
|
||||
hash = fav[1]
|
||||
if fav[2] == FavType.track:
|
||||
hash = fav.hash
|
||||
type = fav.type
|
||||
|
||||
if type == FavType.track:
|
||||
tracks.append(hash) if hash in track_master_hash else None
|
||||
|
||||
if fav[2] == FavType.artist:
|
||||
if type == FavType.artist:
|
||||
artists.append(hash) if hash in artist_master_hash else None
|
||||
|
||||
if fav[2] == FavType.album:
|
||||
if type == FavType.album:
|
||||
albums.append(hash) if hash in album_master_hash else None
|
||||
|
||||
count = {
|
||||
@@ -202,35 +188,26 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
"artists": len(artists),
|
||||
}
|
||||
|
||||
src_tracks = sorted(TrackStore.tracks, key=lambda x: x.trackhash)
|
||||
src_albums = sorted(AlbumStore.albums, key=lambda x: x.albumhash)
|
||||
src_artists = sorted(ArtistStore.artists, key=lambda x: x.artisthash)
|
||||
|
||||
tracks = use_bisection(src_tracks, "trackhash", tracks, limit=track_limit)
|
||||
albums = use_bisection(src_albums, "albumhash", albums, limit=album_limit)
|
||||
artists = use_bisection(src_artists, "artisthash", artists, limit=artist_limit)
|
||||
|
||||
tracks = remove_none(tracks)
|
||||
albums = remove_none(albums)
|
||||
artists = remove_none(artists)
|
||||
tracks = TrackTable.get_tracks_by_trackhashes(tracks, limit=track_limit)
|
||||
albums = AlbumTable.get_albums_by_albumhashes(albums, limit=album_limit)
|
||||
artists = ArtistTable.get_artists_by_artisthashes(artists, limit=artist_limit)
|
||||
|
||||
recents = []
|
||||
# first_n = favs
|
||||
|
||||
for fav in favs:
|
||||
# INFO: hash is [1], type is [2], timestamp is [3]
|
||||
if len(recents) >= largest:
|
||||
break
|
||||
|
||||
if fav[2] == FavType.album:
|
||||
album = next((a for a in albums if a.albumhash == fav[1]), None)
|
||||
if fav.type == FavType.album:
|
||||
album = next((a for a in albums if a.albumhash == fav.hash), None)
|
||||
|
||||
if album is None:
|
||||
continue
|
||||
|
||||
album = serialize_for_card(album)
|
||||
album["help_text"] = "album"
|
||||
album["time"] = timestamp_to_time_passed(fav[3])
|
||||
album["time"] = timestamp_to_time_passed(fav.timestamp)
|
||||
|
||||
recents.append(
|
||||
{
|
||||
@@ -239,15 +216,15 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
}
|
||||
)
|
||||
|
||||
if fav[2] == FavType.artist:
|
||||
artist = next((a for a in artists if a.artisthash == fav[1]), None)
|
||||
if fav.type == FavType.artist:
|
||||
artist = next((a for a in artists if a.artisthash == fav.hash), None)
|
||||
|
||||
if artist is None:
|
||||
continue
|
||||
|
||||
artist = serialize_artist(artist)
|
||||
artist["help_text"] = "artist"
|
||||
artist["time"] = timestamp_to_time_passed(fav[3])
|
||||
artist["time"] = timestamp_to_time_passed(fav.timestamp)
|
||||
|
||||
recents.append(
|
||||
{
|
||||
@@ -256,15 +233,15 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
}
|
||||
)
|
||||
|
||||
if fav[2] == FavType.track:
|
||||
track = next((t for t in tracks if t.trackhash == fav[1]), None)
|
||||
if fav.type == FavType.track:
|
||||
track = next((t for t in tracks if t.trackhash == fav.hash), None)
|
||||
|
||||
if track is None:
|
||||
continue
|
||||
|
||||
track = serialize_track(track)
|
||||
track["help_text"] = "track"
|
||||
track["time"] = timestamp_to_time_passed(fav[3])
|
||||
track["time"] = timestamp_to_time_passed(fav.timestamp)
|
||||
|
||||
recents.append({"type": "track", "item": track})
|
||||
|
||||
@@ -284,6 +261,5 @@ def check_favorite(query: FavoritesAddBody):
|
||||
"""
|
||||
itemhash = query.hash
|
||||
itemtype = query.type
|
||||
exists = favdb.check_is_favorite(itemhash, itemtype)
|
||||
|
||||
return {"is_favorite": exists}
|
||||
return {"is_favorite": FavoritesTable.check_exists(itemhash, itemtype)}
|
||||
|
||||
+5
-11
@@ -10,11 +10,10 @@ from pydantic import BaseModel, Field
|
||||
from flask_openapi3 import Tag
|
||||
from flask_openapi3 import APIBlueprint
|
||||
from showinfm import show_in_file_manager
|
||||
from memory_profiler import profile
|
||||
|
||||
from app import settings
|
||||
from app.db import TrackTable
|
||||
from app.db.sqlite.settings import SettingsSQLMethods as db
|
||||
from app.config import UserConfig
|
||||
from app.db.libdata import TrackTable
|
||||
from app.lib.folderslib import GetFilesAndDirs, get_folders
|
||||
from app.serializers.track import serialize_track
|
||||
from app.utils.wintools import is_windows, win_replace_slash
|
||||
@@ -40,8 +39,9 @@ def get_folder_tree(body: FolderTree):
|
||||
req_dir = body.folder
|
||||
tracks_only = body.tracks_only
|
||||
|
||||
root_dirs = db.get_root_dirs()
|
||||
root_dirs.sort()
|
||||
|
||||
config = UserConfig()
|
||||
root_dirs = config.rootDirs
|
||||
|
||||
try:
|
||||
if req_dir == "$home" and root_dirs[0] == "$home":
|
||||
@@ -72,12 +72,6 @@ def get_folder_tree(body: FolderTree):
|
||||
|
||||
return res
|
||||
|
||||
# return {
|
||||
# "path": req_dir,
|
||||
# "tracks": tracks,
|
||||
# "folders": sorted(folders, key=lambda i: i.name),
|
||||
# }
|
||||
|
||||
|
||||
def get_all_drives(is_win: bool = False):
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,8 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from datetime import datetime
|
||||
from app.api.apischemas import GenericLimitSchema
|
||||
from app.db import AlbumTable, ArtistTable
|
||||
from app.db.libdata import ArtistTable
|
||||
from app.db.libdata import AlbumTable
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.artists import ArtistStore
|
||||
|
||||
@@ -61,11 +62,11 @@ def get_all_items(path: GetAllItemsPath, query: GetAllItemsQuery):
|
||||
is_artists = path.itemtype == "artists"
|
||||
|
||||
if is_albums:
|
||||
items, total = AlbumTable.get_all(query.start, query.limit)
|
||||
items = AlbumTable.get_all()
|
||||
elif is_artists:
|
||||
items, total = ArtistTable.get_all(query.start, query.limit)
|
||||
items = ArtistTable.get_all()
|
||||
|
||||
# print(items)
|
||||
total = len(items)
|
||||
|
||||
start = query.start
|
||||
limit = query.limit
|
||||
|
||||
+51
-71
@@ -1,3 +1,4 @@
|
||||
from dataclasses import asdict
|
||||
from typing import Any
|
||||
from flask import request
|
||||
from flask_openapi3 import Tag
|
||||
@@ -6,12 +7,13 @@ from pydantic import BaseModel, Field
|
||||
from app.api.auth import admin_required
|
||||
|
||||
from app.db.sqlite.plugins import PluginsMethods as pdb
|
||||
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
||||
from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
|
||||
from app.db.userdata import PluginTable
|
||||
from app.lib import populate
|
||||
from app.lib.tagger import index_everything
|
||||
from app.lib.watchdogg import Watcher as WatchDog
|
||||
from app.logger import log
|
||||
from app.settings import Info, Paths, SessionVarKeys, set_flag
|
||||
from app.settings import Info, Paths, SessionVarKeys
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.artists import ArtistStore
|
||||
from app.store.tracks import TrackStore
|
||||
@@ -48,42 +50,43 @@ def reload_everything(instance_key: str):
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
|
||||
# CHECKPOINT: TEST SETTINGS API ENDPOINTS
|
||||
|
||||
@background
|
||||
def rebuild_store(db_dirs: list[str]):
|
||||
"""
|
||||
Restarts watchdog and rebuilds the music library.
|
||||
"""
|
||||
instance_key = get_random_str()
|
||||
# @background
|
||||
# def rebuild_store(db_dirs: list[str]):
|
||||
# """
|
||||
# Restarts watchdog and rebuilds the music library.
|
||||
# """
|
||||
# instance_key = get_random_str()
|
||||
|
||||
log.info("Rebuilding library...")
|
||||
trackdb.remove_tracks_not_in_folders(db_dirs)
|
||||
reload_everything(instance_key)
|
||||
# log.info("Rebuilding library...")
|
||||
# trackdb.remove_tracks_not_in_folders(db_dirs)
|
||||
# reload_everything(instance_key)
|
||||
|
||||
try:
|
||||
populate.Populate(instance_key=instance_key)
|
||||
except populate.PopulateCancelledError as e:
|
||||
print(e)
|
||||
reload_everything(instance_key)
|
||||
return
|
||||
# try:
|
||||
# populate.Populate(instance_key=instance_key)
|
||||
# except populate.PopulateCancelledError as e:
|
||||
# print(e)
|
||||
# reload_everything(instance_key)
|
||||
# return
|
||||
|
||||
WatchDog().restart()
|
||||
# WatchDog().restart()
|
||||
|
||||
log.info("Rebuilding library... ✅")
|
||||
# log.info("Rebuilding library... ✅")
|
||||
|
||||
|
||||
# I freaking don't know what this function does anymore
|
||||
def finalize(new_: list[str], removed_: list[str], db_dirs_: list[str]):
|
||||
"""
|
||||
Params:
|
||||
new_: will be added to the database
|
||||
removed_: will be removed from the database
|
||||
db_dirs_: will be used to remove tracks that
|
||||
are outside these directories from the database and store.
|
||||
"""
|
||||
sdb.remove_root_dirs(removed_)
|
||||
sdb.add_root_dirs(new_)
|
||||
rebuild_store(db_dirs_)
|
||||
# # I freaking don't know what this function does anymore
|
||||
# def finalize(new_: list[str], removed_: list[str], db_dirs_: list[str]):
|
||||
# """
|
||||
# Params:
|
||||
# new_: will be added to the database
|
||||
# removed_: will be removed from the database
|
||||
# db_dirs_: will be used to remove tracks that
|
||||
# are outside these directories from the database and store.
|
||||
# """
|
||||
# sdb.remove_root_dirs(removed_)
|
||||
# sdb.add_root_dirs(new_)
|
||||
# rebuild_store(db_dirs_)
|
||||
|
||||
|
||||
class AddRootDirsBody(BaseModel):
|
||||
@@ -106,7 +109,8 @@ def add_root_dirs(body: AddRootDirsBody):
|
||||
new_dirs = body.new_dirs
|
||||
removed_dirs = body.removed
|
||||
|
||||
db_dirs = sdb.get_root_dirs()
|
||||
config = UserConfig()
|
||||
db_dirs = config.rootDirs
|
||||
home = "$home"
|
||||
|
||||
db_home = any([d == home for d in db_dirs]) # if $home is in db
|
||||
@@ -114,13 +118,16 @@ def add_root_dirs(body: AddRootDirsBody):
|
||||
|
||||
# handle $home case
|
||||
if db_home and incoming_home:
|
||||
return {"msg": "Not changed!"}
|
||||
return {"msg": "Not changed!"}, 304
|
||||
|
||||
# if $home is the current root dir or the incoming root dir
|
||||
# is $home, remove all root dirs
|
||||
if db_home or incoming_home:
|
||||
sdb.remove_root_dirs(db_dirs)
|
||||
config.rootDirs = []
|
||||
|
||||
if incoming_home:
|
||||
finalize([home], [], [Paths.USER_HOME_DIR])
|
||||
config.rootDirs = [home]
|
||||
index_everything()
|
||||
return {"root_dirs": [home]}
|
||||
|
||||
# ---
|
||||
@@ -136,11 +143,10 @@ def add_root_dirs(body: AddRootDirsBody):
|
||||
pass
|
||||
|
||||
db_dirs.extend(new_dirs)
|
||||
db_dirs = [dir_ for dir_ in db_dirs if dir_ != home]
|
||||
config.rootDirs = [dir_ for dir_ in db_dirs if dir_ != home]
|
||||
|
||||
finalize(new_dirs, removed_dirs, db_dirs)
|
||||
|
||||
return {"root_dirs": db_dirs}
|
||||
index_everything()
|
||||
return {"root_dirs": config.rootDirs}
|
||||
|
||||
|
||||
@api.get("/get-root-dirs")
|
||||
@@ -148,9 +154,7 @@ def get_root_dirs():
|
||||
"""
|
||||
Get root directories
|
||||
"""
|
||||
dirs = sdb.get_root_dirs()
|
||||
|
||||
return {"dirs": dirs}
|
||||
return {"dirs": UserConfig().rootDirs}
|
||||
|
||||
|
||||
# maps settings to their parser flags
|
||||
@@ -170,35 +174,12 @@ def get_all_settings():
|
||||
"""
|
||||
Get all settings
|
||||
"""
|
||||
config = asdict(UserConfig())
|
||||
plugins = PluginTable.get_all()
|
||||
config["plugins"] = plugins
|
||||
config["version"] = Info.SWINGMUSIC_APP_VERSION
|
||||
|
||||
settings = sdb.get_all_settings()
|
||||
plugins = pdb.get_all_plugins()
|
||||
|
||||
key_list = list(mapp.keys())
|
||||
s = {}
|
||||
|
||||
for key in key_list:
|
||||
val_index = key_list.index(key)
|
||||
|
||||
try:
|
||||
s[key] = settings[val_index]
|
||||
|
||||
if type(s[key]) == int:
|
||||
s[key] = bool(s[key])
|
||||
if type(s[key]) == str:
|
||||
s[key] = str(s[key]).split(",")
|
||||
|
||||
except IndexError:
|
||||
s[key] = None
|
||||
|
||||
root_dirs = sdb.get_root_dirs()
|
||||
s["root_dirs"] = root_dirs
|
||||
s["plugins"] = plugins
|
||||
s["version"] = Info.SWINGMUSIC_APP_VERSION
|
||||
|
||||
return {
|
||||
"settings": s,
|
||||
}
|
||||
return config
|
||||
|
||||
|
||||
@background
|
||||
@@ -245,7 +226,6 @@ def set_setting(body: SetSettingBody):
|
||||
value = str(value).split(",")
|
||||
value = set(value)
|
||||
|
||||
set_flag(flag, value)
|
||||
reload_all_for_set_setting()
|
||||
|
||||
# if value is a set, convert it to a string
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ from app.api.apischemas import TrackHashSchema
|
||||
from app.lib.trackslib import get_silence_paddings
|
||||
|
||||
# from app.store.tracks import TrackStore
|
||||
from app.db import TrackTable
|
||||
from app.db.libdata import TrackTable
|
||||
from app.utils.files import guess_mime_type
|
||||
|
||||
bp_tag = Tag(name="File", description="Audio files")
|
||||
|
||||
Reference in New Issue
Block a user