Skip to content

Commit fdd2999

Browse files
committed
playlists: Use the Web API (Fixes mopidy#122, mopidy#182).
Cache playlist web API responses in a simple dict. playlists: Support Spotify's new playlist URI scheme (Fixes mopidy#215). search: uses 'from_token' market.
1 parent 026b113 commit fdd2999

14 files changed

+781
-464
lines changed

mopidy_spotify/backend.py

-14
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ def on_start(self):
6363
self._web_client = web.SpotifyOAuthClient(
6464
self._config['spotify']['client_id'],
6565
self._config['spotify']['client_secret'], self._config['proxy'])
66-
6766
self._web_client.login()
6867

6968
def on_stop(self):
@@ -122,19 +121,6 @@ def on_logged_in(self):
122121
logger.info('Spotify private session activated')
123122
self._session.social.private_session = True
124123

125-
self._session.playlist_container.on(
126-
spotify.PlaylistContainerEvent.CONTAINER_LOADED,
127-
playlists.on_container_loaded)
128-
self._session.playlist_container.on(
129-
spotify.PlaylistContainerEvent.PLAYLIST_ADDED,
130-
playlists.on_playlist_added)
131-
self._session.playlist_container.on(
132-
spotify.PlaylistContainerEvent.PLAYLIST_REMOVED,
133-
playlists.on_playlist_removed)
134-
self._session.playlist_container.on(
135-
spotify.PlaylistContainerEvent.PLAYLIST_MOVED,
136-
playlists.on_playlist_moved)
137-
138124
def on_play_token_lost(self):
139125
if self._session.player.state == spotify.PlayerState.PLAYING:
140126
self.playback.pause()

mopidy_spotify/library.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def get_images(self, uris):
2929
return images.get_images(self._backend._web_client, uris)
3030

3131
def lookup(self, uri):
32-
return lookup.lookup(self._config, self._backend._session, uri)
32+
return lookup.lookup(
33+
self._config, self._backend._session, self._backend._web_client,
34+
uri)
3335

3436
def search(self, query=None, uris=None, exact=False):
3537
return search.search(

mopidy_spotify/lookup.py

+13-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import spotify
66

7-
from mopidy_spotify import translator, utils
7+
from mopidy_spotify import playlists, translator, utils, web
88

99

1010
logger = logging.getLogger(__name__)
@@ -14,25 +14,25 @@
1414
]
1515

1616

17-
def lookup(config, session, uri):
17+
def lookup(config, session, web_client, uri):
1818
try:
19-
sp_link = session.get_link(uri)
19+
web_link = web.parse_uri(uri)
20+
if web_link.type != 'playlist':
21+
sp_link = session.get_link(uri)
2022
except ValueError as exc:
2123
logger.info('Failed to lookup "%s": %s', uri, exc)
2224
return []
2325

2426
try:
25-
if sp_link.type is spotify.LinkType.TRACK:
27+
if web_link.type == 'playlist':
28+
return _lookup_playlist(config, web_client, uri)
29+
elif sp_link.type is spotify.LinkType.TRACK:
2630
return list(_lookup_track(config, sp_link))
2731
elif sp_link.type is spotify.LinkType.ALBUM:
2832
return list(_lookup_album(config, sp_link))
2933
elif sp_link.type is spotify.LinkType.ARTIST:
3034
with utils.time_logger('Artist lookup'):
3135
return list(_lookup_artist(config, sp_link))
32-
elif sp_link.type is spotify.LinkType.PLAYLIST:
33-
return list(_lookup_playlist(config, sp_link))
34-
elif sp_link.type is spotify.LinkType.STARRED:
35-
return list(reversed(list(_lookup_playlist(config, sp_link))))
3636
else:
3737
logger.info(
3838
'Failed to lookup "%s": Cannot handle %r',
@@ -90,12 +90,8 @@ def _lookup_artist(config, sp_link):
9090
yield track
9191

9292

93-
def _lookup_playlist(config, sp_link):
94-
sp_playlist = sp_link.as_playlist()
95-
sp_playlist.load(config['timeout'])
96-
for sp_track in sp_playlist.tracks:
97-
sp_track.load(config['timeout'])
98-
track = translator.to_track(
99-
sp_track, bitrate=config['bitrate'])
100-
if track is not None:
101-
yield track
93+
def _lookup_playlist(config, web_client, uri):
94+
playlist = playlists.playlist_lookup(web_client, uri, config['bitrate'])
95+
if playlist is None:
96+
raise spotify.Error('Playlist Web API lookup failed')
97+
return playlist.tracks

mopidy_spotify/playlists.py

+29-74
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
from mopidy import backend
66

7-
import spotify
8-
97
from mopidy_spotify import translator, utils
108

119

10+
_cache = {}
11+
1212
logger = logging.getLogger(__name__)
1313

1414

@@ -23,25 +23,16 @@ def as_list(self):
2323
return list(self._get_flattened_playlist_refs())
2424

2525
def _get_flattened_playlist_refs(self):
26-
if self._backend._session is None:
26+
if self._backend._web_client is None:
2727
return
2828

29-
if self._backend._session.playlist_container is None:
29+
if self._backend._web_client.user_id is None:
3030
return
3131

32-
username = self._backend._session.user_name
33-
folders = []
34-
35-
for sp_playlist in self._backend._session.playlist_container:
36-
if isinstance(sp_playlist, spotify.PlaylistFolder):
37-
if sp_playlist.type is spotify.PlaylistType.START_FOLDER:
38-
folders.append(sp_playlist.name)
39-
elif sp_playlist.type is spotify.PlaylistType.END_FOLDER:
40-
folders.pop()
41-
continue
42-
32+
web_client = self._backend._web_client
33+
for web_playlist in web_client.get_user_playlists(_cache):
4334
playlist_ref = translator.to_playlist_ref(
44-
sp_playlist, folders=folders, username=username)
35+
web_playlist, web_client.user_id)
4536
if playlist_ref is not None:
4637
yield playlist_ref
4738

@@ -54,38 +45,15 @@ def lookup(self, uri):
5445
return self._get_playlist(uri)
5546

5647
def _get_playlist(self, uri, as_items=False):
57-
try:
58-
sp_playlist = self._backend._session.get_playlist(uri)
59-
except spotify.Error as exc:
60-
logger.debug('Failed to lookup Spotify URI %s: %s', uri, exc)
61-
return
62-
63-
if not sp_playlist.is_loaded:
64-
logger.debug(
65-
'Waiting for Spotify playlist to load: %s', sp_playlist)
66-
sp_playlist.load(self._timeout)
67-
68-
username = self._backend._session.user_name
69-
return translator.to_playlist(
70-
sp_playlist, username=username, bitrate=self._backend._bitrate,
71-
as_items=as_items)
48+
return playlist_lookup(
49+
self._backend._web_client, uri,
50+
self._backend._bitrate, as_items)
7251

7352
def refresh(self):
74-
pass # Not needed as long as we don't cache anything.
53+
pass # TODO: Clear/invalidate all caches on refresh.
7554

7655
def create(self, name):
77-
try:
78-
sp_playlist = (
79-
self._backend._session.playlist_container
80-
.add_new_playlist(name))
81-
except ValueError as exc:
82-
logger.warning(
83-
'Failed creating new Spotify playlist "%s": %s', name, exc)
84-
except spotify.Error:
85-
logger.warning('Failed creating new Spotify playlist "%s"', name)
86-
else:
87-
username = self._backend._session.user_name
88-
return translator.to_playlist(sp_playlist, username=username)
56+
pass # TODO
8957

9058
def delete(self, uri):
9159
pass # TODO
@@ -94,40 +62,27 @@ def save(self, playlist):
9462
pass # TODO
9563

9664

97-
def on_container_loaded(sp_playlist_container):
98-
# Called from the pyspotify event loop, and not in an actor context.
99-
logger.debug('Spotify playlist container loaded')
100-
101-
# This event listener is also called after playlists are added, removed and
102-
# moved, so since Mopidy currently only supports the "playlists_loaded"
103-
# event this is the only place we need to trigger a Mopidy backend event.
104-
backend.BackendListener.send('playlists_loaded')
105-
106-
107-
def on_playlist_added(sp_playlist_container, sp_playlist, index):
108-
# Called from the pyspotify event loop, and not in an actor context.
109-
logger.debug(
110-
'Spotify playlist "%s" added to index %d', sp_playlist.name, index)
111-
112-
# XXX Should Mopidy support more fine grained playlist events which this
113-
# event can trigger?
65+
def playlist_lookup(web_client, uri, bitrate, as_items=False):
66+
if web_client is None:
67+
return
11468

69+
logger.info('Fetching Spotify playlist "%s"', uri)
70+
web_playlist = web_client.get_playlist(uri, _cache)
11571

116-
def on_playlist_removed(sp_playlist_container, sp_playlist, index):
117-
# Called from the pyspotify event loop, and not in an actor context.
118-
logger.debug(
119-
'Spotify playlist "%s" removed from index %d', sp_playlist.name, index)
72+
if web_playlist == {}:
73+
logger.error('Failed to lookup Spotify playlist URI %s', uri)
74+
return
12075

121-
# XXX Should Mopidy support more fine grained playlist events which this
122-
# event can trigger?
76+
return translator.to_playlist(
77+
web_playlist, username=web_client.user_id, bitrate=bitrate,
78+
as_items=as_items)
12379

12480

125-
def on_playlist_moved(
126-
sp_playlist_container, sp_playlist, old_index, new_index):
81+
def on_playlists_loaded():
12782
# Called from the pyspotify event loop, and not in an actor context.
128-
logger.debug(
129-
'Spotify playlist "%s" moved from index %d to %d',
130-
sp_playlist.name, old_index, new_index)
83+
logger.debug('Spotify playlists loaded')
13184

132-
# XXX Should Mopidy support more fine grained playlist events which this
133-
# event can trigger?
85+
# This event listener is also called after playlists are added, removed and
86+
# moved, so since Mopidy currently only supports the "playlists_loaded"
87+
# event this is the only place we need to trigger a Mopidy backend event.
88+
backend.BackendListener.send('playlists_loaded')

mopidy_spotify/search.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def search(config, session, web_client,
2525
return models.SearchResult(uri='spotify:search')
2626

2727
if 'uri' in query:
28-
return _search_by_uri(config, session, query)
28+
return _search_by_uri(config, session, web_client, query)
2929

3030
sp_query = translator.sp_search_query(query)
3131
if not sp_query:
@@ -55,7 +55,7 @@ def search(config, session, web_client,
5555
result = web_client.get('search', params={
5656
'q': sp_query,
5757
'limit': search_count,
58-
'market': web_client.user_country,
58+
'market': 'from_token',
5959
'type': ','.join(types)})
6060

6161
albums = [
@@ -77,10 +77,10 @@ def search(config, session, web_client,
7777
uri=uri, albums=albums, artists=artists, tracks=tracks)
7878

7979

80-
def _search_by_uri(config, session, query):
80+
def _search_by_uri(config, session, web_client, query):
8181
tracks = []
8282
for uri in query['uri']:
83-
tracks += lookup.lookup(config, session, uri)
83+
tracks += lookup.lookup(config, session, web_client, uri)
8484

8585
uri = 'spotify:search'
8686
if len(query['uri']) == 1:

0 commit comments

Comments
 (0)