Merge branch 'feature/async'
This commit is contained in:
@ -1 +1 @@
|
||||
3.4.9
|
||||
3.5.0
|
||||
@ -5,7 +5,9 @@ import random, string, logging, json, pykka, pylast, urllib, urllib2, os, sys, m
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.ioloop
|
||||
import tornado.httpclient
|
||||
import requests
|
||||
import time
|
||||
from mopidy import config, ext
|
||||
from mopidy.core import CoreListener
|
||||
from pkg_resources import parse_version
|
||||
@ -76,7 +78,12 @@ class IrisCore(object):
|
||||
generated = True
|
||||
|
||||
# construct our protocol object, and return
|
||||
return {"clientid": clientid, "connection_id": connection_id, "username": username, "generated": generated}
|
||||
return {
|
||||
"clientid": clientid,
|
||||
"connection_id": connection_id,
|
||||
"username": username,
|
||||
"generated": generated
|
||||
}
|
||||
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
@ -91,14 +98,20 @@ class IrisCore(object):
|
||||
|
||||
|
||||
def broadcast(self, *args, **kwargs):
|
||||
data = kwargs.get('data', None)
|
||||
data = kwargs.get('data', {})
|
||||
callback = kwargs.get('callback', None)
|
||||
|
||||
for connection in self.connections.itervalues():
|
||||
connection['connection'].write_message( json_encode(data) )
|
||||
return {
|
||||
|
||||
response = {
|
||||
'status': 1,
|
||||
'message': 'Broadcast to '+str(len(self.connections))+' connections'
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
##
|
||||
# Connections
|
||||
@ -108,15 +121,21 @@ class IrisCore(object):
|
||||
# to all current connections
|
||||
##
|
||||
|
||||
def get_connections(self, *args, **kwargs):
|
||||
def get_connections(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', None)
|
||||
|
||||
connections = []
|
||||
for connection in self.connections.itervalues():
|
||||
connections.append(connection['client'])
|
||||
|
||||
return {
|
||||
response = {
|
||||
'status': 1,
|
||||
'connections': connections
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def add_connection(self, *args, **kwargs):
|
||||
connection_id = kwargs.get('connection_id', None)
|
||||
@ -161,16 +180,8 @@ class IrisCore(object):
|
||||
logger.error('Failed to close connection to '+ connection_id)
|
||||
|
||||
def set_username(self, *args, **kwargs):
|
||||
try:
|
||||
data = kwargs.get('data', {})
|
||||
except:
|
||||
self.raven_client.captureException()
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'Malformed data',
|
||||
'source': 'set_username'
|
||||
}
|
||||
|
||||
callback = kwargs.get('callback', None)
|
||||
data = kwargs.get('data', {})
|
||||
connection_id = data['connection_id']
|
||||
|
||||
if connection_id in self.connections:
|
||||
@ -181,47 +192,56 @@ class IrisCore(object):
|
||||
'connection': self.connections[connection_id]['client']
|
||||
}
|
||||
)
|
||||
return {
|
||||
response = {
|
||||
'status': 1,
|
||||
'connection_id': connection_id,
|
||||
'username': data['username']
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
else:
|
||||
error = 'Connection "'+data['connection_id']+'" not found'
|
||||
self.raven_client.captureMessage(error)
|
||||
logger.error(error)
|
||||
return {
|
||||
response = {
|
||||
'status': 0,
|
||||
'message': error
|
||||
}
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def deliver_message(self, *args, **kwargs):
|
||||
try:
|
||||
data = kwargs.get('data', {})
|
||||
except:
|
||||
self.raven_client.captureException()
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'Malformed data',
|
||||
'source': 'deliver_message'
|
||||
}
|
||||
callback = kwargs.get('callback', False)
|
||||
data = kwargs.get('data', {})
|
||||
|
||||
if data['connection_id'] in self.connections:
|
||||
self.send_message(connection_id=data['connection_id'], data=data['message'])
|
||||
return {
|
||||
response = {
|
||||
'status': 1,
|
||||
'message': 'Sent message to '+data['connection_id']
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
else:
|
||||
error = 'Connection "'+data['connection_id']+'" not found'
|
||||
self.raven_client.captureMessage(error)
|
||||
logger.error(error)
|
||||
return {
|
||||
response = {
|
||||
'status': 0,
|
||||
'message': error
|
||||
}
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
|
||||
@ -233,6 +253,7 @@ class IrisCore(object):
|
||||
##
|
||||
|
||||
def get_config(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
# handle config setups where there is no username/password
|
||||
# Iris won't work properly anyway, but at least we won't get server errors
|
||||
@ -241,17 +262,22 @@ class IrisCore(object):
|
||||
else:
|
||||
spotify_username = False
|
||||
|
||||
config = {
|
||||
"spotify_username": spotify_username,
|
||||
"country": self.config['iris']['country'],
|
||||
"locale": self.config['iris']['locale'],
|
||||
"authorization_url": self.config['iris']['authorization_url']
|
||||
}
|
||||
return {
|
||||
'config': config
|
||||
response = {
|
||||
'config': {
|
||||
"spotify_username": spotify_username,
|
||||
"country": self.config['iris']['country'],
|
||||
"locale": self.config['iris']['locale'],
|
||||
"authorization_url": self.config['iris']['authorization_url']
|
||||
}
|
||||
}
|
||||
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def get_version(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
url = 'https://pypi.python.org/pypi/Mopidy-Iris/json'
|
||||
req = urllib2.Request(url)
|
||||
|
||||
@ -269,7 +295,7 @@ class IrisCore(object):
|
||||
latest_version = '0.0.0'
|
||||
upgrade_available = False
|
||||
|
||||
return {
|
||||
response = {
|
||||
'status': 1,
|
||||
'version': {
|
||||
'current': self.version,
|
||||
@ -278,16 +304,37 @@ class IrisCore(object):
|
||||
'upgrade_available': upgrade_available
|
||||
}
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def perform_upgrade(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
def perform_upgrade( self ):
|
||||
try:
|
||||
subprocess.check_call(["pip", "install", "--upgrade", "Mopidy-Iris"])
|
||||
return True
|
||||
response = {
|
||||
'status': 1,
|
||||
'message': "Upgrade started"
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.raven_client.captureException(e)
|
||||
return False
|
||||
response = {
|
||||
'status': 0,
|
||||
'message': "Could not start upgrade"
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def restart( self ):
|
||||
def restart(self, *args, **kwargs):
|
||||
os.execl(sys.executable, *([sys.executable]+sys.argv))
|
||||
|
||||
|
||||
@ -300,21 +347,20 @@ class IrisCore(object):
|
||||
##
|
||||
|
||||
def get_radio(self, *args, **kwargs):
|
||||
return {
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
response = {
|
||||
'status': 1,
|
||||
'radio': self.radio
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def change_radio(self, *args, **kwargs):
|
||||
try:
|
||||
data = kwargs.get('data', {})
|
||||
except:
|
||||
self.raven_client.captureException()
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'Malformed data',
|
||||
'source': 'change_radio'
|
||||
}
|
||||
callback = kwargs.get('callback', False)
|
||||
data = kwargs.get('data', {})
|
||||
|
||||
# figure out if we're starting or updating radio mode
|
||||
if data['update'] and self.radio['enabled']:
|
||||
@ -359,18 +405,24 @@ class IrisCore(object):
|
||||
}
|
||||
)
|
||||
|
||||
return self.get_radio({})
|
||||
|
||||
return self.get_radio(callback=callback)
|
||||
|
||||
# failed fetching/adding tracks, so no-go
|
||||
self.radio['enabled'] = 0;
|
||||
return {
|
||||
response = {
|
||||
'status': 0,
|
||||
'message': 'Could not start radio',
|
||||
'radio': self.radio
|
||||
}
|
||||
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
def stop_radio(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
self.radio = {
|
||||
"enabled": 0,
|
||||
@ -391,16 +443,22 @@ class IrisCore(object):
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'status': 1
|
||||
response = {
|
||||
'status': 1,
|
||||
'message': 'Stopped radio'
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
def load_more_tracks( self ):
|
||||
def load_more_tracks(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
# 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({})
|
||||
self.refresh_spotify_token()
|
||||
|
||||
try:
|
||||
token = self.spotify_token
|
||||
@ -478,21 +536,20 @@ class IrisCore(object):
|
||||
##
|
||||
|
||||
def get_queue_metadata(self, *args, **kwargs):
|
||||
return {
|
||||
callback = kwargs.get('callback', False)
|
||||
|
||||
response = {
|
||||
'status': 1,
|
||||
'queue_metadata': self.queue_metadata
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def add_queue_metadata(self, *args, **kwargs):
|
||||
try:
|
||||
data = kwargs.get('data', {})
|
||||
except:
|
||||
self.raven_client.captureException()
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'Malformed data',
|
||||
'source': 'add_queue_metadata'
|
||||
}
|
||||
callback = kwargs.get('callback', False)
|
||||
data = kwargs.get('data', {})
|
||||
|
||||
for tlid in data['tlids']:
|
||||
item = {
|
||||
@ -508,12 +565,18 @@ class IrisCore(object):
|
||||
'queue_metadata': self.queue_metadata
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'status': 1
|
||||
|
||||
response = {
|
||||
'status': 1,
|
||||
'message': 'Added queue metadata'
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def clean_queue_metadata( self ):
|
||||
def clean_queue_metadata(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
cleaned_queue_metadata = {}
|
||||
|
||||
for tltrack in self.core.tracklist.get_tl_tracks().get():
|
||||
@ -530,10 +593,15 @@ class IrisCore(object):
|
||||
'queue_metadata': self.queue_metadata
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'status': 1
|
||||
|
||||
response = {
|
||||
'status': 1,
|
||||
'message': 'Cleaned queue metadata'
|
||||
}
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
##
|
||||
@ -545,11 +613,18 @@ class IrisCore(object):
|
||||
##
|
||||
|
||||
def get_spotify_token(self, *args, **kwargs):
|
||||
return {
|
||||
callback = kwargs.get('callback', False)
|
||||
response = {
|
||||
'spotify_token': self.spotify_token
|
||||
}
|
||||
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def refresh_spotify_token(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', None)
|
||||
|
||||
# Use client_id and client_secret from config
|
||||
# This was introduced in Mopidy-Spotify 3.1.0
|
||||
@ -571,18 +646,21 @@ class IrisCore(object):
|
||||
}
|
||||
)
|
||||
|
||||
return self.get_spotify_token({})
|
||||
return self.get_spotify_token(callback=callback)
|
||||
|
||||
except urllib2.HTTPError as e:
|
||||
self.raven_client.captureException()
|
||||
error = json.loads(e.read())
|
||||
|
||||
return {
|
||||
response = {
|
||||
'status': 0,
|
||||
'message': 'Could not refresh token: '+error['error_description'],
|
||||
'source': 'refresh_spotify_token'
|
||||
}
|
||||
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
##
|
||||
@ -594,27 +672,29 @@ class IrisCore(object):
|
||||
##
|
||||
|
||||
def proxy_request(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', False)
|
||||
origin_request = kwargs.get('request', None)
|
||||
|
||||
try:
|
||||
data = kwargs.get('data', {})
|
||||
except:
|
||||
self.raven_client.captureException()
|
||||
return {
|
||||
callback({
|
||||
'status': 0,
|
||||
'message': 'Malformed data',
|
||||
'source': 'proxy_request'
|
||||
}
|
||||
})
|
||||
|
||||
origin_request = kwargs.get('request', None)
|
||||
|
||||
# Our request includes data, so make sure we POST the data
|
||||
if 'url' not in data:
|
||||
self.raven_client.captureException()
|
||||
return {
|
||||
callback({
|
||||
'status': 0,
|
||||
'message': 'Malformed data (missing URL)',
|
||||
'source': 'proxy_request',
|
||||
'original_request': data
|
||||
}
|
||||
})
|
||||
|
||||
# Construct request headers
|
||||
# If we have an original request, pass through it's headers
|
||||
@ -641,41 +721,32 @@ class IrisCore(object):
|
||||
if "Referrer" in headers:
|
||||
del headers["Referrer"]
|
||||
|
||||
# Now actually attempt the request
|
||||
try:
|
||||
# Our request includes data, so make sure we POST the data
|
||||
if ('data' in data and data['data']):
|
||||
response = requests.post(data['url'], data=data['data'], headers=headers, verify=False)
|
||||
# Our request includes data, so make sure we POST the data
|
||||
if ('data' in data and data['data']):
|
||||
http_client = tornado.httpclient.AsyncHTTPClient()
|
||||
request = tornado.httpclient.HTTPRequest(data['url'], method='POST', data=data['data'], headers=headers, validate_cert=False)
|
||||
http_client.fetch(request, callback=callback)
|
||||
|
||||
# No data, so just a simple GET request
|
||||
else:
|
||||
# No data, so just a simple GET request
|
||||
else:
|
||||
|
||||
# Strip out our origin content-length otherwise this confuses
|
||||
# the target server as content-length doesn't apply to GET requests
|
||||
if "Content-Length" in headers:
|
||||
del headers["Content-Length"]
|
||||
# Strip out our origin content-length otherwise this confuses
|
||||
# the target server as content-length doesn't apply to GET requests
|
||||
if "Content-Length" in headers:
|
||||
del headers["Content-Length"]
|
||||
|
||||
response = requests.get(data['url'], headers=headers, verify=False)
|
||||
http_client = tornado.httpclient.AsyncHTTPClient()
|
||||
request = tornado.httpclient.HTTPRequest(data['url'], headers=headers, validate_cert=False)
|
||||
http_client.fetch(request, callback=callback)
|
||||
|
||||
|
||||
# Attempt to decode body as JSON, otherwise just return plain text
|
||||
try:
|
||||
return {
|
||||
'response_code': int(response.status_code),
|
||||
'response': response.json()
|
||||
}
|
||||
except:
|
||||
return {
|
||||
'response_code': int(response.status_code),
|
||||
'response': response.text
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'Could not complete proxy request',
|
||||
'source': 'proxy_request',
|
||||
'response': e.text,
|
||||
'response_code': int(e.response_code),
|
||||
'original_request': data
|
||||
}
|
||||
##
|
||||
# Simple test method
|
||||
##
|
||||
def test(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', None)
|
||||
time.sleep(1)
|
||||
callback({
|
||||
'status': 1,
|
||||
'message': "Slept for one second"
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
||||
from datetime import datetime
|
||||
from tornado.escape import json_encode, json_decode
|
||||
import tornado.ioloop, tornado.web, tornado.websocket, tornado.template
|
||||
import random, string, logging, uuid, subprocess, pykka, ast, logging, json, urllib, urllib2, mem
|
||||
import random, string, logging, uuid, subprocess, pykka, ast, logging, json, urllib, urllib2, mem, requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -85,34 +85,52 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
# make sure the method exists
|
||||
if hasattr(mem.iris, message['method']):
|
||||
getattr(mem.iris, message['method'])(data=data, callback=lambda response: self.handle_response(response=response, request_id=request_id))
|
||||
|
||||
# make the call, and return it's response
|
||||
response = getattr(mem.iris, message['method'])(data=data)
|
||||
|
||||
if response:
|
||||
response['request_id'] = request_id
|
||||
mem.iris.send_message(connection_id=self.connection_id, data=response)
|
||||
else:
|
||||
mem.iris.raven_client.captureMessage("Method "+message['method']+" does not exist")
|
||||
response = {
|
||||
self.handle_response({
|
||||
'status': 0,
|
||||
'message': 'Method "'+message['method']+'" does not exist',
|
||||
'request_id': request_id
|
||||
}
|
||||
mem.iris.send_message(connection_id=self.connection_id, data=response)
|
||||
})
|
||||
else:
|
||||
mem.iris.raven_client.captureMessage("Method key missing from request")
|
||||
response = {
|
||||
self.handle_response({
|
||||
'status': 0,
|
||||
'message': 'Method key missing from request',
|
||||
'request_id': request_id
|
||||
}
|
||||
mem.iris.send_message(connection_id=self.connection_id, data=response)
|
||||
})
|
||||
|
||||
|
||||
def on_close(self):
|
||||
mem.iris.remove_connection(connection_id=self.connection_id)
|
||||
|
||||
##
|
||||
# Handle a response from our core
|
||||
# This is just our callback from an Async request
|
||||
##
|
||||
def handle_response(self, *args, **kwargs):
|
||||
response = kwargs.get('response', None)
|
||||
request_id = kwargs.get('request_id', False)
|
||||
|
||||
# We've been handed an AsyncHTTPClient callback. This is the case
|
||||
# when our request calls subsequent external requests (eg Spotify, Genius)
|
||||
if isinstance(response, tornado.httpclient.HTTPResponse):
|
||||
response = {
|
||||
'response_code': response.code,
|
||||
'response_reason': response.reason,
|
||||
'response': response.body,
|
||||
'request_id': request_id
|
||||
}
|
||||
|
||||
# Just a regular json object, so not an external request
|
||||
else:
|
||||
response['request_id'] = request_id
|
||||
mem.iris.send_message(connection_id=self.connection_id, data=response)
|
||||
|
||||
mem.iris.send_message(connection_id=self.connection_id, data=response)
|
||||
|
||||
|
||||
|
||||
|
||||
@ -134,39 +152,63 @@ class HttpHandler(tornado.web.RequestHandler):
|
||||
self.set_status(204)
|
||||
self.finish()
|
||||
|
||||
@tornado.web.asynchronous
|
||||
def get(self, slug=None):
|
||||
|
||||
# make sure the method exists
|
||||
if hasattr(mem.iris, slug):
|
||||
getattr(mem.iris, slug)(request=self.request, callback=self.handle_response)
|
||||
|
||||
# make the call, and return it's response
|
||||
self.write(getattr(mem.iris, slug)(request=self.request))
|
||||
else:
|
||||
mem.iris.raven_client.captureMessage("Method "+slug+" does not exist")
|
||||
self.write({
|
||||
'error': 'Method "'+slug+'" does not exist'
|
||||
'status': 0,
|
||||
'message': 'Method "'+slug+'" does not exist'
|
||||
})
|
||||
|
||||
self.finish()
|
||||
|
||||
@tornado.web.asynchronous
|
||||
def post(self, slug=None):
|
||||
|
||||
# make sure the method exists
|
||||
if hasattr(mem.iris, slug):
|
||||
|
||||
try:
|
||||
data = json.loads(self.request.body.decode('utf-8'))
|
||||
|
||||
# make the call, and return it's response
|
||||
self.write(getattr(mem.iris, slug)(data=data, request=self.request))
|
||||
getattr(mem.iris, slug)(data=data, request=self.request, callback=self.handle_response)
|
||||
|
||||
except urllib2.HTTPError as e:
|
||||
self.raven_client.captureException()
|
||||
self.write({
|
||||
'error': 'Invalid JSON payload'
|
||||
'status': 0,
|
||||
'message': 'Invalid JSON payload'
|
||||
})
|
||||
self.finish()
|
||||
|
||||
else:
|
||||
mem.iris.raven_client.captureMessage("Method "+slug+" does not exist")
|
||||
self.write({
|
||||
'error': 'Method "'+slug+'" does not exist'
|
||||
'status': 0,
|
||||
'message': 'Method "'+slug+'" does not exist'
|
||||
})
|
||||
self.finish()
|
||||
|
||||
##
|
||||
# Handle a response from our core
|
||||
# This is just our callback from an Async request
|
||||
##
|
||||
def handle_response(self, response):
|
||||
|
||||
# We've been handed an AsyncHTTPClient callback. This is the case
|
||||
# when our request calls subsequent external requests (eg Spotify, Genius).
|
||||
# We don't need to wrap non-HTTPResponse responses as these are dicts
|
||||
if isinstance(response, tornado.httpclient.HTTPResponse):
|
||||
response = {
|
||||
'response_code': response.code,
|
||||
'response_reason': response.reason,
|
||||
'response': response.body
|
||||
}
|
||||
|
||||
self.write(response)
|
||||
self.finish()
|
||||
|
||||
|
||||
|
||||
|
||||
3
setup.py
3
setup.py
@ -26,7 +26,8 @@ setup(
|
||||
'Mopidy-Local-Images >= 1.0',
|
||||
'ConfigObj >= 5.0.6',
|
||||
'raven >= 6.1.0',
|
||||
'requests >= 2.0.0'
|
||||
'requests >= 2.0.0',
|
||||
'promise >= 2.0.1'
|
||||
],
|
||||
classifiers=[
|
||||
'Environment :: No Input/Output (Daemon)',
|
||||
|
||||
@ -87,31 +87,41 @@ export let sizedImages = function(images){
|
||||
|
||||
|
||||
/**
|
||||
* Digest an array of Mopidy image objects into a universal format
|
||||
* Digest an array of Mopidy image objects into a universal format. We also re-write
|
||||
* image URLs to be absolute to the mopidy server (required for proxy setups).
|
||||
*
|
||||
* @param mopidy = obj (mopidy store object)
|
||||
* @param images = array
|
||||
* @return array
|
||||
**/
|
||||
export let digestMopidyImages = function(mopidy, images){
|
||||
let digested = []
|
||||
var digested = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++){
|
||||
for (var i = 0; i < images.length; i++){
|
||||
|
||||
// Accommodate backends that provide URIs vs URLs
|
||||
let url = images[i].url
|
||||
if (!url && images[i].uri){
|
||||
url = images[i].uri
|
||||
// Image object (ie from images.get)
|
||||
if (typeof images[i] === 'object'){
|
||||
// Accommodate backends that provide URIs vs URLs
|
||||
var url = images[i].url
|
||||
if (!url && images[i].uri){
|
||||
url = images[i].uri
|
||||
}
|
||||
|
||||
// Amend our URL
|
||||
images[i].url = url
|
||||
|
||||
// Replace local images to point directly to our Mopidy server
|
||||
if (url.startsWith('/images/')){
|
||||
url = '//'+mopidy.host+':'+mopidy.port+url
|
||||
}
|
||||
|
||||
// String-based image
|
||||
} else if (typeof images[i] === 'string'){
|
||||
// Replace local images to point directly to our Mopidy server
|
||||
if (images[i].startsWith('/images/')){
|
||||
images[i] = '//'+mopidy.host+':'+mopidy.port+images[i]
|
||||
}
|
||||
}
|
||||
/*
|
||||
// Replace local images to point directly to our Mopidy server
|
||||
if (url.startsWith('/images/')){
|
||||
url = '//'+mopidy.host+':'+mopidy.port+url
|
||||
}
|
||||
*/
|
||||
|
||||
// Amend our URL
|
||||
images[i].url = url
|
||||
|
||||
digested.push(images[i])
|
||||
}
|
||||
|
||||
@ -77,18 +77,21 @@ const CoreMiddleware = (function(){
|
||||
next(action)
|
||||
break
|
||||
|
||||
case 'ALBUM_LOADED':
|
||||
if (action.data) ReactGA.event({ category: 'Album', action: 'Load', label: action.album.uri })
|
||||
case 'TRACK_LOADED':
|
||||
if (action.data) ReactGA.event({ category: 'Track', action: 'Load', label: action.key });
|
||||
|
||||
if (action.track.album && action.track.album.images && action.track.album.images.length > 0){
|
||||
action.track.album.images = helpers.digestMopidyImages(store.getState().mopidy, action.track.album.images);
|
||||
}
|
||||
|
||||
next(action)
|
||||
break
|
||||
|
||||
case 'ALBUM_LOADED':
|
||||
if (action.data) ReactGA.event({ category: 'Album', action: 'Load', label: action.key })
|
||||
|
||||
// make sure our images use mopidy host:port
|
||||
if (action.album.images && action.album.images.length > 0){
|
||||
var images = Object.assign([], action.album.images)
|
||||
for (var i = 0; i < images.length; i++){
|
||||
if (typeof(images[i]) === 'string' && images[i].startsWith('/images/')){
|
||||
images[i] = '//'+store.getState().mopidy.host+':'+store.getState().mopidy.port+images[i]
|
||||
}
|
||||
}
|
||||
action.album.images = images
|
||||
action.album.images = helpers.digestMopidyImages(store.getState().mopidy, action.album.images);
|
||||
}
|
||||
|
||||
next(action)
|
||||
@ -98,15 +101,8 @@ const CoreMiddleware = (function(){
|
||||
if (action.data) ReactGA.event({ category: 'Albums', action: 'Load', label: action.albums.length+' items' })
|
||||
|
||||
for (var i = 0; i < action.albums.length; i++){
|
||||
// make sure our images use mopidy host:port
|
||||
if (action.albums[i].images && action.albums[i].images.length > 0){
|
||||
var images = Object.assign([], action.albums[i].images)
|
||||
for (var j = 0; j < images.length; j++){
|
||||
if (typeof(images[j]) === 'string' && images[j].startsWith('/images/')){
|
||||
images[j] = '//'+store.getState().mopidy.host+':'+store.getState().mopidy.port+images[j]
|
||||
}
|
||||
}
|
||||
action.albums[i].images = images
|
||||
action.albums[i].images = helpers.digestMopidyImages(store.getState().mopidy, action.albums[i].images);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1735,13 +1735,12 @@ const MopidyMiddleware = (function(){
|
||||
instruct(socket, store, 'library.getImages', {uris: action.uris})
|
||||
.then(response => {
|
||||
|
||||
let records = []
|
||||
var records = []
|
||||
for (var uri in response){
|
||||
if (response.hasOwnProperty(uri)){
|
||||
|
||||
let images = response[uri]
|
||||
images = helpers.digestMopidyImages(store.getState().mopidy, images)
|
||||
|
||||
var images = response[uri];
|
||||
images = helpers.digestMopidyImages(store.getState().mopidy, images);
|
||||
records.push({
|
||||
uri: uri,
|
||||
images: images
|
||||
@ -1749,7 +1748,7 @@ const MopidyMiddleware = (function(){
|
||||
}
|
||||
}
|
||||
|
||||
let action_data = {
|
||||
var action_data = {
|
||||
type: (action.context+'_LOADED').toUpperCase()
|
||||
}
|
||||
action_data[action.context] = records
|
||||
|
||||
@ -93,7 +93,7 @@ class User extends React.Component{
|
||||
<ul className="details">
|
||||
{this.props.user.playlists_total ? <li>{this.props.user.playlists_total ? this.props.user.playlists_total.toLocaleString() : 0} playlists</li> : null}
|
||||
{this.props.user.followers ? <li>{this.props.user.followers.total.toLocaleString()} followers</li> : null}
|
||||
{this.isMe() ? <li>You</li> : null}
|
||||
{this.isMe() ? <li><span className="blue-text">You</span></li> : null}
|
||||
</ul>
|
||||
</h2>
|
||||
<div className="actions">
|
||||
|
||||
@ -393,6 +393,12 @@ footer {
|
||||
background: lighten($dark_grey,10%);
|
||||
color: $mid_grey;
|
||||
}
|
||||
|
||||
h1 &,
|
||||
h2 & {
|
||||
line-height: 1.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user