diff --git a/bump_version.sh b/bump_version.sh index b10ea61..c06a17a 100755 --- a/bump_version.sh +++ b/bump_version.sh @@ -9,7 +9,7 @@ message="$2" # Fail if the branch is dirty. git diff --exit-code -echo "$version" > "$ROSE_ROOT/rose_core/.version" +echo "$version" > "$ROSE_ROOT/rose_lib/.version" git add . git commit -am "[Release] v$version - $message" git tag "$version" HEAD -m "v$version" diff --git a/conftest.py b/conftest.py index 584f0f2..7c33175 100644 --- a/conftest.py +++ b/conftest.py @@ -9,10 +9,10 @@ import pytest from click.testing import CliRunner -from rose_core.cache import CACHE_SCHEMA_PATH, process_string_for_fts, update_cache -from rose_core.common import VERSION -from rose_core.config import Config -from rose_core.templates import PathTemplateConfig +from rose_lib.cache import CACHE_SCHEMA_PATH, process_string_for_fts, update_cache +from rose_lib.common import VERSION +from rose_lib.config import Config +from rose_lib.templates import PathTemplateConfig logger = logging.getLogger(__name__) diff --git a/flake.nix b/flake.nix index 7359aa2..9560b95 100644 --- a/flake.nix +++ b/flake.nix @@ -78,7 +78,7 @@ packages = rec { rose = python.pkgs.buildPythonPackage { pname = "rose"; - version = nixpkgs.lib.strings.removeSuffix "\n" (builtins.readFile ./rose_core/.version); + version = nixpkgs.lib.strings.removeSuffix "\n" (builtins.readFile ./rose_lib/.version); src = ./.; propagatedBuildInputs = prod-deps; doCheck = false; diff --git a/rose_cli/__init__.py b/rose_cli/__init__.py new file mode 100644 index 0000000..b4ec019 --- /dev/null +++ b/rose_cli/__init__.py @@ -0,0 +1,3 @@ +from rose_lib import initialize_logging + +initialize_logging() diff --git a/rose_core/__main__.py b/rose_cli/__main__.py similarity index 78% rename from rose_core/__main__.py rename to rose_cli/__main__.py index ca00148..5b692f9 100644 --- a/rose_core/__main__.py +++ b/rose_cli/__main__.py @@ -2,8 +2,8 @@ import click -from rose_core.cli import cli -from rose_core.common import RoseExpectedError +from rose_cli.cli import cli +from rose_lib import RoseExpectedError def main() -> None: diff --git a/rose_core/cli.py b/rose_cli/cli.py similarity index 88% rename from rose_core/cli.py rename to rose_cli/cli.py index 7b55cb4..be8e5a2 100644 --- a/rose_core/cli.py +++ b/rose_cli/cli.py @@ -8,14 +8,15 @@ import os import signal import subprocess +import uuid from dataclasses import dataclass from multiprocessing import Process from pathlib import Path import click -from rose_core.common import VERSION, RoseExpectedError -from rose_core.config import Config +from rose_lib.common import VERSION, RoseExpectedError +from rose_lib.config import Config logger = logging.getLogger(__name__) @@ -43,7 +44,7 @@ class Context: @click.pass_context def cli(cc: click.Context, verbose: bool, config: Path | None = None) -> None: """A music manager with a virtual filesystem.""" - from rose_core.cache import maybe_invalidate_cache_database + from rose_lib.cache import maybe_invalidate_cache_database cc.obj = Context( config=Config.parse(config_path_override=config), @@ -76,7 +77,7 @@ def generate_completion(shell: str) -> None: @click.pass_obj def preview_templates(ctx: Context) -> None: """Preview the configured path templates with sample data.""" - from rose_core.templates import preview_path_templates + from rose_lib.templates import preview_path_templates preview_path_templates(ctx.config) @@ -91,7 +92,7 @@ def cache() -> None: @click.pass_obj def update(ctx: Context, force: bool) -> None: """Synchronize the read cache with new changes in the source directory.""" - from rose_core.cache import update_cache + from rose_lib.cache import update_cache update_cache(ctx.config, force) @@ -101,7 +102,7 @@ def update(ctx: Context, force: bool) -> None: @click.pass_obj def watch(ctx: Context, foreground: bool) -> None: """Start a watchdog to auto-update the cache when the source directory changes.""" - from rose_core.watcher import start_watchdog + from rose_lib.watcher import start_watchdog if not foreground: daemonize(pid_path=ctx.config.watchdog_pid_path) @@ -136,8 +137,8 @@ def fs() -> None: @click.pass_obj def mount(ctx: Context, foreground: bool) -> None: """Mount the virtual filesystem.""" - from rose_core.cache import update_cache - from rose_core.virtualfs import mount_virtualfs + from rose_lib.cache import update_cache + from rose_vfs import mount_virtualfs if not foreground: daemonize() @@ -157,7 +158,7 @@ def mount(ctx: Context, foreground: bool) -> None: @click.pass_obj def unmount(ctx: Context) -> None: """Unmount the virtual filesystem.""" - from rose_core.virtualfs import unmount_virtualfs + from rose_vfs import unmount_virtualfs unmount_virtualfs(ctx.config) @@ -173,7 +174,7 @@ def releases() -> None: @click.pass_obj def print_release(ctx: Context, release: str) -> None: """Print a single release (in JSON). Accepts a release's UUID/path.""" - from rose_core.releases import dump_release + from rose_lib.releases import dump_release release = parse_release_argument(release) click.echo(dump_release(ctx.config, release)) @@ -184,8 +185,8 @@ def print_release(ctx: Context, release: str) -> None: @click.pass_obj def print_all_releases(ctx: Context, matcher: str | None) -> None: """Print all releases (in JSON). Accepts an optional rules matcher to filter the releases.""" - from rose_core.releases import dump_all_releases - from rose_core.rule_parser import MetadataMatcher + from rose_lib.releases import dump_all_releases + from rose_lib.rule_parser import MetadataMatcher parsed_matcher = MetadataMatcher.parse(matcher) if matcher else None click.echo(dump_all_releases(ctx.config, parsed_matcher)) @@ -197,7 +198,7 @@ def print_all_releases(ctx: Context, matcher: str | None) -> None: @click.pass_obj def edit_release(ctx: Context, release: str, resume: Path | None) -> None: """Edit a release's metadata in $EDITOR. Accepts a release's UUID/path.""" - from rose_core.releases import edit_release + from rose_lib.releases import edit_release release = parse_release_argument(release) edit_release(ctx.config, release, resume_file=resume) @@ -208,7 +209,7 @@ def edit_release(ctx: Context, release: str, resume: Path | None) -> None: @click.pass_obj def toggle_new(ctx: Context, release: str) -> None: """Toggle a release's "new"-ness. Accepts a release's UUID/path.""" - from rose_core.releases import toggle_release_new + from rose_lib.releases import toggle_release_new release = parse_release_argument(release) toggle_release_new(ctx.config, release) @@ -222,7 +223,7 @@ def delete_release(ctx: Context, release: str) -> None: Delete a release from the library. The release is moved to the trash bin, following the freedesktop spec. Accepts a release's UUID/path. """ - from rose_core.releases import delete_release + from rose_lib.releases import delete_release release = parse_release_argument(release) delete_release(ctx.config, release) @@ -234,7 +235,7 @@ def delete_release(ctx: Context, release: str) -> None: @click.pass_obj def set_cover_release(ctx: Context, release: str, cover: Path) -> None: """Set/replace the cover art of a release. Accepts a release's UUID/path.""" - from rose_core.releases import set_release_cover_art + from rose_lib.releases import set_release_cover_art release = parse_release_argument(release) set_release_cover_art(ctx.config, release, cover) @@ -245,7 +246,7 @@ def set_cover_release(ctx: Context, release: str, cover: Path) -> None: @click.pass_obj def delete_cover_release(ctx: Context, release: str) -> None: """Delete the cover art of a release.""" - from rose_core.releases import delete_release_cover_art + from rose_lib.releases import delete_release_cover_art release = parse_release_argument(release) delete_release_cover_art(ctx.config, release) @@ -259,8 +260,8 @@ def delete_cover_release(ctx: Context, release: str) -> None: @click.pass_obj def run_rule(ctx: Context, release: str, actions: list[str], dry_run: bool, yes: bool) -> None: """Run rule engine actions on all tracks in a release. Accepts a release's UUID/path.""" - from rose_core.releases import run_actions_on_release - from rose_core.rule_parser import MetadataAction + from rose_lib.releases import run_actions_on_release + from rose_lib.rule_parser import MetadataAction release = parse_release_argument(release) parsed_actions = [MetadataAction.parse(a) for a in actions] @@ -281,7 +282,7 @@ def create_single(ctx: Context, track_path: Path) -> None: Create a single release for the given track, and copy the track into it. Only accepts a track path. """ - from rose_core.releases import create_single_release + from rose_lib.releases import create_single_release create_single_release(ctx.config, track_path) @@ -296,7 +297,7 @@ def tracks() -> None: @click.pass_obj def print_track(ctx: Context, track: str) -> None: """Print a single track (in JSON). Accepts a tracks's UUID/path.""" - from rose_core.tracks import dump_track + from rose_lib.tracks import dump_track track = parse_track_argument(track) click.echo(dump_track(ctx.config, track)) @@ -307,8 +308,8 @@ def print_track(ctx: Context, track: str) -> None: @click.pass_obj def print_all_track(ctx: Context, matcher: str | None = None) -> None: """Print all tracks (in JSON). Accepts an optional rules matcher to filter the tracks.""" - from rose_core.rule_parser import MetadataMatcher - from rose_core.tracks import dump_all_tracks + from rose_lib.rule_parser import MetadataMatcher + from rose_lib.tracks import dump_all_tracks parsed_matcher = MetadataMatcher.parse(matcher) if matcher else None click.echo(dump_all_tracks(ctx.config, parsed_matcher)) @@ -322,8 +323,8 @@ def print_all_track(ctx: Context, matcher: str | None = None) -> None: @click.pass_obj def run_rule_track(ctx: Context, track: str, actions: list[str], dry_run: bool, yes: bool) -> None: """Run rule engine actions on a single track. Accepts a track's UUID/path.""" - from rose_core.rule_parser import MetadataAction - from rose_core.tracks import run_actions_on_track + from rose_lib.rule_parser import MetadataAction + from rose_lib.tracks import run_actions_on_track track = parse_track_argument(track) parsed_actions = [MetadataAction.parse(a) for a in actions] @@ -346,7 +347,7 @@ def collages() -> None: @click.pass_obj def create(ctx: Context, name: str) -> None: """Create a new collage.""" - from rose_core.collages import create_collage + from rose_lib.collages import create_collage create_collage(ctx.config, name) @@ -357,7 +358,7 @@ def create(ctx: Context, name: str) -> None: @click.pass_obj def rename(ctx: Context, old_name: str, new_name: str) -> None: """Rename a collage.""" - from rose_core.collages import rename_collage + from rose_lib.collages import rename_collage rename_collage(ctx.config, old_name, new_name) @@ -367,7 +368,7 @@ def rename(ctx: Context, old_name: str, new_name: str) -> None: @click.pass_obj def delete(ctx: Context, collage: str) -> None: """Delete a collage.""" - from rose_core.collages import delete_collage + from rose_lib.collages import delete_collage delete_collage(ctx.config, collage) @@ -378,7 +379,7 @@ def delete(ctx: Context, collage: str) -> None: @click.pass_obj def add_release(ctx: Context, collage: str, release: str) -> None: """Add a release to a collage. Accepts a collage's name and a release's UUID/path.""" - from rose_core.collages import add_release_to_collage + from rose_lib.collages import add_release_to_collage release = parse_release_argument(release) add_release_to_collage(ctx.config, collage, release) @@ -390,7 +391,7 @@ def add_release(ctx: Context, collage: str, release: str) -> None: @click.pass_obj def remove_release(ctx: Context, collage: str, release: str) -> None: """Remove a release from a collage. Accepts a collage's name and a release's UUID/path.""" - from rose_core.collages import remove_release_from_collage + from rose_lib.collages import remove_release_from_collage release = parse_release_argument(release) remove_release_from_collage(ctx.config, collage, release) @@ -401,7 +402,7 @@ def remove_release(ctx: Context, collage: str, release: str) -> None: @click.pass_obj def edit(ctx: Context, collage: str) -> None: """Edit (reorder/remove releases from) a collage in $EDITOR. Accepts a collage's name.""" - from rose_core.collages import edit_collage_in_editor + from rose_lib.collages import edit_collage_in_editor edit_collage_in_editor(ctx.config, collage) @@ -411,7 +412,7 @@ def edit(ctx: Context, collage: str) -> None: @click.pass_obj def print_collage(ctx: Context, collage: str) -> None: """Print a collage (in JSON). Accepts a collage's name.""" - from rose_core.collages import dump_collage + from rose_lib.collages import dump_collage click.echo(dump_collage(ctx.config, collage)) @@ -420,7 +421,7 @@ def print_collage(ctx: Context, collage: str) -> None: @click.pass_obj def print_all_collages(ctx: Context) -> None: """Print all collages (in JSON).""" - from rose_core.collages import dump_all_collages + from rose_lib.collages import dump_all_collages click.echo(dump_all_collages(ctx.config)) @@ -435,7 +436,7 @@ def playlists() -> None: @click.pass_obj def create_playlist(ctx: Context, name: str) -> None: """Create a new playlist.""" - from rose_core.playlists import create_playlist + from rose_lib.playlists import create_playlist create_playlist(ctx.config, name) @@ -446,7 +447,7 @@ def create_playlist(ctx: Context, name: str) -> None: @click.pass_obj def rename_playlist(ctx: Context, old_name: str, new_name: str) -> None: """Rename a playlist. Accepts a playlist's name.""" - from rose_core.playlists import rename_playlist + from rose_lib.playlists import rename_playlist rename_playlist(ctx.config, old_name, new_name) @@ -456,7 +457,7 @@ def rename_playlist(ctx: Context, old_name: str, new_name: str) -> None: @click.pass_obj def delete_playlist(ctx: Context, playlist: str) -> None: """Delete a playlist. Accepts a playlist's name.""" - from rose_core.playlists import delete_playlist + from rose_lib.playlists import delete_playlist delete_playlist(ctx.config, playlist) @@ -467,7 +468,7 @@ def delete_playlist(ctx: Context, playlist: str) -> None: @click.pass_obj def add_track(ctx: Context, playlist: str, track: str) -> None: """Add a track to a playlist. Accepts a playlist name and a track's UUID/path.""" - from rose_core.playlists import add_track_to_playlist + from rose_lib.playlists import add_track_to_playlist track = parse_track_argument(track) add_track_to_playlist(ctx.config, playlist, track) @@ -479,7 +480,7 @@ def add_track(ctx: Context, playlist: str, track: str) -> None: @click.pass_obj def remove_track(ctx: Context, playlist: str, track: str) -> None: """Remove a track from a playlist. Accepts a playlist name and a track's UUID/path.""" - from rose_core.playlists import remove_track_from_playlist + from rose_lib.playlists import remove_track_from_playlist track = parse_track_argument(track) remove_track_from_playlist(ctx.config, playlist, track) @@ -493,7 +494,7 @@ def edit_playlist(ctx: Context, playlist: str) -> None: Edit a playlist in $EDITOR. Reorder lines to update the ordering of tracks. Delete lines to delete tracks from the playlist. """ - from rose_core.playlists import edit_playlist_in_editor + from rose_lib.playlists import edit_playlist_in_editor edit_playlist_in_editor(ctx.config, playlist) @@ -503,7 +504,7 @@ def edit_playlist(ctx: Context, playlist: str) -> None: @click.pass_obj def print_playlist(ctx: Context, playlist: str) -> None: """Print a playlist (in JSON). Accepts a playlist's name.""" - from rose_core.playlists import dump_playlist + from rose_lib.playlists import dump_playlist click.echo(dump_playlist(ctx.config, playlist)) @@ -512,7 +513,7 @@ def print_playlist(ctx: Context, playlist: str) -> None: @click.pass_obj def print_all_playlists(ctx: Context) -> None: """Print all playlists (in JSON).""" - from rose_core.playlists import dump_all_playlists + from rose_lib.playlists import dump_all_playlists click.echo(dump_all_playlists(ctx.config)) @@ -525,7 +526,7 @@ def set_cover_playlist(ctx: Context, playlist: str, cover: Path) -> None: """ Set the cover art of a playlist. Accepts a playlist name and a path to an image. """ - from rose_core.playlists import set_playlist_cover_art + from rose_lib.playlists import set_playlist_cover_art set_playlist_cover_art(ctx.config, playlist, cover) @@ -535,7 +536,7 @@ def set_cover_playlist(ctx: Context, playlist: str, cover: Path) -> None: @click.pass_obj def delete_cover_playlist(ctx: Context, playlist: str) -> None: """Delete the cover art of a playlist. Accepts a playlist name.""" - from rose_core.playlists import delete_playlist_cover_art + from rose_lib.playlists import delete_playlist_cover_art delete_playlist_cover_art(ctx.config, playlist) @@ -561,8 +562,8 @@ def run( ignore: list[str], ) -> None: """Run an ad hoc rule.""" - from rose_core.rule_parser import MetadataRule - from rose_core.rules import execute_metadata_rule + from rose_lib.rule_parser import MetadataRule + from rose_lib.rules import execute_metadata_rule if not actions: logger.info("No-Op: No actions passed") @@ -577,15 +578,14 @@ def run( @click.pass_obj def run_stored(ctx: Context, dry_run: bool, yes: bool) -> None: """Run the rules stored in the config.""" - from rose_core.rules import execute_stored_metadata_rules + from rose_lib.rules import execute_stored_metadata_rules execute_stored_metadata_rules(ctx.config, dry_run=dry_run, confirm_yes=not yes) def parse_release_argument(r: str) -> str: """Takes in a release argument and normalizes it to the release ID.""" - from rose_core.cache import STORED_DATA_FILE_REGEX - from rose_core.common import valid_uuid + from rose_lib.cache import STORED_DATA_FILE_REGEX if valid_uuid(r): logger.debug(f"Treating release argument {r} as UUID") @@ -615,8 +615,7 @@ def parse_release_argument(r: str) -> str: def parse_track_argument(t: str) -> str: """Takes in a track argument and normalizes it to the track ID.""" - from rose_core.audiotags import AudioTags, UnsupportedFiletypeError - from rose_core.common import valid_uuid + from rose_lib.audiotags import AudioTags, UnsupportedFiletypeError if valid_uuid(t): logger.debug(f"Treating track argument {t} as UUID") @@ -700,3 +699,11 @@ def daemonize(pid_path: Path | None = None) -> None: with pid_path.open("w") as fp: fp.write(str(pid)) os._exit(0) + + +def valid_uuid(x: str) -> bool: + try: + uuid.UUID(x) + return True + except ValueError: + return False diff --git a/rose_core/cli_test.py b/rose_cli/cli_test.py similarity index 96% rename from rose_core/cli_test.py rename to rose_cli/cli_test.py index 1cb28fe..ec853d5 100644 --- a/rose_core/cli_test.py +++ b/rose_cli/cli_test.py @@ -6,8 +6,7 @@ import pytest from click.testing import CliRunner -from rose_core.audiotags import AudioTags -from rose_core.cli import ( +from rose_cli.cli import ( Context, InvalidReleaseArgError, InvalidTrackArgError, @@ -18,8 +17,9 @@ unwatch, watch, ) -from rose_core.config import Config -from rose_core.virtualfs_test import start_virtual_fs +from rose_lib.audiotags import AudioTags +from rose_lib.config import Config +from rose_vfs.virtualfs_test import start_virtual_fs @pytest.mark.usefixtures("seeded_cache") diff --git a/rose_core/__init__.py b/rose_core/__init__.py deleted file mode 100644 index dddb610..0000000 --- a/rose_core/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -import logging -import logging.handlers -import os -import sys -from pathlib import Path - -import appdirs - -logger = logging.getLogger() -logger.setLevel(logging.INFO) - -# appdirs by default has Unix log to $XDG_CACHE_HOME, but I'd rather write logs to $XDG_STATE_HOME. -LOG_HOME = Path(appdirs.user_state_dir("rose")) -if appdirs.system == "darwin": - LOG_HOME = Path(appdirs.user_log_dir("rose")) - -LOG_HOME.mkdir(parents=True, exist_ok=True) -LOGFILE = LOG_HOME / "rose.log" - -# Useful for debugging problems with the virtual FS, since pytest doesn't capture that debug logging -# output. -LOG_EVEN_THOUGH_WERE_IN_TEST = os.environ.get("LOG_TEST", False) - -# Add a logging handler for stdout unless we are testing. Pytest -# captures logging output on its own, so by default, we do not attach our own. -if "pytest" not in sys.modules or LOG_EVEN_THOUGH_WERE_IN_TEST: # pragma: no cover - simple_formatter = logging.Formatter( - "[%(asctime)s] %(levelname)s: %(message)s", - datefmt="%H:%M:%S", - ) - verbose_formatter = logging.Formatter( - "[ts=%(asctime)s.%(msecs)03d] [pid=%(process)d] [src=%(name)s:%(lineno)s] %(levelname)s: %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - - stream_handler = logging.StreamHandler(sys.stderr) - stream_handler.setFormatter( - simple_formatter if not LOG_EVEN_THOUGH_WERE_IN_TEST else verbose_formatter - ) - logger.addHandler(stream_handler) - - file_handler = logging.handlers.RotatingFileHandler( - LOGFILE, - maxBytes=20 * 1024 * 1024, - backupCount=10, - ) - file_handler.setFormatter(verbose_formatter) - logger.addHandler(file_handler) diff --git a/rose_core/.version b/rose_lib/.version similarity index 100% rename from rose_core/.version rename to rose_lib/.version diff --git a/rose_lib/__init__.py b/rose_lib/__init__.py new file mode 100644 index 0000000..d772be9 --- /dev/null +++ b/rose_lib/__init__.py @@ -0,0 +1,221 @@ +import logging +import logging.handlers +import os +import sys +from pathlib import Path + +import appdirs + +from rose_lib.audiotags import ( + SUPPORTED_AUDIO_EXTENSIONS, + AudioTags, + UnsupportedFiletypeError, +) +from rose_lib.cache import ( + STORED_DATA_FILE_REGEX, + CachedRelease, + CachedTrack, + artist_exists, + calculate_release_logtext, + calculate_track_logtext, + collage_exists, + genre_exists, + get_collage, + get_path_of_track_in_playlist, + get_playlist, + get_playlist_cover_path, + get_release, + get_track, + get_tracks_associated_with_release, + label_exists, + list_artists, + list_collages, + list_genres, + list_labels, + list_playlists, + maybe_invalidate_cache_database, + playlist_exists, + update_cache, + update_cache_for_releases, +) +from rose_lib.collages import ( + add_release_to_collage, + create_collage, + delete_collage, + dump_all_collages, + dump_collage, + edit_collage_in_editor, + remove_release_from_collage, + rename_collage, +) +from rose_lib.common import ( + VERSION, + RoseError, + RoseExpectedError, + sanitize_dirname, + sanitize_filename, +) +from rose_lib.config import Config +from rose_lib.playlists import ( + add_track_to_playlist, + create_playlist, + delete_playlist, + delete_playlist_cover_art, + dump_all_playlists, + dump_playlist, + edit_playlist_in_editor, + remove_track_from_playlist, + rename_playlist, + set_playlist_cover_art, +) +from rose_lib.releases import ( + create_single_release, + delete_release, + delete_release_cover_art, + dump_all_releases, + dump_release, + edit_release, + run_actions_on_release, + set_release_cover_art, + toggle_release_new, +) +from rose_lib.rule_parser import MetadataAction, MetadataMatcher, MetadataRule +from rose_lib.rules import execute_metadata_rule, execute_stored_metadata_rules +from rose_lib.templates import ( + PathTemplate, + eval_release_template, + eval_track_template, + preview_path_templates, +) +from rose_lib.tracks import dump_all_tracks, dump_track, run_actions_on_track +from rose_lib.watcher import start_watchdog + +__all__ = [ + "AudioTags", + "CachedRelease", + "CachedTrack", + "Config", + "MetadataAction", + "MetadataMatcher", + "MetadataRule", + "PathTemplate", + "RoseError", + "RoseExpectedError", + "STORED_DATA_FILE_REGEX", # TODO: Revise: is_release_directory / is_track_file + "SUPPORTED_AUDIO_EXTENSIONS", + "UnsupportedFiletypeError", + "VERSION", + "add_release_to_collage", + "add_track_to_playlist", + "artist_exists", + "calculate_release_logtext", # TODO: Rename. + "calculate_track_logtext", # TODO: Rename. + "collage_exists", + "create_collage", + "create_playlist", + "create_single_release", + "delete_collage", + "delete_playlist", + "delete_playlist_cover_art", + "delete_release", + "delete_release_cover_art", + "dump_all_collages", + "dump_all_playlists", + "dump_all_releases", + "dump_all_tracks", + "dump_collage", + "dump_playlist", + "dump_release", + "dump_track", + "edit_collage_in_editor", # TODO: Move editor part to CLI, make this file-submissions. + "edit_playlist_in_editor", # TODO: Move editor part to CLI, make this file-submissions. + "edit_release", + "eval_release_template", # TODO: Rename. + "eval_track_template", # TODO: Rename. + "execute_metadata_rule", + "execute_stored_metadata_rules", + "genre_exists", + "get_collage", + "get_path_of_track_in_playlist", # TODO: Redesign. + "get_playlist", + "get_playlist_cover_path", # TODO: Remove. + "get_release", + "get_track", + "get_tracks_associated_with_release", # TODO: Rename: `get_tracks_of_release` / `dump_release(with_tracks=tracks)` + "label_exists", + "list_artists", + "list_collages", + "list_genres", + "list_labels", + "list_playlists", + "maybe_invalidate_cache_database", + "playlist_exists", + "preview_path_templates", + "remove_release_from_collage", + "remove_track_from_playlist", + "rename_collage", + "rename_playlist", + "run_actions_on_release", + "run_actions_on_track", + "sanitize_dirname", + "sanitize_filename", + "set_playlist_cover_art", + "set_release_cover_art", + "start_watchdog", + "toggle_release_new", + "update_cache", + "update_cache_for_releases", +] + +__logging_initialized = False + + +def initialize_logging() -> None: + global __logging_initialized + if __logging_initialized: + return + __logging_initialized = True + + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + # appdirs by default has Unix log to $XDG_CACHE_HOME, but I'd rather write logs to $XDG_STATE_HOME. + log_home = Path(appdirs.user_state_dir("rose")) + if appdirs.system == "darwin": + log_home = Path(appdirs.user_log_dir("rose")) + + log_home.mkdir(parents=True, exist_ok=True) + log_file = log_home / "rose.log" + + # Useful for debugging problems with the virtual FS, since pytest doesn't capture that debug logging + # output. + log_despite_testing = os.environ.get("LOG_TEST", False) + + # Add a logging handler for stdout unless we are testing. Pytest + # captures logging output on its own, so by default, we do not attach our own. + if "pytest" not in sys.modules or log_despite_testing: # pragma: no cover + simple_formatter = logging.Formatter( + "[%(asctime)s] %(levelname)s: %(message)s", + datefmt="%H:%M:%S", + ) + verbose_formatter = logging.Formatter( + "[ts=%(asctime)s.%(msecs)03d] [pid=%(process)d] [src=%(name)s:%(lineno)s] %(levelname)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + stream_handler = logging.StreamHandler(sys.stderr) + stream_handler.setFormatter( + simple_formatter if not log_despite_testing else verbose_formatter + ) + logger.addHandler(stream_handler) + + file_handler = logging.handlers.RotatingFileHandler( + log_file, + maxBytes=20 * 1024 * 1024, + backupCount=10, + ) + file_handler.setFormatter(verbose_formatter) + logger.addHandler(file_handler) + + +initialize_logging() diff --git a/rose_core/audiotags.py b/rose_lib/audiotags.py similarity index 99% rename from rose_core/audiotags.py rename to rose_lib/audiotags.py index fa6847c..d7856f5 100644 --- a/rose_core/audiotags.py +++ b/rose_lib/audiotags.py @@ -25,7 +25,7 @@ import mutagen.oggopus import mutagen.oggvorbis -from rose_core.common import Artist, ArtistMapping, RoseError, RoseExpectedError, uniq +from rose_lib.common import Artist, ArtistMapping, RoseError, RoseExpectedError, uniq if typing.TYPE_CHECKING: pass diff --git a/rose_core/audiotags_test.py b/rose_lib/audiotags_test.py similarity index 98% rename from rose_core/audiotags_test.py rename to rose_lib/audiotags_test.py index e324dae..c3ec83a 100644 --- a/rose_core/audiotags_test.py +++ b/rose_lib/audiotags_test.py @@ -4,14 +4,14 @@ import pytest from conftest import TEST_TAGGER -from rose_core.audiotags import ( +from rose_lib.audiotags import ( AudioTags, UnsupportedTagValueTypeError, _split_tag, format_artist_string, parse_artist_string, ) -from rose_core.common import Artist, ArtistMapping +from rose_lib.common import Artist, ArtistMapping @pytest.mark.parametrize( diff --git a/rose_core/cache.py b/rose_lib/cache.py similarity index 99% rename from rose_core/cache.py rename to rose_lib/cache.py index ff3cd5a..937b65c 100644 --- a/rose_core/cache.py +++ b/rose_lib/cache.py @@ -50,8 +50,8 @@ import tomllib import uuid6 -from rose_core.audiotags import SUPPORTED_AUDIO_EXTENSIONS, AudioTags -from rose_core.common import ( +from rose_lib.audiotags import SUPPORTED_AUDIO_EXTENSIONS, AudioTags +from rose_lib.common import ( VERSION, Artist, ArtistMapping, @@ -60,8 +60,8 @@ sha256_dataclass, uniq, ) -from rose_core.config import Config -from rose_core.templates import artistsfmt, eval_release_template, eval_track_template +from rose_lib.config import Config +from rose_lib.templates import artistsfmt, eval_release_template, eval_track_template logger = logging.getLogger(__name__) diff --git a/rose_core/cache.sql b/rose_lib/cache.sql similarity index 100% rename from rose_core/cache.sql rename to rose_lib/cache.sql diff --git a/rose_core/cache_test.py b/rose_lib/cache_test.py similarity index 99% rename from rose_core/cache_test.py rename to rose_lib/cache_test.py index 83d101b..f315677 100644 --- a/rose_core/cache_test.py +++ b/rose_lib/cache_test.py @@ -8,8 +8,8 @@ import tomllib from conftest import TEST_COLLAGE_1, TEST_PLAYLIST_1, TEST_RELEASE_1, TEST_RELEASE_2, TEST_RELEASE_3 -from rose_core.audiotags import AudioTags -from rose_core.cache import ( +from rose_lib.audiotags import AudioTags +from rose_lib.cache import ( CACHE_SCHEMA_PATH, STORED_DATA_FILE_REGEX, CachedCollage, @@ -47,8 +47,8 @@ update_cache_evict_nonexistent_releases, update_cache_for_releases, ) -from rose_core.common import VERSION, Artist, ArtistMapping -from rose_core.config import Config +from rose_lib.common import VERSION, Artist, ArtistMapping +from rose_lib.config import Config def test_schema(config: Config) -> None: diff --git a/rose_core/collages.py b/rose_lib/collages.py similarity index 98% rename from rose_core/collages.py rename to rose_lib/collages.py index 70ba16e..a884b39 100644 --- a/rose_core/collages.py +++ b/rose_lib/collages.py @@ -12,7 +12,7 @@ import tomllib from send2trash import send2trash -from rose_core.cache import ( +from rose_lib.cache import ( collage_lock_name, get_collage, get_release_logtext, @@ -21,8 +21,8 @@ update_cache_evict_nonexistent_collages, update_cache_for_collages, ) -from rose_core.common import RoseExpectedError -from rose_core.config import Config +from rose_lib.common import RoseExpectedError +from rose_lib.config import Config logger = logging.getLogger(__name__) diff --git a/rose_core/collages_test.py b/rose_lib/collages_test.py similarity index 97% rename from rose_core/collages_test.py rename to rose_lib/collages_test.py index de31c2e..5656b01 100644 --- a/rose_core/collages_test.py +++ b/rose_lib/collages_test.py @@ -5,8 +5,8 @@ import pytest import tomllib -from rose_core.cache import connect, update_cache -from rose_core.collages import ( +from rose_lib.cache import connect, update_cache +from rose_lib.collages import ( add_release_to_collage, create_collage, delete_collage, @@ -16,7 +16,7 @@ remove_release_from_collage, rename_collage, ) -from rose_core.config import Config +from rose_lib.config import Config def test_remove_release_from_collage(config: Config, source_dir: Path) -> None: @@ -242,7 +242,7 @@ def test_dump_collages(config: Config) -> None: def test_edit_collages_ordering(monkeypatch: Any, config: Config, source_dir: Path) -> None: filepath = source_dir / "!collages" / "Rose Gold.toml" monkeypatch.setattr( - "rose_core.collages.click.edit", lambda x: "\n".join(reversed(x.split("\n"))) + "rose_lib.collages.click.edit", lambda x: "\n".join(reversed(x.split("\n"))) ) edit_collage_in_editor(config, "Rose Gold") @@ -254,7 +254,7 @@ def test_edit_collages_ordering(monkeypatch: Any, config: Config, source_dir: Pa def test_edit_collages_remove_release(monkeypatch: Any, config: Config, source_dir: Path) -> None: filepath = source_dir / "!collages" / "Rose Gold.toml" - monkeypatch.setattr("rose_core.collages.click.edit", lambda x: x.split("\n")[0]) + monkeypatch.setattr("rose_lib.collages.click.edit", lambda x: x.split("\n")[0]) edit_collage_in_editor(config, "Rose Gold") with filepath.open("rb") as fp: diff --git a/rose_core/common.py b/rose_lib/common.py similarity index 96% rename from rose_core/common.py rename to rose_lib/common.py index 1de7ae6..1867c14 100644 --- a/rose_core/common.py +++ b/rose_lib/common.py @@ -7,7 +7,6 @@ import hashlib import os.path import re -import uuid from collections.abc import Iterator from pathlib import Path from typing import Any, TypeVar @@ -72,14 +71,6 @@ def items(self) -> Iterator[tuple[str, list[Artist]]]: yield "djmixer", self.djmixer -def valid_uuid(x: str) -> bool: - try: - uuid.UUID(x) - return True - except ValueError: - return False - - def uniq(xs: list[T]) -> list[T]: rv: list[T] = [] seen: set[T] = set() diff --git a/rose_core/config.py b/rose_lib/config.py similarity index 99% rename from rose_core/config.py rename to rose_lib/config.py index 4c30fb7..91ee9ad 100644 --- a/rose_core/config.py +++ b/rose_lib/config.py @@ -20,9 +20,9 @@ import appdirs import tomllib -from rose_core.common import RoseExpectedError, sanitize_dirname -from rose_core.rule_parser import MetadataRule, RuleSyntaxError -from rose_core.templates import ( +from rose_lib.common import RoseExpectedError, sanitize_dirname +from rose_lib.rule_parser import MetadataRule, RuleSyntaxError +from rose_lib.templates import ( DEFAULT_TEMPLATE_PAIR, InvalidPathTemplateError, PathTemplate, diff --git a/rose_core/config_test.py b/rose_lib/config_test.py similarity index 99% rename from rose_core/config_test.py rename to rose_lib/config_test.py index f513936..37fbace 100644 --- a/rose_core/config_test.py +++ b/rose_lib/config_test.py @@ -4,13 +4,13 @@ import click import pytest -from rose_core.config import ( +from rose_lib.config import ( Config, ConfigNotFoundError, InvalidConfigValueError, MissingConfigKeyError, ) -from rose_core.rule_parser import ( +from rose_lib.rule_parser import ( MatcherPattern, MetadataAction, MetadataMatcher, @@ -18,7 +18,7 @@ ReplaceAction, SplitAction, ) -from rose_core.templates import PathTemplate, PathTemplateConfig, PathTemplatePair +from rose_lib.templates import PathTemplate, PathTemplateConfig, PathTemplatePair def test_config_minimal() -> None: diff --git a/rose_core/playlists.py b/rose_lib/playlists.py similarity index 98% rename from rose_core/playlists.py rename to rose_lib/playlists.py index 73a2fa1..a2727b3 100644 --- a/rose_core/playlists.py +++ b/rose_lib/playlists.py @@ -14,7 +14,7 @@ import tomllib from send2trash import send2trash -from rose_core.cache import ( +from rose_lib.cache import ( get_playlist, get_track_logtext, list_playlists, @@ -23,8 +23,8 @@ update_cache_evict_nonexistent_playlists, update_cache_for_playlists, ) -from rose_core.common import RoseExpectedError -from rose_core.config import Config +from rose_lib.common import RoseExpectedError +from rose_lib.config import Config logger = logging.getLogger(__name__) diff --git a/rose_core/playlists_test.py b/rose_lib/playlists_test.py similarity index 98% rename from rose_core/playlists_test.py rename to rose_lib/playlists_test.py index b8b8e61..2bce313 100644 --- a/rose_core/playlists_test.py +++ b/rose_lib/playlists_test.py @@ -7,9 +7,9 @@ import tomllib from conftest import TEST_PLAYLIST_1, TEST_RELEASE_1 -from rose_core.cache import connect, update_cache -from rose_core.config import Config -from rose_core.playlists import ( +from rose_lib.cache import connect, update_cache +from rose_lib.config import Config +from rose_lib.playlists import ( add_track_to_playlist, create_playlist, delete_playlist, @@ -312,7 +312,7 @@ def test_dump_playlists(config: Config) -> None: def test_edit_playlists_ordering(monkeypatch: Any, config: Config, source_dir: Path) -> None: filepath = source_dir / "!playlists" / "Lala Lisa.toml" monkeypatch.setattr( - "rose_core.playlists.click.edit", lambda x: "\n".join(reversed(x.split("\n"))) + "rose_lib.playlists.click.edit", lambda x: "\n".join(reversed(x.split("\n"))) ) edit_playlist_in_editor(config, "Lala Lisa") @@ -324,7 +324,7 @@ def test_edit_playlists_ordering(monkeypatch: Any, config: Config, source_dir: P def test_edit_playlists_remove_track(monkeypatch: Any, config: Config, source_dir: Path) -> None: filepath = source_dir / "!playlists" / "Lala Lisa.toml" - monkeypatch.setattr("rose_core.playlists.click.edit", lambda x: x.split("\n")[0]) + monkeypatch.setattr("rose_lib.playlists.click.edit", lambda x: x.split("\n")[0]) edit_playlist_in_editor(config, "Lala Lisa") with filepath.open("rb") as fp: @@ -359,7 +359,7 @@ def editfn(x: str) -> str: seen = x return "\n".join(reversed(x.split("\n"))) - monkeypatch.setattr("rose_core.playlists.click.edit", editfn) + monkeypatch.setattr("rose_lib.playlists.click.edit", editfn) edit_playlist_in_editor(config, "You & Me") assert seen == "\n".join([f"BLACKPINK - Track 1 [1990].m4a [{tid}]" for tid in track_ids]) diff --git a/rose_core/releases.py b/rose_lib/releases.py similarity index 98% rename from rose_core/releases.py rename to rose_lib/releases.py index 1aca19f..116df9d 100644 --- a/rose_core/releases.py +++ b/rose_lib/releases.py @@ -17,8 +17,8 @@ import tomllib from send2trash import send2trash -from rose_core.audiotags import AudioTags -from rose_core.cache import ( +from rose_lib.audiotags import AudioTags +from rose_lib.cache import ( STORED_DATA_FILE_REGEX, CachedRelease, CachedTrack, @@ -34,15 +34,15 @@ update_cache_for_playlists, update_cache_for_releases, ) -from rose_core.common import Artist, ArtistMapping, RoseError, RoseExpectedError -from rose_core.config import Config -from rose_core.rule_parser import MetadataAction, MetadataMatcher -from rose_core.rules import ( +from rose_lib.common import Artist, ArtistMapping, RoseError, RoseExpectedError +from rose_lib.config import Config +from rose_lib.rule_parser import MetadataAction, MetadataMatcher +from rose_lib.rules import ( execute_metadata_actions, fast_search_for_matching_releases, filter_release_false_positives_using_read_cache, ) -from rose_core.templates import artistsfmt +from rose_lib.templates import artistsfmt logger = logging.getLogger(__name__) diff --git a/rose_core/releases_test.py b/rose_lib/releases_test.py similarity index 97% rename from rose_core/releases_test.py rename to rose_lib/releases_test.py index 85bf1c3..4a53b3c 100644 --- a/rose_core/releases_test.py +++ b/rose_lib/releases_test.py @@ -8,8 +8,8 @@ import tomllib from conftest import TEST_RELEASE_1 -from rose_core.audiotags import AudioTags -from rose_core.cache import ( +from rose_lib.audiotags import AudioTags +from rose_lib.cache import ( CachedRelease, CachedTrack, connect, @@ -17,9 +17,9 @@ get_tracks_associated_with_release, update_cache, ) -from rose_core.common import Artist, ArtistMapping -from rose_core.config import Config -from rose_core.releases import ( +from rose_lib.common import Artist, ArtistMapping +from rose_lib.config import Config +from rose_lib.releases import ( ReleaseEditFailedError, create_single_release, delete_release, @@ -31,7 +31,7 @@ set_release_cover_art, toggle_release_new, ) -from rose_core.rule_parser import MetadataAction, MetadataMatcher +from rose_lib.rule_parser import MetadataAction, MetadataMatcher def test_delete_release(config: Config) -> None: @@ -165,7 +165,7 @@ def test_edit_release(monkeypatch: Any, config: Config, source_dir: Path) -> Non {{ name = "JISOO", role = "main" }}, ] """ - monkeypatch.setattr("rose_core.collages.click.edit", lambda *_, **__: new_toml) + monkeypatch.setattr("rose_lib.collages.click.edit", lambda *_, **__: new_toml) edit_release(config, release_id) release = get_release(config, release_id) @@ -264,7 +264,7 @@ def test_edit_release_failure_and_resume( {{ name = "JISOO", role = "main" }}, ] """ - monkeypatch.setattr("rose_core.collages.click.edit", lambda *_, **__: bad_toml) + monkeypatch.setattr("rose_lib.collages.click.edit", lambda *_, **__: bad_toml) with pytest.raises(ReleaseEditFailedError) as exc: edit_release(config, release_id) @@ -311,7 +311,7 @@ def editfn(text: str, **_: Any) -> str: assert text == bad_toml return correct_toml - monkeypatch.setattr("rose_core.collages.click.edit", editfn) + monkeypatch.setattr("rose_lib.collages.click.edit", editfn) edit_release(config, release_id, resume_file=resume_file) # Assert the file got deleted. diff --git a/rose_core/rule_parser.py b/rose_lib/rule_parser.py similarity index 99% rename from rose_core/rule_parser.py rename to rose_lib/rule_parser.py index ffe3d64..ccd3c03 100644 --- a/rose_core/rule_parser.py +++ b/rose_lib/rule_parser.py @@ -16,7 +16,7 @@ import click -from rose_core.common import RoseError, RoseExpectedError +from rose_lib.common import RoseError, RoseExpectedError logger = logging.getLogger(__name__) diff --git a/rose_core/rule_parser_test.py b/rose_lib/rule_parser_test.py similarity index 99% rename from rose_core/rule_parser_test.py rename to rose_lib/rule_parser_test.py index 3f3a3df..c70ec2d 100644 --- a/rose_core/rule_parser_test.py +++ b/rose_lib/rule_parser_test.py @@ -3,7 +3,7 @@ import click import pytest -from rose_core.rule_parser import ( +from rose_lib.rule_parser import ( AddAction, DeleteAction, InvalidRuleError, diff --git a/rose_core/rules.py b/rose_lib/rules.py similarity index 99% rename from rose_core/rules.py rename to rose_lib/rules.py index 8596026..7373677 100644 --- a/rose_core/rules.py +++ b/rose_lib/rules.py @@ -22,8 +22,8 @@ import click -from rose_core.audiotags import AudioTags -from rose_core.cache import ( +from rose_lib.audiotags import AudioTags +from rose_lib.cache import ( CachedRelease, CachedTrack, connect, @@ -31,9 +31,9 @@ list_tracks, update_cache_for_releases, ) -from rose_core.common import Artist, RoseError, RoseExpectedError, uniq -from rose_core.config import Config -from rose_core.rule_parser import ( +from rose_lib.common import Artist, RoseError, RoseExpectedError, uniq +from rose_lib.config import Config +from rose_lib.rule_parser import ( RELEASE_TAGS, AddAction, DeleteAction, diff --git a/rose_core/rules_test.py b/rose_lib/rules_test.py similarity index 97% rename from rose_core/rules_test.py rename to rose_lib/rules_test.py index 1ff9847..5a980c9 100644 --- a/rose_core/rules_test.py +++ b/rose_lib/rules_test.py @@ -5,16 +5,16 @@ import pytest -from rose_core.audiotags import AudioTags -from rose_core.cache import ( +from rose_lib.audiotags import AudioTags +from rose_lib.cache import ( list_releases, list_tracks, update_cache, ) -from rose_core.common import Artist -from rose_core.config import Config -from rose_core.rule_parser import MetadataMatcher, MetadataRule -from rose_core.rules import ( +from rose_lib.common import Artist +from rose_lib.config import Config +from rose_lib.rule_parser import MetadataMatcher, MetadataRule +from rose_lib.rules import ( FastSearchResult, TrackTagNotAllowedError, execute_metadata_rule, @@ -307,7 +307,7 @@ def test_chained_action(config: Config, source_dir: Path) -> None: @pytest.mark.timeout(2) def test_confirmation_yes(monkeypatch: Any, config: Config, source_dir: Path) -> None: rule = MetadataRule.parse("tracktitle:Track", ["replace:lalala"]) - monkeypatch.setattr("rose_core.rules.click.confirm", lambda *_, **__: True) + monkeypatch.setattr("rose_lib.rules.click.confirm", lambda *_, **__: True) execute_metadata_rule(config, rule, confirm_yes=True) af = AudioTags.from_file(source_dir / "Test Release 1" / "01.m4a") assert af.title == "lalala" @@ -316,7 +316,7 @@ def test_confirmation_yes(monkeypatch: Any, config: Config, source_dir: Path) -> @pytest.mark.timeout(2) def test_confirmation_no(monkeypatch: Any, config: Config, source_dir: Path) -> None: rule = MetadataRule.parse("tracktitle:Track", ["replace:lalala"]) - monkeypatch.setattr("rose_core.rules.click.confirm", lambda *_, **__: False) + monkeypatch.setattr("rose_lib.rules.click.confirm", lambda *_, **__: False) execute_metadata_rule(config, rule, confirm_yes=True) af = AudioTags.from_file(source_dir / "Test Release 1" / "01.m4a") assert af.title != "lalala" @@ -325,7 +325,7 @@ def test_confirmation_no(monkeypatch: Any, config: Config, source_dir: Path) -> @pytest.mark.timeout(2) def test_confirmation_count(monkeypatch: Any, config: Config, source_dir: Path) -> None: rule = MetadataRule.parse("tracktitle:Track", ["replace:lalala"]) - monkeypatch.setattr("rose_core.rules.click.prompt", Mock(side_effect=["no", "8", "6"])) + monkeypatch.setattr("rose_lib.rules.click.prompt", Mock(side_effect=["no", "8", "6"])) # Abort. execute_metadata_rule(config, rule, confirm_yes=True, enter_number_to_confirm_above_count=1) af = AudioTags.from_file(source_dir / "Test Release 1" / "01.m4a") diff --git a/rose_core/templates.py b/rose_lib/templates.py similarity index 98% rename from rose_core/templates.py rename to rose_lib/templates.py index e2e22cd..612a893 100644 --- a/rose_core/templates.py +++ b/rose_lib/templates.py @@ -16,11 +16,11 @@ import click import jinja2 -from rose_core.common import Artist, ArtistMapping, RoseExpectedError +from rose_lib.common import Artist, ArtistMapping, RoseExpectedError if typing.TYPE_CHECKING: - from rose_core.cache import CachedRelease, CachedTrack - from rose_core.config import Config + from rose_lib.cache import CachedRelease, CachedTrack + from rose_lib.config import Config RELEASE_TYPE_FORMATTER = { "album": "Album", @@ -324,7 +324,7 @@ def preview_path_templates(c: Config) -> None: def _get_preview_releases(c: Config) -> tuple[CachedRelease, CachedRelease]: - from rose_core.cache import CachedRelease + from rose_lib.cache import CachedRelease kimlip = CachedRelease( id="018b268e-ff1e-7a0c-9ac8-7bbb282761f2", @@ -375,7 +375,7 @@ def _preview_release_template(c: Config, label: str, template: PathTemplate) -> def _preview_track_template(c: Config, label: str, template: PathTemplate) -> None: # Import cycle trick :) - from rose_core.cache import CachedTrack + from rose_lib.cache import CachedTrack kimlip, youngforever = _get_preview_releases(c) diff --git a/rose_core/templates_test.py b/rose_lib/templates_test.py similarity index 96% rename from rose_core/templates_test.py rename to rose_lib/templates_test.py index 137caf4..4759c7a 100644 --- a/rose_core/templates_test.py +++ b/rose_lib/templates_test.py @@ -4,10 +4,10 @@ import click from click.testing import CliRunner -from rose_core.cache import CachedRelease, CachedTrack -from rose_core.common import Artist, ArtistMapping -from rose_core.config import Config -from rose_core.templates import ( +from rose_lib.cache import CachedRelease, CachedTrack +from rose_lib.common import Artist, ArtistMapping +from rose_lib.config import Config +from rose_lib.templates import ( PathTemplateConfig, eval_release_template, eval_track_template, diff --git a/rose_core/tracks.py b/rose_lib/tracks.py similarity index 85% rename from rose_core/tracks.py rename to rose_lib/tracks.py index ff2d82c..c0de5fb 100644 --- a/rose_core/tracks.py +++ b/rose_lib/tracks.py @@ -7,15 +7,15 @@ import json import logging -from rose_core.audiotags import AudioTags -from rose_core.cache import ( +from rose_lib.audiotags import AudioTags +from rose_lib.cache import ( get_track, list_tracks, ) -from rose_core.common import RoseExpectedError -from rose_core.config import Config -from rose_core.rule_parser import MetadataAction, MetadataMatcher -from rose_core.rules import ( +from rose_lib.common import RoseExpectedError +from rose_lib.config import Config +from rose_lib.rule_parser import MetadataAction, MetadataMatcher +from rose_lib.rules import ( execute_metadata_actions, fast_search_for_matching_tracks, filter_track_false_positives_using_read_cache, diff --git a/rose_core/tracks_test.py b/rose_lib/tracks_test.py similarity index 97% rename from rose_core/tracks_test.py rename to rose_lib/tracks_test.py index 92d3d53..827398b 100644 --- a/rose_core/tracks_test.py +++ b/rose_lib/tracks_test.py @@ -3,10 +3,10 @@ import pytest -from rose_core.audiotags import AudioTags -from rose_core.config import Config -from rose_core.rule_parser import MetadataAction, MetadataMatcher -from rose_core.tracks import dump_all_tracks, dump_track, run_actions_on_track +from rose_lib.audiotags import AudioTags +from rose_lib.config import Config +from rose_lib.rule_parser import MetadataAction, MetadataMatcher +from rose_lib.tracks import dump_all_tracks, dump_track, run_actions_on_track def test_run_action_on_track(config: Config, source_dir: Path) -> None: diff --git a/rose_core/watcher.py b/rose_lib/watcher.py similarity index 99% rename from rose_core/watcher.py rename to rose_lib/watcher.py index 0bca7e8..69e032d 100644 --- a/rose_core/watcher.py +++ b/rose_lib/watcher.py @@ -36,7 +36,7 @@ ) from watchdog.observers import Observer -from rose_core.cache import ( +from rose_lib.cache import ( update_cache_evict_nonexistent_collages, update_cache_evict_nonexistent_playlists, update_cache_evict_nonexistent_releases, @@ -44,7 +44,7 @@ update_cache_for_playlists, update_cache_for_releases, ) -from rose_core.config import Config +from rose_lib.config import Config logger = logging.getLogger(__name__) diff --git a/rose_core/watcher_test.py b/rose_lib/watcher_test.py similarity index 98% rename from rose_core/watcher_test.py rename to rose_lib/watcher_test.py index 043cb1b..fccf1f2 100644 --- a/rose_core/watcher_test.py +++ b/rose_lib/watcher_test.py @@ -5,9 +5,9 @@ from multiprocessing import Process from conftest import TEST_COLLAGE_1, TEST_PLAYLIST_1, TEST_RELEASE_2, TEST_RELEASE_3, retry_for_sec -from rose_core.cache import connect -from rose_core.config import Config -from rose_core.watcher import start_watchdog +from rose_lib.cache import connect +from rose_lib.config import Config +from rose_lib.watcher import start_watchdog @contextmanager diff --git a/rose_vfs/__init__.py b/rose_vfs/__init__.py new file mode 100644 index 0000000..0d3dbaa --- /dev/null +++ b/rose_vfs/__init__.py @@ -0,0 +1,9 @@ +from rose_lib import initialize_logging +from rose_vfs.virtualfs import mount_virtualfs, unmount_virtualfs + +__all__ = [ + "mount_virtualfs", + "unmount_virtualfs", +] + +initialize_logging() diff --git a/rose_core/virtualfs.py b/rose_vfs/virtualfs.py similarity index 99% rename from rose_core/virtualfs.py rename to rose_vfs/virtualfs.py index 5039fce..d62c1b9 100644 --- a/rose_core/virtualfs.py +++ b/rose_vfs/virtualfs.py @@ -52,15 +52,29 @@ import llfuse -from rose_core.audiotags import SUPPORTED_AUDIO_EXTENSIONS, AudioTags -from rose_core.cache import ( +from rose_lib import ( STORED_DATA_FILE_REGEX, + SUPPORTED_AUDIO_EXTENSIONS, + AudioTags, CachedRelease, CachedTrack, + Config, + PathTemplate, + RoseError, + add_release_to_collage, + add_track_to_playlist, artist_exists, calculate_release_logtext, calculate_track_logtext, collage_exists, + create_collage, + create_playlist, + delete_collage, + delete_playlist, + delete_playlist_cover_art, + delete_release, + eval_release_template, + eval_track_template, genre_exists, get_collage, get_path_of_track_in_playlist, @@ -75,33 +89,18 @@ list_genres, list_labels, list_playlists, - list_releases_delete_this, playlist_exists, - update_cache_for_releases, -) -from rose_core.collages import ( - add_release_to_collage, - create_collage, - delete_collage, remove_release_from_collage, - rename_collage, -) -from rose_core.common import RoseError, sanitize_dirname, sanitize_filename -from rose_core.config import Config -from rose_core.playlists import ( - add_track_to_playlist, - create_playlist, - delete_playlist, - delete_playlist_cover_art, remove_track_from_playlist, + rename_collage, rename_playlist, + sanitize_dirname, + sanitize_filename, set_playlist_cover_art, -) -from rose_core.releases import ( - delete_release, set_release_cover_art, + update_cache_for_releases, ) -from rose_core.templates import PathTemplate, eval_release_template, eval_track_template +from rose_lib.cache import list_releases_delete_this logger = logging.getLogger(__name__) diff --git a/rose_core/virtualfs_test.py b/rose_vfs/virtualfs_test.py similarity index 99% rename from rose_core/virtualfs_test.py rename to rose_vfs/virtualfs_test.py index cddbe55..07d34f9 100644 --- a/rose_core/virtualfs_test.py +++ b/rose_vfs/virtualfs_test.py @@ -10,9 +10,9 @@ import pytest from conftest import retry_for_sec -from rose_core.audiotags import AudioTags -from rose_core.config import Config -from rose_core.virtualfs import mount_virtualfs, unmount_virtualfs +from rose_lib.audiotags import AudioTags +from rose_lib.config import Config +from rose_vfs.virtualfs import mount_virtualfs, unmount_virtualfs R1_VNAME = "Techno Man & Bass Man - 2023. Release 1" R2_VNAME = "Violin Woman (feat. Conductor Woman) - 2021. Release 2" diff --git a/setup.py b/setup.py index d34ce64..ec005d2 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import setuptools -with open("rose_core/.version") as f: +with open("rose_lib/.version") as f: version = f.read().strip() setuptools.setup( @@ -10,9 +10,9 @@ author="blissful", author_email="blissful@sunsetglow.net", license="Apache-2.0", - entry_points={"console_scripts": ["rose = rose_core.__main__:main"]}, + entry_points={"console_scripts": ["rose = rose_lib.__main__:main"]}, packages=setuptools.find_namespace_packages(where="."), - package_data={"rose_core": ["*.sql", ".version"]}, + package_data={"rose_lib": ["*.sql", ".version"]}, install_requires=[ "appdirs", "click",