From 4e6e1f03dcb521f8278aae1d4bd8b21731c2b030 Mon Sep 17 00:00:00 2001 From: geoffrey45 Date: Sat, 21 Jan 2023 18:07:20 +0300 Subject: [PATCH] move imgserver to app/api folder + add sqlite methods to configure custom root directories + add sqlite.settings module + remove date and app name from logger messages + add api route to browse directories --- app/api/__init__.py | 16 +++- app/api/folder.py | 27 ++++++ .../__init__.py => api/imgserver.py} | 2 +- app/api/settings.py | 61 +++++++++++++ app/db/sqlite/settings.py | 85 +++++++++++++------ app/db/sqlite/tracks.py | 2 + app/lib/populate.py | 14 ++- app/logger.py | 2 +- manage.py | 1 - 9 files changed, 179 insertions(+), 31 deletions(-) rename app/{imgserver/__init__.py => api/imgserver.py} (97%) create mode 100644 app/api/settings.py diff --git a/app/api/__init__.py b/app/api/__init__.py index 5e7a5c7..3d662b2 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -5,8 +5,17 @@ This module combines all API blueprints into a single Flask app instance. from flask import Flask from flask_cors import CORS -from app.api import album, artist, favorites, folder, playlist, search, track -from app.imgserver import imgbp as imgserver +from app.api import ( + album, + artist, + favorites, + folder, + playlist, + search, + track, + settings, + imgserver, +) def create_api(): @@ -25,6 +34,7 @@ def create_api(): app.register_blueprint(folder.folderbp) app.register_blueprint(playlist.playlistbp) app.register_blueprint(favorites.favbp) - app.register_blueprint(imgserver) + app.register_blueprint(imgserver.imgbp) + app.register_blueprint(settings.settingsbp) return app diff --git a/app/api/folder.py b/app/api/folder.py index 88ac6cb..1cdcb05 100644 --- a/app/api/folder.py +++ b/app/api/folder.py @@ -1,11 +1,13 @@ """ Contains all the folder routes. """ +import os from flask import Blueprint, request from app import settings from app.lib.folderslib import GetFilesAndDirs + folderbp = Blueprint("folder", __name__, url_prefix="/") @@ -30,3 +32,28 @@ def get_folder_tree(): "tracks": tracks, "folders": sorted(folders, key=lambda i: i.name), } + + +@folderbp.route("/folder/dir-browser", methods=["POST"]) +def list_folders(): + """ + Returns a list of all the folders in the given folder. + """ + data = request.get_json() + + try: + req_dir: str = data["folder"] + except KeyError: + req_dir = settings.HOME_DIR + + if req_dir == "$home": + req_dir = settings.HOME_DIR + + entries = os.scandir(req_dir) + + dirs = [e.name for e in entries if e.is_dir() and not e.name.startswith(".")] + dirs = [{"name": d, "path": os.path.join(req_dir, d)} for d in dirs] + + return { + "folders": sorted(dirs, key=lambda i: i["name"]), + } diff --git a/app/imgserver/__init__.py b/app/api/imgserver.py similarity index 97% rename from app/imgserver/__init__.py rename to app/api/imgserver.py index 228ac36..ee582ae 100644 --- a/app/imgserver/__init__.py +++ b/app/api/imgserver.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from flask import Blueprint, request, send_from_directory +from flask import Blueprint, send_from_directory imgbp = Blueprint("imgserver", __name__, url_prefix="/img") SUPPORTED_IMAGES = (".jpg", ".png", ".webp", ".jpeg") diff --git a/app/api/settings.py b/app/api/settings.py new file mode 100644 index 0000000..840d715 --- /dev/null +++ b/app/api/settings.py @@ -0,0 +1,61 @@ +from flask import Blueprint, request +from app.db.sqlite.settings import SettingsSQLMethods as sdb + +settingsbp = Blueprint("settings", __name__, url_prefix="/") + + +@settingsbp.route("/settings/add-root-dirs", methods=["POST"]) +def add_root_dirs(): + """ + Add custom root directories to the database. + """ + msg = {"msg": "Failed! No directories were given."} + + data = request.get_json() + + if data is None: + return msg, 400 + + try: + new_dirs = data["new_dirs"] + removed_dirs = data["removed"] + except KeyError: + return msg, 400 + + sdb.add_root_dirs(new_dirs) + sdb.remove_root_dirs(removed_dirs) + + return {"msg": "Added root directories to the database."} + + +@settingsbp.route("/settings/get-root-dirs", methods=["GET"]) +def get_root_dirs(): + """ + Get custom root directories from the database. + """ + dirs = sdb.get_root_dirs() + + return {"dirs": dirs} + + +# CURRENTLY UNUSED ROUTE 👇 +@settingsbp.route("/settings/remove-root-dirs", methods=["POST"]) +def remove_root_dirs(): + """ + Remove custom root directories from the database. + """ + msg = {"msg": "Failed! No directories were given."} + + data = request.get_json() + + if data is None: + return msg, 400 + + try: + dirs = data["dirs"] + except KeyError: + return msg, 400 + + sdb.remove_root_dirs(dirs) + + return {"msg": "Removed root directories from the database."} diff --git a/app/db/sqlite/settings.py b/app/db/sqlite/settings.py index b31041a..901aff1 100644 --- a/app/db/sqlite/settings.py +++ b/app/db/sqlite/settings.py @@ -7,44 +7,81 @@ class SettingsSQLMethods: Methods for interacting with the settings table. """ - @staticmethod - def update_root_dirs(dirs: list[str]): - """ - Updates custom root directories in the database. - """ - - sql = "UPDATE settings SET root_dirs = ?" - dirs_str = json.dumps(dirs) - - with SQLiteManager(userdata_db=True) as cur: - cur.execute(sql, (dirs_str,)) - @staticmethod def get_root_dirs() -> list[str]: """ Gets custom root directories from the database. """ - sql = "SELECT value FROM settings" + sql = "SELECT root_dirs FROM settings" with SQLiteManager(userdata_db=True) as cur: cur.execute(sql) + dirs = cur.fetchall() - data = cur.fetchone() - - if data is not None: - return json.loads(data[0]) - - return [] + return [dir[0] for dir in dirs] @staticmethod - def update_exclude_dirs(dirs: list[str]): + def add_root_dirs(dirs: list[str]): """ - Updates excluded directories in the database. + Add custom root directories to the database. """ - sql = "UPDATE settings SET exclude_dirs = ?" - dirs_str = json.dumps(dirs) + sql = "INSERT INTO settings (root_dirs) VALUES (?)" + existing_dirs = SettingsSQLMethods.get_root_dirs() + + dirs = [dir for dir in dirs if dir not in existing_dirs] + + if len(dirs) == 0: + return with SQLiteManager(userdata_db=True) as cur: - cur.execute(sql, (dirs_str,)) + for _dir in dirs: + cur.execute(sql, (_dir,)) + + @staticmethod + def remove_root_dirs(dirs: list[str]): + """ + Remove custom root directories from the database. + """ + + sql = "DELETE FROM settings WHERE root_dirs = ?" + + with SQLiteManager(userdata_db=True) as cur: + for _dir in dirs: + cur.execute(sql, (_dir,)) + + @staticmethod + def add_excluded_dirs(dirs: list[str]): + """ + Add custom exclude directories to the database. + """ + + sql = "INSERT INTO settings (exclude_dirs) VALUES (?)" + + with SQLiteManager(userdata_db=True) as cur: + cur.executemany(sql, dirs) + + @staticmethod + def remove_excluded_dirs(dirs: list[str]): + """ + Remove custom exclude directories from the database. + """ + + sql = "DELETE FROM settings WHERE exclude_dirs = ?" + + with SQLiteManager(userdata_db=True) as cur: + cur.executemany(sql, dirs) + + @staticmethod + def get_excluded_dirs() -> list[str]: + """ + Gets custom exclude directories from the database. + """ + + sql = "SELECT exclude_dirs FROM settings" + + with SQLiteManager(userdata_db=True) as cur: + cur.execute(sql) + dirs = cur.fetchall() + return [dir[0] for dir in dirs] diff --git a/app/db/sqlite/tracks.py b/app/db/sqlite/tracks.py index d83961c..32abcab 100644 --- a/app/db/sqlite/tracks.py +++ b/app/db/sqlite/tracks.py @@ -61,6 +61,8 @@ class SQLiteTrackMethods: ), ) + # TODO: rewrite the above code using an ordered dict and destructuring + @classmethod def insert_many_tracks(cls, tracks: list[dict]): """ diff --git a/app/lib/populate.py b/app/lib/populate.py index d542075..7a6fce5 100644 --- a/app/lib/populate.py +++ b/app/lib/populate.py @@ -3,6 +3,7 @@ from tqdm import tqdm from app import settings from app.db.sqlite.tracks import SQLiteTrackMethods +from app.db.sqlite.settings import SettingsSQLMethods as sdb from app.db.store import Store from app.lib.taglib import extract_thumb, get_tags @@ -27,7 +28,18 @@ class Populate: tracks = get_all_tracks() tracks = list(tracks) - files = run_fast_scandir(settings.HOME_DIR, full=True)[1] + dirs_to_scan = sdb.get_root_dirs() + + if len(dirs_to_scan) == 0: + log.error( + "The root directory is not set. No folders will be scanned for music files. Open the app in your web browser to configure." + ) + return + + files = [] + + for _dir in dirs_to_scan: + files.extend(run_fast_scandir(_dir, full=True)[1]) untagged = self.filter_untagged(tracks, files) diff --git a/app/logger.py b/app/logger.py index aec7263..9a82b2b 100644 --- a/app/logger.py +++ b/app/logger.py @@ -18,7 +18,7 @@ class CustomFormatter(logging.Formatter): # format = ( # "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" # ) - format_ = "[%(asctime)s]@%(name)s • %(message)s" + format_ = "%(message)s" FORMATS = { logging.DEBUG: grey + format_ + reset, diff --git a/manage.py b/manage.py index 549081a..d9eef36 100644 --- a/manage.py +++ b/manage.py @@ -186,6 +186,5 @@ if __name__ == "__main__": use_reloader=False, ) -# TODO: Find out how to print in color: red for errors, etc. # TODO: Find a way to verify the host string # TODO: Organize code in this file: move args to new file, etc.