Merge branch 'develop'
This commit is contained in:
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -39,12 +39,14 @@ class List extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="list-item header cf">
|
||||
{
|
||||
this.props.columns.map((col, col_index) => {
|
||||
var className = 'col '+col.name.replace('.','_')
|
||||
return <div className={className} key={col_index}>{ col.label ? col.label : col.name }</div>
|
||||
})
|
||||
}
|
||||
<div className="liner">
|
||||
{
|
||||
this.props.columns.map((col, col_index) => {
|
||||
var className = 'col '+col.name.replace('.','_')
|
||||
return <div className={className} key={col_index}>{ col.label ? col.label : col.name }</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -54,7 +56,7 @@ class List extends React.Component{
|
||||
var value = row
|
||||
|
||||
for (var i = 0; i < key.length; i++){
|
||||
if (typeof(value[key[i]]) === 'undefined'){
|
||||
if (value[key[i]] === undefined){
|
||||
return <span>-</span>
|
||||
} else if (typeof(value[key[i]]) === 'string' && value[key[i]].replace(' ','') == ''){
|
||||
return <span>-</span>
|
||||
|
||||
@ -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"> ({seed.type})</span>
|
||||
{!seed.unresolved ? <span className="grey-text"> ({seed.type})</span> : null}
|
||||
<button className="discrete remove-uri no-hover" onClick={e => this.removeSeed(seed.uri)}>
|
||||
<FontAwesome name="close" /> Remove
|
||||
</button>
|
||||
|
||||
@ -262,7 +262,9 @@ export default class Track extends React.Component{
|
||||
return (
|
||||
<div className={className}>
|
||||
{track_actions}
|
||||
{track_columns}
|
||||
<div className="liner">
|
||||
{track_columns}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -466,7 +466,7 @@ class Settings extends React.Component {
|
||||
<FontAwesome name="github" /> GitHub
|
||||
</a>
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/" target="_blank" style={{display: 'inline-block', verticalAlign: 'middle'}}><img alt="Creative Commons License" src="https://i.creativecommons.org/l/by-nc/4.0/88x31.png" /></a>
|
||||
<a className="button" href="http://creativecommons.org/licenses/by-nc/4.0/" target="_blank"><FontAwesome name="creative-commons" /> Licence</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
|
||||
.list {
|
||||
.list-item {
|
||||
@include clearfix;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
@ -12,13 +11,17 @@
|
||||
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 14px 30px 13px 10px;
|
||||
margin: 0 -10px -1px -10px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
border-top: 1px solid rgba(255,255,255,0.05);
|
||||
|
||||
.liner {
|
||||
@include clearfix;
|
||||
padding: 14px 30px 13px 10px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgba(255,255,255,0.08) !important;
|
||||
|
||||
@ -229,11 +232,11 @@
|
||||
.list-item {
|
||||
|
||||
&.can-sort {
|
||||
padding-left: 70px !important;
|
||||
padding-left: 60px !important;
|
||||
}
|
||||
|
||||
&:not(.can-sort){
|
||||
padding-left: 42px !important;
|
||||
padding-left: 35px !important;
|
||||
}
|
||||
|
||||
.select-zone {
|
||||
@ -247,8 +250,9 @@
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 27px;
|
||||
top: 50%;
|
||||
left: 17px;
|
||||
margin-top: -3px;
|
||||
pointer-events: none;
|
||||
color: $white;
|
||||
z-index: 1;
|
||||
@ -262,8 +266,9 @@
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
position: absolute;
|
||||
top: 23px;
|
||||
top: 50%;
|
||||
left: 14px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,8 +284,9 @@
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
top: 50%;
|
||||
left: 6px;
|
||||
margin-top: -6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@ -290,7 +296,10 @@
|
||||
@include responsive($bp_medium){
|
||||
|
||||
.list-item {
|
||||
padding: 12px !important;
|
||||
|
||||
.liner {
|
||||
padding: 12px !important;
|
||||
}
|
||||
|
||||
.source {
|
||||
position: static;
|
||||
|
||||
Reference in New Issue
Block a user