Frontend spawns handlers; Handlers add themselves to Frontend ref

This commit is contained in:
James Barnsley
2017-02-17 09:11:23 +13:00
parent cf8672f00c
commit 16d057cd17
4 changed files with 109 additions and 122 deletions

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals
import logging, os, json
import tornado.web
import tornado.websocket
from mopidy import config, ext
from frontend import IrisFrontend
from frontend import IrisFrontend, make_iris_factory
from http import HttpHandler
from websocket import WebsocketHandler
@ -39,32 +40,8 @@ class Extension( ext.Extension ):
# Add web extension
registry.add('http:app', {
'name': self.ext_name,
'factory': factory
'factory': make_iris_factory(
registry['http:app'],
registry['http:static']
)
})
# add our frontend
registry.add('frontend', IrisFrontend)
def factory(config, core):
path = os.path.join( os.path.dirname(__file__), 'static')
frontend = IrisFrontend(config, core)
return [
(r"/images/(.*)", tornado.web.StaticFileHandler, {
"path": config['local-images']['image_dir']
}),
(r'/http/([^/]*)', HttpHandler, {
'core': core,
'frontend': frontend,
'config': config
}),
(r'/ws/?', WebsocketHandler, {
'core': core,
'frontend': frontend
}),
(r'/(.*)', tornado.web.StaticFileHandler, {
"path": path,
"default_filename": "index.html"
}),
]

