feat: exit the Populate function when another one is started

+ add test for the extract_fetured_artists_from_title function
This commit is contained in:
geoffrey45 2023-01-24 22:40:19 +03:00
parent 2ba1b0386e
commit af4221e0c7
10 changed files with 528 additions and 385 deletions

View File

@ -4,7 +4,7 @@ from app import settings
from app.logger import log
from app.lib import populate
from app.db.store import Store
from app.utils import background
from app.utils import background, get_random_str
from app.lib.watchdogg import Watcher as WatchDog
from app.db.sqlite.settings import SettingsSQLMethods as sdb
@ -17,21 +17,32 @@ def get_child_dirs(parent: str, children: list[str]):
return [_dir for _dir in children if _dir.startswith(parent) and _dir != parent]
@background
def rebuild_store(db_dirs: list[str]):
def reload_everything():
"""
Restarts the watchdog and rebuilds the music library.
Reloads all stores using the current database items
"""
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()
@background
def rebuild_store(db_dirs: list[str]):
"""
Restarts the watchdog and rebuilds the music library.
"""
log.info("Rebuilding library...")
Store.remove_tracks_by_dir_except(db_dirs)
reload_everything()
key = get_random_str()
try:
populate.Populate(key=key)
except populate.PopulateCancelledError:
reload_everything()
return
WatchDog().restart()
log.info("Rebuilding library... ✅")

View File

@ -8,7 +8,7 @@ from requests import ReadTimeout
from app import utils
from app.lib.artistlib import CheckArtistImages
from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors
from app.lib.populate import Populate, ProcessTrackThumbnails
from app.lib.populate import Populate, ProcessTrackThumbnails, PopulateCancelledError
from app.lib.trackslib import validate_tracks
from app.logger import log
@ -23,8 +23,11 @@ def run_periodic_checks():
validate_tracks()
while True:
try:
Populate(key=utils.get_random_str())
except PopulateCancelledError:
pass
Populate()
ProcessTrackThumbnails()
ProcessAlbumColors()
ProcessArtistColors()

View File

@ -16,6 +16,12 @@ from app.utils import run_fast_scandir
get_all_tracks = SQLiteTrackMethods.get_all_tracks
insert_many_tracks = SQLiteTrackMethods.insert_many_tracks
POPULATE_KEY = ""
class PopulateCancelledError(Exception):
pass
class Populate:
"""
@ -25,7 +31,10 @@ class Populate:
also checks if the album art exists in the image path, if not tries to extract it.
"""
def __init__(self) -> None:
def __init__(self, key: str) -> None:
global POPULATE_KEY
POPULATE_KEY = key
tracks = get_all_tracks()
tracks = list(tracks)
@ -54,7 +63,7 @@ class Populate:
log.info("All clear, no unread files.")
return
self.tag_untagged(untagged)
self.tag_untagged(untagged, key)
@staticmethod
def filter_untagged(tracks: list[Track], files: list[str]):
@ -62,7 +71,7 @@ class Populate:
return set(files) - set(tagged_files)
@staticmethod
def tag_untagged(untagged: set[str]):
def tag_untagged(untagged: set[str], key: str):
log.info("Found %s new tracks", len(untagged))
tagged_tracks: list[dict] = []
tagged_count = 0
@ -71,6 +80,9 @@ class Populate:
fav_tracks = "-".join([t[1] for t in fav_tracks])
for file in tqdm(untagged, desc="Reading files"):
if POPULATE_KEY != key:
raise PopulateCancelledError('Populate key changed')
tags = get_tags(file)
if tags is not None:

View File

@ -40,7 +40,6 @@ class Watcher:
while trials < 10:
try:
dirs = sdb.get_root_dirs()
print(dirs)
dir_map = [
{"original": d, "realpath": os.path.realpath(d)} for d in dirs
]

View File

