mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
document + rename stuff
This commit is contained in:
@@ -56,8 +56,8 @@
|
||||
then artist B is similar to A with the same weight, unless overwritten.
|
||||
- Figure out how to update album/artist tables instead of deleting all rows when the app starts
|
||||
- Move get all filtering and sorting operations to the database since all sort keys are table columns
|
||||
- Replace the DbManager class with cls.execute()
|
||||
|
||||
- Paginate the following endpoints:
|
||||
1. Folder tracks
|
||||
2. Playlist tracks
|
||||
|
||||
+1
-4
@@ -76,6 +76,7 @@ def create_api():
|
||||
CORS(app, origins="*", supports_credentials=True)
|
||||
|
||||
# RESPONSE COMPRESSION
|
||||
# Only compress JSON responses
|
||||
Compress(app)
|
||||
app.config["COMPRESS_MIMETYPES"] = [
|
||||
"application/json",
|
||||
@@ -84,10 +85,6 @@ def create_api():
|
||||
# JWT
|
||||
jwt = JWTManager(app)
|
||||
|
||||
# @jwt.user_identity_loader
|
||||
# def user_identity_lookup(user):
|
||||
# return user
|
||||
|
||||
@jwt.user_lookup_loader
|
||||
def user_lookup_callback(_jwt_header, jwt_data):
|
||||
identity = jwt_data["sub"]
|
||||
|
||||
+20
-12
@@ -9,14 +9,12 @@ from sqlalchemy import (
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.orm import (
|
||||
DeclarativeBase,
|
||||
MappedAsDataclass,
|
||||
Session
|
||||
)
|
||||
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Session
|
||||
|
||||
from app.db.engine import DbEngine
|
||||
|
||||
|
||||
# Enable foreign key constraints for SQLite
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
@@ -25,9 +23,12 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
|
||||
|
||||
class DbManager:
|
||||
""" """
|
||||
|
||||
def __init__(self, commit: bool = False):
|
||||
self.commit = commit
|
||||
self.conn = DbEngine.engine.connect()
|
||||
|
||||
with Session(DbEngine.engine) as session:
|
||||
session.connection
|
||||
|
||||
@@ -42,9 +43,15 @@ class DbManager:
|
||||
|
||||
|
||||
class Base(MappedAsDataclass, DeclarativeBase):
|
||||
"""
|
||||
Base class for all database models.
|
||||
|
||||
It has methods common to all tables. eg. `insert_one`, `insert_many`, `remove_all`, `remove_one`, `all`, `count`.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def execute(cls, stmt: Any, commit: bool = False):
|
||||
with DbManager(commit=commit) as conn:
|
||||
with DbEngine.manager(commit=commit) as conn:
|
||||
return conn.execute(stmt)
|
||||
|
||||
@classmethod
|
||||
@@ -52,8 +59,7 @@ class Base(MappedAsDataclass, DeclarativeBase):
|
||||
"""
|
||||
Inserts multiple items into the database.
|
||||
"""
|
||||
with DbManager(commit=True) as conn:
|
||||
return conn.execute(insert(cls).values(items))
|
||||
return cls.execute(insert(cls).values(items), commit=True)
|
||||
|
||||
@classmethod
|
||||
def insert_one(cls, item: dict[str, Any]):
|
||||
@@ -64,12 +70,11 @@ class Base(MappedAsDataclass, DeclarativeBase):
|
||||
|
||||
@classmethod
|
||||
def remove_all(cls):
|
||||
with DbManager(commit=True) as conn:
|
||||
conn.execute(delete(cls))
|
||||
return cls.execute(delete(cls), commit=True)
|
||||
|
||||
@classmethod
|
||||
def remove_one(cls, id: int):
|
||||
cls.execute(delete(cls).where(cls.id == id), commit=True)
|
||||
return cls.execute(delete(cls).where(cls.id == id), commit=True)
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
@@ -80,5 +85,8 @@ class Base(MappedAsDataclass, DeclarativeBase):
|
||||
return cls.execute(select(func.count()).select_from(cls)).scalar()
|
||||
|
||||
|
||||
def create_all():
|
||||
def create_all_tables():
|
||||
"""
|
||||
Creates all the tables that build on the Base class.
|
||||
"""
|
||||
Base().metadata.create_all(DbEngine.engine)
|
||||
|
||||
+26
-1
@@ -1,5 +1,30 @@
|
||||
from contextlib import contextmanager
|
||||
from sqlalchemy import Engine
|
||||
|
||||
|
||||
class DbEngine:
|
||||
engine: Engine = None
|
||||
"""
|
||||
The database engine instance.
|
||||
"""
|
||||
|
||||
engine: Engine
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def manager(cls, commit: bool):
|
||||
"""
|
||||
This context manager manages access to the database.
|
||||
|
||||
When the context manager is entered, it returns a connection object that can be used to execute SQL statements.
|
||||
|
||||
If the `commit` parameter is set to `True`, the context manager will commit the transaction when it exits.
|
||||
"""
|
||||
|
||||
try:
|
||||
conn = cls.engine.connect()
|
||||
yield conn.execution_options(preserve_rowcount=True)
|
||||
|
||||
if commit:
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
+4
-1
@@ -23,6 +23,9 @@ from typing import Any, Iterable, Optional
|
||||
def create_all():
|
||||
"""
|
||||
Create all the tables defined in this file.
|
||||
|
||||
NOTE: We need this function because the MasterBase does not collect
|
||||
the tables defined here (as they are grand-children of the MasterBase)
|
||||
"""
|
||||
Base.metadata.create_all(DbEngine.engine)
|
||||
|
||||
@@ -317,7 +320,7 @@ class AlbumTable(Base):
|
||||
# NOTE: The artist dict keys need to in the same order they appear in the db for this to work!
|
||||
select(AlbumTable).where(AlbumTable.artisthashes.contains(artist))
|
||||
)
|
||||
albums[artist] = (albums_to_dataclasses(result.fetchall()))
|
||||
albums[artist] = albums_to_dataclasses(result.fetchall())
|
||||
|
||||
return albums
|
||||
|
||||
|
||||
@@ -90,10 +90,10 @@ class SQLiteManager:
|
||||
if self.test_db_path:
|
||||
db_path = self.test_db_path
|
||||
else:
|
||||
db_path = settings.Db.get_app_db_path()
|
||||
db_path = settings.DbPaths.get_app_db_path()
|
||||
|
||||
if self.userdata_db:
|
||||
db_path = settings.Db.get_userdata_db_path()
|
||||
db_path = settings.DbPaths.get_userdata_db_path()
|
||||
|
||||
self.conn = sqlite3.connect(
|
||||
db_path,
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
Contains methods relating to albums.
|
||||
"""
|
||||
|
||||
from dataclasses import asdict
|
||||
from typing import Any
|
||||
from itertools import groupby
|
||||
|
||||
|
||||
from app.models.track import Track
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.tracks import TrackStore
|
||||
|
||||
|
||||
def remove_duplicate_on_merge_versions(tracks: list[Track]) -> list[Track]:
|
||||
def remove_duplicate_on_merge_versions(tracks: list[Track]):
|
||||
"""
|
||||
Removes duplicate tracks when merging versions of the same album.
|
||||
"""
|
||||
@@ -21,8 +15,6 @@ def remove_duplicate_on_merge_versions(tracks: list[Track]) -> list[Track]:
|
||||
|
||||
|
||||
def sort_by_track_no(tracks: list[Track]):
|
||||
# tracks = [asdict(t) for t in tracks]
|
||||
|
||||
for t in tracks:
|
||||
track = str(t.track).zfill(3)
|
||||
t._pos = int(f"{t.disc}{track}")
|
||||
|
||||
@@ -145,34 +145,3 @@ class CheckArtistImages:
|
||||
|
||||
if url is not None:
|
||||
return DownloadImage(url, name=f"{artist['artisthash']}.webp")
|
||||
|
||||
|
||||
# def fetch_album_bio(title: str, albumartist: str) -> str | None: """ Returns the album bio for a given album. """
|
||||
# last_fm_url = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={}&artist={}&album={
|
||||
# }&format=json".format( settings.Paths.LAST_FM_API_KEY, albumartist, title )
|
||||
|
||||
# try:
|
||||
# response = requests.get(last_fm_url)
|
||||
# data = response.json()
|
||||
# except:
|
||||
# return None
|
||||
|
||||
# try:
|
||||
# bio = data["album"]["wiki"]["summary"].split('<a href="https://www.last.fm/')[0]
|
||||
# except KeyError:
|
||||
# bio = None
|
||||
|
||||
# return bio
|
||||
|
||||
|
||||
# class FetchAlbumBio:
|
||||
# """
|
||||
# Returns the album bio for a given album.
|
||||
# """
|
||||
|
||||
# def __init__(self, title: str, albumartist: str):
|
||||
# self.title = title
|
||||
# self.albumartist = albumartist
|
||||
|
||||
# def __call__(self):
|
||||
# return fetch_album_bio(self.title, self.albumartist)
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ SUPPORTED_FILES = tuple(f".{file}" for file in FILES)
|
||||
|
||||
|
||||
# ===== SQLite =====
|
||||
class Db:
|
||||
class DbPaths:
|
||||
APP_DB_NAME = "swing.db"
|
||||
USER_DATA_DB_NAME = "userdata.db"
|
||||
|
||||
|
||||
+6
-8
@@ -5,14 +5,12 @@ Applies migrations.
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from app.db.userdata import UserTable
|
||||
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
|
||||
from app.migrations import apply_migrations
|
||||
from app.settings import Db
|
||||
|
||||
from app.db import create_all
|
||||
from app.db.libdata import create_all as create_all_libdata
|
||||
from app.settings import DbPaths
|
||||
|
||||
from app.db.engine import DbEngine
|
||||
from app.db import create_all_tables
|
||||
from app.db.libdata import create_all as create_user_tables
|
||||
|
||||
|
||||
def run_migrations():
|
||||
@@ -27,14 +25,14 @@ def setup_sqlite():
|
||||
Create Sqlite databases and tables.
|
||||
"""
|
||||
DbEngine.engine = create_engine(
|
||||
f"sqlite+pysqlite:///{Db.get_app_db_path()}",
|
||||
f"sqlite+pysqlite:///{DbPaths.get_app_db_path()}",
|
||||
echo=False,
|
||||
max_overflow=20,
|
||||
pool_size=10,
|
||||
)
|
||||
|
||||
create_all()
|
||||
create_all_libdata()
|
||||
create_all_tables()
|
||||
create_user_tables()
|
||||
|
||||
if not UserTable.get_all():
|
||||
UserTable.insert_default_user()
|
||||
|
||||
@@ -23,7 +23,6 @@ from app.api import create_api
|
||||
from app.arg_handler import ProcessArgs
|
||||
from app.lib.tagger import IndexEverything
|
||||
from app.lib.watchdogg import Watcher as WatchDog
|
||||
from app.periodic_scan import run_periodic_scans
|
||||
from app.plugins.register import register_plugins
|
||||
from app.settings import FLASKVARS, TCOLOR, Info
|
||||
from app.setup import load_into_mem, run_setup
|
||||
@@ -32,8 +31,15 @@ from app.utils.filesystem import get_home_res_path
|
||||
from app.utils.paths import getClientFilesExtensions
|
||||
from app.utils.threading import background
|
||||
|
||||
mimetypes.add_type("text/css", ".css")
|
||||
# Load mimetypes for the web client's static files
|
||||
# Loading mimetypes should happen automatically but
|
||||
# sometimes the mimetypes are not loaded correctly
|
||||
# eg. when the Registry is messed up on Windows.
|
||||
|
||||
# See the following issues:
|
||||
# https://github.com/swingmx/swingmusic/issues/137
|
||||
|
||||
mimetypes.add_type("text/css", ".css")
|
||||
mimetypes.add_type("text/javascript", ".js")
|
||||
mimetypes.add_type("text/plain", ".txt")
|
||||
mimetypes.add_type("text/html", ".html")
|
||||
@@ -166,6 +172,8 @@ def serve_client_files(path: str):
|
||||
gzipped_path = path + ".gz"
|
||||
user_agent = request.headers.get("User-Agent")
|
||||
|
||||
# INFO: Safari doesn't support gzip encoding
|
||||
# See issue: https://github.com/swingmx/swingmusic/issues/155
|
||||
is_safari = user_agent.find("Safari") >= 0 and user_agent.find("Chrome") < 0
|
||||
|
||||
if is_safari:
|
||||
|
||||
Reference in New Issue
Block a user