mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
port search to stores
+ fix favorites
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
Contains all the album routes.
|
Contains all the album routes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -54,6 +55,9 @@ def get_album_tracks_and_info(body: AlbumHashSchema):
|
|||||||
track_total = sum({int(t.extra.get("track_total", 1) or 1) for t in tracks})
|
track_total = sum({int(t.extra.get("track_total", 1) or 1) for t in tracks})
|
||||||
avg_bitrate = sum(t.bitrate for t in tracks) // (len(tracks) or 1)
|
avg_bitrate = sum(t.bitrate for t in tracks) // (len(tracks) or 1)
|
||||||
|
|
||||||
|
album.fav_userids = [1]
|
||||||
|
pprint(album)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"info": album,
|
"info": album,
|
||||||
"extra": {
|
"extra": {
|
||||||
|
|||||||
+34
-12
@@ -11,7 +11,11 @@ from app.db.libdata import AlbumTable, TrackTable
|
|||||||
from app.db.userdata import FavoritesTable
|
from app.db.userdata import FavoritesTable
|
||||||
from app.models import FavType
|
from app.models import FavType
|
||||||
from app.settings import Defaults
|
from app.settings import Defaults
|
||||||
from app.utils.bisection import use_bisection
|
|
||||||
|
from app.store.albums import AlbumStore
|
||||||
|
from app.store.artists import ArtistStore
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
from app.serializers.track import serialize_track, serialize_tracks
|
from app.serializers.track import serialize_track, serialize_tracks
|
||||||
from app.serializers.artist import (
|
from app.serializers.artist import (
|
||||||
serialize_for_card as serialize_artist,
|
serialize_for_card as serialize_artist,
|
||||||
@@ -46,14 +50,27 @@ def toggle_favorite(body: FavoritesAddBody):
|
|||||||
"""
|
"""
|
||||||
Adds a favorite to the database.
|
Adds a favorite to the database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
FavoritesTable.insert_item({"hash": body.hash, "type": body.type})
|
FavoritesTable.insert_item({"hash": body.hash, "type": body.type})
|
||||||
|
except:
|
||||||
|
return {"msg": "Failed! An error occured"}, 500
|
||||||
|
|
||||||
if body.type == FavType.track:
|
if body.type == FavType.track:
|
||||||
TrackTable.set_is_favorite(body.hash, True)
|
entry = TrackStore.trackhashmap.get(body.hash)
|
||||||
|
if entry is not None:
|
||||||
|
entry.toggle_favorite_user()
|
||||||
|
|
||||||
elif body.type == FavType.album:
|
elif body.type == FavType.album:
|
||||||
AlbumTable.set_is_favorite(body.hash, True)
|
entry = AlbumStore.albummap.get(body.hash)
|
||||||
|
|
||||||
|
if entry is not None:
|
||||||
|
entry.toggle_favorite_user()
|
||||||
elif body.type == FavType.artist:
|
elif body.type == FavType.artist:
|
||||||
ArtistTable.set_is_favorite(body.hash, True)
|
entry = ArtistStore.artistmap.get(body.hash)
|
||||||
|
|
||||||
|
if entry is not None:
|
||||||
|
entry.toggle_favorite_user()
|
||||||
|
|
||||||
return {"msg": "Added to favorites"}
|
return {"msg": "Added to favorites"}
|
||||||
|
|
||||||
@@ -95,7 +112,8 @@ def get_favorite_albums(query: GetAllOfTypeQuery):
|
|||||||
fav_albums, total = FavoritesTable.get_fav_albums(query.start, query.limit)
|
fav_albums, total = FavoritesTable.get_fav_albums(query.start, query.limit)
|
||||||
fav_albums.reverse()
|
fav_albums.reverse()
|
||||||
|
|
||||||
return {"albums": serialize_for_card_many(fav_albums), "total": total}
|
albums = AlbumStore.get_albums_by_hashes(a.hash for a in fav_albums)
|
||||||
|
return {"albums": serialize_for_card_many(albums), "total": total}
|
||||||
|
|
||||||
|
|
||||||
@api.get("/tracks")
|
@api.get("/tracks")
|
||||||
@@ -104,6 +122,10 @@ def get_favorite_tracks(query: GetAllOfTypeQuery):
|
|||||||
Get favorite tracks
|
Get favorite tracks
|
||||||
"""
|
"""
|
||||||
tracks, total = FavoritesTable.get_fav_tracks(query.start, query.limit)
|
tracks, total = FavoritesTable.get_fav_tracks(query.start, query.limit)
|
||||||
|
|
||||||
|
tracks.reverse()
|
||||||
|
tracks = TrackTable.get_tracks_by_trackhashes([t.hash for t in tracks])
|
||||||
|
|
||||||
return {"tracks": serialize_tracks(tracks), "total": total}
|
return {"tracks": serialize_tracks(tracks), "total": total}
|
||||||
|
|
||||||
|
|
||||||
@@ -118,6 +140,7 @@ def get_favorite_artists(query: GetAllOfTypeQuery):
|
|||||||
)
|
)
|
||||||
artists.reverse()
|
artists.reverse()
|
||||||
|
|
||||||
|
artists = ArtistStore.get_artists_by_hashes(a.hash for a in artists)
|
||||||
return {"artists": [serialize_artist(a) for a in artists], "total": total}
|
return {"artists": [serialize_artist(a) for a in artists], "total": total}
|
||||||
|
|
||||||
|
|
||||||
@@ -164,9 +187,9 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
|||||||
albums = []
|
albums = []
|
||||||
artists = []
|
artists = []
|
||||||
|
|
||||||
track_master_hash = TrackTable.get_all_hashes()
|
track_master_hash = TrackStore.trackhashmap.keys()
|
||||||
album_master_hash = AlbumTable.get_all_hashes()
|
album_master_hash = AlbumStore.albummap.keys()
|
||||||
artist_master_hash = ArtistTable.get_all_hashes()
|
artist_master_hash = ArtistStore.artistmap.keys()
|
||||||
|
|
||||||
# INFO: Filter out invalid hashes (file not found or tags edited)
|
# INFO: Filter out invalid hashes (file not found or tags edited)
|
||||||
for fav in favs:
|
for fav in favs:
|
||||||
@@ -188,12 +211,11 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
|||||||
"artists": len(artists),
|
"artists": len(artists),
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks = TrackTable.get_tracks_by_trackhashes(tracks, limit=track_limit)
|
tracks = TrackStore.get_tracks_by_trackhashes(tracks[:track_limit])
|
||||||
albums = AlbumTable.get_albums_by_albumhashes(albums, limit=album_limit)
|
albums = AlbumStore.get_albums_by_hashes(albums[:album_limit])
|
||||||
artists = ArtistTable.get_artists_by_artisthashes(artists, limit=artist_limit)
|
artists = ArtistStore.get_artists_by_hashes(artists[:artist_limit])
|
||||||
|
|
||||||
recents = []
|
recents = []
|
||||||
# first_n = favs
|
|
||||||
|
|
||||||
for fav in favs:
|
for fav in favs:
|
||||||
if len(recents) >= largest:
|
if len(recents) >= largest:
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ from flask_openapi3 import APIBlueprint
|
|||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from app.api.apischemas import TrackHashSchema
|
from app.api.apischemas import TrackHashSchema
|
||||||
|
|
||||||
from app.db.libdata import AlbumTable, ArtistTable, TrackTable
|
|
||||||
from app.db.userdata import ScrobbleTable
|
from app.db.userdata import ScrobbleTable
|
||||||
from app.settings import Defaults
|
from app.settings import Defaults
|
||||||
|
from app.store.albums import AlbumStore
|
||||||
|
from app.store.artists import ArtistStore
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
bp_tag = Tag(name="Logger", description="Log item plays")
|
bp_tag = Tag(name="Logger", description="Log item plays")
|
||||||
api = APIBlueprint("logger", __name__, url_prefix="/logger", abp_tags=[bp_tag])
|
api = APIBlueprint("logger", __name__, url_prefix="/logger", abp_tags=[bp_tag])
|
||||||
@@ -33,14 +35,27 @@ def log_track(body: LogTrackBody):
|
|||||||
if not timestamp or duration < 5:
|
if not timestamp or duration < 5:
|
||||||
return {"msg": "Invalid entry."}, 400
|
return {"msg": "Invalid entry."}, 400
|
||||||
|
|
||||||
track = TrackTable.get_track_by_trackhash(body.trackhash)
|
trackentry = TrackStore.trackhashmap.get(body.trackhash)
|
||||||
|
if trackentry is None:
|
||||||
if track is None:
|
|
||||||
return {"msg": "Track not found."}, 404
|
return {"msg": "Track not found."}, 404
|
||||||
|
|
||||||
ScrobbleTable.add(dict(body))
|
ScrobbleTable.add(dict(body))
|
||||||
TrackTable.increment_playcount(body.trackhash, duration, timestamp)
|
|
||||||
AlbumTable.increment_playcount(track.albumhash, duration, timestamp)
|
# Update play data on the in-memory stores
|
||||||
ArtistTable.increment_playcount(track.artisthashes, duration, timestamp)
|
track = trackentry.tracks[0]
|
||||||
|
album = AlbumStore.albummap.get(track.albumhash)
|
||||||
|
|
||||||
|
if album:
|
||||||
|
album.increment_playcount(duration, timestamp)
|
||||||
|
|
||||||
|
for hash in track.artisthashes:
|
||||||
|
artist = ArtistStore.artistmap.get(hash)
|
||||||
|
|
||||||
|
if artist:
|
||||||
|
artist.increment_playcount(duration, timestamp)
|
||||||
|
|
||||||
|
track = TrackStore.trackhashmap.get(body.trackhash)
|
||||||
|
if track:
|
||||||
|
track.increment_playcount(duration, timestamp)
|
||||||
|
|
||||||
return {"msg": "recorded"}, 201
|
return {"msg": "recorded"}, 201
|
||||||
|
|||||||
+8
-8
@@ -9,9 +9,9 @@ from flask_openapi3 import APIBlueprint
|
|||||||
|
|
||||||
from app import models
|
from app import models
|
||||||
from app.api.apischemas import GenericLimitSchema
|
from app.api.apischemas import GenericLimitSchema
|
||||||
from app.db.libdata import TrackTable
|
|
||||||
from app.lib import searchlib
|
from app.lib import searchlib
|
||||||
from app.settings import Defaults
|
from app.settings import Defaults
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
|
|
||||||
tag = Tag(name="Search", description="Search for tracks, albums and artists")
|
tag = Tag(name="Search", description="Search for tracks, albums and artists")
|
||||||
@@ -31,7 +31,7 @@ class Search:
|
|||||||
Calls :class:`SearchTracks` which returns the tracks that fuzzily match
|
Calls :class:`SearchTracks` which returns the tracks that fuzzily match
|
||||||
the search terms. Then adds them to the `SearchResults` store.
|
the search terms. Then adds them to the `SearchResults` store.
|
||||||
"""
|
"""
|
||||||
self.tracks = TrackTable.get_all()
|
self.tracks = TrackStore.get_flat_list()
|
||||||
return searchlib.TopResults().search(self.query, tracks_only=True)
|
return searchlib.TopResults().search(self.query, tracks_only=True)
|
||||||
|
|
||||||
def search_artists(self):
|
def search_artists(self):
|
||||||
@@ -124,7 +124,7 @@ def get_top_results(query: TopResultsQuery):
|
|||||||
|
|
||||||
class SearchLoadMoreQuery(SearchQuery):
|
class SearchLoadMoreQuery(SearchQuery):
|
||||||
type: str = Field(description="The type of search", example="tracks")
|
type: str = Field(description="The type of search", example="tracks")
|
||||||
index: int = Field(description="The index to start from", default=0)
|
start: int = Field(description="The index to start from", default=0)
|
||||||
|
|
||||||
|
|
||||||
@api.get("/loadmore")
|
@api.get("/loadmore")
|
||||||
@@ -136,26 +136,26 @@ def search_load_more(query: SearchLoadMoreQuery):
|
|||||||
|
|
||||||
NOTE: You must first initiate a search using the `/search` endpoint.
|
NOTE: You must first initiate a search using the `/search` endpoint.
|
||||||
"""
|
"""
|
||||||
query = query.q
|
q = query.q
|
||||||
item_type = query.type
|
item_type = query.type
|
||||||
index = query.index
|
index = query.start
|
||||||
|
|
||||||
if item_type == "tracks":
|
if item_type == "tracks":
|
||||||
t = Search(query).search_tracks()
|
t = Search(q).search_tracks()
|
||||||
return {
|
return {
|
||||||
"tracks": t[index : index + SEARCH_COUNT],
|
"tracks": t[index : index + SEARCH_COUNT],
|
||||||
"more": len(t) > index + SEARCH_COUNT,
|
"more": len(t) > index + SEARCH_COUNT,
|
||||||
}
|
}
|
||||||
|
|
||||||
elif item_type == "albums":
|
elif item_type == "albums":
|
||||||
a = Search(query).search_albums()
|
a = Search(q).search_albums()
|
||||||
return {
|
return {
|
||||||
"albums": a[index : index + SEARCH_COUNT],
|
"albums": a[index : index + SEARCH_COUNT],
|
||||||
"more": len(a) > index + SEARCH_COUNT,
|
"more": len(a) > index + SEARCH_COUNT,
|
||||||
}
|
}
|
||||||
|
|
||||||
elif item_type == "artists":
|
elif item_type == "artists":
|
||||||
a = Search(query).search_artists()
|
a = Search(q).search_artists()
|
||||||
return {
|
return {
|
||||||
"artists": a[index : index + SEARCH_COUNT],
|
"artists": a[index : index + SEARCH_COUNT],
|
||||||
"more": len(a) > index + SEARCH_COUNT,
|
"more": len(a) > index + SEARCH_COUNT,
|
||||||
|
|||||||
+3
-2
@@ -10,7 +10,7 @@ from app.db.sqlite.plugins import PluginsMethods as pdb
|
|||||||
from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
|
from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
|
||||||
from app.db.userdata import PluginTable
|
from app.db.userdata import PluginTable
|
||||||
from app.lib import populate
|
from app.lib import populate
|
||||||
from app.lib.tagger import index_everything
|
from app.lib.index import index_everything
|
||||||
from app.lib.watchdogg import Watcher as WatchDog
|
from app.lib.watchdogg import Watcher as WatchDog
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
from app.settings import Info, Paths, SessionVarKeys
|
from app.settings import Info, Paths, SessionVarKeys
|
||||||
@@ -238,7 +238,8 @@ def set_setting(body: SetSettingBody):
|
|||||||
|
|
||||||
@background
|
@background
|
||||||
def run_populate():
|
def run_populate():
|
||||||
populate.Populate(instance_key=get_random_str())
|
# populate.Populate(instance_key=get_random_str())
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@api.get("/trigger-scan")
|
@api.get("/trigger-scan")
|
||||||
|
|||||||
+3
-34
@@ -7,41 +7,10 @@ from sqlalchemy import (
|
|||||||
select,
|
select,
|
||||||
)
|
)
|
||||||
|
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
|
||||||
from sqlalchemy import event
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Session
|
|
||||||
|
|
||||||
from app.db.engine import DbEngine
|
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()
|
|
||||||
cursor.execute("PRAGMA foreign_keys=ON")
|
|
||||||
cursor.close()
|
|
||||||
|
|
||||||
|
|
||||||
class DbManager:
|
|
||||||
""" """
|
|
||||||
|
|
||||||
def __init__(self, commit: bool = False):
|
|
||||||
self.commit = commit
|
|
||||||
self.conn = DbEngine.engine.connect()
|
|
||||||
|
|
||||||
with Session(DbEngine.engine) as session:
|
|
||||||
session.connection
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self.conn.execution_options(preserve_rowcount=True)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
if self.commit:
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
self.conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Base(MappedAsDataclass, DeclarativeBase):
|
class Base(MappedAsDataclass, DeclarativeBase):
|
||||||
"""
|
"""
|
||||||
Base class for all database models.
|
Base class for all database models.
|
||||||
@@ -51,8 +20,8 @@ class Base(MappedAsDataclass, DeclarativeBase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, stmt: Any, commit: bool = False):
|
def execute(cls, stmt: Any, commit: bool = False):
|
||||||
with DbEngine.manager(commit=commit) as conn:
|
with DbEngine.manager(commit=commit) as session:
|
||||||
return conn.execute(stmt)
|
return session.execute(stmt)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def insert_many(cls, items: list[dict[str, Any]]):
|
def insert_many(cls, items: list[dict[str, Any]]):
|
||||||
|
|||||||
+20
-5
@@ -1,5 +1,18 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from sqlalchemy import Engine
|
import gc
|
||||||
|
from sqlalchemy import Engine, event
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(Engine, "connect")
|
||||||
|
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
cursor.execute("PRAGMA journal_mode=WAL")
|
||||||
|
cursor.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
cursor.execute("PRAGMA cache_size=10000")
|
||||||
|
cursor.execute("PRAGMA foreign_keys=ON")
|
||||||
|
cursor.execute("PRAGMA temp_store=MEMORY")
|
||||||
|
cursor.execute("PRAGMA mmap_size=30000000000")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
class DbEngine:
|
class DbEngine:
|
||||||
@@ -11,20 +24,22 @@ class DbEngine:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def manager(cls, commit: bool):
|
def manager(cls, commit: bool = False):
|
||||||
"""
|
"""
|
||||||
This context manager manages access to the database.
|
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.
|
When the context manager is entered, it returns a session 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.
|
If the `commit` parameter is set to `True`, the context manager will commit the transaction when it exits.
|
||||||
"""
|
"""
|
||||||
|
conn = cls.engine.connect()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = cls.engine.connect()
|
|
||||||
yield conn.execution_options(preserve_rowcount=True)
|
yield conn.execution_options(preserve_rowcount=True)
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise e
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
+22
-26
@@ -1,6 +1,5 @@
|
|||||||
from app.db import (
|
from app.db import (
|
||||||
Base as MasterBase,
|
Base as MasterBase,
|
||||||
DbManager,
|
|
||||||
)
|
)
|
||||||
from app.db.utils import (
|
from app.db.utils import (
|
||||||
album_to_dataclass,
|
album_to_dataclass,
|
||||||
@@ -13,7 +12,7 @@ from app.db.utils import (
|
|||||||
from app.models import Album as AlbumModel
|
from app.models import Album as AlbumModel
|
||||||
from app.utils.remove_duplicates import remove_duplicates
|
from app.utils.remove_duplicates import remove_duplicates
|
||||||
from app.db.engine import DbEngine
|
from app.db.engine import DbEngine
|
||||||
from sqlalchemy import JSON, Boolean, Integer, String, delete, select, update
|
from sqlalchemy import JSON, Integer, String, delete, select, update
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
|
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ def create_all():
|
|||||||
class Base(MasterBase, DeclarativeBase):
|
class Base(MasterBase, DeclarativeBase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_hashes(cls, create_date: int | None = None):
|
def get_all_hashes(cls, create_date: int | None = None):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
if create_date:
|
if create_date:
|
||||||
if cls.__tablename__ == "track":
|
if cls.__tablename__ == "track":
|
||||||
stmt = select(TrackTable.trackhash).where(
|
stmt = select(TrackTable.trackhash).where(
|
||||||
@@ -67,7 +66,7 @@ class Base(MasterBase, DeclarativeBase):
|
|||||||
hash (str): The hash value.
|
hash (str): The hash value.
|
||||||
is_favorite (bool): The value of the 'is_favorite' flag.
|
is_favorite (bool): The value of the 'is_favorite' flag.
|
||||||
"""
|
"""
|
||||||
with DbManager(commit=True) as conn:
|
with DbEngine.manager(commit=True) as conn:
|
||||||
if cls.__tablename__ == "track":
|
if cls.__tablename__ == "track":
|
||||||
stmt = (
|
stmt = (
|
||||||
update(cls)
|
update(cls)
|
||||||
@@ -129,7 +128,6 @@ class TrackTable(Base):
|
|||||||
title: Mapped[str] = mapped_column(String())
|
title: Mapped[str] = mapped_column(String())
|
||||||
track: Mapped[int] = mapped_column(Integer())
|
track: Mapped[int] = mapped_column(Integer())
|
||||||
trackhash: Mapped[str] = mapped_column(String(), index=True)
|
trackhash: Mapped[str] = mapped_column(String(), index=True)
|
||||||
# is_favorite: Mapped[Optional[bool]] = mapped_column(Boolean())
|
|
||||||
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
@@ -139,13 +137,13 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls))
|
result = conn.execute(select(cls))
|
||||||
return tracks_to_dataclasses(result.fetchall())
|
return tracks_to_dataclasses(result.fetchall())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_tracks_by_filepaths(cls, filepaths: list[str]):
|
def get_tracks_by_filepaths(cls, filepaths: list[str]):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable)
|
select(TrackTable)
|
||||||
.where(TrackTable.filepath.in_(filepaths))
|
.where(TrackTable.filepath.in_(filepaths))
|
||||||
@@ -155,7 +153,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_tracks_by_albumhash(cls, albumhash: str):
|
def get_tracks_by_albumhash(cls, albumhash: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable).where(TrackTable.albumhash == albumhash)
|
select(TrackTable).where(TrackTable.albumhash == albumhash)
|
||||||
)
|
)
|
||||||
@@ -164,7 +162,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_track_by_trackhash(cls, hash: str, filepath: str = ""):
|
def get_track_by_trackhash(cls, hash: str, filepath: str = ""):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
if filepath:
|
if filepath:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable)
|
select(TrackTable)
|
||||||
@@ -186,7 +184,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_tracks_by_artisthash(cls, artisthash: str):
|
def get_tracks_by_artisthash(cls, artisthash: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable).where(TrackTable.artists.contains(artisthash))
|
select(TrackTable).where(TrackTable.artists.contains(artisthash))
|
||||||
)
|
)
|
||||||
@@ -194,7 +192,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_tracks_in_path(cls, path: str):
|
def get_tracks_in_path(cls, path: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable)
|
select(TrackTable)
|
||||||
.where(TrackTable.filepath.contains(path))
|
.where(TrackTable.filepath.contains(path))
|
||||||
@@ -204,7 +202,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_tracks_by_trackhashes(cls, hashes: Iterable[str], limit: int | None = None):
|
def get_tracks_by_trackhashes(cls, hashes: Iterable[str], limit: int | None = None):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable)
|
select(TrackTable)
|
||||||
.where(TrackTable.trackhash.in_(hashes))
|
.where(TrackTable.trackhash.in_(hashes))
|
||||||
@@ -221,7 +219,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_recently_added(cls, start: int, limit: int):
|
def get_recently_added(cls, start: int, limit: int):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(TrackTable)
|
select(TrackTable)
|
||||||
.order_by(TrackTable.last_mod.desc())
|
.order_by(TrackTable.last_mod.desc())
|
||||||
@@ -243,7 +241,7 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_tracks_by_filepaths(cls, filepaths: set[str]):
|
def remove_tracks_by_filepaths(cls, filepaths: set[str]):
|
||||||
with DbManager(commit=True) as conn:
|
with DbEngine.manager(commit=True) as conn:
|
||||||
conn.execute(delete(TrackTable).where(TrackTable.filepath.in_(filepaths)))
|
conn.execute(delete(TrackTable).where(TrackTable.filepath.in_(filepaths)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -270,7 +268,6 @@ class AlbumTable(Base):
|
|||||||
og_title: Mapped[str] = mapped_column(String())
|
og_title: Mapped[str] = mapped_column(String())
|
||||||
title: Mapped[str] = mapped_column(String())
|
title: Mapped[str] = mapped_column(String())
|
||||||
trackcount: Mapped[int] = mapped_column(Integer())
|
trackcount: Mapped[int] = mapped_column(Integer())
|
||||||
# is_favorite: Mapped[Optional[bool]] = mapped_column(Boolean())
|
|
||||||
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
@@ -280,14 +277,14 @@ class AlbumTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(AlbumTable))
|
result = conn.execute(select(AlbumTable))
|
||||||
all = result.fetchall()
|
all = result.fetchall()
|
||||||
return albums_to_dataclasses(all)
|
return albums_to_dataclasses(all)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_album_by_albumhash(cls, hash: str):
|
def get_album_by_albumhash(cls, hash: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(AlbumTable).where(AlbumTable.albumhash == hash)
|
select(AlbumTable).where(AlbumTable.albumhash == hash)
|
||||||
)
|
)
|
||||||
@@ -298,7 +295,7 @@ class AlbumTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_albums_by_albumhashes(cls, hashes: Iterable[str], limit: int | None = None):
|
def get_albums_by_albumhashes(cls, hashes: Iterable[str], limit: int | None = None):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(AlbumTable).where(AlbumTable.albumhash.in_(hashes)).limit(limit)
|
select(AlbumTable).where(AlbumTable.albumhash.in_(hashes)).limit(limit)
|
||||||
)
|
)
|
||||||
@@ -312,7 +309,7 @@ class AlbumTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_albums_by_artisthashes(cls, artisthashes: list[str]):
|
def get_albums_by_artisthashes(cls, artisthashes: list[str]):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
albums: dict[str, list[AlbumModel]] = {}
|
albums: dict[str, list[AlbumModel]] = {}
|
||||||
|
|
||||||
for artist in artisthashes:
|
for artist in artisthashes:
|
||||||
@@ -325,7 +322,7 @@ class AlbumTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_albums_by_base_title(cls, base_title: str):
|
def get_albums_by_base_title(cls, base_title: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(AlbumTable).where(AlbumTable.base_title == base_title)
|
select(AlbumTable).where(AlbumTable.base_title == base_title)
|
||||||
)
|
)
|
||||||
@@ -333,7 +330,7 @@ class AlbumTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_albums_by_artisthash(cls, artisthash: str):
|
def get_albums_by_artisthash(cls, artisthash: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(AlbumTable).where(AlbumTable.artisthashes.contains(artisthash))
|
select(AlbumTable).where(AlbumTable.artisthashes.contains(artisthash))
|
||||||
)
|
)
|
||||||
@@ -359,7 +356,6 @@ class ArtistTable(Base):
|
|||||||
genres: Mapped[str] = mapped_column(JSON())
|
genres: Mapped[str] = mapped_column(JSON())
|
||||||
name: Mapped[str] = mapped_column(String(), index=True)
|
name: Mapped[str] = mapped_column(String(), index=True)
|
||||||
trackcount: Mapped[int] = mapped_column(Integer())
|
trackcount: Mapped[int] = mapped_column(Integer())
|
||||||
# is_favorite: Mapped[Optional[bool]] = mapped_column(Boolean())
|
|
||||||
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
||||||
@@ -369,14 +365,14 @@ class ArtistTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls))
|
result = conn.execute(select(cls))
|
||||||
all = result.fetchall()
|
all = result.fetchall()
|
||||||
return artists_to_dataclasses(all)
|
return artists_to_dataclasses(all)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_artist_by_hash(cls, artisthash: str):
|
def get_artist_by_hash(cls, artisthash: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(ArtistTable).where(ArtistTable.artisthash == artisthash)
|
select(ArtistTable).where(ArtistTable.artisthash == artisthash)
|
||||||
)
|
)
|
||||||
@@ -384,7 +380,7 @@ class ArtistTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_artisthashes_not_in(cls, artisthashes: list[str]):
|
def get_artisthashes_not_in(cls, artisthashes: list[str]):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(ArtistTable.artisthash, ArtistTable.name).where(
|
select(ArtistTable.artisthash, ArtistTable.name).where(
|
||||||
~ArtistTable.artisthash.in_(artisthashes)
|
~ArtistTable.artisthash.in_(artisthashes)
|
||||||
@@ -396,7 +392,7 @@ class ArtistTable(Base):
|
|||||||
def get_artists_by_artisthashes(
|
def get_artists_by_artisthashes(
|
||||||
cls, hashes: Iterable[str], limit: int | None = None
|
cls, hashes: Iterable[str], limit: int | None = None
|
||||||
):
|
):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(ArtistTable)
|
select(ArtistTable)
|
||||||
.where(ArtistTable.artisthash.in_(hashes))
|
.where(ArtistTable.artisthash.in_(hashes))
|
||||||
|
|||||||
+5
-3
@@ -1,9 +1,11 @@
|
|||||||
from app.db import Base, DbManager
|
from app.db import Base
|
||||||
|
|
||||||
|
|
||||||
from sqlalchemy import Integer, insert, select, update
|
from sqlalchemy import Integer, insert, select, update
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from app.db.engine import DbEngine
|
||||||
|
|
||||||
|
|
||||||
class MigrationTable(Base):
|
class MigrationTable(Base):
|
||||||
__tablename__ = "dbmigration"
|
__tablename__ = "dbmigration"
|
||||||
@@ -13,7 +15,7 @@ class MigrationTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_version(cls, version: int):
|
def set_version(cls, version: int):
|
||||||
with DbManager(commit=True) as conn:
|
with DbEngine.manager(commit=True) as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
update(cls).where(cls.id == 1).values(version=version)
|
update(cls).where(cls.id == 1).values(version=version)
|
||||||
)
|
)
|
||||||
@@ -23,7 +25,7 @@ class MigrationTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_version(cls):
|
def get_version(cls):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls.version).where(cls.id == 1))
|
result = conn.execute(select(cls.version).where(cls.id == 1))
|
||||||
result = result.fetchone()
|
result = result.fetchone()
|
||||||
|
|
||||||
|
|||||||
+44
-63
@@ -18,6 +18,7 @@ from sqlalchemy import (
|
|||||||
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from app.db.engine import DbEngine
|
||||||
from app.db.utils import (
|
from app.db.utils import (
|
||||||
albums_to_dataclasses,
|
albums_to_dataclasses,
|
||||||
artists_to_dataclasses,
|
artists_to_dataclasses,
|
||||||
@@ -27,14 +28,13 @@ from app.db.utils import (
|
|||||||
plugin_to_dataclasses,
|
plugin_to_dataclasses,
|
||||||
similar_artist_to_dataclass,
|
similar_artist_to_dataclass,
|
||||||
similar_artists_to_dataclass,
|
similar_artists_to_dataclass,
|
||||||
tracklog_to_dataclass,
|
|
||||||
tracklog_to_dataclasses,
|
tracklog_to_dataclasses,
|
||||||
tracks_to_dataclasses,
|
tracks_to_dataclasses,
|
||||||
user_to_dataclass,
|
user_to_dataclass,
|
||||||
user_to_dataclasses,
|
user_to_dataclasses,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.db import Base, DbManager
|
from app.db import Base
|
||||||
from app.utils.auth import get_current_userid, hash_password
|
from app.utils.auth import get_current_userid, hash_password
|
||||||
|
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ class UserTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_id(cls, id: int):
|
def get_by_id(cls, id: int):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls).where(cls.id == id))
|
result = conn.execute(select(cls).where(cls.id == id))
|
||||||
res = result.fetchone()
|
res = result.fetchone()
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ class UserTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_username(cls, username: str):
|
def get_by_username(cls, username: str):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls).where(cls.username == username))
|
result = conn.execute(select(cls).where(cls.username == username))
|
||||||
res = result.fetchone()
|
res = result.fetchone()
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class UserTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_one(cls, user: dict[str, Any]):
|
def update_one(cls, user: dict[str, Any]):
|
||||||
with DbManager(commit=True) as conn:
|
with DbEngine.manager(commit=True) as conn:
|
||||||
conn.execute(update(cls).where(cls.id == user["id"]).values(user))
|
conn.execute(update(cls).where(cls.id == user["id"]).values(user))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -126,7 +126,7 @@ class SimilarArtistTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls))
|
result = conn.execute(select(cls))
|
||||||
return similar_artists_to_dataclass(result.fetchall())
|
return similar_artists_to_dataclass(result.fetchall())
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ class SimilarArtistTable(Base):
|
|||||||
Check whether an artisthash exists in the database.
|
Check whether an artisthash exists in the database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(
|
result = conn.execute(
|
||||||
select(cls.artisthash).where(cls.artisthash == artisthash)
|
select(cls.artisthash).where(cls.artisthash == artisthash)
|
||||||
)
|
)
|
||||||
@@ -148,7 +148,7 @@ class SimilarArtistTable(Base):
|
|||||||
Get a single artist by hash.
|
Get a single artist by hash.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls).where(cls.artisthash == artisthash))
|
result = conn.execute(select(cls).where(cls.artisthash == artisthash))
|
||||||
result = result.fetchone()
|
result = result.fetchone()
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ class FavoritesTable(Base):
|
|||||||
__tablename__ = "favorite"
|
__tablename__ = "favorite"
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
hash: Mapped[str] = mapped_column(String())
|
hash: Mapped[str] = mapped_column(String(), unique=True)
|
||||||
type: Mapped[str] = mapped_column(String(), index=True)
|
type: Mapped[str] = mapped_column(String(), index=True)
|
||||||
timestamp: Mapped[int] = mapped_column(Integer(), index=True)
|
timestamp: Mapped[int] = mapped_column(Integer(), index=True)
|
||||||
userid: Mapped[int] = mapped_column(
|
userid: Mapped[int] = mapped_column(
|
||||||
@@ -172,7 +172,7 @@ class FavoritesTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls):
|
||||||
with DbManager() as conn:
|
with DbEngine.manager() as conn:
|
||||||
result = conn.execute(select(cls))
|
result = conn.execute(select(cls))
|
||||||
return favorites_to_dataclass(result.fetchall())
|
return favorites_to_dataclass(result.fetchall())
|
||||||
|
|
||||||
@@ -181,12 +181,12 @@ class FavoritesTable(Base):
|
|||||||
item["timestamp"] = int(datetime.datetime.now().timestamp())
|
item["timestamp"] = int(datetime.datetime.now().timestamp())
|
||||||
item["userid"] = get_current_userid()
|
item["userid"] = get_current_userid()
|
||||||
|
|
||||||
with DbManager(commit=True) as conn:
|
with DbEngine.manager(commit=True) as conn:
|
||||||
conn.execute(insert(cls).values(item))
|
conn.execute(insert(cls).values(item))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_item(cls, item: dict[str, Any]):
|
def remove_item(cls, item: dict[str, Any]):
|
||||||
with DbManager(commit=True) as conn:
|
with DbEngine.manager(commit=True) as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
delete(cls).where(
|
delete(cls).where(
|
||||||
(cls.hash == item["hash"]) & (cls.type == item["type"])
|
(cls.hash == item["hash"]) & (cls.type == item["type"])
|
||||||
@@ -199,12 +199,13 @@ class FavoritesTable(Base):
|
|||||||
return result.fetchone() is not None
|
return result.fetchone() is not None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_of_type(cls, table: Any, field: Any, type: str, start: int, limit: int):
|
def get_all_of_type(cls, type: str, start: int, limit: int):
|
||||||
result = cls.execute(
|
result = cls.execute(
|
||||||
select(table)
|
select(cls)
|
||||||
.select_from(join(table, cls, field == cls.hash))
|
# .select_from(join(table, cls, field == cls.hash))
|
||||||
.where(and_(cls.type == type, cls.userid == get_current_userid()))
|
.where(and_(cls.type == type, cls.userid == get_current_userid())).offset(
|
||||||
.offset(start)
|
start
|
||||||
|
)
|
||||||
# INFO: If start is 0, fetch all so we can get the total count
|
# INFO: If start is 0, fetch all so we can get the total count
|
||||||
.limit(limit if start != 0 else None)
|
.limit(limit if start != 0 else None)
|
||||||
)
|
)
|
||||||
@@ -218,30 +219,18 @@ class FavoritesTable(Base):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fav_tracks(cls, start: int, limit: int):
|
def get_fav_tracks(cls, start: int, limit: int):
|
||||||
from .libdata import TrackTable
|
result, total = cls.get_all_of_type("track", start, limit)
|
||||||
|
return favorites_to_dataclass(result), total
|
||||||
result, total = cls.get_all_of_type(
|
|
||||||
TrackTable, TrackTable.trackhash, "track", start, limit
|
|
||||||
)
|
|
||||||
return tracks_to_dataclasses(result), total
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fav_albums(cls, start: int, limit: int):
|
def get_fav_albums(cls, start: int, limit: int):
|
||||||
from .libdata import AlbumTable
|
result, total = cls.get_all_of_type("album", start, limit)
|
||||||
|
return favorites_to_dataclass(result), total
|
||||||
result, total = cls.get_all_of_type(
|
|
||||||
AlbumTable, AlbumTable.albumhash, "album", start, limit
|
|
||||||
)
|
|
||||||
return albums_to_dataclasses(result), total
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fav_artists(cls, start: int, limit: int):
|
def get_fav_artists(cls, start: int, limit: int):
|
||||||
from .libdata import ArtistTable
|
result, total = cls.get_all_of_type("artist", start, limit)
|
||||||
|
return favorites_to_dataclass(result), total
|
||||||
result, total = cls.get_all_of_type(
|
|
||||||
ArtistTable, ArtistTable.artisthash, "artist", start, limit
|
|
||||||
)
|
|
||||||
return artists_to_dataclasses(result), total
|
|
||||||
|
|
||||||
|
|
||||||
class ScrobbleTable(Base):
|
class ScrobbleTable(Base):
|
||||||
@@ -265,7 +254,7 @@ class ScrobbleTable(Base):
|
|||||||
return cls.insert_one(item)
|
return cls.insert_one(item)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls, start: int, limit: int):
|
def get_all(cls, start: int, limit: int | None):
|
||||||
result = cls.execute(
|
result = cls.execute(
|
||||||
select(cls)
|
select(cls)
|
||||||
.where(cls.userid == get_current_userid())
|
.where(cls.userid == get_current_userid())
|
||||||
@@ -325,7 +314,7 @@ class PlaylistTable(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_trackhashes(cls, id: int) -> list[str]:
|
def get_trackhashes(cls, id: int):
|
||||||
result = cls.execute(
|
result = cls.execute(
|
||||||
select(cls.trackhashes).where(
|
select(cls.trackhashes).where(
|
||||||
(cls.id == id) & (cls.userid == get_current_userid())
|
(cls.id == id) & (cls.userid == get_current_userid())
|
||||||
@@ -388,32 +377,24 @@ class PlaylistTable(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# class PlaylistTrackTable(Base):
|
class ArtistData(Base):
|
||||||
# __tablename__ = "playlisttrack"
|
__tablename__ = "artistdata"
|
||||||
|
|
||||||
# id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
# trackhash: Mapped[str] = mapped_column(String(), index=True)
|
artisthash: Mapped[str] = mapped_column(String(), index=True)
|
||||||
# playlistid: Mapped[int] = mapped_column(
|
color: Mapped[str] = mapped_column(String(), nullable=True)
|
||||||
# Integer(), ForeignKey("playlist.id", ondelete="cascade")
|
bio: Mapped[str] = mapped_column(String(), nullable=True)
|
||||||
# )
|
info: Mapped[dict[str, Any]] = mapped_column(JSON(), nullable=True)
|
||||||
# index: Mapped[int] = mapped_column(Integer())
|
extra: Mapped[dict[str, Any]] = mapped_column(
|
||||||
# userid: Mapped[int] = mapped_column(
|
JSON(), nullable=True, default_factory=dict
|
||||||
# Integer(), ForeignKey("user.id", ondelete="cascade")
|
)
|
||||||
# )
|
|
||||||
|
|
||||||
# @classmethod
|
@classmethod
|
||||||
# def count_by_playlist()
|
def find_one(cls, artisthash: str):
|
||||||
|
result = cls.execute(select(cls).where(cls.artisthash == artisthash))
|
||||||
|
return result.fetchone()
|
||||||
|
|
||||||
# @classmethod
|
@classmethod
|
||||||
# def insert_many(cls, playlistid: int, trackhashes: list[str]):
|
def get_all_colors(cls) -> dict[str, str]:
|
||||||
# userid = get_current_userid()
|
result = cls.execute(select(cls.artisthash, cls.color))
|
||||||
# items = [
|
return dict(result.fetchall())
|
||||||
# {
|
|
||||||
# "index": index,
|
|
||||||
# "userid": userid,
|
|
||||||
# "trackhash": trackhash,
|
|
||||||
# "playlistid": playlistid,
|
|
||||||
# }
|
|
||||||
# for index, trackhash in enumerate(trackhashes)
|
|
||||||
# ]
|
|
||||||
# return cls.execute(insert(cls).values(items), commit=True)
|
|
||||||
|
|||||||
+7
-9
@@ -12,9 +12,10 @@ from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
|
|||||||
from app.db.sqlite.artistcolors import SQLiteArtistMethods as adb
|
from app.db.sqlite.artistcolors import SQLiteArtistMethods as adb
|
||||||
from app.db.sqlite.utils import SQLiteManager
|
from app.db.sqlite.utils import SQLiteManager
|
||||||
|
|
||||||
# from app.store.artists import ArtistStore
|
from app.db.userdata import ArtistData
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
from app.lib.errors import PopulateCancelledError
|
from app.lib.errors import PopulateCancelledError
|
||||||
|
from app.store.artists import ArtistStore
|
||||||
from app.utils.progressbar import tqdm
|
from app.utils.progressbar import tqdm
|
||||||
|
|
||||||
PROCESS_ALBUM_COLORS_KEY = ""
|
PROCESS_ALBUM_COLORS_KEY = ""
|
||||||
@@ -100,24 +101,21 @@ class ProcessArtistColors:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, instance_key: str) -> None:
|
def __init__(self, instance_key: str) -> None:
|
||||||
# all_artists = [a for a in ArtistStore.artists if len(a.colors) == 0]
|
all_artists = ArtistStore.get_flat_list()
|
||||||
|
|
||||||
global PROCESS_ARTIST_COLORS_KEY
|
global PROCESS_ARTIST_COLORS_KEY
|
||||||
PROCESS_ARTIST_COLORS_KEY = instance_key
|
PROCESS_ARTIST_COLORS_KEY = instance_key
|
||||||
|
|
||||||
with SQLiteManager() as cur:
|
|
||||||
try:
|
try:
|
||||||
for artist in tqdm(
|
for artist in tqdm(all_artists, desc="Processing missing artist colors"):
|
||||||
all_artists, desc="Processing missing artist colors"
|
|
||||||
):
|
|
||||||
if PROCESS_ARTIST_COLORS_KEY != instance_key:
|
if PROCESS_ARTIST_COLORS_KEY != instance_key:
|
||||||
raise PopulateCancelledError(
|
raise PopulateCancelledError(
|
||||||
"A newer 'ProcessArtistColors' instance is running. Stopping this one."
|
"A newer 'ProcessArtistColors' instance is running. Stopping this one."
|
||||||
)
|
)
|
||||||
|
|
||||||
exists = adb.exists(artist.artisthash, cur=cur)
|
# exists = adb.exists(artist.artisthash, cur=cur)
|
||||||
|
artist = ArtistData.find_one(artist.artisthash)
|
||||||
if exists:
|
if artist and artist.color is not None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
colors = process_color(artist.artisthash, is_album=False)
|
colors = process_color(artist.artisthash, is_album=False)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from app.lib.mapstuff import map_favorites, map_scrobble_data
|
||||||
|
from app.lib.populate import CordinateMedia
|
||||||
|
from app.lib.tagger import IndexTracks
|
||||||
|
from app.store.folder import FolderStore
|
||||||
|
|
||||||
|
|
||||||
|
import gc
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from app.utils.threading import background
|
||||||
|
|
||||||
|
|
||||||
|
class IndexEverything:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
IndexTracks(instance_key=time())
|
||||||
|
FolderStore.load_filepaths()
|
||||||
|
map_scrobble_data()
|
||||||
|
map_favorites()
|
||||||
|
# CordinateMedia(instance_key=str(time()))
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
|
||||||
|
@background
|
||||||
|
def index_everything():
|
||||||
|
return IndexEverything()
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from app.db.userdata import FavoritesTable, ScrobbleTable
|
||||||
|
from app.store.albums import AlbumStore
|
||||||
|
from app.store.artists import ArtistStore
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def map_scrobble_data():
|
||||||
|
"""
|
||||||
|
Maps scrobble data to the in-memory stores.
|
||||||
|
|
||||||
|
The scrobble data is loaded from the database and grouped by trackhash.
|
||||||
|
The album and artist scrobble data (for those tracks) are then incremented based on the data.
|
||||||
|
"""
|
||||||
|
records = ScrobbleTable.get_all(0, None)
|
||||||
|
|
||||||
|
# group records by trackhash
|
||||||
|
grouped: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
# aggregate playcount, playduration and lastplayed
|
||||||
|
item = grouped.setdefault(record.trackhash, {})
|
||||||
|
item["playcount"] = item.get("playcount", 0) + 1
|
||||||
|
item["playduration"] = item.get("playduration", 0) + record.duration
|
||||||
|
item["lastplayed"] = max(item.get("lastplayed", 0), record.timestamp)
|
||||||
|
|
||||||
|
# increment playcount, playduration and lastplayed for albums and artists
|
||||||
|
for trackhash, data in grouped.items():
|
||||||
|
track = TrackStore.trackhashmap.get(trackhash)
|
||||||
|
|
||||||
|
if track is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
track.increment_playcount(data["playduration"], data["lastplayed"])
|
||||||
|
|
||||||
|
album = AlbumStore.albummap.get(track.tracks[0].albumhash)
|
||||||
|
if album:
|
||||||
|
album.increment_playcount(data["playduration"], data["lastplayed"])
|
||||||
|
|
||||||
|
for artisthash in track.tracks[0].artisthashes:
|
||||||
|
artist = ArtistStore.artistmap.get(artisthash)
|
||||||
|
if artist:
|
||||||
|
artist.increment_playcount(data["playduration"], data["lastplayed"])
|
||||||
|
|
||||||
|
|
||||||
|
def map_favorites():
|
||||||
|
"""
|
||||||
|
Maps favorites data to the in-memory stores.
|
||||||
|
"""
|
||||||
|
favorites = FavoritesTable.get_all()
|
||||||
|
|
||||||
|
for entry in favorites:
|
||||||
|
if entry.type == "album":
|
||||||
|
album = AlbumStore.albummap.get(entry.hash)
|
||||||
|
if album:
|
||||||
|
album.toggle_favorite_user(entry.userid)
|
||||||
|
|
||||||
|
elif entry.type == "artist":
|
||||||
|
artist = ArtistStore.artistmap.get(entry.hash)
|
||||||
|
if artist:
|
||||||
|
artist.toggle_favorite_user(entry.userid)
|
||||||
|
|
||||||
|
elif entry.type == "track":
|
||||||
|
track = TrackStore.trackhashmap.get(entry.hash)
|
||||||
|
if track:
|
||||||
|
track.toggle_favorite_user(entry.userid)
|
||||||
@@ -10,8 +10,9 @@ from typing import Any
|
|||||||
from PIL import Image, ImageSequence
|
from PIL import Image, ImageSequence
|
||||||
|
|
||||||
from app import settings
|
from app import settings
|
||||||
from app.db.libdata import AlbumTable, TrackTable
|
from app.db.libdata import TrackTable
|
||||||
from app.models.track import Track
|
from app.models.track import Track
|
||||||
|
from app.store.albums import AlbumStore
|
||||||
|
|
||||||
def create_thumbnail(image: Any, img_path: str) -> str:
|
def create_thumbnail(image: Any, img_path: str) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -115,8 +116,7 @@ def get_first_4_images(
|
|||||||
if len(albums) == 4:
|
if len(albums) == 4:
|
||||||
break
|
break
|
||||||
|
|
||||||
# albums = AlbumStore.get_albums_by_hashes(albums)
|
albums = AlbumStore.get_albums_by_hashes(albums)
|
||||||
albums = AlbumTable.get_albums_by_albumhashes(albums)
|
|
||||||
images = [
|
images = [
|
||||||
{
|
{
|
||||||
"image": album.image,
|
"image": album.image,
|
||||||
|
|||||||
+10
-164
@@ -1,28 +1,22 @@
|
|||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
import os
|
import os
|
||||||
from collections import deque
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
from requests import ConnectionError as RequestConnectionError
|
from requests import ConnectionError as RequestConnectionError
|
||||||
from requests import ReadTimeout
|
from requests import ReadTimeout
|
||||||
|
|
||||||
from app import settings
|
from app import settings
|
||||||
from app.db.libdata import ArtistTable
|
|
||||||
from app.db.libdata import AlbumTable, TrackTable
|
|
||||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
|
||||||
|
|
||||||
# from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as lastfmdb
|
|
||||||
from app.db.sqlite.tracks import SQLiteTrackMethods
|
from app.db.sqlite.tracks import SQLiteTrackMethods
|
||||||
from app.lib.artistlib import CheckArtistImages
|
from app.lib.artistlib import CheckArtistImages
|
||||||
from app.lib.colorlib import ProcessArtistColors
|
from app.lib.colorlib import ProcessArtistColors
|
||||||
from app.lib.errors import PopulateCancelledError
|
from app.lib.errors import PopulateCancelledError
|
||||||
from app.lib.taglib import extract_thumb
|
from app.lib.taglib import extract_thumb
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
from app.models import Album, Artist, Track
|
from app.models import Album, Artist
|
||||||
from app.models.lastfm import SimilarArtist
|
from app.models.lastfm import SimilarArtist
|
||||||
from app.requests.artists import fetch_similar_artists
|
from app.requests.artists import fetch_similar_artists
|
||||||
from app.utils.filesystem import run_fast_scandir
|
from app.store.albums import AlbumStore
|
||||||
|
from app.store.artists import ArtistStore
|
||||||
from app.utils.network import has_connection
|
from app.utils.network import has_connection
|
||||||
from app.utils.progressbar import tqdm
|
from app.utils.progressbar import tqdm
|
||||||
|
|
||||||
@@ -35,46 +29,6 @@ remove_tracks_by_filepaths = SQLiteTrackMethods.remove_tracks_by_filepaths
|
|||||||
POPULATE_KEY = ""
|
POPULATE_KEY = ""
|
||||||
|
|
||||||
|
|
||||||
class Populate:
|
|
||||||
"""
|
|
||||||
Populates the database with all songs in the music directory
|
|
||||||
|
|
||||||
checks if the song is in the database, if not, it adds it
|
|
||||||
also checks if the album art exists in the image path, if not tries to extract it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# def __init__(self, instance_key: str) -> None:
|
|
||||||
# return
|
|
||||||
|
|
||||||
# if len(dirs_to_scan) == 0:
|
|
||||||
# log.warning(
|
|
||||||
# (
|
|
||||||
# "The root directory is not configured. "
|
|
||||||
# + "Open the app in your webbrowser to configure."
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
# return
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# if dirs_to_scan[0] == "$home":
|
|
||||||
# dirs_to_scan = [settings.Paths.USER_HOME_DIR]
|
|
||||||
# except IndexError:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# files = set()
|
|
||||||
|
|
||||||
# for _dir in dirs_to_scan:
|
|
||||||
# files = files.union(run_fast_scandir(_dir, full=True)[1])
|
|
||||||
|
|
||||||
# unmodified, modified_tracks = self.remove_modified(tracks)
|
|
||||||
# untagged = files - unmodified
|
|
||||||
|
|
||||||
# if len(untagged) != 0:
|
|
||||||
# self.tag_untagged(untagged, instance_key)
|
|
||||||
|
|
||||||
# self.extract_thumb_with_overwrite(modified_tracks)
|
|
||||||
|
|
||||||
|
|
||||||
class CordinateMedia:
|
class CordinateMedia:
|
||||||
"""
|
"""
|
||||||
Cordinates the extracting of thumbnails
|
Cordinates the extracting of thumbnails
|
||||||
@@ -117,102 +71,6 @@ class CordinateMedia:
|
|||||||
log.warn(e)
|
log.warn(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
# @staticmethod
|
|
||||||
# def remove_modified(tracks: Generator[TrackTable, None, None]):
|
|
||||||
# """
|
|
||||||
# Removes tracks from the database that have been modified
|
|
||||||
# since they were added to the database.
|
|
||||||
# """
|
|
||||||
|
|
||||||
# unmodified_paths = set()
|
|
||||||
# modified_tracks: list[TrackTable] = []
|
|
||||||
# modified_paths = set()
|
|
||||||
|
|
||||||
# for track in tracks:
|
|
||||||
# try:
|
|
||||||
# if track.last_mod == round(os.path.getmtime(track.filepath)):
|
|
||||||
# unmodified_paths.add(track.filepath)
|
|
||||||
# continue
|
|
||||||
# except (FileNotFoundError, OSError) as e:
|
|
||||||
# log.warning(e) # REVIEW More informations = good
|
|
||||||
# TrackStore.remove_track_obj(track)
|
|
||||||
# remove_tracks_by_filepaths(track.filepath)
|
|
||||||
|
|
||||||
# modified_paths.add(track.filepath)
|
|
||||||
# modified_tracks.append(track)
|
|
||||||
|
|
||||||
# TrackStore.remove_tracks_by_filepaths(modified_paths)
|
|
||||||
# remove_tracks_by_filepaths(modified_paths)
|
|
||||||
|
|
||||||
# return unmodified_paths, modified_tracks
|
|
||||||
|
|
||||||
# @staticmethod
|
|
||||||
# def tag_untagged(untagged: set[str], key: str):
|
|
||||||
# pass
|
|
||||||
# for file in tqdm(untagged, desc="Reading files"):
|
|
||||||
# if POPULATE_KEY != key:
|
|
||||||
# log.warning("'Populate.tag_untagged': Populate key changed")
|
|
||||||
# return
|
|
||||||
|
|
||||||
# tags = get_tags(file)
|
|
||||||
|
|
||||||
# if tags is not None:
|
|
||||||
# TrackTable.insert_one(tags)
|
|
||||||
|
|
||||||
# =============================================
|
|
||||||
|
|
||||||
# log.info("Found %s new tracks", len(untagged))
|
|
||||||
# # tagged_tracks: deque[dict] = deque()
|
|
||||||
# # tagged_count = 0
|
|
||||||
|
|
||||||
# favs = favdb.get_fav_tracks()
|
|
||||||
# records = dict()
|
|
||||||
|
|
||||||
# for fav in favs:
|
|
||||||
# r = records.setdefault(fav[1], set())
|
|
||||||
# r.add(fav[4])
|
|
||||||
|
|
||||||
# tagged_tracks.append(tags)
|
|
||||||
# track = Track(**tags)
|
|
||||||
|
|
||||||
# track.fav_userids = list(records.get(track.trackhash, set()))
|
|
||||||
|
|
||||||
# TrackStore.add_track(track)
|
|
||||||
|
|
||||||
# if not AlbumStore.album_exists(track.albumhash):
|
|
||||||
# AlbumStore.add_album(AlbumStore.create_album(track))
|
|
||||||
|
|
||||||
# for artist in track.artists:
|
|
||||||
# if not ArtistStore.artist_exists(artist.artisthash):
|
|
||||||
# ArtistStore.add_artist(Artist(artist.name))
|
|
||||||
|
|
||||||
# for artist in track.albumartists:
|
|
||||||
# if not ArtistStore.artist_exists(artist.artisthash):
|
|
||||||
# ArtistStore.add_artist(Artist(artist.name))
|
|
||||||
|
|
||||||
# tagged_count += 1
|
|
||||||
# else:
|
|
||||||
# log.warning("Could not read file: %s", file)
|
|
||||||
|
|
||||||
# if len(tagged_tracks) > 0:
|
|
||||||
# log.info("Adding %s tracks to database", len(tagged_tracks))
|
|
||||||
# insert_many_tracks(tagged_tracks)
|
|
||||||
|
|
||||||
# log.info("Added %s/%s tracks", tagged_count, len(untagged))
|
|
||||||
|
|
||||||
# @staticmethod
|
|
||||||
# def extract_thumb_with_overwrite(tracks: list[TrackTable]):
|
|
||||||
# """
|
|
||||||
# Extracts the thumbnail from a list of filepaths,
|
|
||||||
# overwriting the existing thumbnail if it exists,
|
|
||||||
# for modified files.
|
|
||||||
# """
|
|
||||||
# for track in tracks:
|
|
||||||
# try:
|
|
||||||
# extract_thumb(track.filepath, track.image, overwrite=True)
|
|
||||||
# except FileNotFoundError:
|
|
||||||
# continue
|
|
||||||
|
|
||||||
|
|
||||||
def get_image(_map: tuple[str, Album]):
|
def get_image(_map: tuple[str, Album]):
|
||||||
"""
|
"""
|
||||||
@@ -228,26 +86,12 @@ def get_image(_map: tuple[str, Album]):
|
|||||||
if POPULATE_KEY != instance_key:
|
if POPULATE_KEY != instance_key:
|
||||||
raise PopulateCancelledError("'ProcessTrackThumbnails': Populate key changed")
|
raise PopulateCancelledError("'ProcessTrackThumbnails': Populate key changed")
|
||||||
|
|
||||||
matching_tracks = filter(
|
matching_tracks = AlbumStore.get_album_tracks(album.albumhash)
|
||||||
lambda t: t.albumhash == album.albumhash,
|
|
||||||
TrackTable.get_tracks_by_albumhash(album.albumhash),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
for track in matching_tracks:
|
||||||
track = next(matching_tracks)
|
if extract_thumb(track.filepath, track.image):
|
||||||
extracted = extract_thumb(track.filepath, track.image)
|
|
||||||
|
|
||||||
while not extracted:
|
|
||||||
try:
|
|
||||||
track = next(matching_tracks)
|
|
||||||
extracted = extract_thumb(track.filepath, track.image)
|
|
||||||
except StopIteration:
|
|
||||||
break
|
break
|
||||||
|
|
||||||
return
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_cpu_count():
|
def get_cpu_count():
|
||||||
"""
|
"""
|
||||||
@@ -274,7 +118,7 @@ class ProcessTrackThumbnails:
|
|||||||
|
|
||||||
# filter out albums that already have thumbnails
|
# filter out albums that already have thumbnails
|
||||||
albums = filter(
|
albums = filter(
|
||||||
lambda album: album.albumhash not in processed, AlbumTable.get_all()
|
lambda album: album.albumhash not in processed, AlbumStore.get_flat_list()
|
||||||
)
|
)
|
||||||
albums = list(albums)
|
albums = list(albums)
|
||||||
|
|
||||||
@@ -330,7 +174,9 @@ class FetchSimilarArtistsLastFM:
|
|||||||
processed = ".".join(a.artisthash for a in processed)
|
processed = ".".join(a.artisthash for a in processed)
|
||||||
|
|
||||||
# filter out artists that already have similar artists
|
# filter out artists that already have similar artists
|
||||||
artists = filter(lambda a: a.artisthash not in processed, ArtistTable.get_all())
|
artists = filter(
|
||||||
|
lambda a: a.artisthash not in processed, ArtistStore.get_flat_list()
|
||||||
|
)
|
||||||
artists = list(artists)
|
artists = list(artists)
|
||||||
|
|
||||||
# process the rest
|
# process the rest
|
||||||
|
|||||||
+16
-25
@@ -9,7 +9,8 @@ from unidecode import unidecode
|
|||||||
|
|
||||||
from app import models
|
from app import models
|
||||||
from app.config import UserConfig
|
from app.config import UserConfig
|
||||||
from app.db.libdata import AlbumTable, ArtistTable, TrackTable
|
|
||||||
|
# from app.db.libdata import AlbumTable, ArtistTable, TrackTable
|
||||||
|
|
||||||
# from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
# from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||||
from app.models.enums import FavType
|
from app.models.enums import FavType
|
||||||
@@ -19,9 +20,9 @@ from app.serializers.album import serialize_for_card_many as serialize_albums
|
|||||||
from app.serializers.artist import serialize_for_cards
|
from app.serializers.artist import serialize_for_cards
|
||||||
from app.serializers.track import serialize_track, serialize_tracks
|
from app.serializers.track import serialize_track, serialize_tracks
|
||||||
|
|
||||||
# from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
# from app.store.artists import ArtistStore
|
from app.store.artists import ArtistStore
|
||||||
# from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
from app.utils.remove_duplicates import remove_duplicates
|
from app.utils.remove_duplicates import remove_duplicates
|
||||||
|
|
||||||
@@ -54,8 +55,7 @@ class Limit:
|
|||||||
class SearchTracks:
|
class SearchTracks:
|
||||||
def __init__(self, query: str) -> None:
|
def __init__(self, query: str) -> None:
|
||||||
self.query = query
|
self.query = query
|
||||||
# self.tracks = TrackStore.tracks
|
self.tracks = TrackStore.get_flat_list()
|
||||||
self.tracks = TrackTable.get_all()
|
|
||||||
|
|
||||||
def __call__(self) -> List[models.Track]:
|
def __call__(self) -> List[models.Track]:
|
||||||
"""
|
"""
|
||||||
@@ -78,8 +78,7 @@ class SearchTracks:
|
|||||||
class SearchArtists:
|
class SearchArtists:
|
||||||
def __init__(self, query: str) -> None:
|
def __init__(self, query: str) -> None:
|
||||||
self.query = query
|
self.query = query
|
||||||
# self.artists = ArtistStore.artists
|
self.artists = ArtistStore.get_flat_list()
|
||||||
self.artists = ArtistTable.get_all()
|
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""
|
"""
|
||||||
@@ -101,8 +100,7 @@ class SearchArtists:
|
|||||||
class SearchAlbums:
|
class SearchAlbums:
|
||||||
def __init__(self, query: str) -> None:
|
def __init__(self, query: str) -> None:
|
||||||
self.query = query
|
self.query = query
|
||||||
# self.albums = AlbumStore.albums
|
self.albums = AlbumStore.get_flat_list()
|
||||||
self.albums = AlbumTable.get_all()
|
|
||||||
|
|
||||||
def __call__(self) -> List[models.Album]:
|
def __call__(self) -> List[models.Album]:
|
||||||
"""
|
"""
|
||||||
@@ -169,9 +167,9 @@ class TopResults:
|
|||||||
def collect_all():
|
def collect_all():
|
||||||
all_items: list[_type] = []
|
all_items: list[_type] = []
|
||||||
|
|
||||||
all_items.extend(ArtistTable.get_all())
|
all_items.extend(ArtistStore.get_flat_list())
|
||||||
all_items.extend(TrackTable.get_all())
|
all_items.extend(TrackStore.get_flat_list())
|
||||||
all_items.extend(AlbumTable.get_all())
|
all_items.extend(TrackStore.get_flat_list())
|
||||||
|
|
||||||
return all_items, get_titles(all_items)
|
return all_items, get_titles(all_items)
|
||||||
|
|
||||||
@@ -194,7 +192,7 @@ class TopResults:
|
|||||||
return {"type": "track", "item": item}
|
return {"type": "track", "item": item}
|
||||||
|
|
||||||
if isinstance(item, models.Album):
|
if isinstance(item, models.Album):
|
||||||
tracks = TrackTable.get_tracks_by_albumhash(item.albumhash)
|
tracks = TrackStore.get_tracks_by_albumhash(item.albumhash)
|
||||||
tracks = remove_duplicates(tracks)
|
tracks = remove_duplicates(tracks)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -212,19 +210,13 @@ class TopResults:
|
|||||||
track_count = 0
|
track_count = 0
|
||||||
duration = 0
|
duration = 0
|
||||||
|
|
||||||
tracks = TrackTable.get_tracks_by_artisthash(item.artisthash)
|
tracks = TrackStore.get_tracks_by_artisthash(item.artisthash)
|
||||||
tracks = remove_duplicates(tracks)
|
tracks = remove_duplicates(tracks)
|
||||||
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
track_count += 1
|
track_count += 1
|
||||||
duration += track.duration
|
duration += track.duration
|
||||||
|
|
||||||
# album_count = AlbumStore.count_albums_by_artisthash(item.artisthash)
|
|
||||||
|
|
||||||
# item.set_trackcount(track_count)
|
|
||||||
# item.set_albumcount(album_count)
|
|
||||||
# item.set_duration(duration)
|
|
||||||
|
|
||||||
return {"type": "artist", "item": item}
|
return {"type": "artist", "item": item}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -235,8 +227,7 @@ class TopResults:
|
|||||||
tracks.extend(SearchTracks(query)())
|
tracks.extend(SearchTracks(query)())
|
||||||
|
|
||||||
if item["type"] == "album":
|
if item["type"] == "album":
|
||||||
t = TrackTable.get_tracks_by_albumhash(item["item"].albumhash)
|
t = TrackStore.get_tracks_by_albumhash(item["item"].albumhash)
|
||||||
# t = TrackStore.get_tracks_by_albumhash(item["item"].albumhash)
|
|
||||||
t.sort(key=lambda x: x.last_mod)
|
t.sort(key=lambda x: x.last_mod)
|
||||||
|
|
||||||
# if there are less than the limit, get more tracks
|
# if there are less than the limit, get more tracks
|
||||||
@@ -249,7 +240,7 @@ class TopResults:
|
|||||||
|
|
||||||
if item["type"] == "artist":
|
if item["type"] == "artist":
|
||||||
# t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash)
|
# t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash)
|
||||||
t = TrackTable.get_tracks_by_artisthash(item["item"].artisthash)
|
t = TrackStore.get_tracks_by_artisthash(item["item"].artisthash)
|
||||||
|
|
||||||
# if there are less than the limit, get more tracks
|
# if there are less than the limit, get more tracks
|
||||||
if len(t) < limit:
|
if len(t) < limit:
|
||||||
@@ -271,7 +262,7 @@ class TopResults:
|
|||||||
|
|
||||||
if item["type"] == "artist":
|
if item["type"] == "artist":
|
||||||
# albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash)
|
# albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash)
|
||||||
albums = AlbumTable.get_albums_by_artisthash(item["item"].artisthash)
|
albums = AlbumStore.get_albums_by_artisthash(item["item"].artisthash)
|
||||||
|
|
||||||
# if there are less than the limit, get more albums
|
# if there are less than the limit, get more albums
|
||||||
if len(albums) < limit:
|
if len(albums) < limit:
|
||||||
|
|||||||
+6
-34
@@ -1,12 +1,9 @@
|
|||||||
import gc
|
|
||||||
import os
|
import os
|
||||||
from pprint import pprint
|
|
||||||
from time import time
|
|
||||||
from app import settings
|
from app import settings
|
||||||
from app.config import UserConfig
|
from app.config import UserConfig
|
||||||
from app.db.libdata import ArtistTable
|
|
||||||
from app.db.libdata import TrackTable
|
from app.db.libdata import TrackTable
|
||||||
from app.lib.populate import CordinateMedia
|
|
||||||
|
# from app.lib.populate import CordinateMedia
|
||||||
from app.lib.taglib import extract_thumb, get_tags
|
from app.lib.taglib import extract_thumb, get_tags
|
||||||
from app.models.album import Album
|
from app.models.album import Album
|
||||||
from app.models.artist import Artist
|
from app.models.artist import Artist
|
||||||
@@ -17,7 +14,6 @@ from app.utils.parsers import get_base_album_title
|
|||||||
from app.utils.progressbar import tqdm
|
from app.utils.progressbar import tqdm
|
||||||
|
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
from app.utils.threading import background
|
|
||||||
|
|
||||||
POPULATE_KEY: float = 0
|
POPULATE_KEY: float = 0
|
||||||
|
|
||||||
@@ -142,7 +138,6 @@ class IndexTracks:
|
|||||||
print("Done")
|
print("Done")
|
||||||
|
|
||||||
|
|
||||||
# class IndexAlbums:
|
|
||||||
def create_albums():
|
def create_albums():
|
||||||
albums = dict()
|
albums = dict()
|
||||||
all_tracks: list[Track] = TrackTable.get_all()
|
all_tracks: list[Track] = TrackTable.get_all()
|
||||||
@@ -165,7 +160,7 @@ def create_albums():
|
|||||||
"playduration": track.playduration,
|
"playduration": track.playduration,
|
||||||
"title": track.album,
|
"title": track.album,
|
||||||
"trackcount": 1,
|
"trackcount": 1,
|
||||||
"extra": {}
|
"extra": {},
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
album = albums[track.albumhash]
|
album = albums[track.albumhash]
|
||||||
@@ -187,13 +182,11 @@ def create_albums():
|
|||||||
genres.append(genre)
|
genres.append(genre)
|
||||||
|
|
||||||
album["genres"] = genres
|
album["genres"] = genres
|
||||||
album["genrehashes"] = " ".join([g['genrehash'] for g in genres])
|
album["genrehashes"] = " ".join([g["genrehash"] for g in genres])
|
||||||
album["base_title"], _ = get_base_album_title(album["og_title"])
|
album["base_title"], _ = get_base_album_title(album["og_title"])
|
||||||
|
|
||||||
del genres
|
del genres
|
||||||
|
|
||||||
# AlbumTable.remove_all()
|
|
||||||
# AlbumTable.insert_many(list(albums.values()))
|
|
||||||
return [Album(**album) for album in albums.values()]
|
return [Album(**album) for album in albums.values()]
|
||||||
|
|
||||||
|
|
||||||
@@ -227,9 +220,7 @@ def create_artists():
|
|||||||
"playduration": track.playduration,
|
"playduration": track.playduration,
|
||||||
"trackcount": None,
|
"trackcount": None,
|
||||||
"tracks": (
|
"tracks": (
|
||||||
{track.trackhash}
|
{track.trackhash} if thisartist.get("in_track", True) else set()
|
||||||
if thisartist.get("in_track", True)
|
|
||||||
else set()
|
|
||||||
),
|
),
|
||||||
"extra": {},
|
"extra": {},
|
||||||
}
|
}
|
||||||
@@ -261,7 +252,7 @@ def create_artists():
|
|||||||
genres.append(genre)
|
genres.append(genre)
|
||||||
|
|
||||||
artist["genres"] = genres
|
artist["genres"] = genres
|
||||||
artist["genrehashes"] = " ".join([g['genrehash'] for g in genres])
|
artist["genrehashes"] = " ".join([g["genrehash"] for g in genres])
|
||||||
artist["name"] = sorted(artist["names"])[0]
|
artist["name"] = sorted(artist["names"])[0]
|
||||||
|
|
||||||
# INFO: Delete temporary keys
|
# INFO: Delete temporary keys
|
||||||
@@ -272,25 +263,6 @@ def create_artists():
|
|||||||
# INFO: Delete local variables
|
# INFO: Delete local variables
|
||||||
del genres
|
del genres
|
||||||
|
|
||||||
# ArtistTable.remove_all()
|
|
||||||
# ArtistTable.insert_many(list(artists.values()))
|
|
||||||
# del artists
|
|
||||||
return [Artist(**artist) for artist in artists.values()]
|
return [Artist(**artist) for artist in artists.values()]
|
||||||
|
|
||||||
|
|
||||||
class IndexEverything:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
IndexTracks(instance_key=time())
|
|
||||||
# IndexAlbums()
|
|
||||||
# IndexArtists()
|
|
||||||
FolderStore.load_filepaths()
|
|
||||||
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# CordinateMedia(instance_key=str(time()))
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
|
|
||||||
@background
|
|
||||||
def index_everything():
|
|
||||||
return IndexEverything()
|
|
||||||
|
|||||||
+18
-5
@@ -1,11 +1,10 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import datetime
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from ..utils.hashing import create_hash
|
|
||||||
from ..utils.parsers import get_base_title_and_versions, parse_feat_from_title
|
|
||||||
from .artist import Artist
|
|
||||||
from .track import Track
|
from .track import Track
|
||||||
|
from ..utils.hashing import create_hash
|
||||||
|
from app.utils.auth import get_current_userid
|
||||||
|
from ..utils.parsers import get_base_title_and_versions
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@@ -27,7 +26,6 @@ class Album:
|
|||||||
og_title: str
|
og_title: str
|
||||||
title: str
|
title: str
|
||||||
trackcount: int
|
trackcount: int
|
||||||
# is_favorite: bool
|
|
||||||
lastplayed: int
|
lastplayed: int
|
||||||
playcount: int
|
playcount: int
|
||||||
playduration: int
|
playduration: int
|
||||||
@@ -37,6 +35,21 @@ class Album:
|
|||||||
type: str = "album"
|
type: str = "album"
|
||||||
image: str = ""
|
image: str = ""
|
||||||
versions: list[str] = dataclasses.field(default_factory=list)
|
versions: list[str] = dataclasses.field(default_factory=list)
|
||||||
|
fav_userids: list[int] = dataclasses.field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_favorite(self):
|
||||||
|
return get_current_userid() in self.fav_userids
|
||||||
|
|
||||||
|
def toggle_favorite_user(self, userid: int):
|
||||||
|
"""
|
||||||
|
Adds or removes the given user from the list of users
|
||||||
|
who have favorited the album.
|
||||||
|
"""
|
||||||
|
if userid in self.fav_userids:
|
||||||
|
self.fav_userids.remove(userid)
|
||||||
|
else:
|
||||||
|
self.fav_userids.append(userid)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.image = self.albumhash + ".webp"
|
self.image = self.albumhash + ".webp"
|
||||||
|
|||||||
+17
-1
@@ -1,6 +1,7 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from app.utils.auth import get_current_userid
|
||||||
from app.utils.hashing import create_hash
|
from app.utils.hashing import create_hash
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +47,6 @@ class Artist:
|
|||||||
genrehashes: list[str]
|
genrehashes: list[str]
|
||||||
name: str
|
name: str
|
||||||
trackcount: int
|
trackcount: int
|
||||||
# is_favorite: bool
|
|
||||||
lastplayed: int
|
lastplayed: int
|
||||||
playcount: int
|
playcount: int
|
||||||
playduration: int
|
playduration: int
|
||||||
@@ -55,5 +55,21 @@ class Artist:
|
|||||||
id: int = -1
|
id: int = -1
|
||||||
image: str = ""
|
image: str = ""
|
||||||
|
|
||||||
|
fav_userids: list[int] = dataclasses.field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_favorite(self):
|
||||||
|
return get_current_userid() in self.fav_userids
|
||||||
|
|
||||||
|
def toggle_favorite_user(self, userid: int):
|
||||||
|
"""
|
||||||
|
Adds or removes the given user from the list of users
|
||||||
|
who have favorited this artist.
|
||||||
|
"""
|
||||||
|
if userid in self.fav_userids:
|
||||||
|
self.fav_userids.remove(userid)
|
||||||
|
else:
|
||||||
|
self.fav_userids.append(userid)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.image = self.artisthash + ".webp"
|
self.image = self.artisthash + ".webp"
|
||||||
+11
-157
@@ -38,12 +38,22 @@ class Track:
|
|||||||
_pos: int = 0
|
_pos: int = 0
|
||||||
_ati: str = ""
|
_ati: str = ""
|
||||||
image: str = ""
|
image: str = ""
|
||||||
fav_userids: set = field(default_factory=set)
|
fav_userids: list[int] = field(default_factory=list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_favorite(self):
|
def is_favorite(self):
|
||||||
return get_current_userid() in self.fav_userids
|
return get_current_userid() in self.fav_userids
|
||||||
|
|
||||||
|
def toggle_favorite_user(self, userid: int):
|
||||||
|
"""
|
||||||
|
Adds or removes the given user from the list of users
|
||||||
|
who have favorited the track.
|
||||||
|
"""
|
||||||
|
if userid in self.fav_userids:
|
||||||
|
self.fav_userids.remove(userid)
|
||||||
|
else:
|
||||||
|
self.fav_userids.append(userid)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.image = self.albumhash + ".webp"
|
self.image = self.albumhash + ".webp"
|
||||||
self.extra = {
|
self.extra = {
|
||||||
@@ -51,159 +61,3 @@ class Track:
|
|||||||
"track_total": self.extra.get("track_total", 0),
|
"track_total": self.extra.get("track_total", 0),
|
||||||
"samplerate": self.extra.get("samplerate", -1),
|
"samplerate": self.extra.get("samplerate", -1),
|
||||||
}
|
}
|
||||||
|
|
||||||
# album: str
|
|
||||||
# albumartists: str | list[ArtistMinimal]
|
|
||||||
# albumhash: str
|
|
||||||
# artists: str | list[ArtistMinimal]
|
|
||||||
# bitrate: int
|
|
||||||
# copyright: str
|
|
||||||
# date: int
|
|
||||||
# disc: int
|
|
||||||
# duration: int
|
|
||||||
# filepath: str
|
|
||||||
# folder: str
|
|
||||||
# genre: str | list[str]
|
|
||||||
# title: str
|
|
||||||
# track: int
|
|
||||||
# trackhash: str
|
|
||||||
# last_mod: str | int
|
|
||||||
|
|
||||||
# image: str = ""
|
|
||||||
# artist_hashes: str = ""
|
|
||||||
|
|
||||||
# """
|
|
||||||
# A string of user ids separated by commas.
|
|
||||||
# """
|
|
||||||
# # is_favorite: bool = False
|
|
||||||
|
|
||||||
# # temporary attributes
|
|
||||||
# _pos: int = 0 # for sorting tracks by disc and track number
|
|
||||||
# _ati: str = (
|
|
||||||
# "" # (album track identifier) for removing duplicates when merging album versions
|
|
||||||
# )
|
|
||||||
|
|
||||||
# og_title: str = ""
|
|
||||||
# og_album: str = ""
|
|
||||||
# created_date: float = 0.0
|
|
||||||
|
|
||||||
# def set_created_date(self):
|
|
||||||
# try:
|
|
||||||
# self.created_date = Path(self.filepath).stat().st_ctime
|
|
||||||
# except FileNotFoundError:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# def __post_init__(self):
|
|
||||||
# self.og_title = self.title
|
|
||||||
# self.og_album = self.album
|
|
||||||
# self.last_mod = int(self.last_mod)
|
|
||||||
# self.date = int(self.date)
|
|
||||||
|
|
||||||
# # add a trailing slash to the folder path
|
|
||||||
# # to avoid matching a folder starting with the same name as the root path
|
|
||||||
# # eg. .../Music and .../Music Videos
|
|
||||||
# self.folder = os.path.join(self.folder, "")
|
|
||||||
|
|
||||||
# if self.artists is not None:
|
|
||||||
# artists = split_artists(self.artists)
|
|
||||||
# new_title = self.title
|
|
||||||
|
|
||||||
# if get_flag(SessionVarKeys.EXTRACT_FEAT):
|
|
||||||
# featured, new_title = parse_feat_from_title(self.title)
|
|
||||||
# original_lower = "-".join([create_hash(a) for a in artists])
|
|
||||||
# artists.extend(
|
|
||||||
# a for a in featured if create_hash(a) not in original_lower
|
|
||||||
# )
|
|
||||||
|
|
||||||
# self.artist_hashes = "-".join(create_hash(a, decode=True) for a in artists)
|
|
||||||
# self.artists = [ArtistMinimal(a) for a in artists]
|
|
||||||
|
|
||||||
# albumartists = split_artists(self.albumartists)
|
|
||||||
|
|
||||||
# if not albumartists:
|
|
||||||
# self.albumartists = self.artists[:1]
|
|
||||||
# else:
|
|
||||||
# self.albumartists = [ArtistMinimal(a) for a in albumartists]
|
|
||||||
|
|
||||||
# if get_flag(SessionVarKeys.REMOVE_PROD):
|
|
||||||
# new_title = remove_prod(new_title)
|
|
||||||
|
|
||||||
# if track is a single
|
|
||||||
# if self.og_title == self.album:
|
|
||||||
# self.rename_album(new_title)
|
|
||||||
|
|
||||||
# if get_flag(SessionVarKeys.REMOVE_REMASTER_FROM_TRACK):
|
|
||||||
# new_title = clean_title(new_title)
|
|
||||||
|
|
||||||
# self.title = new_title
|
|
||||||
|
|
||||||
# if get_flag(SessionVarKeys.CLEAN_ALBUM_TITLE):
|
|
||||||
# self.album, _ = get_base_title_and_versions(
|
|
||||||
# self.album, get_versions=False
|
|
||||||
# )
|
|
||||||
|
|
||||||
# if get_flag(SessionVarKeys.MERGE_ALBUM_VERSIONS):
|
|
||||||
# self.recreate_albumhash()
|
|
||||||
|
|
||||||
# self.image = self.albumhash + ".webp"
|
|
||||||
|
|
||||||
# if self.genre is not None and self.genre != "":
|
|
||||||
# self.genre = self.genre.lower()
|
|
||||||
# separators = {"/", ";", "&"}
|
|
||||||
|
|
||||||
# contains_rnb = "r&b" in self.genre
|
|
||||||
# contains_rock = "rock & roll" in self.genre
|
|
||||||
|
|
||||||
# if contains_rnb:
|
|
||||||
# self.genre = self.genre.replace("r&b", "RnB")
|
|
||||||
|
|
||||||
# if contains_rock:
|
|
||||||
# self.genre = self.genre.replace("rock & roll", "rock")
|
|
||||||
|
|
||||||
# for s in separators:
|
|
||||||
# self.genre: str = self.genre.replace(s, ",")
|
|
||||||
|
|
||||||
# self.genre = self.genre.split(",")
|
|
||||||
# self.genre = [g.strip() for g in self.genre]
|
|
||||||
|
|
||||||
# self.recreate_hash()
|
|
||||||
# self.set_created_date()
|
|
||||||
|
|
||||||
# def recreate_hash(self):
|
|
||||||
# """
|
|
||||||
# Recreates a track hash if the track title was altered
|
|
||||||
# to prevent duplicate tracks having different hashes.
|
|
||||||
# """
|
|
||||||
# if self.og_title == self.title and self.og_album == self.album:
|
|
||||||
# return
|
|
||||||
|
|
||||||
# self.trackhash = create_hash(
|
|
||||||
# ", ".join(a.name for a in self.artists), self.og_album, self.title
|
|
||||||
# )
|
|
||||||
|
|
||||||
# def recreate_artists_hash(self):
|
|
||||||
# """
|
|
||||||
# Recreates a track's artist hashes if the artist list was altered
|
|
||||||
# """
|
|
||||||
# self.artist_hashes = "-".join(a.artisthash for a in self.artists)
|
|
||||||
|
|
||||||
# def recreate_albumhash(self):
|
|
||||||
# """
|
|
||||||
# Recreates an albumhash of a track to merge all versions of an album.
|
|
||||||
# """
|
|
||||||
# albumartists = (a.name for a in self.albumartists)
|
|
||||||
# self.albumhash = create_hash(self.album, *albumartists)
|
|
||||||
|
|
||||||
# def rename_album(self, new_album: str):
|
|
||||||
# """
|
|
||||||
# Renames an album
|
|
||||||
# """
|
|
||||||
# self.album = new_album
|
|
||||||
|
|
||||||
# def add_artists(self, artists: list[str], new_album_title: str):
|
|
||||||
# for artist in artists:
|
|
||||||
# if create_hash(artist, decode=True) not in self.artist_hashes:
|
|
||||||
# self.artists.append(ArtistMinimal(artist))
|
|
||||||
|
|
||||||
# self.recreate_artists_hash()
|
|
||||||
# self.rename_album(new_album_title)
|
|
||||||
|
|||||||
+19
-20
@@ -5,32 +5,31 @@ This module contains functions for the server
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from app.config import UserConfig
|
from app.config import UserConfig
|
||||||
from app.lib.populate import Populate, PopulateCancelledError
|
from app.lib.populate import PopulateCancelledError
|
||||||
from app.utils.generators import get_random_str
|
from app.utils.generators import get_random_str
|
||||||
from app.utils.threading import background
|
from app.utils.threading import background
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
|
|
||||||
|
|
||||||
@background
|
# @background
|
||||||
def run_periodic_scans():
|
# def run_periodic_scans():
|
||||||
"""
|
# """
|
||||||
Runs periodic scans.
|
# Runs periodic scans.
|
||||||
|
|
||||||
Periodic scans are checks that run every few minutes
|
# Periodic scans are checks that run every few minutes
|
||||||
in the background to do stuff like:
|
# in the background to do stuff like:
|
||||||
- checking for new music
|
# - checking for new music
|
||||||
- delete deleted entries
|
# - delete deleted entries
|
||||||
- downloading artist images, and other data.
|
# - downloading artist images, and other data.
|
||||||
"""
|
# """
|
||||||
# ValidateAlbumThumbs()
|
# # ValidateAlbumThumbs()
|
||||||
# ValidatePlaylistThumbs()
|
# # ValidatePlaylistThumbs()
|
||||||
|
|
||||||
while UserConfig().enablePeriodicScans:
|
# while UserConfig().enablePeriodicScans:
|
||||||
|
|
||||||
try:
|
# try:
|
||||||
Populate(instance_key=get_random_str())
|
# except PopulateCancelledError:
|
||||||
except PopulateCancelledError:
|
# log.error("'run_periodic_scans': Periodic scan cancelled.")
|
||||||
log.error("'run_periodic_scans': Periodic scan cancelled.")
|
# pass
|
||||||
pass
|
|
||||||
|
|
||||||
time.sleep(UserConfig().scanInterval)
|
# time.sleep(UserConfig().scanInterval)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from app.lib.tagger import create_albums
|
|||||||
from app.models import Album, Track
|
from app.models import Album, Track
|
||||||
from app.store.artists import ArtistStore
|
from app.store.artists import ArtistStore
|
||||||
from app.utils import flatten
|
from app.utils import flatten
|
||||||
|
from app.utils.auth import get_current_userid
|
||||||
from app.utils.customlist import CustomList
|
from app.utils.customlist import CustomList
|
||||||
from app.utils.remove_duplicates import remove_duplicates
|
from app.utils.remove_duplicates import remove_duplicates
|
||||||
|
|
||||||
@@ -28,6 +29,17 @@ class AlbumMapEntry:
|
|||||||
def basetitle(self):
|
def basetitle(self):
|
||||||
return self.album.base_title
|
return self.album.base_title
|
||||||
|
|
||||||
|
def increment_playcount(self, duration: int, timestamp: int):
|
||||||
|
self.album.lastplayed = timestamp
|
||||||
|
self.album.playduration += duration
|
||||||
|
self.album.playcount += 1
|
||||||
|
|
||||||
|
def toggle_favorite_user(self, userid: int | None = None):
|
||||||
|
if userid is None:
|
||||||
|
userid = get_current_userid()
|
||||||
|
|
||||||
|
self.album.toggle_favorite_user(userid)
|
||||||
|
|
||||||
|
|
||||||
class AlbumStore:
|
class AlbumStore:
|
||||||
albums: list[Album] = CustomList()
|
albums: list[Album] = CustomList()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from app.db.sqlite.artistcolors import SQLiteArtistMethods as ardb
|
|||||||
from app.lib.tagger import create_artists
|
from app.lib.tagger import create_artists
|
||||||
from app.models import Artist
|
from app.models import Artist
|
||||||
from app.utils import flatten
|
from app.utils import flatten
|
||||||
|
from app.utils.auth import get_current_userid
|
||||||
from app.utils.bisection import use_bisection
|
from app.utils.bisection import use_bisection
|
||||||
from app.utils.customlist import CustomList
|
from app.utils.customlist import CustomList
|
||||||
from app.utils.progressbar import tqdm
|
from app.utils.progressbar import tqdm
|
||||||
@@ -22,6 +23,17 @@ class ArtistMapEntry:
|
|||||||
self.albumhashes: set[str] = set()
|
self.albumhashes: set[str] = set()
|
||||||
self.trackhashes: set[str] = set()
|
self.trackhashes: set[str] = set()
|
||||||
|
|
||||||
|
def increment_playcount(self, duration: int, timestamp: int):
|
||||||
|
self.artist.lastplayed = timestamp
|
||||||
|
self.artist.playduration += duration
|
||||||
|
self.artist.playcount += 1
|
||||||
|
|
||||||
|
def toggle_favorite_user(self, userid: int | None = None):
|
||||||
|
if userid is None:
|
||||||
|
userid = get_current_userid()
|
||||||
|
|
||||||
|
self.artist.toggle_favorite_user(userid)
|
||||||
|
|
||||||
|
|
||||||
class ArtistStore:
|
class ArtistStore:
|
||||||
artists: list[Artist] = CustomList()
|
artists: list[Artist] = CustomList()
|
||||||
|
|||||||
+18
-70
@@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
from typing import Callable, Iterable
|
from typing import Callable, Iterable
|
||||||
from flask_jwt_extended import current_user
|
|
||||||
from app.db.libdata import TrackTable
|
from app.db.libdata import TrackTable
|
||||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
|
||||||
|
|
||||||
# from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
|
|
||||||
from app.db.userdata import FavoritesTable
|
|
||||||
from app.models import Track
|
from app.models import Track
|
||||||
|
from app.utils.auth import get_current_userid
|
||||||
from app.utils.remove_duplicates import remove_duplicates
|
from app.utils.remove_duplicates import remove_duplicates
|
||||||
|
|
||||||
TRACKS_LOAD_KEY = ""
|
TRACKS_LOAD_KEY = ""
|
||||||
@@ -34,12 +31,24 @@ class TrackGroup:
|
|||||||
"""
|
"""
|
||||||
self.tracks.remove(track)
|
self.tracks.remove(track)
|
||||||
|
|
||||||
def set_fav_userids(self, userids: set[int]):
|
def increment_playcount(self, duration: int, timestamp: int):
|
||||||
"""
|
"""
|
||||||
Sets the favorite userids.
|
Increments the playcount of all tracks in the group.
|
||||||
"""
|
"""
|
||||||
for track in self.tracks:
|
for track in self.tracks:
|
||||||
track.fav_userids = userids
|
track.playcount += 1
|
||||||
|
track.lastplayed = timestamp
|
||||||
|
track.playduration += duration
|
||||||
|
|
||||||
|
def toggle_favorite_user(self, userid: int | None = None):
|
||||||
|
"""
|
||||||
|
Adds or removes a user from the list of users who have favorited the track.
|
||||||
|
"""
|
||||||
|
if userid is None:
|
||||||
|
userid = get_current_userid()
|
||||||
|
|
||||||
|
for track in self.tracks:
|
||||||
|
track.toggle_favorite_user(userid)
|
||||||
|
|
||||||
def get_best(self):
|
def get_best(self):
|
||||||
"""
|
"""
|
||||||
@@ -47,21 +56,6 @@ class TrackGroup:
|
|||||||
"""
|
"""
|
||||||
return max(self.tracks, key=lambda x: x.bitrate)
|
return max(self.tracks, key=lambda x: x.bitrate)
|
||||||
|
|
||||||
def toggle_favorite(self, remove: bool = False):
|
|
||||||
"""
|
|
||||||
Adds a track to the favorites.
|
|
||||||
"""
|
|
||||||
|
|
||||||
userids = set(self.tracks[0].fav_userids)
|
|
||||||
|
|
||||||
if remove:
|
|
||||||
userids.remove(current_user["id"])
|
|
||||||
else:
|
|
||||||
userids.add(current_user["id"])
|
|
||||||
|
|
||||||
for track in self.tracks:
|
|
||||||
track.fav_userids = userids
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.tracks)
|
return len(self.tracks)
|
||||||
|
|
||||||
@@ -72,6 +66,7 @@ class classproperty(property):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __get__(self, owner_self, owner_cls):
|
def __get__(self, owner_self, owner_cls):
|
||||||
|
if self.fget:
|
||||||
return self.fget(owner_cls)
|
return self.fget(owner_cls)
|
||||||
|
|
||||||
|
|
||||||
@@ -118,35 +113,6 @@ class TrackStore:
|
|||||||
else:
|
else:
|
||||||
cls.trackhashmap[track.trackhash].append(track)
|
cls.trackhashmap[track.trackhash].append(track)
|
||||||
|
|
||||||
# favs = favdb.get_fav_tracks()
|
|
||||||
favs = FavoritesTable.get_all()
|
|
||||||
records: dict[str, set[int]] = dict()
|
|
||||||
|
|
||||||
# convert records: {trackhash: {userid, userid, ...}}
|
|
||||||
for fav in favs:
|
|
||||||
if fav.hash not in records:
|
|
||||||
# if trackhash not in dict, add it
|
|
||||||
# and set the value to a set containing the userid
|
|
||||||
records[fav.hash] = {fav.userid}
|
|
||||||
|
|
||||||
# if trackhash is in dict, add the userid to the set
|
|
||||||
records[fav.hash].add(fav.userid)
|
|
||||||
|
|
||||||
for record in records:
|
|
||||||
if instance_key != TRACKS_LOAD_KEY:
|
|
||||||
return
|
|
||||||
|
|
||||||
group = cls.trackhashmap.get(record, None)
|
|
||||||
|
|
||||||
if not group:
|
|
||||||
continue
|
|
||||||
|
|
||||||
group.set_fav_userids(records.get(record, set()))
|
|
||||||
|
|
||||||
# print("Done!")
|
|
||||||
# print(cls.trackhashmap.get("0d6b22c19c").tracks[0].fav_userids)
|
|
||||||
# sys.exit(0)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_track(cls, track: Track):
|
def add_track(cls, track: Track):
|
||||||
"""
|
"""
|
||||||
@@ -219,24 +185,6 @@ class TrackStore:
|
|||||||
"""
|
"""
|
||||||
return len(cls.trackhashmap.get(trackhash, []))
|
return len(cls.trackhashmap.get(trackhash, []))
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def toggle_favorite(cls, trackhash: str, remove: bool = False):
|
|
||||||
"""
|
|
||||||
Adds a track to the favorites.
|
|
||||||
"""
|
|
||||||
|
|
||||||
group = cls.trackhashmap.get(trackhash)
|
|
||||||
|
|
||||||
if group:
|
|
||||||
group.toggle_favorite(remove=remove)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def remove_track_from_fav(cls, trackhash: str):
|
|
||||||
"""
|
|
||||||
Removes a track from the favorites.
|
|
||||||
"""
|
|
||||||
return cls.toggle_favorite(trackhash, remove=True)
|
|
||||||
|
|
||||||
# ================================================
|
# ================================================
|
||||||
# ================== GETTERS =====================
|
# ================== GETTERS =====================
|
||||||
# ================================================
|
# ================================================
|
||||||
@@ -332,7 +280,7 @@ class TrackStore:
|
|||||||
"""
|
"""
|
||||||
predicate = lambda artisthashes, artisthash: artisthash in artisthashes
|
predicate = lambda artisthashes, artisthash: artisthash in artisthashes
|
||||||
return cls.find_tracks_by(
|
return cls.find_tracks_by(
|
||||||
key="artist_hashes", value=artisthash, predicate=predicate
|
key="artisthashes", value=artisthash, predicate=predicate
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import setproctitle
|
|||||||
|
|
||||||
from app.api import create_api
|
from app.api import create_api
|
||||||
from app.arg_handler import ProcessArgs
|
from app.arg_handler import ProcessArgs
|
||||||
from app.lib.tagger import IndexEverything
|
from app.lib.mapstuff import map_favorites, map_scrobble_data
|
||||||
|
from app.lib.index import IndexEverything
|
||||||
from app.lib.watchdogg import Watcher as WatchDog
|
from app.lib.watchdogg import Watcher as WatchDog
|
||||||
from app.plugins.register import register_plugins
|
from app.plugins.register import register_plugins
|
||||||
from app.settings import FLASKVARS, TCOLOR, Info
|
from app.settings import FLASKVARS, TCOLOR, Info
|
||||||
@@ -65,6 +66,8 @@ def bg_run_setup():
|
|||||||
pass
|
pass
|
||||||
# run_periodic_scans()
|
# run_periodic_scans()
|
||||||
IndexEverything()
|
IndexEverything()
|
||||||
|
# map_scrobble_data()
|
||||||
|
# map_favorites()
|
||||||
|
|
||||||
|
|
||||||
# @background
|
# @background
|
||||||
|
|||||||
Reference in New Issue
Block a user