Making radio a little more robust; Only refresh spotify token when expired; Investigating #223

This commit is contained in:
James Barnsley
2017-11-21 14:50:37 +13:00
4 changed files with 119 additions and 73 deletions

View File

@ -374,39 +374,53 @@ class IrisCore(object):
# We only want to play the first batch
added = self.core.tracklist.add(uris = uris[0:3])
if (not added.get()):
logger.error("No recommendations added to queue")
self.radio['enabled'] = 0;
error = {
'message': 'No recommendations added to queue',
'radio': self.radio
}
if (callback):
callback(False, error)
else:
return error
# Save results (minus first batch) for later use
self.radio['results'] = uris[3:]
if added.get():
if starting:
self.core.playback.play()
self.broadcast(
data={
'type': 'radio_started',
'radio': self.radio
}
)
else:
self.broadcast(
data={
'type': 'radio_changed',
'radio': self.radio
}
)
if starting:
self.core.playback.play()
self.broadcast(
data={
'type': 'radio_started',
'radio': self.radio
}
)
else:
self.broadcast(
data={
'type': 'radio_changed',
'radio': self.radio
}
)
self.get_radio(callback=callback)
return
self.get_radio(callback=callback)
return
# failed fetching/adding tracks, so no-go
self.radio['enabled'] = 0;
error = {
'message': 'Could not start radio',
'radio': self.radio
}
if (callback):
callback(False, error)
# Failed fetching/adding tracks, so no-go
else:
return error
logger.error("No recommendations returned by Spotify")
self.radio['enabled'] = 0;
error = {
'message': 'Could not start radio',
'radio': self.radio
}
if (callback):
callback(False, error)
else:
return error
def stop_radio(self, *args, **kwargs):
@ -442,13 +456,10 @@ class IrisCore(object):
def load_more_tracks(self, *args, **kwargs):
# this is crude, but it means we don't need to handle expired tokens
# TODO: address this when it's clear what Jodal and the team want to do with Pyspotify
self.refresh_spotify_token()
try:
token = self.spotify_token
token = token['access_token']
self.get_spotify_token()
spotify_token = self.spotify_token
access_token = spotify_token['access_token']
except:
error = 'IrisFrontend: access_token missing or invalid'
logger.error(error)
@ -462,7 +473,7 @@ class IrisCore(object):
url = url+'&limit=50'
req = urllib2.Request(url)
req.add_header('Authorization', 'Bearer '+self.spotify_token['access_token'])
req.add_header('Authorization', 'Bearer '+access_token)
response = urllib2.urlopen(req, timeout=30).read()
response_dict = json.loads(response)
@ -556,21 +567,6 @@ class IrisCore(object):
self.queue_metadata = cleaned_queue_metadata
self.broadcast(
data={
'type': 'queue_metadata_changed',
'queue_metadata': self.queue_metadata
}
)
response = {
'message': 'Cleaned queue metadata'
}
if (callback):
callback(response)
else:
return response
##
# Spotify authentication
@ -582,6 +578,11 @@ class IrisCore(object):
def get_spotify_token(self, *args, **kwargs):
callback = kwargs.get('callback', False)
# Expired, so go get a new one
if (not self.spotify_token or self.spotify_token['expires_at'] <= time.time()):
self.refresh_spotify_token()
response = {
'spotify_token': self.spotify_token
}
@ -608,6 +609,10 @@ class IrisCore(object):
request = tornado.httpclient.HTTPRequest(url, method='POST', body=urllib.urlencode(data))
response = http_client.fetch(request)
token = json.loads(response.body)
token['expires_at'] = time.time() + token['expires_in']
self.spotify_token = token
self.broadcast(
data={
'type': 'spotify_token_changed',
@ -615,12 +620,9 @@ class IrisCore(object):
}
)
token = json.loads(response.body)
self.spotify_token = token
response = {
'spotify_token': token
}
if (callback):
callback(response)
else:

View File

@ -19,9 +19,9 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
def on_start(self):
logger.info('Starting Iris '+mem.iris.version)
def track_playback_ended( self, tl_track, time_position ):
def track_playback_ended(self, tl_track, time_position):
mem.iris.check_for_radio_update()
def tracklist_changed( self ):
def tracklist_changed(self):
mem.iris.clean_queue_metadata()

View File