@ -1,7 +1,9 @@
"""
This module contains mini functions for the server.
"""
import random
import re
import string
from pathlib import Path
from datetime import datetime
@ -32,16 +34,19 @@ def background(func):
return background_func
def run_fast_scandir(__dir: str, full=False) -> tuple[list[str], list[str]]:
def run_fast_scandir(_dir: str, full=False) -> tuple[list[str], list[str]]:
"""
Scans a directory for files with a specific extension. Returns a list of files and folders in the directory.
"""
if _dir == "":
return [], []
subfolders = []
files = []
try:
for _files in os.scandir(__dir):
for _files in os.scandir(_dir):
if _files.is_dir() and not _files.name.startswith("."):
subfolders.append(_files.path)
if _files.is_file():
@ -54,7 +59,7 @@ def run_fast_scandir(__dir: str, full=False) -> tuple[list[str], list[str]]:
sub_dirs, _files = run_fast_scandir(_dir, full=True)
subfolders.extend(sub_dirs)
files.extend(_files)
except PermissionError:
except (PermissionError, FileNotFoundError, ValueError):
return [], []
return subfolders, files
@ -177,13 +182,11 @@ def get_artists_from_tracks(tracks: list[models.Track]) -> set[str]:
def get_albumartists(albums: list[models.Album]) -> set[str]:
artists = set()
# master_artist_list = [a.albumartists for a in albums]
for album in albums:
albumartists = [a.name for a in album.albumartists] # type: ignore
artists.update(albumartists)
# return [models.Artist(a) for a in artists]
return artists
@ -231,7 +234,10 @@ def get_home_res_path(filename: str):
"""
Returns a path to resources in the home directory of this project. Used to resolve resources in builds.
"""
try:
return (CWD / ".." / filename).resolve()
except ValueError:
return None
def get_ip():
@ -258,9 +264,6 @@ def split_artists(src: str):
return [a.strip() for a in artists]
def extract_featured_artists_from_title(title: str) -> list[str]:
"""
Extracts featured artists from a song title using regex.
@ -276,3 +279,8 @@ def extract_featured_artists_from_title(title: str) -> list[str]:
return artists
def get_random_str(length=5):
"""
Generates a random string of length `length`.
"""
return "".join(random.choices(string.ascii_letters + string.digits, k=length))

750
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,21 +9,24 @@ python = ">=3.10,<3.12"
Flask = "^2.0.2"
Flask-Cors = "^3.0.10"
requests = "^2.27.1"
watchdog = "^2.2.0"
watchdog = "^2.2.1"
gunicorn = "^20.1.0"
Pillow = "^9.0.1"
"colorgram.py" = "^1.2.0"
tqdm = "^4.64.0"
rapidfuzz = "^2.13.7"
tinytag = "^1.8.1"
hypothesis = "^6.56.3"
pytest = "^7.1.3"
Unidecode = "^1.3.6"
pyinstaller = "^5.7.0"
[tool.poetry.dev-dependencies]
pylint = "^2.15.5"
black = {version = "^22.6.0", allow-prereleases = true}
pytest = "^7.1.3"
hypothesis = "^6.56.3"
pyinstaller = "^5.7.0"
[tool.poetry.dev-dependencies.black]
version = "^22.6.0"
allow-prereleases = true
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@ -5,21 +5,5 @@ gpath=$(poetry run which gunicorn)
# $pytest # -q
while getopts ':s' opt; do
case $opt in
s)
echo "Starting image server"
cd "./app"
"$gpath" -b 0.0.0.0:1971 -w 1 --threads=1 "imgserver:app" &
cd ../
echo "Done ✅"
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
echo "Starting swing"
"$gpath" -b 0.0.0.0:1970 --threads=2 "manage:create_api()"

View File

@ -1,18 +0,0 @@
from app.utils import extract_featured_artists_from_title
def test_extract_featured_artists_from_title():
test_titles = [
"Own it (Featuring Ed Sheeran & Stormzy)",
"Godzilla (Deluxe)(Feat. Juice Wrld)(Deluxe)",
"Simmer (with Burna Boy)",
]
expected_test_artists = [
["Ed Sheeran", "Stormzy"],
['Juice Wrld'],
["Burna Boy"]
]
for title, expected in zip(test_titles, expected_test_artists):
assert extract_featured_artists_from_title(title) == expected

33
tests/test_utils.py Normal file
View File

@ -0,0 +1,33 @@
import app.utils
from hypothesis import given, strategies as st
from app.utils import extract_featured_artists_from_title
def test_extract_featured_artists_from_title():
test_titles = [
"Own it (Featuring Ed Sheeran & Stormzy)",
"Autograph (On my line)(Feat. Lil Peep)(Deluxe)",
"Why so sad? (with Juice Wrld, Lil Peep)",
"Why so sad? (with Juice Wrld/Lil Peep)",
"Simmer (with Burna Boy)",
"Simmer (without Burna Boy)"
]
results = [
["Ed Sheeran", "Stormzy"],
['Lil Peep'],
["Juice Wrld", "Lil Peep"],
["Juice Wrld", "Lil Peep"],
["Burna Boy"],
[]
]
for title, expected in zip(test_titles, results):
assert extract_featured_artists_from_title(title) == expected
# === HYPOTHESIS GHOSTWRITER TESTS ===
@given(__dir=st.text(), full=st.booleans())
def test_fuzz_run_fast_scandir(__dir: str, full) -> None:
app.utils.run_fast_scandir(_dir=__dir, full=full)