[bug] Fix issue with search
Some checks failed
Some checks failed
This commit is contained in:
@ -21,6 +21,7 @@ class Extension(ext.Extension):
|
||||
schema["artists"] = config.String(optional=True)
|
||||
schema["playlist_prefix"] = config.String(optional=True)
|
||||
schema["refresh_interval"] = config.Integer(optional=True, minimum=0)
|
||||
schema["search_uris"] = config.List(optional=True)
|
||||
return schema
|
||||
|
||||
def validate_environment(self) -> None:
|
||||
|
||||
@ -15,3 +15,7 @@ artists =
|
||||
playlist_prefix = [Smart]
|
||||
# Refresh interval in hours (0 = only on startup)
|
||||
refresh_interval = 0
|
||||
# Comma-separated list of URI schemes to restrict searches to.
|
||||
# Example: local:, file:
|
||||
# Leave empty to search all backends.
|
||||
search_uris =
|
||||
|
||||
@ -19,20 +19,24 @@ def parse_decade(decade_str: str) -> str:
|
||||
return decade_str
|
||||
|
||||
|
||||
def _search(core: CoreProxy, field: str, value: str) -> list[Track]:
|
||||
def _search(
|
||||
core: CoreProxy, field: str, value: str, uris: list[Uri] | None = None,
|
||||
) -> list[Track]:
|
||||
query = cast("Query[SearchField]", {field: [value]})
|
||||
try:
|
||||
result = core.library.search(query).get()
|
||||
result = core.library.search(query, uris=uris).get()
|
||||
except Exception:
|
||||
logger.exception("Search failed for %s=%s", field, value)
|
||||
return []
|
||||
return _extract_tracks(result)
|
||||
|
||||
|
||||
def build_decade_mix(core: CoreProxy, decade: str) -> list[Track]:
|
||||
def build_decade_mix(
|
||||
core: CoreProxy, decade: str, uris: list[Uri] | None = None,
|
||||
) -> list[Track]:
|
||||
query = cast("Query[SearchField]", {"date": [parse_decade(decade)]})
|
||||
try:
|
||||
result = core.library.search(query).get()
|
||||
result = core.library.search(query, uris=uris).get()
|
||||
except Exception:
|
||||
logger.exception("Decade search failed for %s", decade)
|
||||
return []
|
||||
@ -41,12 +45,16 @@ def build_decade_mix(core: CoreProxy, decade: str) -> list[Track]:
|
||||
return tracks
|
||||
|
||||
|
||||
def build_genre_mix(core: CoreProxy, genre: str) -> list[Track]:
|
||||
return _search(core, "genre", genre)
|
||||
def build_genre_mix(
|
||||
core: CoreProxy, genre: str, uris: list[Uri] | None = None,
|
||||
) -> list[Track]:
|
||||
return _search(core, "genre", genre, uris=uris)
|
||||
|
||||
|
||||
def build_artist_mix(core: CoreProxy, artist: str) -> list[Track]:
|
||||
return _search(core, "artist", artist)
|
||||
def build_artist_mix(
|
||||
core: CoreProxy, artist: str, uris: list[Uri] | None = None,
|
||||
) -> list[Track]:
|
||||
return _search(core, "artist", artist, uris=uris)
|
||||
|
||||
|
||||
def build_album_mix(core: CoreProxy, album_uri: str) -> list[Track]:
|
||||
@ -60,7 +68,9 @@ def build_album_mix(core: CoreProxy, album_uri: str) -> list[Track]:
|
||||
return flat
|
||||
|
||||
|
||||
def build_instant_mix(core: CoreProxy, track_uri: str, limit: int = 50) -> list[Track]:
|
||||
def build_instant_mix(
|
||||
core: CoreProxy, track_uri: str, limit: int = 50, uris: list[Uri] | None = None,
|
||||
) -> list[Track]:
|
||||
try:
|
||||
lookup_result = core.library.lookup(cast("list[Uri]", [track_uri])).get()
|
||||
except Exception:
|
||||
@ -85,7 +95,7 @@ def build_instant_mix(core: CoreProxy, track_uri: str, limit: int = 50) -> list[
|
||||
for genre in genres:
|
||||
g_query = cast("Query[SearchField]", {"genre": [genre]})
|
||||
try:
|
||||
genre_result = core.library.search(g_query).get()
|
||||
genre_result = core.library.search(g_query, uris=uris).get()
|
||||
except Exception:
|
||||
logger.exception("Genre search failed for %s", genre)
|
||||
continue
|
||||
@ -99,7 +109,7 @@ def build_instant_mix(core: CoreProxy, track_uri: str, limit: int = 50) -> list[
|
||||
break
|
||||
a_query = cast("Query[SearchField]", {"artist": [artist]})
|
||||
try:
|
||||
artist_result = core.library.search(a_query).get()
|
||||
artist_result = core.library.search(a_query, uris=uris).get()
|
||||
except Exception:
|
||||
logger.exception("Artist search failed for %s", artist)
|
||||
continue
|
||||
@ -149,13 +159,17 @@ def save_smart_playlist(
|
||||
|
||||
def refresh_smart_playlists(core: CoreProxy, config_dict: dict) -> None:
|
||||
prefix = config_dict.get("playlist_prefix", "[Smart]")
|
||||
raw = config_dict.get("search_uris", "")
|
||||
uris: list[Uri] | None = None
|
||||
if raw:
|
||||
uris = cast("list[Uri]", [u.strip() for u in raw.split(",") if u.strip()])
|
||||
|
||||
decades_raw = config_dict.get("decades", "")
|
||||
if decades_raw:
|
||||
decades = [d.strip() for d in decades_raw.split(",") if d.strip()]
|
||||
for decade in decades:
|
||||
try:
|
||||
tracks = build_decade_mix(core, decade)
|
||||
tracks = build_decade_mix(core, decade, uris=uris)
|
||||
except Exception:
|
||||
logger.exception("Failed to build decade mix for %s", decade)
|
||||
continue
|
||||
@ -167,7 +181,7 @@ def refresh_smart_playlists(core: CoreProxy, config_dict: dict) -> None:
|
||||
genres = [d.strip() for d in genres_raw.split(",") if d.strip()]
|
||||
for genre in genres:
|
||||
try:
|
||||
tracks = build_genre_mix(core, genre)
|
||||
tracks = build_genre_mix(core, genre, uris=uris)
|
||||
except Exception:
|
||||
logger.exception("Failed to build genre mix for %s", genre)
|
||||
continue
|
||||
@ -179,7 +193,7 @@ def refresh_smart_playlists(core: CoreProxy, config_dict: dict) -> None:
|
||||
artists = [a.strip() for a in artists_raw.split(",") if a.strip()]
|
||||
for artist in artists:
|
||||
try:
|
||||
tracks = build_artist_mix(core, artist)
|
||||
tracks = build_artist_mix(core, artist, uris=uris)
|
||||
except Exception:
|
||||
logger.exception("Failed to build artist mix for %s", artist)
|
||||
continue
|
||||
|
||||
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, cast
|
||||
import tornado.web
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mopidy_smartplaylists.compat import Config, CoreProxy
|
||||
from mopidy_smartplaylists.compat import Config, CoreProxy, Uri
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mopidy.models import Playlist
|
||||
@ -26,9 +26,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DecadeMixHandler(tornado.web.RequestHandler):
|
||||
def initialize(self, core: CoreProxy, prefix: str) -> None:
|
||||
def initialize(
|
||||
self, core: CoreProxy, prefix: str, uris: list[Uri] | None = None,
|
||||
) -> None:
|
||||
self.core = core
|
||||
self.prefix = prefix
|
||||
self.uris = uris
|
||||
|
||||
def post(self) -> None:
|
||||
data = json.loads(self.request.body)
|
||||
@ -37,7 +40,7 @@ class DecadeMixHandler(tornado.web.RequestHandler):
|
||||
self.set_status(400)
|
||||
self.write({"error": "Missing 'decade' in request body"})
|
||||
return
|
||||
tracks = build_decade_mix(self.core, decade)
|
||||
tracks = build_decade_mix(self.core, decade, uris=self.uris)
|
||||
if not tracks:
|
||||
self.write({"playlist": None, "tracks": 0})
|
||||
return
|
||||
@ -54,9 +57,12 @@ class DecadeMixHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class GenreMixHandler(tornado.web.RequestHandler):
|
||||
def initialize(self, core: CoreProxy, prefix: str) -> None:
|
||||
def initialize(
|
||||
self, core: CoreProxy, prefix: str, uris: list[Uri] | None = None,
|
||||
) -> None:
|
||||
self.core = core
|
||||
self.prefix = prefix
|
||||
self.uris = uris
|
||||
|
||||
def post(self) -> None:
|
||||
data = json.loads(self.request.body)
|
||||
@ -65,7 +71,7 @@ class GenreMixHandler(tornado.web.RequestHandler):
|
||||
self.set_status(400)
|
||||
self.write({"error": "Missing 'genre' in request body"})
|
||||
return
|
||||
tracks = build_genre_mix(self.core, genre)
|
||||
tracks = build_genre_mix(self.core, genre, uris=self.uris)
|
||||
if not tracks:
|
||||
self.write({"playlist": None, "tracks": 0})
|
||||
return
|
||||
@ -82,9 +88,12 @@ class GenreMixHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class ArtistMixHandler(tornado.web.RequestHandler):
|
||||
def initialize(self, core: CoreProxy, prefix: str) -> None:
|
||||
def initialize(
|
||||
self, core: CoreProxy, prefix: str, uris: list[Uri] | None = None,
|
||||
) -> None:
|
||||
self.core = core
|
||||
self.prefix = prefix
|
||||
self.uris = uris
|
||||
|
||||
def post(self) -> None:
|
||||
data = json.loads(self.request.body)
|
||||
@ -93,7 +102,7 @@ class ArtistMixHandler(tornado.web.RequestHandler):
|
||||
self.set_status(400)
|
||||
self.write({"error": "Missing 'artist' in request body"})
|
||||
return
|
||||
tracks = build_artist_mix(self.core, artist)
|
||||
tracks = build_artist_mix(self.core, artist, uris=self.uris)
|
||||
if not tracks:
|
||||
self.write({"playlist": None, "tracks": 0})
|
||||
return
|
||||
@ -143,9 +152,12 @@ class AlbumMixHandler(tornado.web.RequestHandler):
|
||||
|
||||
|
||||
class InstantMixHandler(tornado.web.RequestHandler):
|
||||
def initialize(self, core: CoreProxy, prefix: str) -> None:
|
||||
def initialize(
|
||||
self, core: CoreProxy, prefix: str, uris: list[Uri] | None = None,
|
||||
) -> None:
|
||||
self.core = core
|
||||
self.prefix = prefix
|
||||
self.uris = uris
|
||||
|
||||
def post(self) -> None:
|
||||
data = json.loads(self.request.body)
|
||||
@ -155,12 +167,17 @@ class InstantMixHandler(tornado.web.RequestHandler):
|
||||
self.set_status(400)
|
||||
self.write({"error": "Missing 'uri' in request body"})
|
||||
return
|
||||
tracks = build_instant_mix(self.core, track_uri, limit)
|
||||
tracks = build_instant_mix(self.core, track_uri, limit, uris=self.uris)
|
||||
if not tracks:
|
||||
self.write({"playlist": None, "tracks": 0})
|
||||
return
|
||||
|
||||
lookup_result = self.core.library.lookup([track_uri]).get()
|
||||
try:
|
||||
lookup_result = self.core.library.lookup([track_uri]).get()
|
||||
except Exception:
|
||||
logger.exception("Track lookup failed for %s", track_uri)
|
||||
self.write({"playlist": None, "tracks": 0})
|
||||
return
|
||||
seed_name = "Instant Mix"
|
||||
for uri_tracks in lookup_result.values():
|
||||
for t in uri_tracks:
|
||||
@ -204,7 +221,17 @@ class StatusHandler(tornado.web.RequestHandler):
|
||||
self.core = core
|
||||
|
||||
def get(self) -> None:
|
||||
result = self.core.playlists.as_list().get()
|
||||
try:
|
||||
result = self.core.playlists.as_list().get()
|
||||
except Exception:
|
||||
logger.exception("Failed to list playlists")
|
||||
self.set_status(500)
|
||||
self.write({
|
||||
"error": "Failed to list playlists",
|
||||
"smart_playlists": [],
|
||||
"count": 0,
|
||||
})
|
||||
return
|
||||
result_typed = cast("list[Playlist]", result)
|
||||
smart = [p for p in result_typed if p.uri and "smartplaylists" in p.uri]
|
||||
self.write(
|
||||
@ -222,15 +249,25 @@ class StatusHandler(tornado.web.RequestHandler):
|
||||
)
|
||||
|
||||
|
||||
def _parse_search_uris(config: Config) -> list[Uri] | None:
|
||||
raw = config.get("smartplaylists", {}).get("search_uris", "")
|
||||
if raw:
|
||||
uris = cast("list[Uri]", [u.strip() for u in raw.split(",") if u.strip()])
|
||||
return uris or None
|
||||
return None
|
||||
|
||||
|
||||
def app_factory(config: Config, core: CoreProxy) -> list[tuple]:
|
||||
prefix = config.get("smartplaylists", {}).get("playlist_prefix", "[Smart]")
|
||||
uris = _parse_search_uris(config)
|
||||
|
||||
return [
|
||||
(r"/decade", DecadeMixHandler, {"core": core, "prefix": prefix}),
|
||||
(r"/genre", GenreMixHandler, {"core": core, "prefix": prefix}),
|
||||
(r"/artist", ArtistMixHandler, {"core": core, "prefix": prefix}),
|
||||
(r"/decade", DecadeMixHandler, {"core": core, "prefix": prefix, "uris": uris}),
|
||||
(r"/genre", GenreMixHandler, {"core": core, "prefix": prefix, "uris": uris}),
|
||||
(r"/artist", ArtistMixHandler, {"core": core, "prefix": prefix, "uris": uris}),
|
||||
(r"/album", AlbumMixHandler, {"core": core, "prefix": prefix}),
|
||||
(r"/instant-mix", InstantMixHandler, {"core": core, "prefix": prefix}),
|
||||
(r"/instant-mix", InstantMixHandler,
|
||||
{"core": core, "prefix": prefix, "uris": uris}),
|
||||
(r"/refresh", RefreshHandler, {"core": core, "config": config}),
|
||||
(r"/status", StatusHandler, {"core": core}),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user