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.
|
||||
"""
|
||||
|
||||
from pprint import pprint
|
||||
import random
|
||||
|
||||
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})
|
||||
avg_bitrate = sum(t.bitrate for t in tracks) // (len(tracks) or 1)
|
||||
|
||||
album.fav_userids = [1]
|
||||
pprint(album)
|
||||
|
||||
return {
|
||||
"info": album,
|
||||
"extra": {
|
||||
|
||||
+35
-13
@@ -11,7 +11,11 @@ from app.db.libdata import AlbumTable, TrackTable
|
||||
from app.db.userdata import FavoritesTable
|
||||
from app.models import FavType
|
||||
from app.settings import Defaults
|
||||
from app.utils.bisection import use_bisection
|
||||
|
||||
from app.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.artist import (
|
||||
serialize_for_card as serialize_artist,
|
||||
@@ -46,14 +50,27 @@ def toggle_favorite(body: FavoritesAddBody):
|
||||
"""
|
||||
Adds a favorite to the database.
|
||||
"""
|
||||
FavoritesTable.insert_item({"hash": body.hash, "type": body.type})
|
||||
|
||||
try:
|
||||
FavoritesTable.insert_item({"hash": body.hash, "type": body.type})
|
||||
except:
|
||||
return {"msg": "Failed! An error occured"}, 500
|
||||
|
||||
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:
|
||||
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:
|
||||
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"}
|
||||
|
||||
@@ -95,7 +112,8 @@ def get_favorite_albums(query: GetAllOfTypeQuery):
|
||||
fav_albums, total = FavoritesTable.get_fav_albums(query.start, query.limit)
|
||||
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")
|
||||
@@ -104,6 +122,10 @@ def get_favorite_tracks(query: GetAllOfTypeQuery):
|
||||
Get favorite tracks
|
||||
"""
|
||||
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}
|
||||
|
||||
|
||||
@@ -118,6 +140,7 @@ def get_favorite_artists(query: GetAllOfTypeQuery):
|
||||
)
|
||||
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}
|
||||
|
||||
|
||||
@@ -164,9 +187,9 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
albums = []
|
||||
artists = []
|
||||
|
||||
track_master_hash = TrackTable.get_all_hashes()
|
||||
album_master_hash = AlbumTable.get_all_hashes()
|
||||
artist_master_hash = ArtistTable.get_all_hashes()
|
||||
track_master_hash = TrackStore.trackhashmap.keys()
|
||||
album_master_hash = AlbumStore.albummap.keys()
|
||||
artist_master_hash = ArtistStore.artistmap.keys()
|
||||
|
||||
# INFO: Filter out invalid hashes (file not found or tags edited)
|
||||
for fav in favs:
|
||||
@@ -188,12 +211,11 @@ def get_all_favorites(query: GetAllFavoritesQuery):
|
||||
"artists": len(artists),
|
||||
}
|
||||
|
||||
tracks = TrackTable.get_tracks_by_trackhashes(tracks, limit=track_limit)
|
||||
albums = AlbumTable.get_albums_by_albumhashes(albums, limit=album_limit)
|
||||
artists = ArtistTable.get_artists_by_artisthashes(artists, limit=artist_limit)
|
||||
tracks = TrackStore.get_tracks_by_trackhashes(tracks[:track_limit])
|
||||
albums = AlbumStore.get_albums_by_hashes(albums[:album_limit])
|
||||
artists = ArtistStore.get_artists_by_hashes(artists[:artist_limit])
|
||||
|
||||
recents = []
|
||||
# first_n = favs
|
||||
|
||||
for fav in favs:
|
||||
if len(recents) >= largest:
|
||||
|
||||
@@ -3,9 +3,11 @@ from flask_openapi3 import APIBlueprint
|
||||
from pydantic import Field
|
||||
from app.api.apischemas import TrackHashSchema
|
||||
|
||||
from app.db.libdata import AlbumTable, ArtistTable, TrackTable
|
||||
from app.db.userdata import ScrobbleTable
|
||||
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")
|
||||
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:
|
||||
return {"msg": "Invalid entry."}, 400
|
||||
|
||||
track = TrackTable.get_track_by_trackhash(body.trackhash)
|
||||
|
||||
if track is None:
|
||||
trackentry = TrackStore.trackhashmap.get(body.trackhash)
|
||||
if trackentry is None:
|
||||
return {"msg": "Track not found."}, 404
|
||||
|
||||
ScrobbleTable.add(dict(body))
|
||||
TrackTable.increment_playcount(body.trackhash, duration, timestamp)
|
||||
AlbumTable.increment_playcount(track.albumhash, duration, timestamp)
|
||||
ArtistTable.increment_playcount(track.artisthashes, duration, timestamp)
|
||||
|
||||
# Update play data on the in-memory stores
|
||||
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
|
||||
|
||||
+8
-8
@@ -9,9 +9,9 @@ from flask_openapi3 import APIBlueprint
|
||||
|
||||
from app import models
|
||||
from app.api.apischemas import GenericLimitSchema
|
||||
from app.db.libdata import TrackTable
|
||||
from app.lib import searchlib
|
||||
from app.settings import Defaults
|
||||
from app.store.tracks import TrackStore
|
||||
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
def search_artists(self):
|
||||
@@ -124,7 +124,7 @@ def get_top_results(query: TopResultsQuery):
|
||||
|
||||
class SearchLoadMoreQuery(SearchQuery):
|
||||
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")
|
||||
@@ -136,26 +136,26 @@ def search_load_more(query: SearchLoadMoreQuery):
|
||||
|
||||
NOTE: You must first initiate a search using the `/search` endpoint.
|
||||
"""
|
||||
query = query.q
|
||||
q = query.q
|
||||
item_type = query.type
|
||||
index = query.index
|
||||
index = query.start
|
||||
|
||||
if item_type == "tracks":
|
||||
t = Search(query).search_tracks()
|
||||
t = Search(q).search_tracks()
|
||||
return {
|
||||
"tracks": t[index : index + SEARCH_COUNT],
|
||||
"more": len(t) > index + SEARCH_COUNT,
|
||||
}
|
||||
|
||||
elif item_type == "albums":
|
||||
a = Search(query).search_albums()
|
||||
a = Search(q).search_albums()
|
||||
return {
|
||||
"albums": a[index : index + SEARCH_COUNT],
|
||||
"more": len(a) > index + SEARCH_COUNT,
|
||||
}
|
||||
|
||||
elif item_type == "artists":
|
||||
a = Search(query).search_artists()
|
||||
a = Search(q).search_artists()
|
||||
return {
|
||||
"artists": a[index : 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.userdata import PluginTable
|
||||
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.logger import log
|
||||
from app.settings import Info, Paths, SessionVarKeys
|
||||
@@ -238,7 +238,8 @@ def set_setting(body: SetSettingBody):
|
||||
|
||||
@background
|
||||
def run_populate():
|
||||
populate.Populate(instance_key=get_random_str())
|
||||
# populate.Populate(instance_key=get_random_str())
|
||||
pass
|
||||
|
||||
|
||||
@api.get("/trigger-scan")
|
||||
|
||||
+3
-34
@@ -7,41 +7,10 @@ from sqlalchemy import (
|
||||
select,
|
||||
)
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Session
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
|
||||
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):
|
||||
"""
|
||||
Base class for all database models.
|
||||
@@ -51,8 +20,8 @@ class Base(MappedAsDataclass, DeclarativeBase):
|
||||
|
||||
@classmethod
|
||||
def execute(cls, stmt: Any, commit: bool = False):
|
||||
with DbEngine.manager(commit=commit) as conn:
|
||||
return conn.execute(stmt)
|
||||
with DbEngine.manager(commit=commit) as session:
|
||||
return session.execute(stmt)
|
||||
|
||||
@classmethod
|
||||
def insert_many(cls, items: list[dict[str, Any]]):
|
||||
|
||||
+20
-5
@@ -1,5 +1,18 @@
|
||||
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:
|
||||
@@ -11,20 +24,22 @@ class DbEngine:
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def manager(cls, commit: bool):
|
||||
def manager(cls, commit: bool = False):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
conn = cls.engine.connect()
|
||||
|
||||
try:
|
||||
conn = cls.engine.connect()
|
||||
yield conn.execution_options(preserve_rowcount=True)
|
||||
|
||||
if commit:
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
raise e
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
+22
-26
@@ -1,6 +1,5 @@
|
||||
from app.db import (
|
||||
Base as MasterBase,
|
||||
DbManager,
|
||||
)
|
||||
from app.db.utils import (
|
||||
album_to_dataclass,
|
||||
@@ -13,7 +12,7 @@ from app.db.utils import (
|
||||
from app.models import Album as AlbumModel
|
||||
from app.utils.remove_duplicates import remove_duplicates
|
||||
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
|
||||
|
||||
|
||||
@@ -33,7 +32,7 @@ def create_all():
|
||||
class Base(MasterBase, DeclarativeBase):
|
||||
@classmethod
|
||||
def get_all_hashes(cls, create_date: int | None = None):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
if create_date:
|
||||
if cls.__tablename__ == "track":
|
||||
stmt = select(TrackTable.trackhash).where(
|
||||
@@ -67,7 +66,7 @@ class Base(MasterBase, DeclarativeBase):
|
||||
hash (str): The hash value.
|
||||
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":
|
||||
stmt = (
|
||||
update(cls)
|
||||
@@ -129,7 +128,6 @@ class TrackTable(Base):
|
||||
title: Mapped[str] = mapped_column(String())
|
||||
track: Mapped[int] = mapped_column(Integer())
|
||||
trackhash: Mapped[str] = mapped_column(String(), index=True)
|
||||
# is_favorite: Mapped[Optional[bool]] = mapped_column(Boolean())
|
||||
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
@@ -139,13 +137,13 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(select(cls))
|
||||
return tracks_to_dataclasses(result.fetchall())
|
||||
|
||||
@classmethod
|
||||
def get_tracks_by_filepaths(cls, filepaths: list[str]):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(TrackTable)
|
||||
.where(TrackTable.filepath.in_(filepaths))
|
||||
@@ -155,7 +153,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_tracks_by_albumhash(cls, albumhash: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(TrackTable).where(TrackTable.albumhash == albumhash)
|
||||
)
|
||||
@@ -164,7 +162,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_track_by_trackhash(cls, hash: str, filepath: str = ""):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
if filepath:
|
||||
result = conn.execute(
|
||||
select(TrackTable)
|
||||
@@ -186,7 +184,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_tracks_by_artisthash(cls, artisthash: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(TrackTable).where(TrackTable.artists.contains(artisthash))
|
||||
)
|
||||
@@ -194,7 +192,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_tracks_in_path(cls, path: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(TrackTable)
|
||||
.where(TrackTable.filepath.contains(path))
|
||||
@@ -204,7 +202,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
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(
|
||||
select(TrackTable)
|
||||
.where(TrackTable.trackhash.in_(hashes))
|
||||
@@ -221,7 +219,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_recently_added(cls, start: int, limit: int):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(TrackTable)
|
||||
.order_by(TrackTable.last_mod.desc())
|
||||
@@ -243,7 +241,7 @@ class TrackTable(Base):
|
||||
|
||||
@classmethod
|
||||
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)))
|
||||
|
||||
@classmethod
|
||||
@@ -270,7 +268,6 @@ class AlbumTable(Base):
|
||||
og_title: Mapped[str] = mapped_column(String())
|
||||
title: Mapped[str] = mapped_column(String())
|
||||
trackcount: Mapped[int] = mapped_column(Integer())
|
||||
# is_favorite: Mapped[Optional[bool]] = mapped_column(Boolean())
|
||||
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
@@ -280,14 +277,14 @@ class AlbumTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(select(AlbumTable))
|
||||
all = result.fetchall()
|
||||
return albums_to_dataclasses(all)
|
||||
|
||||
@classmethod
|
||||
def get_album_by_albumhash(cls, hash: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(AlbumTable).where(AlbumTable.albumhash == hash)
|
||||
)
|
||||
@@ -298,7 +295,7 @@ class AlbumTable(Base):
|
||||
|
||||
@classmethod
|
||||
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(
|
||||
select(AlbumTable).where(AlbumTable.albumhash.in_(hashes)).limit(limit)
|
||||
)
|
||||
@@ -312,7 +309,7 @@ class AlbumTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_albums_by_artisthashes(cls, artisthashes: list[str]):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
albums: dict[str, list[AlbumModel]] = {}
|
||||
|
||||
for artist in artisthashes:
|
||||
@@ -325,7 +322,7 @@ class AlbumTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_albums_by_base_title(cls, base_title: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(AlbumTable).where(AlbumTable.base_title == base_title)
|
||||
)
|
||||
@@ -333,7 +330,7 @@ class AlbumTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_albums_by_artisthash(cls, artisthash: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(AlbumTable).where(AlbumTable.artisthashes.contains(artisthash))
|
||||
)
|
||||
@@ -359,7 +356,6 @@ class ArtistTable(Base):
|
||||
genres: Mapped[str] = mapped_column(JSON())
|
||||
name: Mapped[str] = mapped_column(String(), index=True)
|
||||
trackcount: Mapped[int] = mapped_column(Integer())
|
||||
# is_favorite: Mapped[Optional[bool]] = mapped_column(Boolean())
|
||||
lastplayed: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
playcount: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
playduration: Mapped[int] = mapped_column(Integer(), default=0)
|
||||
@@ -369,14 +365,14 @@ class ArtistTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(select(cls))
|
||||
all = result.fetchall()
|
||||
return artists_to_dataclasses(all)
|
||||
|
||||
@classmethod
|
||||
def get_artist_by_hash(cls, artisthash: str):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(ArtistTable).where(ArtistTable.artisthash == artisthash)
|
||||
)
|
||||
@@ -384,7 +380,7 @@ class ArtistTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_artisthashes_not_in(cls, artisthashes: list[str]):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(ArtistTable.artisthash, ArtistTable.name).where(
|
||||
~ArtistTable.artisthash.in_(artisthashes)
|
||||
@@ -396,7 +392,7 @@ class ArtistTable(Base):
|
||||
def get_artists_by_artisthashes(
|
||||
cls, hashes: Iterable[str], limit: int | None = None
|
||||
):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(ArtistTable)
|
||||
.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.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.engine import DbEngine
|
||||
|
||||
|
||||
class MigrationTable(Base):
|
||||
__tablename__ = "dbmigration"
|
||||
@@ -13,7 +15,7 @@ class MigrationTable(Base):
|
||||
|
||||
@classmethod
|
||||
def set_version(cls, version: int):
|
||||
with DbManager(commit=True) as conn:
|
||||
with DbEngine.manager(commit=True) as conn:
|
||||
result = conn.execute(
|
||||
update(cls).where(cls.id == 1).values(version=version)
|
||||
)
|
||||
@@ -23,7 +25,7 @@ class MigrationTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_version(cls):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(select(cls.version).where(cls.id == 1))
|
||||
result = result.fetchone()
|
||||
|
||||
|
||||
+44
-63
@@ -18,6 +18,7 @@ from sqlalchemy import (
|
||||
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.engine import DbEngine
|
||||
from app.db.utils import (
|
||||
albums_to_dataclasses,
|
||||
artists_to_dataclasses,
|
||||
@@ -27,14 +28,13 @@ from app.db.utils import (
|
||||
plugin_to_dataclasses,
|
||||
similar_artist_to_dataclass,
|
||||
similar_artists_to_dataclass,
|
||||
tracklog_to_dataclass,
|
||||
tracklog_to_dataclasses,
|
||||
tracks_to_dataclasses,
|
||||
user_to_dataclass,
|
||||
user_to_dataclasses,
|
||||
)
|
||||
|
||||
from app.db import Base, DbManager
|
||||
from app.db import Base
|
||||
from app.utils.auth import get_current_userid, hash_password
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class UserTable(Base):
|
||||
|
||||
@classmethod
|
||||
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))
|
||||
res = result.fetchone()
|
||||
|
||||
@@ -86,7 +86,7 @@ class UserTable(Base):
|
||||
|
||||
@classmethod
|
||||
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))
|
||||
res = result.fetchone()
|
||||
|
||||
@@ -95,7 +95,7 @@ class UserTable(Base):
|
||||
|
||||
@classmethod
|
||||
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))
|
||||
|
||||
@classmethod
|
||||
@@ -126,7 +126,7 @@ class SimilarArtistTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(select(cls))
|
||||
return similar_artists_to_dataclass(result.fetchall())
|
||||
|
||||
@@ -136,7 +136,7 @@ class SimilarArtistTable(Base):
|
||||
Check whether an artisthash exists in the database.
|
||||
"""
|
||||
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(
|
||||
select(cls.artisthash).where(cls.artisthash == artisthash)
|
||||
)
|
||||
@@ -148,7 +148,7 @@ class SimilarArtistTable(Base):
|
||||
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 = result.fetchone()
|
||||
|
||||
@@ -160,7 +160,7 @@ class FavoritesTable(Base):
|
||||
__tablename__ = "favorite"
|
||||
|
||||
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)
|
||||
timestamp: Mapped[int] = mapped_column(Integer(), index=True)
|
||||
userid: Mapped[int] = mapped_column(
|
||||
@@ -172,7 +172,7 @@ class FavoritesTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
with DbManager() as conn:
|
||||
with DbEngine.manager() as conn:
|
||||
result = conn.execute(select(cls))
|
||||
return favorites_to_dataclass(result.fetchall())
|
||||
|
||||
@@ -181,12 +181,12 @@ class FavoritesTable(Base):
|
||||
item["timestamp"] = int(datetime.datetime.now().timestamp())
|
||||
item["userid"] = get_current_userid()
|
||||
|
||||
with DbManager(commit=True) as conn:
|
||||
with DbEngine.manager(commit=True) as conn:
|
||||
conn.execute(insert(cls).values(item))
|
||||
|
||||
@classmethod
|
||||
def remove_item(cls, item: dict[str, Any]):
|
||||
with DbManager(commit=True) as conn:
|
||||
with DbEngine.manager(commit=True) as conn:
|
||||
conn.execute(
|
||||
delete(cls).where(
|
||||
(cls.hash == item["hash"]) & (cls.type == item["type"])
|
||||
@@ -199,12 +199,13 @@ class FavoritesTable(Base):
|
||||
return result.fetchone() is not None
|
||||
|
||||
@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(
|
||||
select(table)
|
||||
.select_from(join(table, cls, field == cls.hash))
|
||||
.where(and_(cls.type == type, cls.userid == get_current_userid()))
|
||||
.offset(start)
|
||||
select(cls)
|
||||
# .select_from(join(table, cls, field == cls.hash))
|
||||
.where(and_(cls.type == type, cls.userid == get_current_userid())).offset(
|
||||
start
|
||||
)
|
||||
# INFO: If start is 0, fetch all so we can get the total count
|
||||
.limit(limit if start != 0 else None)
|
||||
)
|
||||
@@ -218,30 +219,18 @@ class FavoritesTable(Base):
|
||||
|
||||
@classmethod
|
||||
def get_fav_tracks(cls, start: int, limit: int):
|
||||
from .libdata import TrackTable
|
||||
|
||||
result, total = cls.get_all_of_type(
|
||||
TrackTable, TrackTable.trackhash, "track", start, limit
|
||||
)
|
||||
return tracks_to_dataclasses(result), total
|
||||
result, total = cls.get_all_of_type("track", start, limit)
|
||||
return favorites_to_dataclass(result), total
|
||||
|
||||
@classmethod
|
||||
def get_fav_albums(cls, start: int, limit: int):
|
||||
from .libdata import AlbumTable
|
||||
|
||||
result, total = cls.get_all_of_type(
|
||||
AlbumTable, AlbumTable.albumhash, "album", start, limit
|
||||
)
|
||||
return albums_to_dataclasses(result), total
|
||||
result, total = cls.get_all_of_type("album", start, limit)
|
||||
return favorites_to_dataclass(result), total
|
||||
|
||||
@classmethod
|
||||
def get_fav_artists(cls, start: int, limit: int):
|
||||
from .libdata import ArtistTable
|
||||
|
||||
result, total = cls.get_all_of_type(
|
||||
ArtistTable, ArtistTable.artisthash, "artist", start, limit
|
||||
)
|
||||
return artists_to_dataclasses(result), total
|
||||
result, total = cls.get_all_of_type("artist", start, limit)
|
||||
return favorites_to_dataclass(result), total
|
||||
|
||||
|
||||
class ScrobbleTable(Base):
|
||||
@@ -265,7 +254,7 @@ class ScrobbleTable(Base):
|
||||
return cls.insert_one(item)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, start: int, limit: int):
|
||||
def get_all(cls, start: int, limit: int | None):
|
||||
result = cls.execute(
|
||||
select(cls)
|
||||
.where(cls.userid == get_current_userid())
|
||||
@@ -325,7 +314,7 @@ class PlaylistTable(Base):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_trackhashes(cls, id: int) -> list[str]:
|
||||
def get_trackhashes(cls, id: int):
|
||||
result = cls.execute(
|
||||
select(cls.trackhashes).where(
|
||||
(cls.id == id) & (cls.userid == get_current_userid())
|
||||
@@ -388,32 +377,24 @@ class PlaylistTable(Base):
|
||||
)
|
||||
|
||||
|
||||
# class PlaylistTrackTable(Base):
|
||||
# __tablename__ = "playlisttrack"
|
||||
class ArtistData(Base):
|
||||
__tablename__ = "artistdata"
|
||||
|
||||
# id: Mapped[int] = mapped_column(primary_key=True)
|
||||
# trackhash: Mapped[str] = mapped_column(String(), index=True)
|
||||
# playlistid: Mapped[int] = mapped_column(
|
||||
# Integer(), ForeignKey("playlist.id", ondelete="cascade")
|
||||
# )
|
||||
# index: Mapped[int] = mapped_column(Integer())
|
||||
# userid: Mapped[int] = mapped_column(
|
||||
# Integer(), ForeignKey("user.id", ondelete="cascade")
|
||||
# )
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
artisthash: Mapped[str] = mapped_column(String(), index=True)
|
||||
color: Mapped[str] = mapped_column(String(), nullable=True)
|
||||
bio: Mapped[str] = mapped_column(String(), nullable=True)
|
||||
info: Mapped[dict[str, Any]] = mapped_column(JSON(), nullable=True)
|
||||
extra: Mapped[dict[str, Any]] = mapped_column(
|
||||
JSON(), nullable=True, default_factory=dict
|
||||
)
|
||||
|
||||
# @classmethod
|
||||
# def count_by_playlist()
|
||||
@classmethod
|
||||
def find_one(cls, artisthash: str):
|
||||
result = cls.execute(select(cls).where(cls.artisthash == artisthash))
|
||||
return result.fetchone()
|
||||
|
||||
# @classmethod
|
||||
# def insert_many(cls, playlistid: int, trackhashes: list[str]):
|
||||
# userid = get_current_userid()
|
||||
# items = [
|
||||
# {
|
||||
# "index": index,
|
||||
# "userid": userid,
|
||||
# "trackhash": trackhash,
|
||||
# "playlistid": playlistid,
|
||||
# }
|
||||
# for index, trackhash in enumerate(trackhashes)
|
||||
# ]
|
||||
# return cls.execute(insert(cls).values(items), commit=True)
|
||||
@classmethod
|
||||
def get_all_colors(cls) -> dict[str, str]:
|
||||
result = cls.execute(select(cls.artisthash, cls.color))
|
||||
return dict(result.fetchall())
|
||||
|
||||
+20
-22
@@ -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.utils import SQLiteManager
|
||||
|
||||
# from app.store.artists import ArtistStore
|
||||
from app.db.userdata import ArtistData
|
||||
from app.logger import log
|
||||
from app.lib.errors import PopulateCancelledError
|
||||
from app.store.artists import ArtistStore
|
||||
from app.utils.progressbar import tqdm
|
||||
|
||||
PROCESS_ALBUM_COLORS_KEY = ""
|
||||
@@ -100,32 +101,29 @@ class ProcessArtistColors:
|
||||
"""
|
||||
|
||||
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
|
||||
PROCESS_ARTIST_COLORS_KEY = instance_key
|
||||
|
||||
with SQLiteManager() as cur:
|
||||
try:
|
||||
for artist in tqdm(
|
||||
all_artists, desc="Processing missing artist colors"
|
||||
):
|
||||
if PROCESS_ARTIST_COLORS_KEY != instance_key:
|
||||
raise PopulateCancelledError(
|
||||
"A newer 'ProcessArtistColors' instance is running. Stopping this one."
|
||||
)
|
||||
try:
|
||||
for artist in tqdm(all_artists, desc="Processing missing artist colors"):
|
||||
if PROCESS_ARTIST_COLORS_KEY != instance_key:
|
||||
raise PopulateCancelledError(
|
||||
"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 artist and artist.color is not None:
|
||||
continue
|
||||
|
||||
if exists:
|
||||
continue
|
||||
colors = process_color(artist.artisthash, is_album=False)
|
||||
|
||||
colors = process_color(artist.artisthash, is_album=False)
|
||||
if colors is None:
|
||||
continue
|
||||
|
||||
if colors is None:
|
||||
continue
|
||||
|
||||
artist.set_colors(colors)
|
||||
adb.insert_one_artist(cur, artist.artisthash, colors)
|
||||
finally:
|
||||
cur.close()
|
||||
artist.set_colors(colors)
|
||||
adb.insert_one_artist(cur, artist.artisthash, colors)
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
@@ -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 app import settings
|
||||
from app.db.libdata import AlbumTable, TrackTable
|
||||
from app.db.libdata import TrackTable
|
||||
from app.models.track import Track
|
||||
from app.store.albums import AlbumStore
|
||||
|
||||
def create_thumbnail(image: Any, img_path: str) -> str:
|
||||
"""
|
||||
@@ -115,8 +116,7 @@ def get_first_4_images(
|
||||
if len(albums) == 4:
|
||||
break
|
||||
|
||||
# albums = AlbumStore.get_albums_by_hashes(albums)
|
||||
albums = AlbumTable.get_albums_by_albumhashes(albums)
|
||||
albums = AlbumStore.get_albums_by_hashes(albums)
|
||||
images = [
|
||||
{
|
||||
"image": album.image,
|
||||
|
||||
+11
-165
@@ -1,28 +1,22 @@
|
||||
from dataclasses import asdict
|
||||
import os
|
||||
from collections import deque
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Generator
|
||||
|
||||
from requests import ConnectionError as RequestConnectionError
|
||||
from requests import ReadTimeout
|
||||
|
||||
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.lib.artistlib import CheckArtistImages
|
||||
from app.lib.colorlib import ProcessArtistColors
|
||||
from app.lib.errors import PopulateCancelledError
|
||||
from app.lib.taglib import extract_thumb
|
||||
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.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.progressbar import tqdm
|
||||
|
||||
@@ -35,46 +29,6 @@ remove_tracks_by_filepaths = SQLiteTrackMethods.remove_tracks_by_filepaths
|
||||
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:
|
||||
"""
|
||||
Cordinates the extracting of thumbnails
|
||||
@@ -117,102 +71,6 @@ class CordinateMedia:
|
||||
log.warn(e)
|
||||
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]):
|
||||
"""
|
||||
@@ -228,25 +86,11 @@ def get_image(_map: tuple[str, Album]):
|
||||
if POPULATE_KEY != instance_key:
|
||||
raise PopulateCancelledError("'ProcessTrackThumbnails': Populate key changed")
|
||||
|
||||
matching_tracks = filter(
|
||||
lambda t: t.albumhash == album.albumhash,
|
||||
TrackTable.get_tracks_by_albumhash(album.albumhash),
|
||||
)
|
||||
matching_tracks = AlbumStore.get_album_tracks(album.albumhash)
|
||||
|
||||
try:
|
||||
track = next(matching_tracks)
|
||||
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
|
||||
|
||||
return
|
||||
except StopIteration:
|
||||
pass
|
||||
for track in matching_tracks:
|
||||
if extract_thumb(track.filepath, track.image):
|
||||
break
|
||||
|
||||
|
||||
def get_cpu_count():
|
||||
@@ -274,7 +118,7 @@ class ProcessTrackThumbnails:
|
||||
|
||||
# filter out albums that already have thumbnails
|
||||
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)
|
||||
|
||||
@@ -330,7 +174,9 @@ class FetchSimilarArtistsLastFM:
|
||||
processed = ".".join(a.artisthash for a in processed)
|
||||
|
||||
# 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)
|
||||
|
||||
# process the rest
|
||||
|
||||
+16
-25
@@ -9,7 +9,8 @@ from unidecode import unidecode
|
||||
|
||||
from app import models
|
||||
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.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.track import serialize_track, serialize_tracks
|
||||
|
||||
# from app.store.albums import AlbumStore
|
||||
# from app.store.artists import ArtistStore
|
||||
# from app.store.tracks import TrackStore
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.artists import ArtistStore
|
||||
from app.store.tracks import TrackStore
|
||||
|
||||
from app.utils.remove_duplicates import remove_duplicates
|
||||
|
||||
@@ -54,8 +55,7 @@ class Limit:
|
||||
class SearchTracks:
|
||||
def __init__(self, query: str) -> None:
|
||||
self.query = query
|
||||
# self.tracks = TrackStore.tracks
|
||||
self.tracks = TrackTable.get_all()
|
||||
self.tracks = TrackStore.get_flat_list()
|
||||
|
||||
def __call__(self) -> List[models.Track]:
|
||||
"""
|
||||
@@ -78,8 +78,7 @@ class SearchTracks:
|
||||
class SearchArtists:
|
||||
def __init__(self, query: str) -> None:
|
||||
self.query = query
|
||||
# self.artists = ArtistStore.artists
|
||||
self.artists = ArtistTable.get_all()
|
||||
self.artists = ArtistStore.get_flat_list()
|
||||
|
||||
def __call__(self):
|
||||
"""
|
||||
@@ -101,8 +100,7 @@ class SearchArtists:
|
||||
class SearchAlbums:
|
||||
def __init__(self, query: str) -> None:
|
||||
self.query = query
|
||||
# self.albums = AlbumStore.albums
|
||||
self.albums = AlbumTable.get_all()
|
||||
self.albums = AlbumStore.get_flat_list()
|
||||
|
||||
def __call__(self) -> List[models.Album]:
|
||||
"""
|
||||
@@ -169,9 +167,9 @@ class TopResults:
|
||||
def collect_all():
|
||||
all_items: list[_type] = []
|
||||
|
||||
all_items.extend(ArtistTable.get_all())
|
||||
all_items.extend(TrackTable.get_all())
|
||||
all_items.extend(AlbumTable.get_all())
|
||||
all_items.extend(ArtistStore.get_flat_list())
|
||||
all_items.extend(TrackStore.get_flat_list())
|
||||
all_items.extend(TrackStore.get_flat_list())
|
||||
|
||||
return all_items, get_titles(all_items)
|
||||
|
||||
@@ -194,7 +192,7 @@ class TopResults:
|
||||
return {"type": "track", "item": item}
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
@@ -212,19 +210,13 @@ class TopResults:
|
||||
track_count = 0
|
||||
duration = 0
|
||||
|
||||
tracks = TrackTable.get_tracks_by_artisthash(item.artisthash)
|
||||
tracks = TrackStore.get_tracks_by_artisthash(item.artisthash)
|
||||
tracks = remove_duplicates(tracks)
|
||||
|
||||
for track in tracks:
|
||||
track_count += 1
|
||||
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}
|
||||
|
||||
@staticmethod
|
||||
@@ -235,8 +227,7 @@ class TopResults:
|
||||
tracks.extend(SearchTracks(query)())
|
||||
|
||||
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)
|
||||
|
||||
# if there are less than the limit, get more tracks
|
||||
@@ -249,7 +240,7 @@ class TopResults:
|
||||
|
||||
if item["type"] == "artist":
|
||||
# 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 len(t) < limit:
|
||||
@@ -271,7 +262,7 @@ class TopResults:
|
||||
|
||||
if item["type"] == "artist":
|
||||
# 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 len(albums) < limit:
|
||||
|
||||
+6
-34
@@ -1,12 +1,9 @@
|
||||
import gc
|
||||
import os
|
||||
from pprint import pprint
|
||||
from time import time
|
||||
from app import settings
|
||||
from app.config import UserConfig
|
||||
from app.db.libdata import ArtistTable
|
||||
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.models.album import Album
|
||||
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.logger import log
|
||||
from app.utils.threading import background
|
||||
|
||||
POPULATE_KEY: float = 0
|
||||
|
||||
@@ -142,7 +138,6 @@ class IndexTracks:
|
||||
print("Done")
|
||||
|
||||
|
||||
# class IndexAlbums:
|
||||
def create_albums():
|
||||
albums = dict()
|
||||
all_tracks: list[Track] = TrackTable.get_all()
|
||||
@@ -165,7 +160,7 @@ def create_albums():
|
||||
"playduration": track.playduration,
|
||||
"title": track.album,
|
||||
"trackcount": 1,
|
||||
"extra": {}
|
||||
"extra": {},
|
||||
}
|
||||
else:
|
||||
album = albums[track.albumhash]
|
||||
@@ -187,13 +182,11 @@ def create_albums():
|
||||
genres.append(genre)
|
||||
|
||||
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"])
|
||||
|
||||
del genres
|
||||
|
||||
# AlbumTable.remove_all()
|
||||
# AlbumTable.insert_many(list(albums.values()))
|
||||
return [Album(**album) for album in albums.values()]
|
||||
|
||||
|
||||
@@ -227,9 +220,7 @@ def create_artists():
|
||||
"playduration": track.playduration,
|
||||
"trackcount": None,
|
||||
"tracks": (
|
||||
{track.trackhash}
|
||||
if thisartist.get("in_track", True)
|
||||
else set()
|
||||
{track.trackhash} if thisartist.get("in_track", True) else set()
|
||||
),
|
||||
"extra": {},
|
||||
}
|
||||
@@ -261,7 +252,7 @@ def create_artists():
|
||||
genres.append(genre)
|
||||
|
||||
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]
|
||||
|
||||
# INFO: Delete temporary keys
|
||||
@@ -272,25 +263,6 @@ def create_artists():
|
||||
# INFO: Delete local variables
|
||||
del genres
|
||||
|
||||
# ArtistTable.remove_all()
|
||||
# ArtistTable.insert_many(list(artists.values()))
|
||||
# del artists
|
||||
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 datetime
|
||||
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 ..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)
|
||||
@@ -27,7 +26,6 @@ class Album:
|
||||
og_title: str
|
||||
title: str
|
||||
trackcount: int
|
||||
# is_favorite: bool
|
||||
lastplayed: int
|
||||
playcount: int
|
||||
playduration: int
|
||||
@@ -37,6 +35,21 @@ class Album:
|
||||
type: str = "album"
|
||||
image: str = ""
|
||||
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):
|
||||
self.image = self.albumhash + ".webp"
|
||||
|
||||
+18
-2
@@ -1,6 +1,7 @@
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.utils.auth import get_current_userid
|
||||
from app.utils.hashing import create_hash
|
||||
|
||||
|
||||
@@ -46,7 +47,6 @@ class Artist:
|
||||
genrehashes: list[str]
|
||||
name: str
|
||||
trackcount: int
|
||||
# is_favorite: bool
|
||||
lastplayed: int
|
||||
playcount: int
|
||||
playduration: int
|
||||
@@ -55,5 +55,21 @@ class Artist:
|
||||
id: int = -1
|
||||
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):
|
||||
self.image = self.artisthash + ".webp"
|
||||
self.image = self.artisthash + ".webp"
|
||||
|
||||
+11
-157
@@ -38,12 +38,22 @@ class Track:
|
||||
_pos: int = 0
|
||||
_ati: str = ""
|
||||
image: str = ""
|
||||
fav_userids: set = field(default_factory=set)
|
||||
fav_userids: list[int] = 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 track.
|
||||
"""
|
||||
if userid in self.fav_userids:
|
||||
self.fav_userids.remove(userid)
|
||||
else:
|
||||
self.fav_userids.append(userid)
|
||||
|
||||
def __post_init__(self):
|
||||
self.image = self.albumhash + ".webp"
|
||||
self.extra = {
|
||||
@@ -51,159 +61,3 @@ class Track:
|
||||
"track_total": self.extra.get("track_total", 0),
|
||||
"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
|
||||
|
||||
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.threading import background
|
||||
from app.logger import log
|
||||
|
||||
|
||||
@background
|
||||
def run_periodic_scans():
|
||||
"""
|
||||
Runs periodic scans.
|
||||
# @background
|
||||
# def run_periodic_scans():
|
||||
# """
|
||||
# Runs periodic scans.
|
||||
|
||||
Periodic scans are checks that run every few minutes
|
||||
in the background to do stuff like:
|
||||
- checking for new music
|
||||
- delete deleted entries
|
||||
- downloading artist images, and other data.
|
||||
"""
|
||||
# ValidateAlbumThumbs()
|
||||
# ValidatePlaylistThumbs()
|
||||
# Periodic scans are checks that run every few minutes
|
||||
# in the background to do stuff like:
|
||||
# - checking for new music
|
||||
# - delete deleted entries
|
||||
# - downloading artist images, and other data.
|
||||
# """
|
||||
# # ValidateAlbumThumbs()
|
||||
# # ValidatePlaylistThumbs()
|
||||
|
||||
while UserConfig().enablePeriodicScans:
|
||||
# while UserConfig().enablePeriodicScans:
|
||||
|
||||
try:
|
||||
Populate(instance_key=get_random_str())
|
||||
except PopulateCancelledError:
|
||||
log.error("'run_periodic_scans': Periodic scan cancelled.")
|
||||
pass
|
||||
# try:
|
||||
# except PopulateCancelledError:
|
||||
# log.error("'run_periodic_scans': Periodic scan cancelled.")
|
||||
# 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.store.artists import ArtistStore
|
||||
from app.utils import flatten
|
||||
from app.utils.auth import get_current_userid
|
||||
from app.utils.customlist import CustomList
|
||||
from app.utils.remove_duplicates import remove_duplicates
|
||||
|
||||
@@ -28,6 +29,17 @@ class AlbumMapEntry:
|
||||
def basetitle(self):
|
||||
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:
|
||||
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.models import Artist
|
||||
from app.utils import flatten
|
||||
from app.utils.auth import get_current_userid
|
||||
from app.utils.bisection import use_bisection
|
||||
from app.utils.customlist import CustomList
|
||||
from app.utils.progressbar import tqdm
|
||||
@@ -22,6 +23,17 @@ class ArtistMapEntry:
|
||||
self.albumhashes: 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:
|
||||
artists: list[Artist] = CustomList()
|
||||
|
||||
+19
-71
@@ -2,13 +2,10 @@
|
||||
|
||||
import itertools
|
||||
from typing import Callable, Iterable
|
||||
from flask_jwt_extended import current_user
|
||||
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.utils.auth import get_current_userid
|
||||
from app.utils.remove_duplicates import remove_duplicates
|
||||
|
||||
TRACKS_LOAD_KEY = ""
|
||||
@@ -34,12 +31,24 @@ class TrackGroup:
|
||||
"""
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
@@ -47,21 +56,6 @@ class TrackGroup:
|
||||
"""
|
||||
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):
|
||||
return len(self.tracks)
|
||||
|
||||
@@ -72,7 +66,8 @@ class classproperty(property):
|
||||
"""
|
||||
|
||||
def __get__(self, owner_self, owner_cls):
|
||||
return self.fget(owner_cls)
|
||||
if self.fget:
|
||||
return self.fget(owner_cls)
|
||||
|
||||
|
||||
class TrackStore:
|
||||
@@ -118,35 +113,6 @@ class TrackStore:
|
||||
else:
|
||||
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
|
||||
def add_track(cls, track: Track):
|
||||
"""
|
||||
@@ -219,24 +185,6 @@ class TrackStore:
|
||||
"""
|
||||
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 =====================
|
||||
# ================================================
|
||||
@@ -332,7 +280,7 @@ class TrackStore:
|
||||
"""
|
||||
predicate = lambda artisthashes, artisthash: artisthash in artisthashes
|
||||
return cls.find_tracks_by(
|
||||
key="artist_hashes", value=artisthash, predicate=predicate
|
||||
key="artisthashes", value=artisthash, predicate=predicate
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -21,7 +21,8 @@ import setproctitle
|
||||
|
||||
from app.api import create_api
|
||||
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.plugins.register import register_plugins
|
||||
from app.settings import FLASKVARS, TCOLOR, Info
|
||||
@@ -65,6 +66,8 @@ def bg_run_setup():
|
||||
pass
|
||||
# run_periodic_scans()
|
||||
IndexEverything()
|
||||
# map_scrobble_data()
|
||||
# map_favorites()
|
||||
|
||||
|
||||
# @background
|
||||
|
||||
Reference in New Issue
Block a user