diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index e2cebe0..1189323 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -2,26 +2,34 @@ name: Check and test on: [push, pull_request] +env: + FORCE_COLOR: 1 + jobs: build: strategy: matrix: + os: ["ubuntu-latest"] python-version: - "3.8" # minimum required - "3.12" # latest - "3.13-dev" # next + include: + - python-version: 3.8 + os: windows-2022 - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.python-version == '3.13-dev' }} steps: - - uses: actions/checkout@v3 - - run: pip install poetry - - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 + - run: pipx install poetry + - uses: actions/setup-python@v5 + id: setup-python with: python-version: ${{ matrix.python-version }} cache: poetry - - run: poetry env use $(which python) + - run: poetry env use ${{ steps.setup-python.outputs.python-path }} - run: poetry install - run: poetry run ruff format --check - run: poetry run ruff check @@ -34,9 +42,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: pip install poetry - - uses: actions/setup-python@v3 + - uses: actions/setup-python@v5 with: python-version: 3.8 cache: poetry diff --git a/beetsplug/alternatives.py b/beetsplug/alternatives.py index 0f646ba..35690a6 100644 --- a/beetsplug/alternatives.py +++ b/beetsplug/alternatives.py @@ -12,6 +12,7 @@ import argparse +import logging import os.path import threading import traceback @@ -24,11 +25,20 @@ from beets.library import Item, Library, parse_query_string from beets.plugins import BeetsPlugin from beets.ui import Subcommand, UserError, decargs, get_path_formats, input_yn, print_ -from beets.util import FilesystemError, bytestring_path, displayable_path, syspath +from beets.util import ( + FilesystemError, + bytestring_path, + displayable_path, + syspath, +) from typing_extensions import override import beetsplug.convert as convert +logger = logging.getLogger(__name__) +logger.propagate = True +logger.setLevel(logging.DEBUG) + def _remove(path, soft=True): """Remove the file. If `soft`, then no error will be raised if the @@ -213,6 +223,7 @@ def item_change_actions(self, item: Item, path: bytes, dest: bytes) -> List[Acti actions.append(Action.MOVE) item_mtime_alt = os.path.getmtime(syspath(path)) + if item_mtime_alt < os.path.getmtime(syspath(item.path)): actions.append(Action.WRITE) album = item.get_album() diff --git a/test/cli_test.py b/test/cli_test.py index f70429c..b36cda3 100644 --- a/test/cli_test.py +++ b/test/cli_test.py @@ -1,7 +1,9 @@ import os import os.path +import platform import shutil from pathlib import Path +from time import sleep import pytest from beets import util @@ -22,6 +24,7 @@ assert_not_file_tag, assert_symlink, control_stdin, + convert_command, ) @@ -34,8 +37,7 @@ def test_external(self, tmp_path: Path): external_dir = str(tmp_path / "myplayer") self.config["convert"]["formats"] = { "aac": { - "command": "bash -c \"cp '$source' '$dest';" - + "printf ISAAC >> '$dest'\"", + "command": convert_command("ISAAC"), "extension": "m4a", }, } @@ -110,6 +112,7 @@ def test_external(self, tmp_path: Path): assert_file_tag(external_beet, b"ISAAC") +@pytest.mark.skipif(platform.system() == "Windows", reason="no symlinks on windows") class TestSymlinkView(TestHelper): """Test alternatives with the ``link`` format producing symbolic links.""" @@ -320,6 +323,7 @@ def test_move_and_write_after_tags_changed(self): old_path = self.get_path(item) assert_is_file(old_path) + sleep(0.1) item["title"] = "a new title" item.store() item.write() @@ -410,7 +414,7 @@ def touch_art(item, image_path): # Make a copy of the artwork, so that changing mtime/content won't # affect the repository. image_path = bytes(tmp_path / "image") - shutil.copy(self.IMAGE_FIXTURE1, check_type(syspath(image_path), bytes)) + shutil.copy(self.IMAGE_FIXTURE1, syspath(image_path)) # type: ignore touch_art(item, image_path) # Add a cover image, assert that it is being embedded. @@ -479,9 +483,7 @@ class TestExternalConvert(TestHelper): @pytest.fixture(autouse=True) def _external_convert(self, tmp_path: Path, _setup: None): external_dir = str(tmp_path) - self.config["convert"]["formats"] = { - "ogg": "bash -c \"cp '$source' '$dest';" + "printf ISOGG >> '$dest'\"" - } + self.config["convert"]["formats"] = {"ogg": convert_command("ISOGG")} self.config["alternatives"] = { "myexternal": { "directory": external_dir, @@ -546,9 +548,7 @@ def test_no_move_on_extension_change(self): item = self.add_track(myexternal="true", format="m4a") self.runcli("alt", "update", "myexternal") - self.config["convert"]["formats"] = { - "mp3": "bash -c \"cp '$source' '$dest';" + "printf ISMP3 >> '$dest'\"" - } + self.config["convert"]["formats"] = {"mp3": convert_command("ISMP3")} self.config["alternatives"]["myexternal"]["formats"] = "mp3" # Assert that this re-encodes instead of copying the ogg file @@ -558,6 +558,7 @@ def test_no_move_on_extension_change(self): assert_file_tag(converted_path, b"ISMP3") +@pytest.mark.skipif(platform.system() == "Windows", reason="converter not implemented") class TestExternalConvertWorker(TestHelper): """Test alternatives with non-empty ``format`` option, i.e. transcoding some of the files. In contrast to the previous test, these test do use diff --git a/test/helper.py b/test/helper.py index d97783d..0419807 100644 --- a/test/helper.py +++ b/test/helper.py @@ -1,4 +1,5 @@ import os +import platform import sys from concurrent import futures from contextlib import contextmanager @@ -261,3 +262,20 @@ def submit(self, *args, **kwargs): def shutdown(self, wait=True): pass + + +def convert_command(tag: str) -> str: + """Return a convert shell command that copies the file and adds a tag to the files end.""" + + system = platform.system() + if system == "Windows": + return ( + 'powershell -Command "' + "Copy-Item -Path '$source' -Destination '$dest';" + f"Add-Content -Path '$dest' -Value {tag} -NoNewline" + '"' + ) + elif system == "Linux": + return f"bash -c \"cp '$source' '$dest'; printf {tag} >> '$dest'\"" + else: + raise Exception(f"Unsupported system: {system}")