Adding callback-ability to all core methods

This commit is contained in:
James Barnsley
2017-10-13 21:19:37 +13:00
parent b147b4f736
commit fe011d7451
2 changed files with 199 additions and 127 deletions

View File

@ -78,7 +78,12 @@ class IrisCore(object):
generated = True generated = True
# construct our protocol object, and return # 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): def send_message(self, *args, **kwargs):
@ -93,14 +98,20 @@ class IrisCore(object):
def broadcast(self, *args, **kwargs): 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(): for connection in self.connections.itervalues():
connection['connection'].write_message( json_encode(data) ) connection['connection'].write_message( json_encode(data) )
return {
response = {
'status': 1, 'status': 1,
'message': 'Broadcast to '+str(len(self.connections))+' connections' 'message': 'Broadcast to '+str(len(self.connections))+' connections'
} }
if (callback):
callback(response)
else:
return response
## ##
# Connections # Connections
@ -110,15 +121,21 @@ class IrisCore(object):
# to all current connections # to all current connections
## ##
def get_connections(self, *args, **kwargs): def get_connections(self, *args, **kwargs):
callback = kwargs.get('callback', None)
connections = [] connections = []
for connection in self.connections.itervalues(): for connection in self.connections.itervalues():
connections.append(connection['client']) connections.append(connection['client'])
return { response = {
'status': 1, 'status': 1,
'connections': connections 'connections': connections
} }
if (callback):
callback(response)
else:
return response
def add_connection(self, *args, **kwargs): def add_connection(self, *args, **kwargs):
connection_id = kwargs.get('connection_id', None) connection_id = kwargs.get('connection_id', None)
@ -163,16 +180,8 @@ class IrisCore(object):
logger.error('Failed to close connection to '+ connection_id) logger.error('Failed to close connection to '+ connection_id)
def set_username(self, *args, **kwargs): def set_username(self, *args, **kwargs):
try: callback = kwargs.get('callback', None)
data = kwargs.get('data', {}) data = kwargs.get('data', {})
except:
self.raven_client.captureException()
return {
'status': 0,
'message': 'Malformed data',
'source': 'set_username'
}
connection_id = data['connection_id'] connection_id = data['connection_id']
if connection_id in self.connections: if connection_id in self.connections:
@ -183,47 +192,56 @@ class IrisCore(object):
'connection': self.connections[connection_id]['client'] 'connection': self.connections[connection_id]['client']
} }
) )
return { response = {
'status': 1, 'status': 1,
'connection_id': connection_id, 'connection_id': connection_id,
'username': data['username'] 'username': data['username']
} }
if (callback):
callback(response)
else:
return response
else: else:
error = 'Connection "'+data['connection_id']+'" not found' error = 'Connection "'+data['connection_id']+'" not found'
self.raven_client.captureMessage(error) self.raven_client.captureMessage(error)
logger.error(error) logger.error(error)
return { response = {
'status': 0, 'status': 0,
'message': error 'message': error
} }
if (callback):
callback(response)
else:
return response
def deliver_message(self, *args, **kwargs): def deliver_message(self, *args, **kwargs):
try: callback = kwargs.get('callback', False)
data = kwargs.get('data', {}) data = kwargs.get('data', {})
except:
self.raven_client.captureException()
return {
'status': 0,
'message': 'Malformed data',
'source': 'deliver_message'
}
if data['connection_id'] in self.connections: if data['connection_id'] in self.connections:
self.send_message(connection_id=data['connection_id'], data=data['message']) self.send_message(connection_id=data['connection_id'], data=data['message'])
return { response = {
'status': 1, 'status': 1,
'message': 'Sent message to '+data['connection_id'] 'message': 'Sent message to '+data['connection_id']
} }
if (callback):
callback(response)
else:
return response
else: else:
error = 'Connection "'+data['connection_id']+'" not found' error = 'Connection "'+data['connection_id']+'" not found'
self.raven_client.captureMessage(error) self.raven_client.captureMessage(error)
logger.error(error) logger.error(error)
return { response = {
'status': 0, 'status': 0,
'message': error 'message': error
} }
if (callback):
callback(response)
else:
return response
@ -235,6 +253,7 @@ class IrisCore(object):
## ##
def get_config(self, *args, **kwargs): def get_config(self, *args, **kwargs):
callback = kwargs.get('callback', False)
# handle config setups where there is no username/password # handle config setups where there is no username/password
# Iris won't work properly anyway, but at least we won't get server errors # Iris won't work properly anyway, but at least we won't get server errors
@ -243,17 +262,22 @@ class IrisCore(object):
else: else:
spotify_username = False spotify_username = False
config = { response = {
"spotify_username": spotify_username, 'config': {
"country": self.config['iris']['country'], "spotify_username": spotify_username,
"locale": self.config['iris']['locale'], "country": self.config['iris']['country'],
"authorization_url": self.config['iris']['authorization_url'] "locale": self.config['iris']['locale'],
} "authorization_url": self.config['iris']['authorization_url']
return { }
'config': config
} }
if (callback):
callback(response)
else:
return response
def get_version(self, *args, **kwargs): def get_version(self, *args, **kwargs):
callback = kwargs.get('callback', False)
url = 'https://pypi.python.org/pypi/Mopidy-Iris/json' url = 'https://pypi.python.org/pypi/Mopidy-Iris/json'
req = urllib2.Request(url) req = urllib2.Request(url)
@ -271,7 +295,7 @@ class IrisCore(object):
latest_version = '0.0.0' latest_version = '0.0.0'
upgrade_available = False upgrade_available = False
return { response = {
'status': 1, 'status': 1,
'version': { 'version': {
'current': self.version, 'current': self.version,
@ -280,16 +304,37 @@ class IrisCore(object):
'upgrade_available': upgrade_available '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: try:
subprocess.check_call(["pip", "install", "--upgrade", "Mopidy-Iris"]) 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: except subprocess.CalledProcessError as e:
self.raven_client.captureException(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)) os.execl(sys.executable, *([sys.executable]+sys.argv))
@ -302,21 +347,20 @@ class IrisCore(object):
## ##
def get_radio(self, *args, **kwargs): def get_radio(self, *args, **kwargs):
return { callback = kwargs.get('callback', False)
response = {
'status': 1, 'status': 1,
'radio': self.radio 'radio': self.radio
} }
if (callback):
callback(response)
else:
return response
def change_radio(self, *args, **kwargs): def change_radio(self, *args, **kwargs):
try: callback = kwargs.get('callback', False)
data = kwargs.get('data', {}) data = kwargs.get('data', {})
except:
self.raven_client.captureException()
return {
'status': 0,
'message': 'Malformed data',
'source': 'change_radio'
}
# figure out if we're starting or updating radio mode # figure out if we're starting or updating radio mode
if data['update'] and self.radio['enabled']: 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 # failed fetching/adding tracks, so no-go
self.radio['enabled'] = 0; self.radio['enabled'] = 0;
return { response = {
'status': 0, 'status': 0,
'message': 'Could not start radio', 'message': 'Could not start radio',
'radio': self.radio 'radio': self.radio
} }
if (callback):
callback(response)
else:
return response
def stop_radio(self, *args, **kwargs): def stop_radio(self, *args, **kwargs):
callback = kwargs.get('callback', False)
self.radio = { self.radio = {
"enabled": 0, "enabled": 0,
@ -393,16 +443,22 @@ class IrisCore(object):
} }
) )
return { response = {
'status': 1 '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 # 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 # 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: try:
token = self.spotify_token token = self.spotify_token
@ -480,21 +536,20 @@ class IrisCore(object):
## ##
def get_queue_metadata(self, *args, **kwargs): def get_queue_metadata(self, *args, **kwargs):
return { callback = kwargs.get('callback', False)
response = {
'status': 1, 'status': 1,
'queue_metadata': self.queue_metadata 'queue_metadata': self.queue_metadata
} }
if (callback):
callback(response)
else:
return response
def add_queue_metadata(self, *args, **kwargs): def add_queue_metadata(self, *args, **kwargs):
try: callback = kwargs.get('callback', False)
data = kwargs.get('data', {}) data = kwargs.get('data', {})
except:
self.raven_client.captureException()
return {
'status': 0,
'message': 'Malformed data',
'source': 'add_queue_metadata'
}
for tlid in data['tlids']: for tlid in data['tlids']:
item = { item = {
@ -510,12 +565,18 @@ class IrisCore(object):
'queue_metadata': self.queue_metadata 'queue_metadata': self.queue_metadata
} }
) )
return { response = {
'status': 1 '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 = {} cleaned_queue_metadata = {}
for tltrack in self.core.tracklist.get_tl_tracks().get(): for tltrack in self.core.tracklist.get_tl_tracks().get():
@ -532,10 +593,15 @@ class IrisCore(object):
'queue_metadata': self.queue_metadata 'queue_metadata': self.queue_metadata
} }
) )
return { response = {
'status': 1 '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): def get_spotify_token(self, *args, **kwargs):
return { callback = kwargs.get('callback', False)
response = {
'spotify_token': self.spotify_token 'spotify_token': self.spotify_token
} }
if (callback):
callback(response)
else:
return response
def refresh_spotify_token(self, *args, **kwargs): def refresh_spotify_token(self, *args, **kwargs):
callback = kwargs.get('callback', None)
# Use client_id and client_secret from config # Use client_id and client_secret from config
# This was introduced in Mopidy-Spotify 3.1.0 # 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: except urllib2.HTTPError as e:
self.raven_client.captureException() self.raven_client.captureException()
error = json.loads(e.read()) error = json.loads(e.read())
response = {
return {
'status': 0, 'status': 0,
'message': 'Could not refresh token: '+error['error_description'], 'message': 'Could not refresh token: '+error['error_description'],
'source': 'refresh_spotify_token' 'source': 'refresh_spotify_token'
} }
if (callback):
callback(response)
else:
return response
## ##
# Proxy a request to an external provider # Proxy a request to an external provider
@ -595,29 +672,29 @@ class IrisCore(object):
## ##
def proxy_request(self, *args, **kwargs): def proxy_request(self, *args, **kwargs):
callback = kwargs.get('callback', None) callback = kwargs.get('callback', False)
origin_request = kwargs.get('request', None) origin_request = kwargs.get('request', None)
try: try:
data = kwargs.get('data', {}) data = kwargs.get('data', {})
except: except:
self.raven_client.captureException() self.raven_client.captureException()
return { callback({
'status': 0, 'status': 0,
'message': 'Malformed data', 'message': 'Malformed data',
'source': 'proxy_request' 'source': 'proxy_request'
} })
# Our request includes data, so make sure we POST the data # Our request includes data, so make sure we POST the data
if 'url' not in data: if 'url' not in data:
self.raven_client.captureException() self.raven_client.captureException()
return { callback({
'status': 0, 'status': 0,
'message': 'Malformed data (missing URL)', 'message': 'Malformed data (missing URL)',
'source': 'proxy_request', 'source': 'proxy_request',
'original_request': data 'original_request': data
} })
# Construct request headers # Construct request headers
# If we have an original request, pass through it's headers # If we have an original request, pass through it's headers
@ -644,51 +721,32 @@ class IrisCore(object):
if "Referrer" in headers: if "Referrer" in headers:
del headers["Referrer"] del headers["Referrer"]
# Now actually attempt the request # Our request includes data, so make sure we POST the data
try: if ('data' in data and data['data']):
# Our request includes data, so make sure we POST the data http_client = tornado.httpclient.AsyncHTTPClient()
if ('data' in data and data['data']): request = tornado.httpclient.HTTPRequest(data['url'], method='POST', data=data['data'], headers=headers, validate_cert=False)
http_client = tornado.httpclient.AsyncHTTPClient() http_client.fetch(request, callback=callback)
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 # No data, so just a simple GET request
else: else:
# Strip out our origin content-length otherwise this confuses # Strip out our origin content-length otherwise this confuses
# the target server as content-length doesn't apply to GET requests # the target server as content-length doesn't apply to GET requests
if "Content-Length" in headers: if "Content-Length" in headers:
del headers["Content-Length"] del headers["Content-Length"]
http_client = tornado.httpclient.AsyncHTTPClient() http_client = tornado.httpclient.AsyncHTTPClient()
request = tornado.httpclient.HTTPRequest(data['url'], headers=headers, validate_cert=False) request = tornado.httpclient.HTTPRequest(data['url'], headers=headers, validate_cert=False)
http_client.fetch(request, callback=callback) 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
}
##
# Simple test method
##
def test(self, *args, **kwargs): def test(self, *args, **kwargs):
callback = kwargs.get('callback', None) callback = kwargs.get('callback', None)
time.sleep(1) time.sleep(1)
callback({ callback({
'status': 1, '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!"
})

View File

@ -114,11 +114,17 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
response = kwargs.get('response', None) response = kwargs.get('response', None)
request_id = kwargs.get('request_id', False) 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): if isinstance(response, tornado.httpclient.HTTPResponse):
response = { response = {
'body': response.body, 'response_code': response.code,
'response_reason': response.reason,
'response': response.body,
'request_id': request_id 'request_id': request_id
} }
# Just a regular json object, so not an external request
else: else:
response['request_id'] = request_id 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)
@ -187,10 +193,18 @@ class HttpHandler(tornado.web.RequestHandler):
# This is just our callback from an Async request # This is just our callback from an Async request
## ##
def handle_response(self, response): 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): if isinstance(response, tornado.httpclient.HTTPResponse):
self.write(response.body) response = {
else: 'response_code': response.code,
self.write(response) 'response_reason': response.reason,
'response': response.body
}
self.write(response)
self.finish() self.finish()