Compare commits

...

3 Commits

Author SHA1 Message Date
fb031fcaf6
Implement move instruction generation 2025-06-01 12:11:35 +02:00
86a870d429
Avoid shutil where we can 2025-06-01 11:30:49 +02:00
3d9f8d46ee
Implement move instruction 2025-06-01 10:58:40 +02:00
2 changed files with 48 additions and 2 deletions

View File

@ -15,4 +15,7 @@ class MoveInstruction(Instruction):
new_path: Path
def __call__(self) -> None:
raise NotImplementedError() # TODO
if self.new_path.exists():
raise FileExistsError("New path already exists, not moving anything")
self.old_path.rename(self.new_path)

View File

@ -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)