From 9ef2b13ad6bb063d29e6d28428fdf00f70146ea0 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Sat, 1 May 2010 13:10:48 +0100 Subject: [PATCH] comments, update client, load html from disk, blanking --- openlp/core/lib/eventreceiver.py | 13 ++ openlp/core/ui/slidecontroller.py | 18 ++ openlp/plugins/alerts/lib/alertsmanager.py | 7 +- openlp/plugins/remotes/html/index.html | 117 +++++++++++ openlp/plugins/remotes/lib/httpserver.py | 227 ++++++++++----------- openlp/plugins/remotes/remoteplugin.py | 16 +- scripts/openlp-remoteclient.py | 37 ++-- 7 files changed, 295 insertions(+), 140 deletions(-) create mode 100644 openlp/plugins/remotes/html/index.html diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index 73bfbbe61..d21e3f167 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -83,6 +83,12 @@ class EventReceiver(QtCore.QObject): Returns a slidecontroller_{live|preview}_text_response with an array of dictionaries with the tag and verse text + ``slidecontroller_{live|preview}_blank`` + Request that the output screen is blanked + + ``slidecontroller_{live|preview}_unblank`` + Request that the output screen is unblanked + ``slidecontroller_live_spin_delay`` Pushes out the loop delay @@ -132,6 +138,9 @@ class EventReceiver(QtCore.QObject): ``videodisplay_stop`` Stop playing a media item + ``videodisplay_background`` + Replace the background video + ``theme_update_list`` send out message with new themes @@ -196,6 +205,10 @@ class EventReceiver(QtCore.QObject): ``bibles_stop_import`` Stops the Bible Import + ``remotes_poll_request`` + Waits for openlp to do something "interesting" and sends a + remotes_poll_response signal when it does + """ def __init__(self): """ diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 462f276bb..c0d0d915f 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -341,6 +341,12 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_set' % self.type_prefix), self.onSlideSelectedIndex) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'slidecontroller_%s_blank' % self.type_prefix), + self.onSlideBlank) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'slidecontroller_%s_unblank' % self.type_prefix), + self.onSlideUnblank) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_text_request' % self.type_prefix), self.onTextRequest) @@ -616,6 +622,18 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.selectRow(index) self.onSlideSelected() + def onSlideBlank(self): + """ + Handle the slidecontroller blank event + """ + self.onBlankDisplay(True) + + def onSlideUnblank(self): + """ + Handle the slidecontroller unblank event + """ + self.onBlankDisplay(False) + def onBlankDisplay(self, checked): """ Handle the blank screen button diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index ee91e190e..35be3b7c2 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -75,8 +75,11 @@ class AlertsManager(QtCore.QObject): Called via a alerts_text event. Message is single element array containing text """ - self.displayAlert(message[0]) - + if message: + self.displayAlert(message[0]) + else: + self.displayAlert(u'') + def displayAlert(self, text=u''): """ Called from the Alert Tab to display an alert diff --git a/openlp/plugins/remotes/html/index.html b/openlp/plugins/remotes/html/index.html new file mode 100644 index 000000000..25b08fd43 --- /dev/null +++ b/openlp/plugins/remotes/html/index.html @@ -0,0 +1,117 @@ + + +OpenLP Controller + + + +

OpenLP Controller

+ + +
+ + +
+ + +
+ + +
+ +
+
+ +
+
+ OpenLP website + + + diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 6f0ee3217..2c4105dab 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -24,12 +24,14 @@ ############################################################################### import logging +import os import json import urlparse from PyQt4 import QtCore, QtNetwork from openlp.core.lib import Receiver +from openlp.core.utils import AppLocation log = logging.getLogger(__name__) @@ -40,14 +42,25 @@ class HttpServer(object): http://localhost:4316/send/alerts_text?q=your%20alert%20text """ def __init__(self, parent): + """ + Initialise the httpserver, and start the server + """ log.debug(u'Initialise httpserver') self.parent = parent + self.html_dir = os.path.join( + AppLocation.get_directory(AppLocation.PluginsDir), + u'remotes', u'html') self.connections = [] self.current_item = None self.current_slide = None self.start_tcp() def start_tcp(self): + """ + Start the http server, use the port in the settings default to 4316 + Listen out for slide and song changes so they can be broadcast to + clients. Listen out for socket connections + """ log.debug(u'Start TCP server') port = QtCore.QSettings().value( self.parent.settingsSection + u'/remote port', @@ -66,29 +79,48 @@ class HttpServer(object): log.debug(u'TCP listening on port %d' % port) def slide_change(self, row): + """ + Slide change listener. Store the item and tell the clients + """ self.current_slide = row self.send_poll() def item_change(self, items): + """ + Item (song) change listener. Store the slide and tell the clients + """ self.current_item = items[0].title self.send_poll() def send_poll(self): - Receiver.send_message(u'remote_poll_response', + """ + Tell the clients something has changed + """ + Receiver.send_message(u'remotes_poll_response', {'slide': self.current_slide, 'item': self.current_item}) def new_connection(self): + """ + A new http connection has been made. Create a client object to handle + communication + """ log.debug(u'new http connection') socket = self.server.nextPendingConnection() if socket: self.connections.append(HttpConnection(self, socket)) def close_connection(self, connection): + """ + The connection has been closed. Clean up + """ log.debug(u'close http connection') self.connections.remove(connection) def close(self): + """ + Close down the http server + """ log.debug(u'close http server') self.server.close() @@ -98,6 +130,9 @@ class HttpConnection(object): and the client """ def __init__(self, parent, socket): + """ + Initialise the http connection. Listen out for socket signals + """ log.debug(u'Initialise HttpConnection: %s' % socket.peerAddress().toString()) self.socket = socket @@ -108,6 +143,9 @@ class HttpConnection(object): self.disconnected) def ready_read(self): + """ + Data has been sent from the client. Respond to it + """ log.debug(u'ready to read socket') if self.socket.canReadLine(): data = unicode(self.socket.readLine()) @@ -119,7 +157,9 @@ class HttpConnection(object): params = self.load_params(url.query) folders = url.path.split(u'/') if folders[1] == u'': - html = self.process_index() + html = self.serve_file(u'') + elif folders[1] == u'files': + html = self.serve_file(folders[2]) elif folders[1] == u'send': html = self.process_event(folders[2], params) elif folders[1] == u'request': @@ -131,120 +171,39 @@ class HttpConnection(object): html = self.get_404_not_found() self.socket.write(html) self.close() - - def process_index(self): - return u""" - - -OpenLP Controller - - - -

OpenLP Controller

- - -
- - -
- - -
- -
- -
- - -""" - + def serve_file(self, filename): + """ + Send a file to the socket. For now, just .html files + and must be top level inside the html folder. + If subfolders requested return 404, easier for security for the present. + + Ultimately for i18n, this could first look for xx/file.html before + falling back to file.html... where xx is the language, e.g. 'en' + """ + log.debug(u'serve file request %s' % filename) + if not filename: + filename = u'index.html' + if os.path.basename(filename) != filename: + return None + (fileroot, ext) = os.path.splitext(filename) + if ext != u'.html': + return None + path = os.path.join(self.parent.html_dir, filename) + try: + f = open(path, u'rb') + except: + log.exception(u'Failed to open %s' % path) + return None + log.debug(u'Opened %s' % path) + html = f.read() + f.close() + return html + def load_params(self, query): + """ + Decode the query string parameters sent from the browser + """ params = urlparse.parse_qs(query) if not params: return None @@ -252,6 +211,11 @@ send_event("remote_poll_request"); return params['q'] def process_event(self, event, params): + """ + Send a signal to openlp to perform an action. + Currently lets anything through. Later we should restrict and perform + basic parameter checking, otherwise rogue clients could crash openlp + """ if params: Receiver.send_message(event, params) else: @@ -259,6 +223,16 @@ send_event("remote_poll_request"); return u'OK' def process_request(self, event, params): + """ + Client has requested data. Send the signal and parameters for openlp + to handle, then listen out for a corresponding _request signal + which will have the data to return. + For most event timeout after 10 seconds (i.e. incase the signal + recipient isn't listening) + remotes_poll_request is a special case, this is a ajax long poll which + is just waiting for slide change/song change activity. This can wait + longer (one minute) + """ if not event.endswith(u'_request'): return False self.event = event @@ -269,7 +243,7 @@ send_event("remote_poll_request"); self.timer.setSingleShot(True) QtCore.QObject.connect(self.timer, QtCore.SIGNAL(u'timeout()'), self.timeout) - if event == 'remote_poll_request': + if event == 'remotes_poll_request': self.timer.start(60000) else: self.timer.start(10000) @@ -280,6 +254,10 @@ send_event("remote_poll_request"); return True def process_response(self, data): + """ + The recipient of a _request signal has sent data. Convert this to + json and return it to client + """ if not self.socket: return self.timer.stop() @@ -289,19 +267,32 @@ send_event("remote_poll_request"); self.close() def get_200_ok(self): + """ + Successful request. Send OK headers. Assume html for now. + """ return u'HTTP/1.1 200 OK\r\n' + \ u'Content-Type: text/html; charset="utf-8"\r\n' + \ u'\r\n' def get_404_not_found(self): + """ + Invalid url. Say so + """ return u'HTTP/1.1 404 Not Found\r\n'+ \ u'Content-Type: text/html; charset="utf-8"\r\n' + \ u'\r\n' def get_408_timeout(self): + """ + A _request hasn't returned anything in the timeout period. + Return timeout + """ return u'HTTP/1.1 408 Request Timeout\r\n' def timeout(self): + """ + Listener for timeout signal + """ if not self.socket: return html = self.get_408_timeout() @@ -309,10 +300,16 @@ send_event("remote_poll_request"); self.close() def disconnected(self): + """ + The client has disconnected. Tidy up + """ log.debug(u'socket disconnected') self.close() def close(self): + """ + The server has closed the connection. Tidy up + """ if not self.socket: return log.debug(u'close socket') diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 0eec0739d..2c632d90d 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -36,17 +36,26 @@ class RemotesPlugin(Plugin): log.info(u'Remote Plugin loaded') def __init__(self, plugin_helpers): + """ + remotes constructor + """ Plugin.__init__(self, u'Remotes', u'1.9.1', plugin_helpers) self.weight = -1 self.server = None def initialise(self): + """ + Initialise the remotes plugin, and start the http server + """ log.debug(u'initialise') Plugin.initialise(self) self.insert_toolbox_item() self.server = HttpServer(self) def finalise(self): + """ + Tidy up and close down the http server + """ log.debug(u'finalise') self.remove_toolbox_item() if self.server: @@ -59,8 +68,11 @@ class RemotesPlugin(Plugin): return RemoteTab(self.name) def about(self): + """ + Information about this plugin + """ about_text = self.trUtf8('Remote Plugin
This plugin ' 'provides the ability to send messages to a running version of ' - 'openlp on a different computer.
The Primary use for this ' - 'would be to send alerts from a creche') + 'openlp on a different computer via a web browser or other app
' + 'The Primary use for this would be to send alerts from a creche') return about_text diff --git a/scripts/openlp-remoteclient.py b/scripts/openlp-remoteclient.py index de3099920..3ff7ccc05 100755 --- a/scripts/openlp-remoteclient.py +++ b/scripts/openlp-remoteclient.py @@ -24,34 +24,33 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import socket +import urllib import sys from optparse import OptionParser -def sendData(options, message): - addr = (options.address, options.port) +def sendData(options): + addr = 'http://%s:%s/send/%s?q=%s' % (options.address, options.port, + options.event, options.message) try: - UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) - UDPSock.sendto(message, addr) - print u'message sent ', message, addr + urllib.urlopen(addr) + print u'Message sent ', addr except: - print u'Errow thrown ', sys.exc_info()[1] - -def format_message(options): - return u'%s:%s' % (u'alert', options.message) + print u'Error thrown ', sys.exc_info()[1] def main(): - usage = "usage: %prog [options] arg1 arg2" + usage = "usage: %prog [-a address] [-p port] [-e event] [-m message]" parser = OptionParser(usage=usage) - parser.add_option("-v", "--verbose", - action="store_true", dest="verbose", default=True, - help="make lots of noise [%default]") parser.add_option("-p", "--port", default=4316, help="IP Port number %default ") parser.add_option("-a", "--address", - help="Recipient address ") + help="Recipient address ", + default="localhost") + parser.add_option("-e", "--event", + help="Action to be performed", + default="alerts_text") parser.add_option("-m", "--message", - help="Message to be passed for the action") + help="Message to be passed for the action", + default="") (options, args) = parser.parse_args() if args: @@ -60,12 +59,8 @@ def main(): elif options.address is None: parser.print_help() parser.error("IP address missing") - elif options.message is None: - parser.print_help() - parser.error("No message passed") else: - text = format_message(options) - sendData(options, text) + sendData(options) if __name__ == u'__main__': main()