Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Erin
mopidy
Commits
2fe6b42b
Commit
2fe6b42b
authored
Oct 04, 2018
by
Eliot Berriot
Browse files
Merge branch 'browse' into 'master'
Browse artists and favorites See merge request
funkwhale/mopidy!3
parents
8d51087d
6a3f0949
Changes
12
Hide whitespace changes
Inline
Side-by-side
README.rst
View file @
2fe6b42b
...
...
@@ -15,6 +15,8 @@ Features
--------
* Searching for tracks, albums and artists available in your Funkwhale instance
* Browse all artists and albums
* Browse your favorites
* Simple configuration
Installation
...
...
@@ -47,15 +49,19 @@ To enable the extension, add the following to your ``mopidy.conf`` file::
username = demo
# Password to use when authenticating (leave empty fo anonymous access)
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
try using the demo server).
After that, reload your mopidy daemon, and you should be good!
Todo
----
- Browse
Funkwhal
e library and playlists
- Browse
us
e library and playlists
.. _Mopidy: https://www.mopidy.com/
...
...
mopidy_funkwhale/__init__.py
View file @
2fe6b42b
...
...
@@ -9,39 +9,41 @@ import os
__author__
=
"""Eliot Berriot"""
__email__
=
'
contact+funkwhale@eliotberriot.com
'
__version__
=
'
0.1.0
'
__email__
=
"
contact+funkwhale@eliotberriot.com
"
__version__
=
"
0.1.0
"
logger
=
logging
.
getLogger
(
__name__
)
class
Extension
(
mopidy
.
ext
.
Extension
):
dist_name
=
'
Mopidy-Funkwhale
'
ext_name
=
'
funkwhale
'
dist_name
=
"
Mopidy-Funkwhale
"
ext_name
=
"
funkwhale
"
version
=
__version__
def
get_default_config
(
self
):
conf_file
=
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'
ext.conf
'
)
conf_file
=
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
"
ext.conf
"
)
return
mopidy
.
config
.
read
(
conf_file
)
def
get_config_schema
(
self
):
schema
=
super
(
Extension
,
self
).
get_config_schema
()
schema
[
'url'
]
=
mopidy
.
config
.
String
()
schema
[
'username'
]
=
mopidy
.
config
.
String
(
optional
=
True
)
schema
[
'password'
]
=
mopidy
.
config
.
Secret
(
optional
=
True
)
schema
[
"url"
]
=
mopidy
.
config
.
String
()
schema
[
"username"
]
=
mopidy
.
config
.
String
(
optional
=
True
)
schema
[
"password"
]
=
mopidy
.
config
.
Secret
(
optional
=
True
)
schema
[
"cache_duration"
]
=
mopidy
.
config
.
Integer
(
optional
=
True
)
return
schema
def
validate_config
(
self
,
config
):
if
not
config
.
getboolean
(
'
funkwhale
'
,
'
enabled
'
):
if
not
config
.
getboolean
(
"
funkwhale
"
,
"
enabled
"
):
return
username
=
config
.
getstring
(
'
funkwhale
'
,
'
username
'
)
password
=
config
.
getstring
(
'
funkwhale
'
,
'
password
'
)
username
=
config
.
getstring
(
"
funkwhale
"
,
"
username
"
)
password
=
config
.
getstring
(
"
funkwhale
"
,
"
password
"
)
if
any
([
username
,
password
])
and
not
all
([
username
,
password
]):
raise
mopidy
.
ext
.
ExtensionError
(
'
You need to provide username and password to authenticate with the funkwhale backend
'
"
You need to provide username and password to authenticate with the funkwhale backend
"
)
def
setup
(
self
,
registry
):
from
.
import
actor
registry
.
add
(
'backend'
,
actor
.
FunkwhaleBackend
)
registry
.
add
(
"backend"
,
actor
.
FunkwhaleBackend
)
mopidy_funkwhale/actor.py
View file @
2fe6b42b
...
...
@@ -14,34 +14,37 @@ logger = logging.getLogger(__name__)
class
FunkwhaleBackend
(
pykka
.
ThreadingActor
,
backend
.
Backend
):
def
__init__
(
self
,
config
,
audio
):
super
(
FunkwhaleBackend
,
self
).
__init__
()
self
.
config
=
config
self
.
remote
=
client
.
Funkwhale
Client
(
config
)
self
.
client
=
client
.
API
Client
(
config
)
self
.
library
=
library
.
FunkwhaleLibraryProvider
(
backend
=
self
)
self
.
playback
=
FunkwhalePlaybackProvider
(
audio
=
audio
,
backend
=
self
)
self
.
uri_schemes
=
[
'
funkwhale
'
,
'
fw
'
]
self
.
uri_schemes
=
[
"
funkwhale
"
,
"
fw
"
]
def
on_start
(
self
):
username
=
self
.
remote
.
username
if
username
is
not
None
:
logger
.
info
(
'Logged in to Funkwhale as "%s" on "%s"'
,
username
,
self
.
config
[
'funkwhale'
][
'url'
])
if
self
.
client
.
username
is
not
None
:
self
.
client
.
login
()
logger
.
info
(
'Logged in to Funkwhale as "%s" on "%s"'
,
self
.
client
.
username
,
self
.
config
[
"funkwhale"
][
"url"
],
)
else
:
logger
.
info
(
'Using "%s" anonymously'
,
self
.
config
[
'
funkwhale
'
][
'
url
'
])
logger
.
info
(
'Using "%s" anonymously'
,
self
.
config
[
"
funkwhale
"
][
"
url
"
])
class
FunkwhalePlaybackProvider
(
backend
.
PlaybackProvider
):
def
translate_uri
(
self
,
uri
):
_
,
id
=
library
.
parse_uri
(
uri
)
track
=
self
.
backend
.
remote
.
http_
client
.
get_track
(
id
)
track
=
self
.
backend
.
client
.
get_track
(
id
)
if
track
is
None
:
return
None
url
=
track
[
'
listen_url
'
]
if
url
.
startswith
(
'/'
):
url
=
self
.
backend
.
config
[
'
funkwhale
'
][
'
url
'
]
+
url
if
self
.
backend
.
remote
.
token
:
url
+=
'
?jwt=
'
+
self
.
backend
.
remote
.
token
url
=
track
[
"
listen_url
"
]
if
url
.
startswith
(
"/"
):
url
=
self
.
backend
.
config
[
"
funkwhale
"
][
"
url
"
]
+
url
if
self
.
backend
.
client
.
token
:
url
+=
"
?jwt=
"
+
self
.
backend
.
client
.
token
return
url
mopidy_funkwhale/client.py
View file @
2fe6b42b
from
__future__
import
unicode_literals
import
logging
import
requests
from
mopidy
import
httpclient
,
exceptions
from
.
import
Extension
,
__version__
logger
=
logging
.
getLogger
(
__name__
)
class
SessionWithUrlBase
(
requests
.
Session
):
# In Python 3 you could place `url_base` after `*args`, but not in Python 2.
...
...
@@ -17,74 +20,103 @@ class SessionWithUrlBase(requests.Session):
# Next line of code is here for example purposes only.
# You really shouldn't just use string concatenation here,
# 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
)
def
get_requests_session
(
url
,
proxy_config
,
user_agent
):
if
not
url
.
endswith
(
'/'
):
url
+=
'/'
url
+=
'
api/v1/
'
if
not
url
.
endswith
(
"/"
):
url
+=
"/"
url
+=
"
api/v1/
"
proxy
=
httpclient
.
format_proxy
(
proxy_config
)
full_user_agent
=
httpclient
.
format_user_agent
(
user_agent
)
session
=
SessionWithUrlBase
(
url_base
=
url
)
session
.
proxies
.
update
({
'
http
'
:
proxy
,
'
https
'
:
proxy
})
session
.
headers
.
update
({
'
user-agent
'
:
full_user_agent
})
session
.
proxies
.
update
({
"
http
"
:
proxy
,
"
https
"
:
proxy
})
session
.
headers
.
update
({
"
user-agent
"
:
full_user_agent
})
return
session
def
login
(
session
,
username
,
password
):
response
=
session
.
post
(
'token/'
,
{
'username'
:
username
,
'password'
:
password
})
if
not
username
:
return
response
=
session
.
post
(
"token/"
,
{
"username"
:
username
,
"password"
:
password
})
try
:
response
.
raise_for_status
()
except
requests
.
exceptions
.
HTTPError
:
raise
exceptions
.
BackendError
(
'
Authentication failed for user %s
'
%
(
username
,))
token
=
response
.
json
()[
'
token
'
]
session
.
headers
.
update
({
'
Authorization
'
:
'
JWT %s
'
%
(
token
,)})
raise
exceptions
.
BackendError
(
"
Authentication failed for user %s
"
%
(
username
,))
token
=
response
.
json
()[
"
token
"
]
session
.
headers
.
update
({
"
Authorization
"
:
"
JWT %s
"
%
(
token
,)})
return
token
class
APIClient
(
object
):
def
__init__
(
self
,
session
):
self
.
session
=
session
def
__init__
(
self
,
config
):
self
.
config
=
config
self
.
session
=
get_requests_session
(
config
[
"funkwhale"
][
"url"
],
proxy_config
=
config
[
"proxy"
],
user_agent
=
"%s/%s"
%
(
Extension
.
dist_name
,
__version__
),
)
self
.
username
=
self
.
config
[
"funkwhale"
][
"username"
]
self
.
token
=
None
def
login
(
self
):
self
.
username
=
self
.
config
[
"funkwhale"
][
"username"
]
if
self
.
username
:
self
.
token
=
login
(
self
.
session
,
self
.
config
[
"funkwhale"
][
"username"
],
self
.
config
[
"funkwhale"
][
"password"
],
)
else
:
self
.
token
=
None
def
search
(
self
,
query
):
response
=
self
.
session
.
get
(
'
search
'
,
params
=
{
'
query
'
:
query
})
response
=
self
.
session
.
get
(
"
search
"
,
params
=
{
"
query
"
:
query
})
response
.
raise_for_status
()
return
response
.
json
()
def
get_track
(
self
,
id
):
response
=
self
.
session
.
get
(
'
tracks/{}/
'
.
format
(
id
))
response
=
self
.
session
.
get
(
"
tracks/{}/
"
.
format
(
id
))
response
.
raise_for_status
()
return
response
.
json
()
def
list_tracks
(
self
,
filters
):
response
=
self
.
session
.
get
(
'
tracks/
'
,
params
=
filters
)
response
=
self
.
session
.
get
(
"
tracks/
"
,
params
=
filters
)
response
.
raise_for_status
()
return
response
.
json
()
def
list_artists
(
self
,
filters
):
response
=
self
.
session
.
get
(
"artists/"
,
params
=
filters
)
response
.
raise_for_status
()
return
response
.
json
()
class
FunkwhaleClient
(
object
):
def
list_albums
(
self
,
filters
):
response
=
self
.
session
.
get
(
"albums/"
,
params
=
filters
)
response
.
raise_for_status
()
return
response
.
json
()
def
__init__
(
self
,
config
):
super
(
FunkwhaleClient
,
self
).
__init__
()
self
.
page_size
=
config
[
'funkwhale'
].
get
(
'page_size'
,
50
)
self
.
http_client
=
APIClient
(
session
=
get_requests_session
(
config
[
'funkwhale'
][
'url'
],
proxy_config
=
config
[
'proxy'
],
user_agent
=
'%s/%s'
%
(
Extension
.
dist_name
,
__version__
),
)
)
self
.
username
=
config
[
'funkwhale'
][
'username'
]
self
.
token
=
None
if
config
[
'funkwhale'
][
'username'
]:
self
.
token
=
login
(
self
.
http_client
.
session
,
config
[
'funkwhale'
][
'username'
],
config
[
'funkwhale'
][
'password'
])
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
mopidy_funkwhale/ext.conf
View file @
2fe6b42b
...
...
@@ -6,3 +6,6 @@ url = https://demo.funkwhale.audio
username
=
demo
# Password to use when authenticating (leave empty fo anonymous access)
password
=
demo
# duration of cache entries before they are removed, in seconds
# 0 to cache forever, empty to disable cache
cache_duration
=
600
mopidy_funkwhale/library.py
View file @
2fe6b42b
...
...
@@ -3,12 +3,21 @@ from __future__ import unicode_literals
import
collections
import
logging
import
re
import
time
import
urllib
from
mopidy
import
backend
,
models
logger
=
logging
.
getLogger
(
__name__
)
def
generate_uri
(
path
):
return
"funkwhale:directory:%s"
%
path
def
new_folder
(
name
,
path
):
return
models
.
Ref
.
directory
(
uri
=
generate_uri
(
path
),
name
=
name
)
def
simplify_search_query
(
query
):
...
...
@@ -19,165 +28,294 @@ def simplify_search_query(query):
r
.
extend
(
v
)
else
:
r
.
append
(
v
)
return
' '
.
join
(
r
)
return
" "
.
join
(
r
)
if
isinstance
(
query
,
list
):
return
' '
.
join
(
query
)
return
" "
.
join
(
query
)
else
:
return
query
class
Cache
(
collections
.
OrderedDict
):
def
__init__
(
self
,
max_age
=
0
):
self
.
max_age
=
max_age
super
(
Cache
,
self
).
__init__
()
def
set
(
self
,
key
,
value
):
if
self
.
max_age
is
None
:
return
now
=
time
.
time
()
self
[
key
]
=
(
now
,
value
)
def
get
(
self
,
key
):
if
self
.
max_age
is
None
:
return
value
=
super
(
Cache
,
self
).
get
(
key
)
if
value
is
None
:
return
now
=
time
.
time
()
t
,
v
=
value
if
self
.
max_age
and
t
+
self
.
max_age
<
now
:
# entry is too old, we delete it
del
self
[
key
]
return
None
return
v
class
FunkwhaleLibraryProvider
(
backend
.
LibraryProvider
):
root_directory
=
models
.
Ref
.
directory
(
uri
=
'funkwhale:directory'
,
name
=
'Funkwhale'
)
root_directory
=
models
.
Ref
.
directory
(
uri
=
"funkwhale:directory"
,
name
=
"Funkwhale"
)
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
FunkwhaleLibraryProvider
,
self
).
__init__
(
*
args
,
**
kwargs
)
self
.
vfs
=
{
'funkwhale:directory'
:
collections
.
OrderedDict
()}
# self.add_to_vfs(new_folder('Favorites', ['favorites']))
self
.
vfs
=
{
"funkwhale:directory"
:
collections
.
OrderedDict
()}
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('Sets', ['sets']))
# self.add_to_vfs(new_folder('Stream', ['stream']))
self
.
cache
=
Cache
(
max_age
=
self
.
backend
.
config
[
"funkwhale"
][
"cache_duration"
])
def
add_to_vfs
(
self
,
_model
):
self
.
vfs
[
'funkwhale:directory'
][
_model
.
uri
]
=
_model
def
list_sets
(
self
):
sets_vfs
=
collections
.
OrderedDict
()
for
(
name
,
set_id
,
tracks
)
in
self
.
backend
.
remote
.
get_sets
():
sets_list
=
new_folder
(
name
,
[
'sets'
,
set_id
])
logger
.
debug
(
'Adding set %s to vfs'
%
sets_list
.
name
)
sets_vfs
[
set_id
]
=
sets_list
return
sets_vfs
.
values
()
def
list_favorites
(
self
):
vfs_list
=
collections
.
OrderedDict
()
for
track
in
self
.
backend
.
remote
.
get_likes
():
logger
.
debug
(
'Adding liked track %s to vfs'
%
track
.
name
)
vfs_list
[
track
.
name
]
=
models
.
Ref
.
track
(
uri
=
track
.
uri
,
name
=
track
.
name
)
return
vfs_list
.
values
()
def
list_user_follows
(
self
):
sets_vfs
=
collections
.
OrderedDict
()
for
(
name
,
user_id
)
in
self
.
backend
.
remote
.
get_followings
():
sets_list
=
new_folder
(
name
,
[
'following'
,
user_id
])
logger
.
debug
(
'Adding set %s to vfs'
%
sets_list
.
name
)
sets_vfs
[
user_id
]
=
sets_list
return
sets_vfs
.
values
()
def
tracklist_to_vfs
(
self
,
track_list
):
vfs_list
=
collections
.
OrderedDict
()
for
temp_track
in
track_list
:
if
not
isinstance
(
temp_track
,
Track
):
temp_track
=
self
.
backend
.
remote
.
parse_track
(
temp_track
)
if
hasattr
(
temp_track
,
'uri'
):
vfs_list
[
temp_track
.
name
]
=
models
.
Ref
.
track
(
uri
=
temp_track
.
uri
,
name
=
temp_track
.
name
)
return
vfs_list
.
values
()
self
.
vfs
[
"funkwhale:directory"
][
_model
.
uri
]
=
_model
def
browse
(
self
,
uri
):
if
not
self
.
vfs
.
get
(
uri
):
(
req_type
,
res_id
)
=
re
.
match
(
r
'.*:(\w*)(?:/(\d*))?'
,
uri
).
groups
()
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
'favorites'
==
req_type
:
return
self
.
list_favorites
()
if
not
self
.
vfs
.
get
(
uri
):
if
uri
.
startswith
(
"funkwhale:directory:"
):
uri
=
uri
.
replace
(
"funkwhale:directory:"
,
""
,
1
)
parts
=
uri
.
split
(
":"
)
remaining
=
parts
[
1
:]
if
len
(
parts
)
>
1
else
[]
handler
=
getattr
(
self
,
"browse_%s"
%
parts
[
0
])
result
,
cache
=
handler
(
remaining
)
if
cache
:
self
.
cache
.
set
(
cache_key
,
result
)
return
result
# root directory
return
self
.
vfs
.
get
(
uri
,
{}).
values
()
def
browse_favorites
(
self
,
remaining
):
if
remaining
==
[]:
return
(
[
new_folder
(
"Recent"
,
"favorites:recent"
),
# new_folder("By artist", "favorites:by-artist"),
],
False
,
)
if
remaining
==
[
"recent"
]:
payload
=
self
.
backend
.
client
.
list_tracks
(
{
"favorites"
:
"true"
,
"ordering"
:
"-creation_date"
,
"page_size"
:
50
}
)
tracks
=
[
convert_to_track
(
row
,
ref
=
True
,
cache
=
self
.
cache
)
for
row
in
self
.
backend
.
client
.
load_all
(
payload
,
max
=
10
)
]
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
):
# TODO Support exact search
if
not
query
:
return
if
'uri'
in
query
:
search_query
=
''
.
join
(
query
[
'uri'
])
url
=
urlparse
(
search_query
)
if
'soundcloud.com'
in
url
.
netloc
:
logger
.
info
(
'Resolving SoundCloud for: %s'
,
search_query
)
return
SearchResult
(
uri
=
'soundcloud:search'
,
tracks
=
self
.
backend
.
remote
.
resolve_url
(
search_query
)
)
else
:
search_query
=
simplify_search_query
(
query
)
logger
.
info
(
'
Searching Funkwhale for: %s
'
,
search_query
)
raw_results
=
self
.
backend
.
remote
.
http_
client
.
search
(
search_query
)
artists
=
[
convert_to_artist
(
row
)
for
row
in
raw_results
[
'
artists
'
]]
albums
=
[
convert_to_album
(
row
)
for
row
in
raw_results
[
'
albums
'
]]
tracks
=
[
convert_to_track
(
row
)
for
row
in
raw_results
[
'
tracks
'
]]
logger
.
info
(
"
Searching Funkwhale for: %s
"
,
search_query
)
raw_results
=
self
.
backend
.
client
.
search
(
search_query
)
artists
=
[
convert_to_artist
(
row
)
for
row
in
raw_results
[
"
artists
"
]]
albums
=
[
convert_to_album
(
row
)
for
row
in
raw_results
[
"
albums
"
]]
tracks
=
[
convert_to_track
(
row
)
for
row
in
raw_results
[
"
tracks
"
]]
return
models
.
SearchResult
(
uri
=
'funkwhale:search'
,
tracks
=
tracks
,
albums
=
albums
,
artists
=
artists
,
uri
=
"funkwhale:search"
,
tracks
=
tracks
,
albums
=
albums
,
artists
=
artists
)
def
lookup
(
self
,
uri
):
if
'fw:'
in
uri
:
uri
=
uri
.
replace
(
'fw:'
,
''
)
from_cache
=
self
.
cache
.
get
(
uri
)
if
from_cache
:
try
:
len
(
from_cache
)
return
from_cache
except
TypeError
:
return
[
from_cache
]