Fix paths for Windows

This commit is contained in:
Šarūnas Nejus 2026-03-23 10:08:51 +00:00
parent 81dac7e64a
commit 5eee28bb5c
No known key found for this signature in database
5 changed files with 22 additions and 20 deletions

View file

@ -8,6 +8,15 @@ from beets import context, util
MaybeBytes = TypeVar("MaybeBytes", bytes, None)
def _is_same_path_or_child(path: bytes, music_dir: bytes) -> bool:
"""Check if path is the music directory itself or resides within it."""
path_cmp = os.path.normcase(os.fsdecode(path))
music_dir_cmp = os.path.normcase(os.fsdecode(music_dir))
return path_cmp == music_dir_cmp or path_cmp.startswith(
os.path.join(music_dir_cmp, "")
)
def normalize_path_for_db(path: MaybeBytes) -> MaybeBytes:
"""Convert an absolute library path to its database representation."""
if not path or not os.path.isabs(path):
@ -17,10 +26,7 @@ def normalize_path_for_db(path: MaybeBytes) -> MaybeBytes:
if not music_dir:
return path
if path == music_dir:
return os.path.relpath(path, music_dir)
if path.startswith(os.path.join(music_dir, b"")):
if _is_same_path_or_child(path, music_dir):
return os.path.relpath(path, music_dir)
return path

View file

@ -12,8 +12,9 @@ from typing import TYPE_CHECKING, ClassVar
from mediafile import MediaFile, UnreadableFileError
import beets
from beets import context, dbcore, logging, plugins, util
from beets import dbcore, logging, plugins, util
from beets.dbcore import types
from beets.dbcore.pathutils import normalize_path_for_db
from beets.util import (
MoveOperation,
bytestring_path,
@ -104,19 +105,15 @@ class LibModel(dbcore.Model["Library"]):
if (
cls._type(field).query is dbcore.query.PathQuery
and query_cls is not dbcore.query.PathQuery
and (music_dir := context.get_music_dir())
):
# Regex, exact, and string queries operate on the raw DB value, so
# strip the library prefix to match the stored relative path.
if isinstance(pattern, bytes):
prefix = os.path.join(music_dir, b"")
if pattern.startswith(prefix):
pattern = os.path.relpath(pattern, music_dir)
pattern = normalize_path_for_db(pattern)
else:
music_dir_str = os.fsdecode(music_dir)
prefix = music_dir_str + os.sep
if pattern.startswith(prefix):
pattern = pattern.removeprefix(prefix)
pattern = os.fsdecode(
normalize_path_for_db(util.bytestring_path(pattern))
)
if field in cls.shared_db_fields:
# This field exists in both tables, so SQLite will encounter
# an OperationalError if we try to use it in a query.

View file

@ -161,7 +161,7 @@ class TestRelativePathMigration:
helper.teardown_beets()
def test_migrate(self, helper: TestHelper):
relative_path = "foo/bar/baz.mp3"
relative_path = os.path.join("foo", "bar", "baz.mp3")
absolute_path = os.fsencode(helper.lib_path / relative_path)
# need to insert the path directly into the database to bypass the path setter

View file

@ -15,9 +15,9 @@
import os
from unittest.mock import Mock, patch
from beets import util
from beets.test import _common
from beets.test.helper import PluginTestCase
from beets.util import bytestring_path
from beetsplug.ipfs import IPFSPlugin
@ -37,10 +37,9 @@ class IPFSPluginTest(PluginTestCase):
try:
if check_item.get("ipfs", with_album=False):
ipfs_item = os.fsdecode(os.path.basename(want_item.path))
want_path = os.path.normpath(
f"/ipfs/{test_album.ipfs}/{ipfs_item}"
want_path = util.normpath(
os.path.join("/ipfs", test_album.ipfs, ipfs_item)
)
want_path = bytestring_path(want_path)
assert check_item.path == want_path
assert (
check_item.get("ipfs", with_album=False)

View file

@ -1094,7 +1094,7 @@ class PathStringTest(BeetsTestCase):
assert isinstance(dest, bytes)
def test_artpath_stores_special_chars(self):
path = b"b\xe1r"
path = bytestring_path("b\xe1r")
alb = self.lib.add_album([self.i])
alb.artpath = path
alb.store()
@ -1131,7 +1131,7 @@ class PathStringTest(BeetsTestCase):
assert isinstance(alb.artpath, bytes)
def test_relative_path_is_stored(self):
relative_path = b"abc/foo.mp3"
relative_path = os.path.join(b"abc", b"foo.mp3")
absolute_path = os.path.join(self.libdir, relative_path)
self.i.path = absolute_path
self.i.store()