Can now browse favorites and artists
parent
8696450267
commit
6a3f0949cd
|
@ -15,6 +15,8 @@ Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* Searching for tracks, albums and artists available in your Funkwhale instance
|
* Searching for tracks, albums and artists available in your Funkwhale instance
|
||||||
|
* Browse all artists and albums
|
||||||
|
* Browse your favorites
|
||||||
* Simple configuration
|
* Simple configuration
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
@ -47,15 +49,19 @@ To enable the extension, add the following to your ``mopidy.conf`` file::
|
||||||
username = demo
|
username = demo
|
||||||
# Password to use when authenticating (leave empty fo anonymous access)
|
# Password to use when authenticating (leave empty fo anonymous access)
|
||||||
password = demo
|
password = demo
|
||||||
|
# duration of cache entries before they are removed, in seconds
|
||||||
|
# 0 to cache forever, empty to disable cache
|
||||||
|
cache_duration = 600
|
||||||
|
|
||||||
Of course, replace the demo values with your actual info (but you can
|
Of course, replace the demo values with your actual info (but you can
|
||||||
try using the demo server).
|
try using the demo server).
|
||||||
|
|
||||||
After that, reload your mopidy daemon, and you should be good!
|
After that, reload your mopidy daemon, and you should be good!
|
||||||
|
|
||||||
Todo
|
Todo
|
||||||
----
|
----
|
||||||
|
|
||||||
- Browse Funkwhale library and playlists
|
- Browse use library and playlists
|
||||||
|
|
||||||
|
|
||||||
.. _Mopidy: https://www.mopidy.com/
|
.. _Mopidy: https://www.mopidy.com/
|
||||||
|
|
|
@ -30,6 +30,7 @@ class Extension(mopidy.ext.Extension):
|
||||||
schema["url"] = mopidy.config.String()
|
schema["url"] = mopidy.config.String()
|
||||||
schema["username"] = mopidy.config.String(optional=True)
|
schema["username"] = mopidy.config.String(optional=True)
|
||||||
schema["password"] = mopidy.config.Secret(optional=True)
|
schema["password"] = mopidy.config.Secret(optional=True)
|
||||||
|
schema["cache_duration"] = mopidy.config.Integer(optional=True)
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
def validate_config(self, config):
|
def validate_config(self, config):
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from mopidy import httpclient, exceptions
|
from mopidy import httpclient, exceptions
|
||||||
|
|
||||||
from . import Extension, __version__
|
from . import Extension, __version__
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SessionWithUrlBase(requests.Session):
|
class SessionWithUrlBase(requests.Session):
|
||||||
# In Python 3 you could place `url_base` after `*args`, but not in Python 2.
|
# In Python 3 you could place `url_base` after `*args`, but not in Python 2.
|
||||||
|
@ -17,7 +20,10 @@ class SessionWithUrlBase(requests.Session):
|
||||||
# Next line of code is here for example purposes only.
|
# Next line of code is here for example purposes only.
|
||||||
# You really shouldn't just use string concatenation here,
|
# You really shouldn't just use string concatenation here,
|
||||||
# take a look at urllib.parse.urljoin instead.
|
# take a look at urllib.parse.urljoin instead.
|
||||||
modified_url = self.url_base + url
|
if url.startswith("http://") or url.startswith("https://"):
|
||||||
|
modified_url = url
|
||||||
|
else:
|
||||||
|
modified_url = self.url_base + url
|
||||||
|
|
||||||
return super(SessionWithUrlBase, self).request(method, modified_url, **kwargs)
|
return super(SessionWithUrlBase, self).request(method, modified_url, **kwargs)
|
||||||
|
|
||||||
|
@ -86,3 +92,31 @@ class APIClient(object):
|
||||||
response = self.session.get("tracks/", params=filters)
|
response = self.session.get("tracks/", params=filters)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
def list_artists(self, filters):
|
||||||
|
response = self.session.get("artists/", params=filters)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def list_albums(self, filters):
|
||||||
|
response = self.session.get("albums/", params=filters)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def load_all(self, first_page, max=0):
|
||||||
|
for i in first_page["results"]:
|
||||||
|
yield i
|
||||||
|
|
||||||
|
next_page = first_page.get("next")
|
||||||
|
counter = 0
|
||||||
|
while next_page:
|
||||||
|
logger.info("Fetching next page of result at url: %s", next_page)
|
||||||
|
response = self.session.get(next_page)
|
||||||
|
response.raise_for_status()
|
||||||
|
payload = response.json()
|
||||||
|
for i in payload["results"]:
|
||||||
|
yield i
|
||||||
|
counter += 1
|
||||||
|
next_page = payload.get("next")
|
||||||
|
if max and counter >= max:
|
||||||
|
next_page = None
|
||||||
|
|
|
@ -6,3 +6,6 @@ url = https://demo.funkwhale.audio
|
||||||
username = demo
|
username = demo
|
||||||
# Password to use when authenticating (leave empty fo anonymous access)
|
# Password to use when authenticating (leave empty fo anonymous access)
|
||||||
password = demo
|
password = demo
|
||||||
|
# duration of cache entries before they are removed, in seconds
|
||||||
|
# 0 to cache forever, empty to disable cache
|
||||||
|
cache_duration = 600
|
||||||
|
|
|
@ -41,10 +41,14 @@ class Cache(collections.OrderedDict):
|
||||||
super(Cache, self).__init__()
|
super(Cache, self).__init__()
|
||||||
|
|
||||||
def set(self, key, value):
|
def set(self, key, value):
|
||||||
|
if self.max_age is None:
|
||||||
|
return
|
||||||
now = time.time()
|
now = time.time()
|
||||||
self[key] = (now, value)
|
self[key] = (now, value)
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
|
if self.max_age is None:
|
||||||
|
return
|
||||||
value = super(Cache, self).get(key)
|
value = super(Cache, self).get(key)
|
||||||
if value is None:
|
if value is None:
|
||||||
return
|
return
|
||||||
|
@ -64,42 +68,146 @@ class FunkwhaleLibraryProvider(backend.LibraryProvider):
|
||||||
super(FunkwhaleLibraryProvider, self).__init__(*args, **kwargs)
|
super(FunkwhaleLibraryProvider, self).__init__(*args, **kwargs)
|
||||||
self.vfs = {"funkwhale:directory": collections.OrderedDict()}
|
self.vfs = {"funkwhale:directory": collections.OrderedDict()}
|
||||||
self.add_to_vfs(new_folder("Favorites", "favorites"))
|
self.add_to_vfs(new_folder("Favorites", "favorites"))
|
||||||
|
self.add_to_vfs(new_folder("Artists", "artists"))
|
||||||
# self.add_to_vfs(new_folder('Following', ['following']))
|
# self.add_to_vfs(new_folder('Following', ['following']))
|
||||||
# self.add_to_vfs(new_folder('Sets', ['sets']))
|
# self.add_to_vfs(new_folder('Sets', ['sets']))
|
||||||
# self.add_to_vfs(new_folder('Stream', ['stream']))
|
# self.add_to_vfs(new_folder('Stream', ['stream']))
|
||||||
self.cache = Cache()
|
self.cache = Cache(max_age=self.backend.config["funkwhale"]["cache_duration"])
|
||||||
|
|
||||||
def add_to_vfs(self, _model):
|
def add_to_vfs(self, _model):
|
||||||
self.vfs["funkwhale:directory"][_model.uri] = _model
|
self.vfs["funkwhale:directory"][_model.uri] = _model
|
||||||
|
|
||||||
def browse(self, uri):
|
def browse(self, uri):
|
||||||
|
cache_key = uri
|
||||||
|
from_cache = self.cache.get(cache_key)
|
||||||
|
if from_cache:
|
||||||
|
try:
|
||||||
|
len(from_cache)
|
||||||
|
return from_cache
|
||||||
|
except TypeError:
|
||||||
|
return [from_cache]
|
||||||
|
|
||||||
if not self.vfs.get(uri):
|
if not self.vfs.get(uri):
|
||||||
if uri.startswith("funkwhale:directory:"):
|
if uri.startswith("funkwhale:directory:"):
|
||||||
uri = uri.replace("funkwhale:directory:", "", 1)
|
uri = uri.replace("funkwhale:directory:", "", 1)
|
||||||
parts = uri.split(":")
|
parts = uri.split(":")
|
||||||
remaining = parts[1:] if len(parts) > 1 else []
|
remaining = parts[1:] if len(parts) > 1 else []
|
||||||
print("PARTS", parts, remaining)
|
|
||||||
handler = getattr(self, "browse_%s" % parts[0])
|
handler = getattr(self, "browse_%s" % parts[0])
|
||||||
return handler(remaining)
|
result, cache = handler(remaining)
|
||||||
|
if cache:
|
||||||
|
self.cache.set(cache_key, result)
|
||||||
|
return result
|
||||||
|
|
||||||
# root directory
|
# root directory
|
||||||
return self.vfs.get(uri, {}).values()
|
return self.vfs.get(uri, {}).values()
|
||||||
|
|
||||||
def browse_favorites(self, remaining):
|
def browse_favorites(self, remaining):
|
||||||
if remaining == []:
|
if remaining == []:
|
||||||
return [
|
return (
|
||||||
new_folder("Recent", "favorites:recent"),
|
[
|
||||||
new_folder("By artist", "favorites:by-artist"),
|
new_folder("Recent", "favorites:recent"),
|
||||||
]
|
# new_folder("By artist", "favorites:by-artist"),
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
if remaining == ["recent"]:
|
if remaining == ["recent"]:
|
||||||
payload = self.backend.client.list_tracks(
|
payload = self.backend.client.list_tracks(
|
||||||
{"favorites": "true", "ordering": "-creation_date", "page_size": 100}
|
{"favorites": "true", "ordering": "-creation_date", "page_size": 50}
|
||||||
)["results"]
|
)
|
||||||
return [
|
tracks = [
|
||||||
convert_to_track(row, ref=True, cache=self.cache) for row in payload
|
convert_to_track(row, ref=True, cache=self.cache)
|
||||||
|
for row in self.backend.client.load_all(payload, max=10)
|
||||||
]
|
]
|
||||||
return []
|
return tracks, True
|
||||||
|
return [], False
|
||||||
|
|
||||||
|
def browse_albums(self, uri_prefix, remaining):
|
||||||
|
if len(remaining) == 2:
|
||||||
|
album = remaining[1]
|
||||||
|
payload = self.backend.client.list_tracks(
|
||||||
|
{
|
||||||
|
"ordering": "position",
|
||||||
|
"page_size": 50,
|
||||||
|
"playable": "true",
|
||||||
|
"album": album,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tracks = [
|
||||||
|
convert_to_track(row, ref=True, cache=self.cache)
|
||||||
|
for row in self.backend.client.load_all(payload)
|
||||||
|
]
|
||||||
|
return tracks
|
||||||
|
else:
|
||||||
|
artist, album = remaining[0], None
|
||||||
|
payload = self.backend.client.list_albums(
|
||||||
|
{
|
||||||
|
"ordering": "title",
|
||||||
|
"page_size": 50,
|
||||||
|
"playable": "true",
|
||||||
|
"artist": artist,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
albums = [
|
||||||
|
convert_to_album(row, uri_prefix=uri_prefix, ref=True)
|
||||||
|
for row in self.backend.client.load_all(payload)
|
||||||
|
]
|
||||||
|
return albums
|
||||||
|
|
||||||
|
def browse_artists(self, remaining):
|
||||||
|
logger.debug("Handling artist route: %s", remaining)
|
||||||
|
if remaining == []:
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
new_folder("Recent", "artists:recent"),
|
||||||
|
new_folder("By name", "artists:by-name"),
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
|
root = remaining[0]
|
||||||
|
end = remaining[1:]
|
||||||
|
albums_uri_prefix = "funkwhale:directory:artists:" + ":".join(
|
||||||
|
[str(i) for i in remaining]
|
||||||
|
)
|
||||||
|
if root == "recent":
|
||||||
|
if end:
|
||||||
|
# list albums
|
||||||
|
return (
|
||||||
|
self.browse_albums(uri_prefix=albums_uri_prefix, remaining=end),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
# list recent artists
|
||||||
|
payload = self.backend.client.list_artists(
|
||||||
|
{"ordering": "-creation_date", "page_size": 50, "playable": "true"}
|
||||||
|
)
|
||||||
|
uri_prefix = "funkwhale:directory:artists:recent"
|
||||||
|
|
||||||
|
artists = [
|
||||||
|
convert_to_artist(row, uri_prefix=uri_prefix, ref=True)
|
||||||
|
for row in self.backend.client.load_all(payload, max=1)
|
||||||
|
]
|
||||||
|
return artists, True
|
||||||
|
|
||||||
|
if root == "by-name":
|
||||||
|
if end:
|
||||||
|
# list albums
|
||||||
|
return (
|
||||||
|
self.browse_albums(uri_prefix=albums_uri_prefix, remaining=end),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
# list recent artists
|
||||||
|
payload = self.backend.client.list_artists(
|
||||||
|
{"ordering": "name", "page_size": 50, "playable": "true"}
|
||||||
|
)
|
||||||
|
uri_prefix = "funkwhale:directory:artists:by-name"
|
||||||
|
artists = [
|
||||||
|
convert_to_artist(row, uri_prefix=uri_prefix, ref=True)
|
||||||
|
for row in self.backend.client.load_all(payload)
|
||||||
|
]
|
||||||
|
return artists, True
|
||||||
|
|
||||||
|
return [], False
|
||||||
|
|
||||||
def search(self, query=None, uris=None, exact=False):
|
def search(self, query=None, uris=None, exact=False):
|
||||||
# TODO Support exact search
|
# TODO Support exact search
|
||||||
|
@ -119,7 +227,6 @@ class FunkwhaleLibraryProvider(backend.LibraryProvider):
|
||||||
)
|
)
|
||||||
|
|
||||||
def lookup(self, uri):
|
def lookup(self, uri):
|
||||||
print("CACHE", self.cache, uri)
|
|
||||||
from_cache = self.cache.get(uri)
|
from_cache = self.cache.get(uri)
|
||||||
if from_cache:
|
if from_cache:
|
||||||
try:
|
try:
|
||||||
|
@ -153,8 +260,10 @@ def parse_uri(uri):
|
||||||
|
|
||||||
|
|
||||||
def cast_to_ref(f):
|
def cast_to_ref(f):
|
||||||
def inner(payload, ref=False, cache=None):
|
def inner(payload, *args, **kwargs):
|
||||||
result = f(payload)
|
ref = kwargs.pop("ref", False)
|
||||||
|
cache = kwargs.pop("cache", None)
|
||||||
|
result = f(payload, *args, **kwargs)
|
||||||
if cache is not None:
|
if cache is not None:
|
||||||
cache.set(result.uri, result)
|
cache.set(result.uri, result)
|
||||||
if ref:
|
if ref:
|
||||||
|
@ -165,9 +274,9 @@ def cast_to_ref(f):
|
||||||
|
|
||||||
|
|
||||||
@cast_to_ref
|
@cast_to_ref
|
||||||
def convert_to_artist(payload):
|
def convert_to_artist(payload, uri_prefix="funkwhale:artists"):
|
||||||
return models.Artist(
|
return models.Artist(
|
||||||
uri="funkwhale:artists:%s" % (payload["id"],),
|
uri=uri_prefix + ":%s" % payload["id"],
|
||||||
name=payload["name"],
|
name=payload["name"],
|
||||||
sortname=payload["name"],
|
sortname=payload["name"],
|
||||||
musicbrainz_id=payload["mbid"],
|
musicbrainz_id=payload["mbid"],
|
||||||
|
@ -175,12 +284,12 @@ def convert_to_artist(payload):
|
||||||
|
|
||||||
|
|
||||||
@cast_to_ref
|
@cast_to_ref
|
||||||
def convert_to_album(payload):
|
def convert_to_album(payload, uri_prefix="funkwhale:albums"):
|
||||||
artist = convert_to_artist(payload["artist"])
|
artist = convert_to_artist(payload["artist"])
|
||||||
image = payload["cover"]["original"] if payload["cover"] else None
|
image = payload["cover"]["original"] if payload["cover"] else None
|
||||||
|
|
||||||
return models.Album(
|
return models.Album(
|
||||||
uri="funkwhale:albums:%s" % (payload["id"],),
|
uri=uri_prefix + ":%s" % payload["id"],
|
||||||
name=payload["title"],
|
name=payload["title"],
|
||||||
musicbrainz_id=payload["mbid"],
|
musicbrainz_id=payload["mbid"],
|
||||||
images=[image] if image else [],
|
images=[image] if image else [],
|
||||||
|
@ -191,11 +300,11 @@ def convert_to_album(payload):
|
||||||
|
|
||||||
|
|
||||||
@cast_to_ref
|
@cast_to_ref
|
||||||
def convert_to_track(payload):
|
def convert_to_track(payload, uri_prefix="funkwhale:tracks"):
|
||||||
artist = convert_to_artist(payload["artist"])
|
artist = convert_to_artist(payload["artist"])
|
||||||
album = convert_to_album(payload["album"])
|
album = convert_to_album(payload["album"])
|
||||||
return models.Track(
|
return models.Track(
|
||||||
uri="funkwhale:tracks:%s" % (payload["id"],),
|
uri=uri_prefix + ":%s" % payload["id"],
|
||||||
name=payload["title"],
|
name=payload["title"],
|
||||||
musicbrainz_id=payload["mbid"],
|
musicbrainz_id=payload["mbid"],
|
||||||
artists=[artist],
|
artists=[artist],
|
||||||
|
|
|
@ -10,7 +10,12 @@ FUNKWHALE_URL = "https://test.funkwhale"
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def config():
|
def config():
|
||||||
return {
|
return {
|
||||||
"funkwhale": {"url": FUNKWHALE_URL, "username": "user", "password": "passw0rd"},
|
"funkwhale": {
|
||||||
|
"url": FUNKWHALE_URL,
|
||||||
|
"username": "user",
|
||||||
|
"password": "passw0rd",
|
||||||
|
"cache_duration": 600,
|
||||||
|
},
|
||||||
"proxy": {},
|
"proxy": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,3 +24,33 @@ def test_client_list_tracks(client, requests_mock):
|
||||||
|
|
||||||
result = client.list_tracks({"artist": 12})
|
result = client.list_tracks({"artist": 12})
|
||||||
assert result == {"hello": "world"}
|
assert result == {"hello": "world"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_list_artists(client, requests_mock):
|
||||||
|
requests_mock.get(
|
||||||
|
client.session.url_base + "artists/?playable=true", json={"hello": "world"}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = client.list_artists({"playable": "true"})
|
||||||
|
assert result == {"hello": "world"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_list_albums(client, requests_mock):
|
||||||
|
requests_mock.get(
|
||||||
|
client.session.url_base + "albums/?playable=true", json={"hello": "world"}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = client.list_albums({"playable": "true"})
|
||||||
|
assert result == {"hello": "world"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_all(client, requests_mock):
|
||||||
|
page1 = {"results": [1, 2, 3], "next": "https://first.page"}
|
||||||
|
page2 = {"results": [4, 5, 6], "next": "https://second.page"}
|
||||||
|
page3 = {"results": [7, 8, 9], "next": None}
|
||||||
|
requests_mock.get(page1["next"], json=page2)
|
||||||
|
requests_mock.get(page2["next"], json=page3)
|
||||||
|
assert (
|
||||||
|
list(client.load_all(page1))
|
||||||
|
== page1["results"] + page2["results"] + page3["results"]
|
||||||
|
)
|
||||||
|
|
|
@ -22,3 +22,4 @@ def test_get_config_schema():
|
||||||
assert "url" in schema
|
assert "url" in schema
|
||||||
assert "username" in schema
|
assert "username" in schema
|
||||||
assert "password" in schema
|
assert "password" in schema
|
||||||
|
assert "cache_duration" in schema
|
||||||
|
|
|
@ -113,19 +113,26 @@ def test_parse_uri(type):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_browse_routing(library, path, expected_handler, mocker, remaining):
|
def test_browse_routing(library, path, expected_handler, mocker, remaining):
|
||||||
handler = mocker.patch.object(library, expected_handler, return_value="test")
|
handler = mocker.patch.object(
|
||||||
|
library, expected_handler, return_value=("test", False)
|
||||||
|
)
|
||||||
|
|
||||||
assert library.browse(path) == "test"
|
assert library.browse(path) == "test"
|
||||||
assert handler.called_once_with(remaining)
|
assert handler.called_once_with(remaining)
|
||||||
|
|
||||||
|
|
||||||
def test_browse_favorites_root(library):
|
def test_browse_favorites_root(library):
|
||||||
expected = [
|
expected = (
|
||||||
models.Ref.directory(uri="funkwhale:directory:favorites:recent", name="Recent"),
|
[
|
||||||
models.Ref.directory(
|
models.Ref.directory(
|
||||||
uri="funkwhale:directory:favorites:by-artist", name="By artist"
|
uri="funkwhale:directory:favorites:recent", name="Recent"
|
||||||
),
|
),
|
||||||
]
|
# models.Ref.directory(
|
||||||
|
# uri="funkwhale:directory:favorites:by-artist", name="By artist"
|
||||||
|
# ),
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
)
|
||||||
assert library.browse_favorites([]) == expected
|
assert library.browse_favorites([]) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,16 +140,96 @@ def test_browse_favorites_recent(library, client, requests_mock):
|
||||||
track = factories.TrackJSONFactory()
|
track = factories.TrackJSONFactory()
|
||||||
url = (
|
url = (
|
||||||
client.session.url_base
|
client.session.url_base
|
||||||
+ "tracks/?favorites=true&page_size=100&&ordering=-creation_date"
|
+ "tracks/?favorites=true&page_size=50&&ordering=-creation_date"
|
||||||
)
|
)
|
||||||
requests_mock.get(url, json={"results": [track]})
|
requests_mock.get(url, json={"results": [track]})
|
||||||
|
|
||||||
expected = [mopidy_funkwhale.library.convert_to_track(track, ref=True)]
|
expected = [mopidy_funkwhale.library.convert_to_track(track, ref=True)], True
|
||||||
result = library.browse_favorites(["recent"])
|
result = library.browse_favorites(["recent"])
|
||||||
|
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_browse_artists_root(library):
|
||||||
|
expected = (
|
||||||
|
[
|
||||||
|
models.Ref.directory(
|
||||||
|
uri="funkwhale:directory:artists:recent", name="Recent"
|
||||||
|
),
|
||||||
|
models.Ref.directory(
|
||||||
|
uri="funkwhale:directory:artists:by-name", name="By name"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
assert library.browse_artists([]) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_browse_artists_recent(client, library, requests_mock):
|
||||||
|
artist1 = factories.ArtistJSONFactory()
|
||||||
|
artist2 = factories.ArtistJSONFactory()
|
||||||
|
url = (
|
||||||
|
client.session.url_base
|
||||||
|
+ "artists/?page_size=50&ordering=-creation_date&playable=true"
|
||||||
|
)
|
||||||
|
requests_mock.get(url, json={"results": [artist1, artist2]})
|
||||||
|
uri_prefix = "funkwhale:directory:artists:recent"
|
||||||
|
|
||||||
|
expected = (
|
||||||
|
[
|
||||||
|
mopidy_funkwhale.library.convert_to_artist(
|
||||||
|
artist1, uri_prefix=uri_prefix, ref=True
|
||||||
|
),
|
||||||
|
mopidy_funkwhale.library.convert_to_artist(
|
||||||
|
artist2, uri_prefix=uri_prefix, ref=True
|
||||||
|
),
|
||||||
|
],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
assert library.browse_artists(["recent"]) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_browse_artists_albums(client, library, requests_mock):
|
||||||
|
album1 = factories.AlbumJSONFactory()
|
||||||
|
album2 = factories.AlbumJSONFactory(artist=album1["artist"])
|
||||||
|
url = (
|
||||||
|
client.session.url_base
|
||||||
|
+ "albums/?page_size=50&ordering=title&playable=true&artist%s"
|
||||||
|
% album1["artist"]["id"]
|
||||||
|
)
|
||||||
|
requests_mock.get(url, json={"results": [album1, album2]})
|
||||||
|
uri_prefix = "funkwhale:directory:artists:by-name:%s" % album1["artist"]["id"]
|
||||||
|
expected = (
|
||||||
|
[
|
||||||
|
mopidy_funkwhale.library.convert_to_album(
|
||||||
|
album1, uri_prefix=uri_prefix, ref=True
|
||||||
|
),
|
||||||
|
mopidy_funkwhale.library.convert_to_album(
|
||||||
|
album2, uri_prefix=uri_prefix, ref=True
|
||||||
|
),
|
||||||
|
],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
assert library.browse_artists(["by-name", album1["artist"]["id"]]) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_browse_artists_album_single(client, library, requests_mock):
|
||||||
|
track = factories.TrackJSONFactory()
|
||||||
|
url = (
|
||||||
|
client.session.url_base
|
||||||
|
+ "tracks/?page_size=50&ordering=position&playable=true&album="
|
||||||
|
+ str(track["album"]["id"])
|
||||||
|
)
|
||||||
|
requests_mock.get(url, json={"results": [track]})
|
||||||
|
expected = ([mopidy_funkwhale.library.convert_to_track(track, ref=True)], True)
|
||||||
|
assert (
|
||||||
|
library.browse_artists(
|
||||||
|
["by-name", track["album"]["artist"]["id"], track["album"]["id"]]
|
||||||
|
)
|
||||||
|
== expected
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_cache_set():
|
def test_cache_set():
|
||||||
cache = mopidy_funkwhale.library.Cache()
|
cache = mopidy_funkwhale.library.Cache()
|
||||||
cache.set("hello:world", "value")
|
cache.set("hello:world", "value")
|
||||||
|
@ -162,3 +249,19 @@ def test_cache_key_too_old():
|
||||||
cache["hello:world"] = (t, "value")
|
cache["hello:world"] = (t, "value")
|
||||||
assert cache.get("hello:world") is None
|
assert cache.get("hello:world") is None
|
||||||
assert "hello:world" not in cache
|
assert "hello:world" not in cache
|
||||||
|
|
||||||
|
|
||||||
|
def test_lookup_from_cache(library):
|
||||||
|
track = object()
|
||||||
|
library.cache.set("funkwhale:artists:42", track)
|
||||||
|
|
||||||
|
result = library.lookup("funkwhale:artists:42")
|
||||||
|
assert result == [track]
|
||||||
|
|
||||||
|
|
||||||
|
def test_lookup_from_cache_iterable(library):
|
||||||
|
track = [object()]
|
||||||
|
library.cache.set("funkwhale:artists:42", track)
|
||||||
|
|
||||||
|
result = library.lookup("funkwhale:artists:42")
|
||||||
|
assert result == track
|
||||||
|
|
Loading…
Reference in New Issue