port search to stores

+ fix favorites
This commit is contained in:
cwilvx
2024-07-27 21:44:33 +03:00
parent 5d32536758
commit b0e904c84f
25 changed files with 428 additions and 666 deletions
+4
View File
@@ -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
View File
@@ -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:
+22 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+25
View File
@@ -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()
+68
View File
@@ -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)
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+12
View File
@@ -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()
+12
View File
@@ -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
View File
@@ -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
+4 -1
View File
@@ -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