diff --git a/server/app/api/__init__.py b/server/app/api/__init__.py index b9b916d2..b8cfb9ee 100644 --- a/server/app/api/__init__.py +++ b/server/app/api/__init__.py @@ -3,25 +3,10 @@ This module contains all the Flask Blueprints and API routes. It also contains a that are used through-out the app. It handles the initialization of the watchdog, checking and creating config dirs and starting the re-indexing process using a background thread. """ -from typing import List -from typing import Set from app import functions from app import helpers -from app import instances -from app import models from app import prep -from app.lib import albumslib -from app.lib import folderslib -from app.lib import playlistlib - -DB_TRACKS = instances.tracks_instance.get_all_tracks() -VALID_FOLDERS: Set[str] = set() - -ALBUMS: List[models.Album] = [] -TRACKS: List[models.Track] = [] -PLAYLISTS: List[models.Playlist] = [] -FOLDERS: List[models.Folder] = List @helpers.background @@ -31,9 +16,6 @@ def initialize() -> None: """ functions.start_watchdog() prep.create_config_dir() - albumslib.create_everything() - folderslib.run_scandir() - playlistlib.create_all_playlists() functions.reindex_tracks() diff --git a/server/app/api/album.py b/server/app/api/album.py index fbdf73d4..2624966d 100644 --- a/server/app/api/album.py +++ b/server/app/api/album.py @@ -1,16 +1,17 @@ """ Contains all the album routes. """ +from pprint import pprint from typing import List from app import api from app import helpers from app import models from app.lib import albumslib -from app.lib import trackslib from flask import Blueprint from flask import request from app.functions import FetchAlbumBio +from app import instances album_bp = Blueprint("album", __name__, url_prefix="") @@ -35,31 +36,31 @@ def get_albums(): return {"albums": albums} -@album_bp.route("/album/tracks", methods=["POST"]) +@album_bp.route("/album", methods=["POST"]) def get_album(): """Returns all the tracks in the given album.""" data = request.get_json() - - album = data["album"] - artist = data["artist"] - - songs = trackslib.get_album_tracks(album, artist) + album, artist = data["album"], data["artist"] albumhash = helpers.create_album_hash(album, artist) - index = albumslib.find_album(api.ALBUMS, albumhash) - album: models.Album = api.ALBUMS[index] - album.count = len(songs) - album.duration = albumslib.get_album_duration(songs) + tracks = instances.tracks_instance.find_tracks_by_hash(albumhash) + tracks = [models.Track(t) for t in tracks] + + album = instances.album_instance.find_album_by_hash(albumhash) + album = models.Album(album) + + album.count = len(tracks) + album.duration = albumslib.get_album_duration(tracks) if ( album.count == 1 - and songs[0].title == album.title - and songs[0].tracknumber == 1 - and songs[0].disknumber == 1 + and tracks[0].title == album.title + and tracks[0].tracknumber == 1 + and tracks[0].disknumber == 1 ): album.is_single = True - return {"songs": songs, "info": album} + return {"tracks": tracks, "info": album} @album_bp.route("/album/bio", methods=["POST"]) @@ -80,14 +81,11 @@ def get_albumartists(): """Returns a list of artists featured in a given album.""" data = request.get_json() - album = data["album"] - artist = data["artist"] + album, artist = data["album"], data["artist"] + albumhash = helpers.create_album_hash(album, artist) - tracks: List[models.Track] = [] - - for track in api.TRACKS: - if track.album == album and track.albumartist == artist: - tracks.append(track) + tracks = instances.tracks_instance.find_tracks_by_hash(albumhash) + tracks = [models.Track(t) for t in tracks] artists = [] diff --git a/server/app/api/playlist.py b/server/app/api/playlist.py index d8730a02..5d023c4f 100644 --- a/server/app/api/playlist.py +++ b/server/app/api/playlist.py @@ -22,8 +22,12 @@ TrackExistsInPlaylist = exceptions.TrackExistsInPlaylist @playlist_bp.route("/playlists", methods=["GET"]) def get_all_playlists(): + """Returns all the playlists.""" + dbplaylists = instances.playlist_instance.get_all_playlists() + dbplaylists = [models.Playlist(p) for p in dbplaylists] + playlists = [ - serializer.Playlist(p, construct_last_updated=False) for p in api.PLAYLISTS + serializer.Playlist(p, construct_last_updated=False) for p in dbplaylists ] playlists.sort( key=lambda p: datetime.strptime(p.lastUpdated, "%Y-%m-%d %H:%M:%S"), @@ -36,7 +40,7 @@ def get_all_playlists(): def create_playlist(): data = request.get_json() - playlist = { + data = { "name": data["name"], "description": "", "pre_tracks": [], @@ -45,21 +49,16 @@ def create_playlist(): "thumb": "", } - try: - for pl in api.PLAYLISTS: - if pl.name == playlist["name"]: - raise PlaylistExists("Playlist already exists.") + dbp = instances.playlist_instance.get_playlist_by_name(data["name"]) - except PlaylistExists as e: - return {"error": str(e)}, 409 + if dbp is not None: + return {"message": "Playlist already exists."}, 409 - upsert_id = instances.playlist_instance.insert_playlist(playlist) + upsert_id = instances.playlist_instance.insert_playlist(data) p = instances.playlist_instance.get_playlist_by_id(upsert_id) - pp = models.Playlist(p) + playlist = models.Playlist(p) - api.PLAYLISTS.append(pp) - - return {"playlist": pp}, 201 + return {"playlist": playlist}, 201 @playlist_bp.route("/playlist//add", methods=["POST"]) @@ -70,22 +69,22 @@ def add_track_to_playlist(playlist_id: str): try: playlistlib.add_track(playlist_id, trackid) - except TrackExistsInPlaylist as e: + except TrackExistsInPlaylist: return {"error": "Track already exists in playlist"}, 409 return {"msg": "I think It's done"}, 200 @playlist_bp.route("/playlist/") -def get_single_p_info(playlistid: str): - p = UseBisection(api.PLAYLISTS, "playlistid", [playlistid])() - playlist: models.Playlist = p[0] +def get_playlist(playlistid: str): + p = instances.playlist_instance.get_playlist_by_id(playlistid) + if p is None: + return {"info": {}, "tracks": []} - if playlist is not None: - tracks = playlist.get_tracks() - return {"info": serializer.Playlist(playlist), "tracks": tracks} + playlist = models.Playlist(p) - return {"info": {}, "tracks": []} + tracks = playlistlib.create_playlist_tracks(playlist.pretracks) + return {"info": serializer.Playlist(playlist), "tracks": tracks} @playlist_bp.route("/playlist//update", methods=["PUT"]) @@ -109,21 +108,21 @@ def update_playlist(playlistid: str): p: models.Playlist = p[0] if playlist is not None: - if image: - image_, thumb_ = playlistlib.save_p_image(image, playlistid) - playlist["image"] = image_ - playlist["thumb"] = thumb_ + if image: + image_, thumb_ = playlistlib.save_p_image(image, playlistid) + playlist["image"] = image_ + playlist["thumb"] = thumb_ - else: - playlist["image"] = p.image.split("/")[-1] - playlist["thumb"] = p.thumb.split("/")[-1] + else: + playlist["image"] = p.image.split("/")[-1] + playlist["thumb"] = p.thumb.split("/")[-1] - p.update_playlist(playlist) - instances.playlist_instance.update_playlist(playlistid, playlist) + p.update_playlist(playlist) + instances.playlist_instance.update_playlist(playlistid, playlist) - return { - "data": serializer.Playlist(p), - } + return { + "data": serializer.Playlist(p), + } return {"msg": "Something shady happened"}, 500 diff --git a/server/app/api/search.py b/server/app/api/search.py index 52a54ca6..f5aac908 100644 --- a/server/app/api/search.py +++ b/server/app/api/search.py @@ -95,18 +95,9 @@ def search(): return { "data": [ - { - "tracks": tracks[:5], - "more": len(tracks) > 5 - }, - { - "albums": albums[:6], - "more": len(albums) > 6 - }, - { - "artists": artists_dicts[:6], - "more": len(artists_dicts) > 6 - }, + {"tracks": tracks[:5], "more": len(tracks) > 5}, + {"albums": albums[:6], "more": len(albums) > 6}, + {"artists": artists_dicts[:6], "more": len(artists_dicts) > 6}, ] } @@ -121,18 +112,18 @@ def search_load_more(): if type == "tracks": return { - "tracks": SEARCH_RESULTS["tracks"][index:index + 5], + "tracks": SEARCH_RESULTS["tracks"][index : index + 5], "more": len(SEARCH_RESULTS["tracks"]) > index + 5, } elif type == "albums": return { - "albums": SEARCH_RESULTS["albums"][index:index + 6], + "albums": SEARCH_RESULTS["albums"][index : index + 6], "more": len(SEARCH_RESULTS["albums"]) > index + 6, } elif type == "artists": return { - "artists": SEARCH_RESULTS["artists"][index:index + 6], + "artists": SEARCH_RESULTS["artists"][index : index + 6], "more": len(SEARCH_RESULTS["artists"]) > index + 6, } diff --git a/server/app/api/track.py b/server/app/api/track.py index 64bf7453..9a3eb587 100644 --- a/server/app/api/track.py +++ b/server/app/api/track.py @@ -6,6 +6,8 @@ from app import instances from flask import Blueprint from flask import send_file +from app import models + track_bp = Blueprint("track", __name__, url_prefix="/") @@ -14,21 +16,15 @@ def send_track_file(trackid): """ Returns an audio file that matches the passed id to the client. """ - try: - files = [] - for f in api.DB_TRACKS: - try: - if f["_id"]["$oid"] == trackid: - files.append(f["filepath"]) - except KeyError: - # Bug: some albums are not found although they exist in `api.ALBUMS`. It has something to do with the bisection method used or sorting. Not sure yet. - pass + track = instances.tracks_instance.get_track_by_id(trackid) - filepath = files[0] - except IndexError: + if track is None: return "File not found", 404 - return send_file(filepath, mimetype="audio/mp3") + track = models.Track(track) + type = track.filepath.split(".")[-1] + + return send_file(track.filepath, mimetype=f"audio/{type}") @track_bp.route("/sample") diff --git a/server/app/db/__init__.py b/server/app/db/__init__.py index e7157d74..fd406360 100644 --- a/server/app/db/__init__.py +++ b/server/app/db/__init__.py @@ -200,3 +200,15 @@ class TrackMethods: Removes a track from the database. Returns a boolean indicating success or failure of the operation. """ pass + + def find_tracks_by_hash(): + """ + Returns all the tracks matching the passed hash. + """ + pass + + def find_tracks_inside_path_regex(): + """ + Returns a list of all the tracks matching the path in the query params. + """ + pass diff --git a/server/app/db/mongodb/albums.py b/server/app/db/mongodb/albums.py index 27b7feb5..b9e124be 100644 --- a/server/app/db/mongodb/albums.py +++ b/server/app/db/mongodb/albums.py @@ -21,16 +21,17 @@ class Albums(MongoAlbums): """ album = album.__dict__ return self.collection.update_one( - { - "album": album["title"], - "artist": album["artist"] - }, - { - "$set": album - }, + {"album": album["title"], "artist": album["artist"]}, + {"$set": album}, upsert=True, ).upserted_id + def insert_many(self, albums: list): + """ + Inserts multiple albums into the database. + """ + return self.collection.insert_many(albums) + def get_all_albums(self) -> list: """ Returns all the albums in the database. @@ -52,9 +53,9 @@ class Albums(MongoAlbums): album = self.collection.find_one({"album": name, "artist": artist}) return convert_one(album) - def get_album_by_artist(self, name: str) -> dict: + def find_album_by_hash(self, hash: str) -> dict: """ - Returns a single album matching the artist in the query params. + Returns a single album matching the hash in the query params. """ - album = self.collection.find_one({"albumartist": name}) + album = self.collection.find_one({"hash": hash}) return convert_one(album) diff --git a/server/app/db/mongodb/playlists.py b/server/app/db/mongodb/playlists.py index d6df804b..7c588169 100644 --- a/server/app/db/mongodb/playlists.py +++ b/server/app/db/mongodb/playlists.py @@ -18,12 +18,8 @@ class Playlists(MongoPlaylists): Inserts a new playlist object into the database. """ return self.collection.update_one( - { - "name": playlist["name"] - }, - { - "$set": playlist - }, + {"name": playlist["name"]}, + {"$set": playlist}, upsert=True, ).upserted_id @@ -41,25 +37,28 @@ class Playlists(MongoPlaylists): playlist = self.collection.find_one({"_id": ObjectId(id)}) return convert_one(playlist) - def add_track_to_playlist(self, playlistid: str, track: dict) -> None: + def set_last_updated(self, playlistid: str) -> None: """ - Adds a track to a playlist. + Sets the lastUpdated field to the current date. """ date = create_new_date() return self.collection.update_one( + {"_id": ObjectId(playlistid)}, + {"$set": {"lastUpdated": date}}, + ) + + def add_track_to_playlist(self, playlistid: str, track: dict) -> None: + """ + Adds a track to a playlist. + """ + self.collection.update_one( { "_id": ObjectId(playlistid), }, - { - "$push": { - "pre_tracks": track - }, - "$set": { - "lastUpdated": date - } - }, + {"$push": {"pre_tracks": track}}, ) + self.set_last_updated(playlistid) def get_playlist_by_name(self, name: str) -> dict: """ diff --git a/server/app/db/mongodb/tracks.py b/server/app/db/mongodb/tracks.py index 6fa72c95..9bd3d0d1 100644 --- a/server/app/db/mongodb/tracks.py +++ b/server/app/db/mongodb/tracks.py @@ -2,9 +2,7 @@ This file contains the AllSongs class for interacting with track documents in MongoDB. """ import pymongo -from app.db.mongodb import convert_many -from app.db.mongodb import convert_one -from app.db.mongodb import MongoTracks +from app.db.mongodb import MongoTracks, convert_many, convert_one from bson import ObjectId @@ -24,17 +22,23 @@ class Tracks(MongoTracks): {"filepath": song_obj["filepath"]}, {"$set": song_obj}, upsert=True ).upserted_id + def insert_many(self, songs: list): + """ + Inserts multiple songs into the database. + """ + return self.collection.insert_many(songs) + def get_all_tracks(self) -> list: """ Returns all tracks in the database. """ return convert_many(self.collection.find()) - def get_song_by_id(self, file_id: str) -> dict: + def get_track_by_id(self, id: str) -> dict: """ Returns a track object by its mongodb id. """ - song = self.collection.find_one({"_id": ObjectId(file_id)}) + song = self.collection.find_one({"_id": ObjectId(id)}) return convert_one(song) def get_song_by_album(self, name: str, artist: str) -> dict: @@ -81,11 +85,20 @@ class Tracks(MongoTracks): def find_songs_by_folder_og(self, query: str) -> list: """ - Returns an unsorted list of all the tracks exactly matching the folder in the query params + Returns an unsorted list of all the track matching the folder in the query params """ songs = self.collection.find({"folder": query}) return convert_many(songs) + def find_tracks_inside_path_regex(self, path: str) -> list: + """ + Returns a list of all the tracks matching the path in the query params. + """ + songs = self.collection.find( + {"filepath": {"$regex": f"^{path}", "$options": "i"}} + ) + return convert_many(songs) + def find_songs_by_artist(self, query: str) -> list: """ Returns a list of all the tracks exactly matching the artists in the query params. @@ -128,3 +141,21 @@ class Tracks(MongoTracks): return True except: return False + + def find_tracks_by_hash(self, hash: str) -> list: + """ + Returns a list of all the tracks matching the hash in the query params. + """ + songs = self.collection.find({"albumhash": hash}) + return convert_many(songs) + + def find_track_by_title_artists_album( + self, title: str, artist: str, album: str + ) -> dict: + """ + Returns a single track matching the title, artist, and album in the query params. + """ + song = self.collection.find_one( + {"title": title, "artists": artist, "album": album} + ) + return convert_one(song) diff --git a/server/app/functions.py b/server/app/functions.py index 31345332..f91f5ff4 100644 --- a/server/app/functions.py +++ b/server/app/functions.py @@ -14,7 +14,9 @@ from app.lib.populate import Populate from PIL import Image from concurrent.futures import ThreadPoolExecutor -from app.lib.trackslib import create_all_tracks +from app.lib.trackslib import validate_tracks +from app.lib import trackslib +from server.app import instances, models @helpers.background @@ -24,6 +26,8 @@ def reindex_tracks(): """ while True: + trackslib.validate_tracks() + populate() CheckArtistImages()() @@ -42,10 +46,6 @@ def populate(): pop = Populate() pop.run() - tracks = create_all_tracks() - api.TRACKS.clear() - api.TRACKS.extend(tracks) - class getArtistImage: """ @@ -104,11 +104,11 @@ class CheckArtistImages: """ Loops through all the tracks and gathers all the artists. """ + ar = instances.tracks_instance.get_all_tracks() + tracks = [models.Track(t) for t in ar] - for song in api.DB_TRACKS: - this_artists: list = song["artists"].split(", ") - - for artist in this_artists: + for t in tracks: + for artist in t.artists: if artist not in self.artists: self.artists.append(artist) diff --git a/server/app/lib/albumslib.py b/server/app/lib/albumslib.py index 2bf6d929..8a4e6baf 100644 --- a/server/app/lib/albumslib.py +++ b/server/app/lib/albumslib.py @@ -4,12 +4,8 @@ This library contains all the functions related to albums. import random from typing import List -from app import api -from app import helpers -from app import instances -from app import models -from app.lib import taglib -from app.lib import trackslib +from app import api, helpers, instances, models +from app.lib import taglib, trackslib from tqdm import tqdm @@ -30,21 +26,12 @@ def get_all_albums() -> List[models.Album]: return albums -def create_everything() -> List[models.Track]: +def validate() -> None: """ Creates album objects for all albums and returns a list of track objects """ - albums: list[models.Album] = get_all_albums() - api.ALBUMS = albums - api.ALBUMS.sort(key=lambda x: x.hash) - - tracks = trackslib.create_all_tracks() - - api.TRACKS.clear() - api.TRACKS.extend(tracks) - api.TRACKS.sort(key=lambda x: x.title) def find_album(albums: List[models.Album], hash: str) -> int | None: @@ -142,8 +129,6 @@ class GetAlbumTracks: self.tracks.remove(track) index = trackslib.find_track(self.tracks, self.hash) - # self.tracks.extend(tracks) - # self.tracks.sort(key=lambda x: x["albumhash"]) return tracks diff --git a/server/app/lib/folderslib.py b/server/app/lib/folderslib.py index bc70f830..81217298 100644 --- a/server/app/lib/folderslib.py +++ b/server/app/lib/folderslib.py @@ -1,15 +1,11 @@ from dataclasses import dataclass from os import scandir -from time import time -from typing import List -from typing import Set from typing import Tuple -from app import api -from app import helpers from app.models import Folder from app.models import Track -from tqdm import tqdm + +from app import instances @dataclass @@ -18,20 +14,12 @@ class Dir: is_sym: bool -def get_valid_folders() -> None: - for track in api.TRACKS: - api.VALID_FOLDERS.add(track.folder) - - def get_folder_track_count(foldername: str) -> int: """ Returns the number of files associated with a folder. """ - count = 0 - for track in api.TRACKS: - if foldername in track.folder: - count += 1 - return count + tracks = instances.tracks_instance.find_tracks_inside_path_regex(foldername) + return len(tracks) def create_folder(dir: Dir) -> Folder: @@ -46,51 +34,6 @@ def create_folder(dir: Dir) -> Folder: return Folder(folder) -def create_all_folders() -> Set[Folder]: - folders: List[Folder] = [] - - for foldername in tqdm(api.VALID_FOLDERS, desc="Creating folders"): - folder = create_folder(foldername) - folders.append(folder) - - return folders - - -def get_subdirs(foldername: str) -> List[Folder]: - """ - Finds and Creates Folder objects for each sub-directory string in the foldername passed. - """ - subdirs = set() - - for folder in api.VALID_FOLDERS: - if foldername in folder: - str0 = folder.replace(foldername, "") - - try: - str1 = str0.split("/")[1] - except IndexError: - str1 = None - - if str1 is not None: - subdirs.add(foldername + "/" + str1) - return [create_folder(dir) for dir in subdirs] - - -@helpers.background -def run_scandir(): - """ - Initiates the creation of all folder objects for each folder with a track in it. - - Runs in a background thread after every 5 minutes. - It calls the - """ - get_valid_folders() - # folders_ = create_all_folders() - """Create all the folder objects before clearing api.FOLDERS""" - - # api.FOLDERS = folders_ - - class getFnF: """ Get files and folders from a directory. @@ -99,15 +42,6 @@ class getFnF: def __init__(self, path: str) -> None: self.path = path - @classmethod - def get_tracks(cls, files: List[str]) -> List[Track]: - """ - Returns a list of Track objects for each file in the given list. - """ - tracks = helpers.UseBisection(api.TRACKS, "filepath", files)() - tracks = filter(lambda t: t is not None, tracks) - return list(tracks) - def __call__(self) -> Tuple[Track, Folder]: try: all = scandir(self.path) @@ -125,9 +59,11 @@ class getFnF: dirs.append(Dir(**dir)) elif entry.is_file() and entry.name.endswith((".mp3", ".flac")): files.append(entry.path) - tracks = self.get_tracks(files) + tracks = instances.tracks_instance.find_songs_by_folder(self.path) + tracks = [Track(track) for track in tracks] folders = [create_folder(dir) for dir in dirs] + folders = filter(lambda f: f.trackcount > 0, folders) return tracks, folders diff --git a/server/app/lib/playlistlib.py b/server/app/lib/playlistlib.py index aabc4cf0..88cdcad5 100644 --- a/server/app/lib/playlistlib.py +++ b/server/app/lib/playlistlib.py @@ -5,6 +5,7 @@ import os import random import string from datetime import datetime +from typing import List from tqdm import tqdm @@ -13,55 +14,37 @@ from app import exceptions from app import instances from app import models from app import settings -from app.lib import trackslib from PIL import Image from PIL import ImageSequence -from progress.bar import Bar from werkzeug import datastructures +from app.lib import trackslib + TrackExistsInPlaylist = exceptions.TrackExistsInPlaylist def add_track(playlistid: str, trackid: str): """ - Adds a track to a playlist in the api.PLAYLISTS dict and to the database. + Adds a track to a playlist to the database. """ - for playlist in api.PLAYLISTS: - if playlist.playlistid == playlistid: - tt = trackslib.get_track_by_id(trackid) + tt = instances.tracks_instance.get_track_by_id(trackid) - track = { - "title": tt.title, - "artists": tt.artists, - "album": tt.album, - } + if tt is None: + return - try: - playlist.add_track(track) - instances.playlist_instance.add_track_to_playlist( - playlistid, track) - return - except TrackExistsInPlaylist as error: - raise error + track = models.Track(tt) + playlist = instances.playlist_instance.get_playlist_by_id(playlistid) -def get_playlist_tracks(pid: str): - for p in api.PLAYLISTS: - if p.playlistid == pid: - return p.tracks + track = { + "title": track.title, + "artists": tt["artists"], + "album": track.album, + } + if track in playlist["pre_tracks"]: + raise TrackExistsInPlaylist - -def create_all_playlists(): - """ - Gets all playlists from the database. - """ - playlists = instances.playlist_instance.get_all_playlists() - - - for playlist in tqdm(playlists, desc="Creating playlists"): - api.PLAYLISTS.append(models.Playlist(playlist)) - - validate_images() + instances.playlist_instance.add_track_to_playlist(playlistid, track) def create_thumbnail(image: any, img_path: str) -> str: @@ -69,8 +52,7 @@ def create_thumbnail(image: any, img_path: str) -> str: Creates a 250 x 250 thumbnail from a playlist image """ thumb_path = "thumb_" + img_path - full_thumb_path = os.path.join(settings.APP_DIR, "images", "playlists", - thumb_path) + full_thumb_path = os.path.join(settings.APP_DIR, "images", "playlists", thumb_path) aspect_ratio = image.width / image.height @@ -88,13 +70,11 @@ def save_p_image(file: datastructures.FileStorage, pid: str): """ img = Image.open(file) - random_str = "".join( - random.choices(string.ascii_letters + string.digits, k=5)) + random_str = "".join(random.choices(string.ascii_letters + string.digits, k=5)) img_path = pid + str(random_str) + ".webp" - full_img_path = os.path.join(settings.APP_DIR, "images", "playlists", - img_path) + full_img_path = os.path.join(settings.APP_DIR, "images", "playlists", img_path) if file.content_type == "image/gif": frames = [] @@ -118,8 +98,10 @@ def validate_images(): Removes all unused images in the images/playlists folder. """ images = [] + p = instances.playlist_instance.get_all_playlists() + playlists = [models.Playlist(p) for p in p] - for playlist in api.PLAYLISTS: + for playlist in playlists: if playlist.image: img_path = playlist.image.split("/")[-1] thumb_path = playlist.thumb.split("/")[-1] @@ -136,3 +118,18 @@ def validate_images(): def create_new_date(): return datetime.now() + + +def create_playlist_tracks(playlist_tracks: List) -> List[models.Track]: + """ + Creates a list of model.Track objects from a list of playlist track dicts. + """ + tracks: List[models.Track] = [] + + for t in playlist_tracks: + track = trackslib.get_p_track(t) + + if track is not None: + tracks.append(models.Track(track)) + + return tracks diff --git a/server/app/lib/populate.py b/server/app/lib/populate.py index f2f7102f..f5500197 100644 --- a/server/app/lib/populate.py +++ b/server/app/lib/populate.py @@ -1,4 +1,5 @@ import os +from pprint import pprint import time from concurrent.futures import ThreadPoolExecutor from copy import deepcopy @@ -12,7 +13,6 @@ from app.helpers import create_album_hash from app.helpers import run_fast_scandir from app.instances import album_instance from app.instances import tracks_instance -from app.lib import folderslib from app.lib.albumslib import create_album from app.lib.albumslib import find_album from app.lib.taglib import get_tags @@ -44,6 +44,7 @@ class Populate: self.db_tracks = tracks_instance.get_all_tracks() self.tag_count = 0 self.exist_count = 0 + self.tracks = [] def run(self): self.check_untagged() @@ -53,17 +54,14 @@ class Populate: return self.tagged_tracks.sort(key=lambda x: x["albumhash"]) - self.tracks = deepcopy(self.tagged_tracks) self.pre_albums = self.create_pre_albums(self.tagged_tracks) self.create_albums(self.pre_albums) self.albums.sort(key=lambda x: x.hash) - api.ALBUMS.sort(key=lambda x: x.hash) - - self.save_albums() self.create_tracks() - # self.create_folders() + + self.save_all() def check_untagged(self): """ @@ -84,7 +82,6 @@ class Populate: t["albumhash"] = create_album_hash(t["album"], t["albumartist"]) self.tagged_tracks.append(t) - api.DB_TRACKS.append(t) self.folders.add(t["folder"]) @@ -95,8 +92,7 @@ class Populate: folder = tags["folder"] self.folders.add(folder) - tags["albumhash"] = create_album_hash(tags["album"], - tags["albumartist"]) + tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"]) self.tagged_tracks.append(tags) api.DB_TRACKS.append(tags) @@ -110,13 +106,6 @@ class Populate: with ThreadPoolExecutor() as executor: executor.map(self.get_tags, self.files) - # with Pool(maxtasksperchild=10, processes=10) as p: - # tags = p.map(get_tags, tqdm(self.files)) - # self.process_tags(tags) - - # for t in tqdm(self.files): - # self.get_tags(t) - d = time.time() - s Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds") @@ -147,7 +136,6 @@ class Populate: self.exist_count += 1 return - self.albums.sort(key=lambda x: x.hash) index = find_track(self.tagged_tracks, albumhash) if index is None: @@ -163,7 +151,6 @@ class Populate: album = Album(album) - api.ALBUMS.append(album) self.albums.append(album) def create_albums(self, albums: List[dict]): @@ -173,8 +160,7 @@ class Populate: for album in tqdm(albums, desc="Building albums"): self.create_album(album) - Log(f"{self.exist_count} of {len(albums)} albums were already in the database" - ) + Log(f"{self.exist_count} of {len(albums)} albums were already in the database") def create_track(self, track: dict): """ @@ -196,38 +182,25 @@ class Populate: pass track["image"] = album.image - - upsert_id = tracks_instance.insert_song(track) - track["_id"] = {"$oid": str(upsert_id)} - - api.TRACKS.append(Track(track)) + return track def create_tracks(self): """ Loops through all the tagged tracks creating complete track objects using the `models.Track` model. """ with ThreadPoolExecutor() as executor: - executor.map(self.create_track, self.tagged_tracks) + iterable = executor.map(self.create_track, self.tagged_tracks) - Log(f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" - ) + self.tracks = [t for t in iterable if t is not None] - def save_albums(self): + Log( + f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" + ) + + def save_all(self): """ Saves the albums to the database. """ - with ThreadPoolExecutor() as executor: - executor.map(album_instance.insert_album, self.albums) - - # def create_folders(self): - # """ - # Creates the folder objects for all the tracks. - # """ - # for folder in tqdm(self.folders, desc="Creating folders"): - # api.VALID_FOLDERS.add(folder) - - # fff = folderslib.create_folder(folder) - # api.FOLDERS.append(fff) - - # Log(f"Created {len(self.folders)} new folders") + album_instance.insert_many([a.__dict__ for a in self.albums]) + tracks_instance.insert_many(self.tracks) diff --git a/server/app/lib/trackslib.py b/server/app/lib/trackslib.py index f580e6cf..3c73af39 100644 --- a/server/app/lib/trackslib.py +++ b/server/app/lib/trackslib.py @@ -2,31 +2,25 @@ This library contains all the functions related to tracks. """ import os +from pprint import pprint from typing import List -from app import api -from app import instances -from app import models +from app import api, instances, models from app.helpers import remove_duplicates from tqdm import tqdm -def create_all_tracks() -> List[models.Track]: +def validate_tracks() -> None: """ Gets all songs under the ~/ directory. """ - tracks: list[models.Track] = [] + entries = instances.tracks_instance.get_all_tracks() - for track in tqdm(api.DB_TRACKS, desc="Creating tracks"): + for track in tqdm(entries, desc="Validating tracks"): try: os.chmod(track["filepath"], 0o755) except FileNotFoundError: instances.tracks_instance.remove_song_by_id(track["_id"]["$oid"]) - api.DB_TRACKS.remove(track) - - tracks.append(models.Track(track)) - - return tracks def get_album_tracks(albumname, artist): @@ -48,7 +42,6 @@ def get_track_by_id(trackid: str) -> models.Track: return track except AttributeError: print("AttributeError") - print(track) def find_track(tracks: list, hash: str) -> int or None: @@ -73,3 +66,9 @@ def find_track(tracks: list, hash: str) -> int or None: right = mid - 1 return None + + +def get_p_track(ptrack): + return instances.tracks_instance.find_track_by_title_artists_album( + ptrack["title"], ptrack["artists"], ptrack["album"] + ) diff --git a/server/app/models.py b/server/app/models.py index f02e100d..3456a707 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -4,7 +4,7 @@ Contains all the models for objects generation and typing. from dataclasses import dataclass, field from typing import List -from app import api, helpers +from app import helpers from app.exceptions import TrackExistsInPlaylist @@ -117,30 +117,6 @@ class Album: return self.artist.lower() == "various artists" -def get_p_track(ptrack): - for track in api.TRACKS: - if ( - track.title == ptrack["title"] - and track.artists == ptrack["artists"] - and ptrack["album"] == track.album - ): - return track - - -def create_playlist_tracks(playlist_tracks: List) -> List[Track]: - """ - Creates a list of model.Track objects from a list of playlist track dicts. - """ - tracks: List[Track] = [] - - for t in playlist_tracks: - track = get_p_track(t) - if track is not None: - tracks.append(track) - - return tracks - - @dataclass class Playlist: """Creates playlist objects""" @@ -148,7 +124,7 @@ class Playlist: playlistid: str name: str tracks: List[Track] - _pre_tracks: list = field(init=False, repr=False) + pretracks: list = field(init=False, repr=False) lastUpdated: int image: str thumb: str @@ -162,16 +138,11 @@ class Playlist: self.description = data["description"] self.image = self.create_img_link(data["image"]) self.thumb = self.create_img_link(data["thumb"]) - self._pre_tracks = data["pre_tracks"] + self.pretracks = data["pre_tracks"] self.tracks = [] self.lastUpdated = data["lastUpdated"] - self.count = len(self._pre_tracks) + self.count = len(self.pretracks) - def get_tracks(self) -> List[Track]: - """ - Generates and returns Track objects from pre_tracks - """ - return create_playlist_tracks(self._pre_tracks) def create_img_link(self, image: str): if image: @@ -180,11 +151,11 @@ class Playlist: return "default.webp" def update_count(self): - self.count = len(self._pre_tracks) + self.count = len(self.pretracks) def add_track(self, track): - if track not in self._pre_tracks: - self._pre_tracks.append(track) + if track not in self.pretracks: + self.pretracks.append(track) self.update_count() self.lastUpdated = helpers.create_new_date() else: diff --git a/server/app/settings.py b/server/app/settings.py index 29029ae7..fb840a26 100644 --- a/server/app/settings.py +++ b/server/app/settings.py @@ -11,7 +11,8 @@ CONFIG_FOLDER = ".alice" HOME_DIR = os.path.expanduser("~") APP_DIR = os.path.join(HOME_DIR, CONFIG_FOLDER) THUMBS_PATH = os.path.join(APP_DIR, "images", "thumbnails") -TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill" +TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill/Wolftyla Radio" +HOME_DIR = TEST_DIR # URL IMG_BASE_URI = "http://127.0.0.1:8900/images/" IMG_ARTIST_URI = IMG_BASE_URI + "artists/" @@ -38,6 +39,7 @@ P_COLORS = [ CPU_COUNT = multiprocessing.cpu_count() + class logger: enable = True diff --git a/src/composables/album.ts b/src/composables/album.ts index 44518b2a..8b38e007 100644 --- a/src/composables/album.ts +++ b/src/composables/album.ts @@ -9,13 +9,13 @@ const getAlbumTracks = async (album: string, artist: string) => { }; await axios - .post(state.settings.uri + "/album/tracks", { + .post(state.settings.uri + "/album", { album: album, artist: artist, }) .then((res) => { data.info = res.data.info; - data.tracks = res.data.songs; + data.tracks = res.data.tracks; }) .catch((err: AxiosError) => { console.error(err);