From 25a4a99b2d944f219daae429252fe72214061ccf Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Wed, 11 Oct 2017 08:32:41 +1300 Subject: [PATCH 1/6] Mopidy image processing and absolut-ing in one --- src/js/helpers.js | 42 ++++++++++++++++++------------ src/js/services/core/middleware.js | 14 +++++++++- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/js/helpers.js b/src/js/helpers.js index 189330dd..72aa8448 100755 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -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]) } diff --git a/src/js/services/core/middleware.js b/src/js/services/core/middleware.js index 929ce5de..8534e78c 100755 --- a/src/js/services/core/middleware.js +++ b/src/js/services/core/middleware.js @@ -77,8 +77,19 @@ const CoreMiddleware = (function(){ next(action) break + case 'TRACK_LOADED': + if (action.data) ReactGA.event({ category: 'Track', action: 'Load', label: action.key }); + + // make sure our images use mopidy host:port + 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.album.uri }) + 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){ @@ -98,6 +109,7 @@ 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) From ebe6c90168a707504c3ad727600348c63f7f6968 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Wed, 11 Oct 2017 08:46:30 +1300 Subject: [PATCH 2/6] Using mopidy image processor --- src/js/services/core/middleware.js | 20 ++------------------ src/js/services/mopidy/middleware.js | 9 ++++----- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/js/services/core/middleware.js b/src/js/services/core/middleware.js index 8534e78c..cf4b799b 100755 --- a/src/js/services/core/middleware.js +++ b/src/js/services/core/middleware.js @@ -80,7 +80,6 @@ const CoreMiddleware = (function(){ case 'TRACK_LOADED': if (action.data) ReactGA.event({ category: 'Track', action: 'Load', label: action.key }); - // make sure our images use mopidy host:port 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); } @@ -91,15 +90,8 @@ const CoreMiddleware = (function(){ 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) @@ -109,16 +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); } } diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 5ef15817..0a795d8b 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -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 From cab8e50bacce543b0e0b5a53af07654bf9c5d078 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 13 Oct 2017 10:02:06 +1300 Subject: [PATCH 3/6] Experimenting with promises, tornado.AsyncHTTPClient, very limited options --- mopidy_iris/core.py | 37 +++++++++++++++++++--- mopidy_iris/handlers.py | 68 ++++++++++++++++++++++++++++++----------- setup.py | 3 +- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py index a3242e52..39fca300 100755 --- a/mopidy_iris/core.py +++ b/mopidy_iris/core.py @@ -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 +from promise import Promise from mopidy import config, ext from mopidy.core import CoreListener from pkg_resources import parse_version @@ -584,7 +586,6 @@ class IrisCore(object): } - ## # Proxy a request to an external provider # @@ -594,6 +595,9 @@ class IrisCore(object): ## def proxy_request(self, *args, **kwargs): + callback = kwargs.get('callback', None) + origin_request = kwargs.get('request', None) + try: data = kwargs.get('data', {}) except: @@ -604,7 +608,6 @@ class IrisCore(object): '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: @@ -645,7 +648,9 @@ class IrisCore(object): 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) + 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: @@ -655,7 +660,9 @@ class IrisCore(object): 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 @@ -679,3 +686,25 @@ class IrisCore(object): 'response_code': int(e.response_code), 'original_request': data } + + + def test(self, *args, **kwargs): + + print "running test 1" + response = requests.get("http://platform.james.plasticstudio.co/test.php?sleep=1") + + return Promise( + lambda resolve, reject: resolve(response.text) + ) + + + def test2(self, *args, **kwargs): + + print "running test 5" + response = requests.get("http://test.barnsley.nz/test.php?sleep=5") + + return Promise( + lambda resolve, reject: resolve(response.text) + ) + + diff --git a/mopidy_iris/handlers.py b/mopidy_iris/handlers.py index c7d67ade..c3cc011e 100755 --- a/mopidy_iris/handlers.py +++ b/mopidy_iris/handlers.py @@ -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__) @@ -76,22 +76,17 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): data['connection_id'] = self.connection_id if 'request_id' in message: - request_id = message['request_id'] + data['request_id'] = message['request_id'] else: - request_id = False + data['request_id'] = False # call the method, as specified in payload if 'method' in message: # make sure the method exists if hasattr(mem.iris, message['method']): + getattr(mem.iris, message['method'])(data=data, callback=self.handle_response) - # 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 = { @@ -113,6 +108,23 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): 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, response): + if isinstance(response, tornado.httpclient.HTTPResponse): + response_obj = { + 'body': response.body, + 'request_id': request_id + } + 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) + self.finish() + @@ -134,39 +146,61 @@ 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): + self.handle_request(getattr(mem.iris, slug)(request=self.request)) - # 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' }) - + 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' }) + self.finish() else: mem.iris.raven_client.captureMessage("Method "+slug+" does not exist") self.write({ 'error': 'Method "'+slug+'" does not exist' }) + self.finish() + + ## + # Handle a response from our core + # This is just our callback from an Async request + ## + def handle_request(self, request): + response = request.then(lambda response: response).get() + self.write(response) + self.finish() + + ## + # Handle a response from our core + # This is just our callback from an Async request + ## + def handle_response(self, response): + if isinstance(response, tornado.httpclient.HTTPResponse): + self.write(response.body) + else: + self.write(response) + self.finish() + + diff --git a/setup.py b/setup.py index 771c5d81..993953aa 100755 --- a/setup.py +++ b/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)', From b147b4f7363dd16457914454d91b37d611faa30d Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 13 Oct 2017 20:48:35 +1300 Subject: [PATCH 4/6] Using lambda to append info to callbacks; Only async for httprequests --- mopidy_iris/core.py | 42 +++++++++++++---------------------------- mopidy_iris/handlers.py | 35 +++++++++++++--------------------- 2 files changed, 26 insertions(+), 51 deletions(-) diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py index 39fca300..0c145e16 100755 --- a/mopidy_iris/core.py +++ b/mopidy_iris/core.py @@ -7,7 +7,7 @@ import tornado.websocket import tornado.ioloop import tornado.httpclient import requests -from promise import Promise +import time from mopidy import config, ext from mopidy.core import CoreListener from pkg_resources import parse_version @@ -664,19 +664,6 @@ class IrisCore(object): 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, @@ -689,22 +676,19 @@ class IrisCore(object): def test(self, *args, **kwargs): - - print "running test 1" - response = requests.get("http://platform.james.plasticstudio.co/test.php?sleep=1") - - return Promise( - lambda resolve, reject: resolve(response.text) - ) - + callback = kwargs.get('callback', None) + time.sleep(1) + callback({ + 'status': 1, + 'message': "Slept for one" + }) def test2(self, *args, **kwargs): - - print "running test 5" - response = requests.get("http://test.barnsley.nz/test.php?sleep=5") - - return Promise( - lambda resolve, reject: resolve(response.text) - ) + callback = kwargs.get('callback', None) + time.sleep(5) + callback({ + 'status': 1, + 'message': "Slept for FIVE!" + }) diff --git a/mopidy_iris/handlers.py b/mopidy_iris/handlers.py index c3cc011e..d970e4f4 100755 --- a/mopidy_iris/handlers.py +++ b/mopidy_iris/handlers.py @@ -76,33 +76,31 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): data['connection_id'] = self.connection_id if 'request_id' in message: - data['request_id'] = message['request_id'] + request_id = message['request_id'] else: - data['request_id'] = False + request_id = False # call the method, as specified in payload if 'method' in message: # make sure the method exists if hasattr(mem.iris, message['method']): - getattr(mem.iris, message['method'])(data=data, callback=self.handle_response) + getattr(mem.iris, message['method'])(data=data, callback=lambda response: self.handle_response(response=response, request_id=request_id)) 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): @@ -112,9 +110,12 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): # Handle a response from our core # This is just our callback from an Async request ## - def handle_response(self, response): + def handle_response(self, *args, **kwargs): + response = kwargs.get('response', None) + request_id = kwargs.get('request_id', False) + if isinstance(response, tornado.httpclient.HTTPResponse): - response_obj = { + response = { 'body': response.body, 'request_id': request_id } @@ -123,7 +124,6 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): mem.iris.send_message(connection_id=self.connection_id, data=response) mem.iris.send_message(connection_id=self.connection_id, data=response) - self.finish() @@ -151,7 +151,7 @@ class HttpHandler(tornado.web.RequestHandler): # make sure the method exists if hasattr(mem.iris, slug): - self.handle_request(getattr(mem.iris, slug)(request=self.request)) + getattr(mem.iris, slug)(request=self.request, callback=self.handle_response) else: mem.iris.raven_client.captureMessage("Method "+slug+" does not exist") @@ -182,15 +182,6 @@ class HttpHandler(tornado.web.RequestHandler): }) self.finish() - ## - # Handle a response from our core - # This is just our callback from an Async request - ## - def handle_request(self, request): - response = request.then(lambda response: response).get() - self.write(response) - self.finish() - ## # Handle a response from our core # This is just our callback from an Async request From fe011d7451b5413a42ff0fe4fc2ac968fd737aeb Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 13 Oct 2017 21:19:37 +1300 Subject: [PATCH 5/6] Adding callback-ability to all core methods --- mopidy_iris/core.py | 304 ++++++++++++++++++++++++---------------- mopidy_iris/handlers.py | 22 ++- 2 files changed, 199 insertions(+), 127 deletions(-) diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py index 0c145e16..1a83bf95 100755 --- a/mopidy_iris/core.py +++ b/mopidy_iris/core.py @@ -78,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): @@ -93,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 @@ -110,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) @@ -163,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: @@ -183,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 @@ -235,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 @@ -243,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) @@ -271,7 +295,7 @@ class IrisCore(object): latest_version = '0.0.0' upgrade_available = False - return { + response = { 'status': 1, 'version': { 'current': self.version, @@ -280,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)) @@ -302,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']: @@ -361,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, @@ -393,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 @@ -480,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 = { @@ -510,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(): @@ -532,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 ## @@ -547,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 @@ -573,18 +646,22 @@ 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 + ## # Proxy a request to an external provider @@ -595,29 +672,29 @@ class IrisCore(object): ## def proxy_request(self, *args, **kwargs): - callback = kwargs.get('callback', None) + 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' - } + }) # 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 @@ -644,51 +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']): - 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) + # 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"] - http_client = tornado.httpclient.AsyncHTTPClient() - request = tornado.httpclient.HTTPRequest(data['url'], headers=headers, validate_cert=False) - http_client.fetch(request, callback=callback) - - 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 - } + http_client = tornado.httpclient.AsyncHTTPClient() + request = tornado.httpclient.HTTPRequest(data['url'], headers=headers, validate_cert=False) + http_client.fetch(request, callback=callback) + ## + # Simple test method + ## def test(self, *args, **kwargs): callback = kwargs.get('callback', None) time.sleep(1) callback({ 'status': 1, - 'message': "Slept for one" + 'message': "Slept for one second" }) - - def test2(self, *args, **kwargs): - callback = kwargs.get('callback', None) - time.sleep(5) - callback({ - 'status': 1, - 'message': "Slept for FIVE!" - }) - - diff --git a/mopidy_iris/handlers.py b/mopidy_iris/handlers.py index d970e4f4..764b7cdd 100755 --- a/mopidy_iris/handlers.py +++ b/mopidy_iris/handlers.py @@ -114,11 +114,17 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): 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 = { - 'body': response.body, + '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) @@ -187,10 +193,18 @@ class HttpHandler(tornado.web.RequestHandler): # 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): - self.write(response.body) - else: - self.write(response) + response = { + 'response_code': response.code, + 'response_reason': response.reason, + 'response': response.body + } + + self.write(response) self.finish() From c36ea44925d5359588f17a9a4930721f4530890b Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Sat, 14 Oct 2017 08:35:37 +1300 Subject: [PATCH 6/6] User 'you' tag; Unifying backend response format --- build_tools/VERSION.md | 2 +- mopidy_iris/handlers.py | 9 ++++++--- src/js/views/User.js | 2 +- src/scss/global/_core.scss | 6 ++++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/build_tools/VERSION.md b/build_tools/VERSION.md index e0940d40..e5b82034 100755 --- a/build_tools/VERSION.md +++ b/build_tools/VERSION.md @@ -1 +1 @@ -3.4.9 \ No newline at end of file +3.5.0 \ No newline at end of file diff --git a/mopidy_iris/handlers.py b/mopidy_iris/handlers.py index 764b7cdd..1c8a3bb9 100755 --- a/mopidy_iris/handlers.py +++ b/mopidy_iris/handlers.py @@ -162,7 +162,8 @@ class HttpHandler(tornado.web.RequestHandler): 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() @@ -177,14 +178,16 @@ class HttpHandler(tornado.web.RequestHandler): except urllib2.HTTPError as e: 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() diff --git a/src/js/views/User.js b/src/js/views/User.js index 57e7e8f4..361d08f4 100755 --- a/src/js/views/User.js +++ b/src/js/views/User.js @@ -93,7 +93,7 @@ class User extends React.Component{
    {this.props.user.playlists_total ?
  • {this.props.user.playlists_total ? this.props.user.playlists_total.toLocaleString() : 0} playlists
  • : null} {this.props.user.followers ?
  • {this.props.user.followers.total.toLocaleString()} followers
  • : null} - {this.isMe() ?
  • You
  • : null} + {this.isMe() ?
  • You
  • : null}
diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss index 91b33c3d..33682d17 100755 --- a/src/scss/global/_core.scss +++ b/src/scss/global/_core.scss @@ -393,6 +393,12 @@ footer { background: lighten($dark_grey,10%); color: $mid_grey; } + + h1 &, + h2 & { + line-height: 1.4em; + vertical-align: middle; + } }