@ -26,15 +26,43 @@ export default class EditRadioModal extends React.Component{
}
handleStart(e){
e.preventDefault()
this.props.pusherActions.startRadio(this.state.seeds)
this.props.uiActions.closeModal()
e.preventDefault();
var valid_seeds = true;
var seeds = this.mapSeeds();
for (var i = 0; i < seeds.length; i++){
if (seeds[i].unresolved !== undefined){
valid_seeds = false;
continue;
}
}
if (valid_seeds){
this.props.pusherActions.startRadio(this.state.seeds);
this.props.uiActions.closeModal();
} else {
this.setState({error_message: "Invalid seed URI(s)"});
}
}
handleUpdate(e){
e.preventDefault()
this.props.pusherActions.updateRadio(this.state.seeds)
this.props.uiActions.closeModal()
e.preventDefault();
var valid_seeds = true;
var seeds = this.mapSeeds();
for (var i = 0; i < seeds.length; i++){
if (seeds[i].unresolved !== undefined){
valid_seeds = false;
continue;
}
}
if (valid_seeds){
this.props.pusherActions.updateRadio(this.state.seeds);
this.props.uiActions.closeModal();
} else {
this.setState({error_message: "Invalid seed URI(s)"});
}
}
handleStop(e){
@ -46,7 +74,7 @@ export default class EditRadioModal extends React.Component{
addSeed(){
if (this.state.uri == ''){
this.setState({error_message: 'Cannot be empty'});
return null;
return;
}
var seeds = Object.assign([],this.state.seeds);
@ -62,7 +90,18 @@ export default class EditRadioModal extends React.Component{
} else {
seeds.push(uris[i]);
this.setState({error_message: null});
}
}
// Resolve
switch (helpers.uriType(uris[i])){
case 'track':
this.props.spotifyActions.getTrack(uris[i]);
break;
case 'artist':
this.props.spotifyActions.getArtist(uris[i]);
break;
}
}
// commit to state
@ -79,10 +118,10 @@ export default class EditRadioModal extends React.Component{
seeds.push(this.state.seeds[i])
}
}
this.setState({seeds: seeds})
this.setState({seeds: seeds});
}
renderSeeds(){
mapSeeds(){
var seeds = []
if (this.state.seeds){
@ -91,20 +130,18 @@ export default class EditRadioModal extends React.Component{
if (uri){
if (helpers.uriType(uri) == 'artist'){
if (this.props.artists && this.props.artists.hasOwnProperty(uri)){
seeds.push(this.props.artists[uri])
seeds.push(this.props.artists[uri]);
} else {
seeds.push({
type: 'artist',
unresolved: true,
uri: uri
})
}
} else if (helpers.uriType(uri) == 'track'){
if (this.props.tracks && this.props.tracks.hasOwnProperty(uri)){
seeds.push(this.props.tracks[uri])
seeds.push(this.props.tracks[uri]);
} else {
seeds.push({
type: 'track',
unresolved: true,
uri: uri
})
@ -114,6 +151,12 @@ export default class EditRadioModal extends React.Component{
}
}
return seeds;
}
renderSeeds(){
var seeds = this.mapSeeds();
if (seeds.length > 0){
return (
<div>
@ -123,7 +166,7 @@ export default class EditRadioModal extends React.Component{
return (
<div className="list-item" key={seed.uri}>
{seed.unresolved ? <span className="grey-text">{seed.uri}</span> : <span>{seed.name}</span> }
<span className="grey-text">&nbsp;({seed.type})</span>
{!seed.unresolved ? <span className="grey-text">&nbsp;({seed.type})</span> : null}
<button className="discrete remove-uri no-hover" onClick={e => this.removeSeed(seed.uri)}>
<FontAwesome name="close" />&nbsp;Remove
</button>

View File

@ -83,7 +83,7 @@ const PusherMiddleware = (function(){
store.dispatch(uiActions.stopLoading(request_id));
reject({message: "Request timed out", method: method, data: data});
},
5000 // 30000
30000
);
// add query to our deferred responses
@ -379,12 +379,13 @@ const PusherMiddleware = (function(){
request(store, 'change_radio', data)
.then(
response => {
store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'));
if (response.status == 0){
store.dispatch(uiActions.createNotification(response.message, 'bad'))
store.dispatch(uiActions.createNotification(response.message, 'bad'));
}
store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'))
},
error => {
error => {
store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'));
store.dispatch(coreActions.handleException(
'Could not change radio',
error