From fb031fcaf6331fbe6cad52b19d8acdebc257f191 Mon Sep 17 00:00:00 2001 From: Jakob Moser Date: Sun, 1 Jun 2025 12:11:35 +0200 Subject: [PATCH] Implement move instruction generation --- karaokatalog/organize/move.py | 45 ++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/karaokatalog/organize/move.py b/karaokatalog/organize/move.py index 7f9f1a6..f83c774 100644 --- a/karaokatalog/organize/move.py +++ b/karaokatalog/organize/move.py @@ -1,13 +1,56 @@ +import re from collections.abc import Sequence from pathlib import Path from karaokatalog.instructions.MoveInstruction import MoveInstruction from karaokatalog.Song import Song +FORBIDDEN_CHARACTERS = re.compile(r'[/<>:"\\|?*]') + + +def _get_canonical_song_dir(song: Song, variant: int = 0) -> Path: + """ + Get the (relative) canonical directory in which this song should be located, that is + the directory f"{song.artist}/{song.title}". + + Illegal characters are replaced. If variant > 0, f" ({variant})" is appended to the result. + """ + variant_suffix = f" ({variant})" if variant > 0 else "" + return Path(FORBIDDEN_CHARACTERS.sub("", song.artist)) / Path( + FORBIDDEN_CHARACTERS.sub("", song.title) + variant_suffix + ) + def move(songs: Sequence[Song], base_dir: Path) -> Sequence[MoveInstruction]: """ Create move instructions to move every song into the proper song directory within the given base_dir. """ - raise NotImplementedError() # TODO + song_dirs = set(song.dir.relative_to(base_dir) for song in songs) + move_instructions = [] + + for song in songs: + absolute_song_dir = song.dir + song_dir = absolute_song_dir.relative_to(base_dir) + canonical_song_dir = _get_canonical_song_dir(song) + + if song_dir not in song_dirs: + # A move instruction has already been generated for the dir this song is in + # (which is possible, because some dirs contain multiple (variants of) songs) + continue + + if song_dir != canonical_song_dir: + # song_dir is not a good name, we want to replace it, so this path will soon be free + song_dirs.remove(song_dir) + + # Find a canonical song dir variant that does not yet exist + variant = 1 + while canonical_song_dir in song_dirs: + canonical_song_dir = _get_canonical_song_dir(song, variant) + + song_dirs.add(canonical_song_dir) + move_instructions.append( + MoveInstruction(absolute_song_dir, base_dir / canonical_song_dir) + ) + + return tuple(move_instructions)