From 22fa3eef4011d4c79a91068c9f3a597b3898cfaa Mon Sep 17 00:00:00 2001 From: geoffrey45 Date: Mon, 23 Jan 2023 17:10:05 +0300 Subject: [PATCH] handle watching ~/ dir + fix bug that caused duplicate album colors in db --- app/api/folder.py | 30 +++++++++++++++++++--- app/api/settings.py | 57 +++++++++++++++++++++++++++++++++++++---- app/db/sqlite/tracks.py | 16 ++++-------- app/db/store.py | 22 +++++++++++++--- app/lib/colorlib.py | 10 +++++--- app/lib/folderslib.py | 1 - app/lib/populate.py | 16 ++++++++---- 7 files changed, 121 insertions(+), 31 deletions(-) diff --git a/app/api/folder.py b/app/api/folder.py index 28547f7a..e1de1f79 100644 --- a/app/api/folder.py +++ b/app/api/folder.py @@ -3,10 +3,14 @@ Contains all the folder routes. """ import os +from pathlib import Path from flask import Blueprint, request from app import settings from app.lib.folderslib import GetFilesAndDirs +from app.db.sqlite.settings import SettingsSQLMethods as db +from app.models import Folder +from app.utils import create_folder_hash api = Blueprint("folder", __name__, url_prefix="/") @@ -19,12 +23,32 @@ def get_folder_tree(): data = request.get_json() if data is not None: - req_dir: str = data["folder"] - else: + try: + req_dir: str = data["folder"] + except KeyError: + req_dir = "$home" + + root_dirs = db.get_root_dirs() + + if req_dir == "$home" and root_dirs[0] == "$home": req_dir = settings.USER_HOME_DIR if req_dir == "$home": - req_dir = settings.USER_HOME_DIR + folders = [Path(f) for f in root_dirs] + + return { + "folders": [ + Folder( + name=f.name, + path=str(f), + has_tracks=True, + is_sym=f.is_symlink(), + path_hash=create_folder_hash(*f.parts[1:]), + ) + for f in folders + ], + "tracks": [], + } tracks, folders = GetFilesAndDirs(req_dir)() diff --git a/app/api/settings.py b/app/api/settings.py index 2f065a5f..358fc4e0 100644 --- a/app/api/settings.py +++ b/app/api/settings.py @@ -1,6 +1,12 @@ from flask import Blueprint, request +from app import settings from app.db.sqlite.settings import SettingsSQLMethods as sdb +from app.lib import populate +from app.logger import log + +from app.db.store import Store +from app.utils import background api = Blueprint("settings", __name__, url_prefix="/") @@ -8,7 +14,22 @@ api = Blueprint("settings", __name__, url_prefix="/") def get_child_dirs(parent: str, children: list[str]): """Returns child directories in a list, given a parent directory""" - return [dir for dir in children if dir.startswith(parent)] + return [dir for dir in children if dir.startswith(parent) and dir != parent] + + +@background +def rebuild_store(db_dirs: list[str]): + log.info("Rebuilding library...") + Store.remove_tracks_by_dir_except(db_dirs) + + Store.load_all_tracks() + Store.process_folders() + Store.load_albums() + Store.load_artists() + + populate.Populate() + + log.info("Rebuilding library... ✅") @api.route("/settings/add-root-dirs", methods=["POST"]) @@ -29,16 +50,42 @@ def add_root_dirs(): except KeyError: return msg, 400 - # --- Unregister child directories --- + def finalize(new_dirs: list[str], removed_dirs: list[str], db_dirs: list[str]): + sdb.remove_root_dirs(removed_dirs) + sdb.add_root_dirs(new_dirs) + rebuild_store(db_dirs) + + # --- db_dirs = sdb.get_root_dirs() + if db_dirs[0] == "$home" and new_dirs[0] == "$home".strip(): + return {"msg": "Not changed!"} + + if db_dirs[0] == "$home": + sdb.remove_root_dirs(db_dirs) + + try: + if new_dirs[0] == "$home": + finalize(["$home"], db_dirs, [settings.USER_HOME_DIR]) + + return {"msg": "Updated!"} + except IndexError: + pass + for _dir in new_dirs: children = get_child_dirs(_dir, db_dirs) removed_dirs.extend(children) - # ------------------------------------ + # --- - sdb.add_root_dirs(new_dirs) - sdb.remove_root_dirs(removed_dirs) + for _dir in removed_dirs: + try: + db_dirs.remove(_dir) + except ValueError: + pass + + db_dirs.extend(new_dirs) + + finalize(new_dirs, removed_dirs, db_dirs) return {"msg": "Updated!"} diff --git a/app/db/sqlite/tracks.py b/app/db/sqlite/tracks.py index 32abcabf..ea1b7607 100644 --- a/app/db/sqlite/tracks.py +++ b/app/db/sqlite/tracks.py @@ -130,15 +130,9 @@ class SQLiteTrackMethods: cur.execute("DELETE FROM tracks WHERE filepath=?", (filepath,)) @staticmethod - def track_exists(filepath: str): - """ - Checks if a track exists in the database using its filepath. - """ + def remove_tracks_by_folders(folders: list[str]): + sql = "DELETE FROM tracks WHERE folder = ?" + with SQLiteManager() as cur: - cur.execute("SELECT * FROM tracks WHERE filepath=?", (filepath,)) - row = cur.fetchone() - - if row is not None: - return True - - return False + for folder in folders: + cur.execute(sql, (folder,)) diff --git a/app/db/store.py b/app/db/store.py index 13d7ea0b..0c7fca11 100644 --- a/app/db/store.py +++ b/app/db/store.py @@ -88,6 +88,17 @@ class Store: cls.tracks.remove(track) break + @classmethod + def remove_tracks_by_dir_except(cls, dirs: list[str]): + """Removes all tracks not in the root directories.""" + to_remove = set() + + for track in cls.tracks: + if not track.folder.startswith(tuple(dirs)): + to_remove.add(track.folder) + + tdb.remove_tracks_by_folders(to_remove) + @classmethod def count_tracks_by_hash(cls, trackhash: str) -> int: """ @@ -197,6 +208,8 @@ class Store: """ Creates a list of folders from the tracks in the store. """ + cls.folders.clear() + all_folders = [track.folder for track in cls.tracks] all_folders = set(all_folders) @@ -212,6 +225,7 @@ class Store: cls.folders.append(folder) + @classmethod def get_folder(cls, path: str): # type: ignore """ @@ -277,6 +291,8 @@ class Store: Loads all albums from the database into the store. """ + cls.albums = [] + albumhashes = set(t.albumhash for t in cls.tracks) for albumhash in tqdm(albumhashes, desc="Loading albums"): @@ -291,9 +307,9 @@ class Store: albumhash = album[1] colors = json.loads(album[2]) - for al in cls.albums: - if al.albumhash == albumhash: - al.set_colors(colors) + for _al in cls.albums: + if _al.albumhash == albumhash: + _al.set_colors(colors) break @classmethod diff --git a/app/lib/colorlib.py b/app/lib/colorlib.py index 43241aea..e3609c68 100644 --- a/app/lib/colorlib.py +++ b/app/lib/colorlib.py @@ -38,9 +38,13 @@ class ProcessAlbumColors: """ def __init__(self) -> None: + db_colors = db.get_all_albums() + db_albumhashes = "-".join([album[1] for album in db_colors]) + + albums = [a for a in Store.albums if a.albumhash not in db_albumhashes] with SQLiteManager() as cur: - for album in tqdm(Store.albums, desc="Processing album colors"): + for album in tqdm(albums, desc="Processing unprocessed album colors"): if len(album.colors) == 0: colors = self.process_color(album) @@ -71,9 +75,9 @@ class ProcessArtistColors: def __init__(self) -> None: db_colors: list[tuple] = list(adb.get_all_artists()) db_artisthashes = "-".join([artist[1] for artist in db_colors]) - all_artists = Store.artists + all_artists = [a for a in Store.artists if a.artisthash not in db_artisthashes] - for artist in tqdm(all_artists, desc="Processing artist colors"): + for artist in tqdm(all_artists, desc="Processing unprocessed artist colors"): if artist.artisthash not in db_artisthashes: self.process_color(artist) diff --git a/app/lib/folderslib.py b/app/lib/folderslib.py index 547ac75b..9062e0cf 100644 --- a/app/lib/folderslib.py +++ b/app/lib/folderslib.py @@ -1,5 +1,4 @@ import os -import pathlib from concurrent.futures import ThreadPoolExecutor from app.db.store import Store diff --git a/app/lib/populate.py b/app/lib/populate.py index ef1d99ef..3f767f78 100644 --- a/app/lib/populate.py +++ b/app/lib/populate.py @@ -25,7 +25,7 @@ class Populate: """ def __init__(self) -> None: - messages = { + text = { "root_unset": "The root directory is not set. Trying to scan the default directory: %s", "default_not_exists": "The directory: %s does not exist. Please open the app in your web browser to set the root directory.", "no_tracks": "No tracks found in: %s. Please open the app in your web browser to set the root directory.", @@ -40,17 +40,23 @@ class Populate: def_dir = "~/Music" if len(dirs_to_scan) == 0: - log.warning(messages["root_unset"], def_dir) + log.warning(text["root_unset"], def_dir) print("...") exists = os.path.exists(settings.MUSIC_DIR) if not exists: - log.warning(messages["default_not_exists"], def_dir) + log.warning(text["default_not_exists"], def_dir) return dirs_to_scan = [settings.MUSIC_DIR] + try: + if dirs_to_scan[0] == "$home": + dirs_to_scan = [settings.USER_HOME_DIR] + except IndexError: + pass + files = [] for _dir in dirs_to_scan: @@ -59,7 +65,7 @@ class Populate: untagged = self.filter_untagged(tracks, files) if initial_dirs_count == 0 and len(untagged) == 0: - log.warning(messages["no_tracks"], def_dir) + log.warning(text["no_tracks"], def_dir) return if initial_dirs_count == 0 and len(untagged) > 0: @@ -76,7 +82,7 @@ class Populate: settings.TCOLOR.ENDC, ) sdb.add_root_dirs(dirs_to_scan) - return + # return if len(untagged) == 0: log.info("All clear, no unread files.")