Build and publish container / build (pull_request) Successful in 3m8s
Lidarr renames an artist or album folder, music-mirror prunes the old path and encodes the new one, and every entry in Apple Music pointing at the old path dies. Music offers no view of those, and the exclamation mark only appears once a track is touched. Reads the XML from Music's own Export Library rather than asking Music itself. A broken track makes AppleScript's `location` raise instead of returning a value, so a bulk query dies on the first one with error -1728 and a per-track loop costs an Apple event apiece. Checked against a single directory walk rather than a test per file. On a fifty-thousand-track library that is around seven thousand directory reads instead of fifty thousand stat calls, and over SMB each of those stats is a network round trip -- which is the difference between seconds and minutes. Two comparisons have to be loosened or most of the library reads as missing. macOS stores filenames decomposed while the share composes them, so the umlauts in Motley Crue are two different byte strings depending on which side wrote the name; both are normalised to NFC. And the share is very likely case-insensitive, so a file is not missing because someone capitalised it differently. The test stage now copies tools/ as well, since the suite covers this and the runtime image deliberately does not carry it.
36 lines
1.3 KiB
Docker
36 lines
1.3 KiB
Docker
# Alpine because there is nothing to compile and nothing to link against: the
|
|
# application is one Python module with no dependencies outside the standard
|
|
# library, so the base image is almost the whole image.
|
|
FROM python:3.13-alpine AS runtime
|
|
|
|
ENV PYTHONUNBUFFERED=1
|
|
|
|
WORKDIR /app
|
|
COPY pyproject.toml README.md ./
|
|
COPY music_curator.py ./
|
|
RUN pip install --no-cache-dir . \
|
|
&& rm -rf build music_curator.egg-info
|
|
|
|
# The store lives here. Mount a volume over it, or the history is re-downloaded
|
|
# from Last.fm every time the container is recreated.
|
|
ENV MUSIC_CURATOR_DB=/data/curator.db
|
|
VOLUME ["/data"]
|
|
|
|
# Runs as root by default so a bind-mounted dataset of any ownership is
|
|
# writable. Override with `user:` in compose to run as the dataset's owner.
|
|
ENTRYPOINT ["music-curator"]
|
|
|
|
# Test stage: build it with `--target test`; a failing test fails the build.
|
|
# The suite talks to a fake transport, never to Last.fm, so it needs no
|
|
# credentials and no network. The published image is the `runtime` stage above
|
|
# and carries none of this.
|
|
FROM runtime AS test
|
|
|
|
RUN pip install --no-cache-dir pytest
|
|
COPY pytest.ini ./
|
|
# The host-side tools are not part of the runtime image, but their tests are
|
|
# part of the suite, so they have to be present for it.
|
|
COPY tools ./tools
|
|
COPY tests ./tests
|
|
RUN python -m pytest
|