View File

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import logging, json, pykka, pylast, urllib, urllib2, os, sys, mopidy_iris, subprocess
@ -8,14 +9,20 @@ from mopidy import config, ext
from mopidy.core import CoreListener
from pkg_resources import parse_version
from spotipy import Spotify
from websocket import WebsocketHandler
from http import HttpHandler
# import logger
logger = logging.getLogger(__name__)
###
# Create our factory
#
# This hooks all our components into the Mopidy registry. Called from __init__.py
##
def make_iris_factory(apps, statics):
def mopidy_app_factory(config, core):
def iris_factory(config, core):
path = os.path.join( os.path.dirname(__file__), 'static')
frontend = IrisFrontend(config, core)
@ -25,19 +32,17 @@ def make_iris_factory(apps, statics):
"path": config['local-images']['image_dir']
}),
(r'/http/([^/]*)', HttpHandler, {
'core': core,
'frontend': frontend,
'config': config
}),
(r'/ws/?', WebsocketHandler, {
'core': core,
'frontend': frontend
}),
"frontend": frontend
}),
(r'/ws/?', WebsocketHandler, {
"frontend": frontend
}),
(r'/(.*)', tornado.web.StaticFileHandler, {
"path": path,
"default_filename": "index.html"
}),
"path": path,
"default_filename": "index.html"
}),
]
return iris_factory
###
# Spotmop supporting frontend
@ -54,6 +59,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.is_root = ( os.geteuid() == 0 )
self.spotify_token = False
self.queue_metadata = {}
self.connections = {}
self.radio = {
"enabled": 0,
"seed_artists": [],
@ -113,7 +119,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.load_more_tracks()
except RuntimeError:
WebsocketHandler.broadcast('error', {'source': 'check_for_radio_update', 'message': 'Could not fetch tracklist length'})
self.websocket.broadcast('error', {'source': 'check_for_radio_update', 'message': 'Could not fetch tracklist length'})
logger.warning('IrisFrontend: Could not fetch tracklist length')
pass
@ -134,7 +140,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
token = token['access_token']
except:
logger.error('IrisFrontend: access_token missing or invalid')
WebsocketHandler.broadcast('error', {'source': 'load_more_tracks', 'message': 'access_token missing or invalid'})
self.websocket.broadcast('error', {'source': 'load_more_tracks', 'message': 'access_token missing or invalid'})
try:
spotify = Spotify( auth = token )
@ -146,7 +152,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.core.tracklist.add( uris = uris )
except:
WebsocketHandler.broadcast('error', {'source': 'load_more_tracks', 'message': 'Failed to fetch Spotify recommendations'})
self.websocket.broadcast('error', {'source': 'load_more_tracks', 'message': 'Failed to fetch Spotify recommendations'})
logger.error('IrisFrontend: Failed to fetch Spotify recommendations')
@ -172,7 +178,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.core.playback.play()
# notify clients
WebsocketHandler.broadcast('radio', { 'radio': self.radio })
self.websocket.broadcast('radio', { 'radio': self.radio })
# return new radio state to initial call
return self.radio
@ -194,7 +200,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.core.playback.stop()
# notify clients
WebsocketHandler.broadcast( 'radio', { 'radio': self.radio })
self.websocket.broadcast( 'radio', { 'radio': self.radio })
# return new radio state to initial call
return self.radio
@ -223,7 +229,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.queue_metadata['tlid_'+str(tlid)] = item
# broadcast to all clients
WebsocketHandler.broadcast('queue_metadata', {'queue_metadata': self.queue_metadata})
self.websocket.broadcast('queue_metadata', {'queue_metadata': self.queue_metadata})
return self.queue_metadata
@ -243,7 +249,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
self.queue_metadata = cleaned_queue_metadata
# broadcast to all clients
WebsocketHandler.broadcast('queue_metadata', {'queue_metadata': self.queue_metadata})
self.websocket.broadcast('queue_metadata', {'queue_metadata': self.queue_metadata})
##

View File

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import logging, json, urllib, urllib2
@ -12,10 +13,8 @@ class HttpHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
def initialize(self, frontend, core, config):
def initialize(self, frontend):
self.frontend = frontend
self.core = core
self.config = config
def get(self, slug=None):

View File

@ -1,45 +1,10 @@
import tornado.ioloop, tornado.web, tornado.websocket, tornado.template
import logging, uuid, subprocess, pykka
from datetime import datetime
from tornado.escape import json_encode, json_decode
logger = logging.getLogger(__name__)
# container for all current pusher connections
connections = {}
frontend = {}
##
# Send a message to an individual connection
#
# @param recipient_connection_ids = array
# @param action = string (action method of this message)
# @param request_id = string (used for callbacks)
# @param data = array (any data required to include in our message)
##
def send_message( recipient_connection_id, action, request_id, data ):
message = {
'action': action,
'request_id': request_id,
'data': data
}
connections[recipient_connection_id]['connection'].write_message( json_encode(message) )
##
# Broadcast a message to all recipients
#
# @param action = string
# @param data = array (the body of our message to send)
##
def broadcast( type, data ):
for connection in connections.itervalues():
message = {
'action': 'broadcast',
'type': type,
'data': data
}
connection['connection'].write_message( json_encode(message) )
# digest a protocol header into it's id/name parts
@ -78,13 +43,15 @@ def digest_protocol( protocol ):
##
class WebsocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self, core, frontend):
self.core = core
self.frontend = frontend
def check_origin(self, origin):
return True
# initiate (not the actual object __init__, but run shortly after)
def initialize(self, frontend):
# add this websocket instance to our Frontend
frontend.websocket = self
self.frontend = frontend
# when a new connection is opened
def open(self):
@ -105,7 +72,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
'ip': self.request.remote_ip,
'created': created
}
connections[connectionid] = {
self.frontend.connections[connectionid] = {
'client': client,
'connection': self
}
@ -113,8 +80,13 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
logger.info( 'Pusher connection established: '+ connectionid +' ('+ clientid +'/'+ username +')' )
# broadcast to all connections that a new user has connected
broadcast( 'new_connection', client )
self.broadcast( 'new_connection', client )
def check_origin(self, origin):
return True
def select_subprotocol(self, subprotocols):
# select one of our subprotocol elements and return it. This confirms the connection has been accepted.
protocols = digest_protocol( subprotocols )
@ -128,6 +100,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
else:
return protocols['clientid']
# server received a message
def on_message(self, message):
messageJson = json_decode(message)
@ -135,9 +108,9 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
# construct the origin client info
messageJson['origin'] = {
'connectionid' : self.connectionid,
'clientid': connections[self.connectionid]['client']['clientid'],
'clientid': self.frontend.connections[self.connectionid]['client']['clientid'],
'ip': self.request.remote_ip,
'username': connections[self.connectionid]['client']['username']
'username': self.frontend.connections[self.connectionid]['client']['username']
}
logger.debug('Pusher message received: '+message)
@ -146,14 +119,14 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
if messageJson['action'] == 'broadcast':
# respond to request with status update
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
{ 'status': 'Ok' }
)
for connection in connections.itervalues():
for connection in self.frontend.connections.itervalues():
if connection['client']['connectionid'] != self.connectionid:
connection['connection'].write_message(messageJson)
@ -161,7 +134,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
elif messageJson['action'] == 'send_authorization':
# make sure we actually have a connection matching the provided connectionid
if messageJson['recipient_connectionid'] in connections:
if messageJson['recipient_connectionid'] in self.frontend.connections:
# send payload to recipient
authorization_message = {
@ -171,10 +144,10 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
'me': messageJson['me'],
'origin': messageJson['origin']
}
connections[messageJson['recipient_connectionid']]['connection'].write_message(authorization_message)
self.frontend.connections[messageJson['recipient_connectionid']]['connection'].write_message(authorization_message)
# respond to request with status update
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -182,7 +155,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
)
else:
# respond to request with status update
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -191,7 +164,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
# fetch our pusher connections
elif messageJson['action'] == 'get_config':
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -202,10 +175,10 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
elif messageJson['action'] == 'get_connections':
connectionsDetailsList = []
for connection in connections.itervalues():
for connection in self.frontend.connections.itervalues():
connectionsDetailsList.append(connection['client'])
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -217,10 +190,10 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
queue_metadata = self.frontend.add_queue_metadata(
messageJson['tlids'],
messageJson['added_from'],
connections[self.connectionid]['client']['username']
self.frontend.connections[self.connectionid]['client']['username']
)
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -231,10 +204,10 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
elif messageJson['action'] == 'get_queue_metadata':
connectionsDetailsList = []
for connection in connections.itervalues():
for connection in self.frontend.connections.itervalues():
connectionsDetailsList.append(connection['client'])
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -245,10 +218,10 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
elif messageJson['action'] == 'set_username':
# username is the only value we allow clients to change
connections[messageJson['origin']['connectionid']]['client']['username'] = messageJson['username']
self.frontend.connections[messageJson['origin']['connectionid']]['client']['username'] = messageJson['username']
# respond to request
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -256,7 +229,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
)
# notify all clients of this change
broadcast( 'connection_updated', { 'connection': connections[messageJson['origin']['connectionid']]['client'] })
self.broadcast( 'connection_updated', { 'connection': self.frontend.connections[messageJson['origin']['connectionid']]['client'] })
# start radio
elif messageJson['action'] == 'start_radio':
@ -269,7 +242,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
'seed_tracks': messageJson['seed_tracks']
}
radio = self.frontend.start_radio( radio )
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -279,7 +252,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
# stop radio
elif messageJson['action'] == 'stop_radio':
radio = self.frontend.stop_radio()
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -288,7 +261,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
# fetch our current radio state
elif messageJson['action'] == 'get_radio':
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -298,7 +271,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
# get system version and check for upgrade
elif messageJson['action'] == 'get_version':
version = self.frontend.get_version()
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -309,7 +282,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
elif messageJson['action'] == 'upgrade':
version = self.frontend.get_version()
upgrade_successful = self.frontend.perform_upgrade()
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -322,7 +295,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
# not an action we recognise!
else:
send_message(
self.send_message(
self.connectionid,
'response',
messageJson['request_id'],
@ -331,20 +304,52 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
logger.debug( 'Pusher: Unhandled message received from '+ self.connectionid )
# connection closed
def on_close(self):
if self.connectionid in connections:
if self.connectionid in self.frontend.connections:
clientRemoved = connections[self.connectionid]['client']
clientRemoved = self.frontend.connections[self.connectionid]['client']
logger.debug( 'Spotmop Pusher connection to '+ self.connectionid +' closed' )
# now actually remove it
try:
del connections[self.connectionid]
del self.frontend.connections[self.connectionid]
except:
logger.info( 'Failed to close connection to '+ self.connectionid )
broadcast( 'client_disconnected', clientRemoved )
self.broadcast( 'client_disconnected', clientRemoved )
##
# Send a message to an individual connection
#
# @param recipient_connection_ids = array
# @param action = string (action method of this message)
# @param request_id = string (used for callbacks)
# @param data = array (any data required to include in our message)
##
def send_message( self, recipient_connection_id, action, request_id, data ):
message = {
'action': action,
'request_id': request_id,
'data': data
}
self.frontend.connections[recipient_connection_id]['connection'].write_message( json_encode(message) )
##
# Broadcast a message to all recipients
#
# @param action = string
# @param data = array (the body of our message to send)
##
def broadcast( self, type, data ):
for connection in self.frontend.connections.itervalues():
message = {
'action': 'broadcast',
'type': type,
'data': data
}
connection['connection'].write_message( json_encode(message) )