Prevent duplicate filesystem mappings (#811)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Tue, 1 Aug 2023 22:33:27 +0000 (00:33 +0200)
committerGitHub <noreply@github.com>
Tue, 1 Aug 2023 22:33:27 +0000 (00:33 +0200)
music_assistant/common/helpers/util.py
music_assistant/common/models/media_items.py
music_assistant/server/controllers/media/base.py
music_assistant/server/providers/filesystem_local/base.py
music_assistant/server/providers/musicbrainz/__init__.py
music_assistant/server/providers/tidal/__init__.py
music_assistant/server/providers/ytmusic/__init__.py

index 343cbd4d7b32fcd10874d3204d93ecf3926c97c7..1a73f44cadb21ed91360466aa85a8115132eb6c6 100755 (executable)
@@ -47,7 +47,7 @@ def try_parse_bool(possible_bool: Any) -> str:
 def create_sort_name(input_str: str) -> str:
     """Create sort name/title from string."""
     input_str = input_str.lower().strip()
-    for item in ["the ", "de ", "les ", "dj "]:
+    for item in ["the ", "de ", "les ", "dj ", ".", "-", "'", "`"]:
         if input_str.startswith(item):
             input_str = input_str.replace(item, "")
     return input_str.strip()
index a7887de04e7bf6d8a01cc7f1cc5421bb7819d2d1..ed0ecc0a3b77651d033b988ba4dc97f18d648555 100755 (executable)
@@ -80,16 +80,13 @@ class ProviderMapping(DataClassDictMixin):
 
     def __hash__(self) -> int:
         """Return custom hash."""
-        return hash((self.provider_instance, self.item_id.lower()))
+        return hash((self.provider_instance, self.item_id))
 
     def __eq__(self, other: ProviderMapping) -> bool:
         """Check equality of two items."""
         if not other:
             return False
-        return (
-            self.provider_instance == other.provider_instance
-            and self.item_id.lower() == other.item_id.lower()
-        )
+        return self.provider_instance == other.provider_instance and self.item_id == other.item_id
 
 
 @dataclass(frozen=True)
index 9b8189435440c8f2b663b87bbda40e5314b256bc..6156bcf040b772fb67a40d6c3276c996002d2d03 100644 (file)
@@ -102,6 +102,7 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
             if self.media_type in (MediaType.ALBUM, MediaType.TRACK):
                 query_parts.append(
                     f"({self.db_table}.name LIKE :search "
+                    f" OR {self.db_table}.sort_name LIKE :search"
                     f" OR {self.db_table}.artists LIKE :search)"
                 )
             else:
index f9bc6d605832394f398ea737a2123472767d7e64..3d20412420ace6bfdf9abb4fdbc46f76d15b6547 100644 (file)
@@ -11,7 +11,7 @@ from dataclasses import dataclass
 import cchardet
 import xmltodict
 
-from music_assistant.common.helpers.util import parse_title_and_version
+from music_assistant.common.helpers.util import create_sort_name, parse_title_and_version
 from music_assistant.common.models.config_entries import (
     ConfigEntry,
     ConfigEntryType,
@@ -790,7 +790,26 @@ class FileSystemProviderBase(MusicProvider):
         """Lookup metadata in Artist folder."""
         assert name or artist_path
         if not artist_path:
-            artist_path = name
+            # check if we have an existing item
+            sort_name = create_sort_name(name)
+            async for item in self.mass.music.artists.iter_library_items(search=sort_name):
+                if not compare_strings(sort_name, item.sort_name):
+                    continue
+                for prov_mapping in item.provider_mappings:
+                    if prov_mapping.provider_instance == self.instance_id:
+                        artist_path = prov_mapping.url
+                        break
+                if artist_path:
+                    break
+            else:
+                # check if we have an artist folder for this artist at root level
+                if await self.exists(name):
+                    artist_path = name
+                elif await self.exists(name.title()):
+                    artist_path = name.title()
+                else:
+                    # use fake artist path as item id which is just the name
+                    artist_path = name
 
         if not name:
             name = artist_path.split(os.sep)[-1]
index e146b6b8989727a6160fd79a0dd8e9410149fb67..cf72824ffa2503875b1470bf4246d7b9bf73cf1e 100644 (file)
@@ -125,7 +125,8 @@ class MusicbrainzProvider(MetadataProvider):
         assert albumname or album_barcode
         for searchartist in (
             artistname,
-            re.sub(LUCENE_SPECIAL, r"\\\1", create_sort_name(artistname)),
+            re.sub(LUCENE_SPECIAL, r"\\\1", artistname),
+            create_sort_name(artistname),
         ):
             if album_barcode:
                 # search by album barcode (EAN or UPC)
index e024348a8c4640fc94e589af5430193676fbe501..945a732aa8bdf0c42196cb4a3df23a2793ece525 100644 (file)
@@ -17,8 +17,6 @@ from tidalapi import Session as TidalSession
 from tidalapi import Track as TidalTrack
 from tidalapi.media import Lyrics as TidalLyrics
 
-from music_assistant.common.helpers.uri import create_uri
-from music_assistant.common.helpers.util import create_sort_name
 from music_assistant.common.models.config_entries import ConfigEntry, ConfigValueType
 from music_assistant.common.models.enums import (
     AlbumType,
@@ -425,8 +423,6 @@ class TidalProvider(MusicProvider):
             item_id=key,
             provider=self.instance_id,
             name=name,
-            uri=create_uri(media_type, self.instance_id, key),
-            sort_name=create_sort_name(self.name),
         )
 
     async def _get_tidal_session(self) -> TidalSession:
index 6544189054cbed46ffa9e471501355955340ab16..e215ce8718d086334a15402c70c2485d2330d739 100644 (file)
@@ -10,8 +10,6 @@ from urllib.parse import unquote
 
 import pytube
 
-from music_assistant.common.helpers.uri import create_uri
-from music_assistant.common.helpers.util import create_sort_name
 from music_assistant.common.models.config_entries import ConfigEntry, ConfigValueType
 from music_assistant.common.models.enums import ConfigEntryType, ProviderFeature
 from music_assistant.common.models.errors import (
@@ -834,12 +832,10 @@ class YoutubeMusicProvider(MusicProvider):
 
     def _get_item_mapping(self, media_type: MediaType, key: str, name: str) -> ItemMapping:
         return ItemMapping(
-            media_type,
-            key,
-            self.instance_id,
-            name,
-            create_uri(media_type, self.instance_id, key),
-            create_sort_name(self.name),
+            media_type=media_type,
+            item_id=key,
+            provider=self.instance_id,
+            name=name,
         )
 
     def _get_artist_item_mapping(self, artist_obj: dict) -> ItemMapping: