openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #19911
[Merge] lp:~trb143/openlp/cherrypy into lp:openlp
Tim Bentley has proposed merging lp:~trb143/openlp/cherrypy into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
For more details, see:
https://code.launchpad.net/~trb143/openlp/cherrypy/+merge/152779
Test merge to see what it looks like
--
https://code.launchpad.net/~trb143/openlp/cherrypy/+merge/152779
Your team OpenLP Core is requested to review the proposed merge of lp:~trb143/openlp/cherrypy into lp:openlp.
=== modified file 'openlp/core/lib/settingstab.py'
--- openlp/core/lib/settingstab.py 2013-02-19 19:50:14 +0000
+++ openlp/core/lib/settingstab.py 2013-03-11 21:06:21 +0000
@@ -36,6 +36,7 @@
from openlp.core.lib import Registry
+
class SettingsTab(QtGui.QWidget):
"""
SettingsTab is a helper widget for plugins to define Tabs for the settings
=== modified file 'openlp/core/ui/exceptionform.py'
--- openlp/core/ui/exceptionform.py 2013-03-05 14:14:37 +0000
+++ openlp/core/ui/exceptionform.py 2013-03-11 21:06:21 +0000
@@ -70,6 +70,11 @@
except ImportError:
MAKO_VERSION = u'-'
try:
+ import cherrypy
+ CHERRYPY_VERSION = cherrypy.__version__
+except ImportError:
+ CHERRYPY_VERSION = u'-'
+try:
import uno
arg = uno.createUnoStruct(u'com.sun.star.beans.PropertyValue')
arg.Name = u'nodepath'
@@ -138,6 +143,7 @@
u'PyEnchant: %s\n' % ENCHANT_VERSION + \
u'PySQLite: %s\n' % SQLITE_VERSION + \
u'Mako: %s\n' % MAKO_VERSION + \
+ u'CherryPy: %s\n' % CHERRYPY_VERSION + \
u'pyUNO bridge: %s\n' % UNO_VERSION
if platform.system() == u'Linux':
if os.environ.get(u'KDE_FULL_SESSION') == u'true':
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2013-03-07 12:59:35 +0000
+++ openlp/core/ui/slidecontroller.py 2013-03-11 21:06:21 +0000
@@ -357,8 +357,9 @@
# Signals
self.preview_list_widget.clicked.connect(self.onSlideSelected)
if self.is_live:
+ # Need to use event as called across threads and UI is updated
+ QtCore.QObject.connect(self, QtCore.SIGNAL(u'slidecontroller_toggle_display'), self.toggle_display)
Registry().register_function(u'slidecontroller_live_spin_delay', self.receive_spin_delay)
- Registry().register_function(u'slidecontroller_toggle_display', self.toggle_display)
self.toolbar.set_widget_visible(self.loop_list, False)
self.toolbar.set_widget_visible(self.wide_menu, False)
else:
=== modified file 'openlp/core/utils/applocation.py'
--- openlp/core/utils/applocation.py 2013-02-27 13:01:42 +0000
+++ openlp/core/utils/applocation.py 2013-03-11 21:06:21 +0000
@@ -63,6 +63,7 @@
VersionDir = 5
CacheDir = 6
LanguageDir = 7
+ SharedData = 8
# Base path where data/config/cache dir is located
BaseDir = None
@@ -149,18 +150,18 @@
if sys.platform == u'win32':
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp', u'data')
- elif dir_type == AppLocation.LanguageDir:
+ elif dir_type == AppLocation.LanguageDir or dir_type == AppLocation.SharedData:
return os.path.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp')
elif sys.platform == u'darwin':
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'HOME'), encoding),
u'Library', u'Application Support', u'openlp', u'Data')
- elif dir_type == AppLocation.LanguageDir:
+ elif dir_type == AppLocation.LanguageDir or dir_type == AppLocation.SharedData:
return os.path.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp')
else:
- if dir_type == AppLocation.LanguageDir:
+ if dir_type == AppLocation.LanguageDir or dir_type == AppLocation.SharedData:
prefixes = [u'/usr/local', u'/usr']
for prefix in prefixes:
directory = os.path.join(prefix, u'share', u'openlp')
=== modified file 'openlp/plugins/remotes/html/stage.js'
--- openlp/plugins/remotes/html/stage.js 2012-12-29 20:56:56 +0000
+++ openlp/plugins/remotes/html/stage.js 2013-03-11 21:06:21 +0000
@@ -26,7 +26,7 @@
window.OpenLP = {
loadService: function (event) {
$.getJSON(
- "/api/service/list",
+ "/stage/api/service/list",
function (data, status) {
OpenLP.nextSong = "";
$("#notes").html("");
@@ -46,7 +46,7 @@
},
loadSlides: function (event) {
$.getJSON(
- "/api/controller/live/text",
+ "/stage/api/controller/live/text",
function (data, status) {
OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0;
@@ -137,7 +137,7 @@
},
pollServer: function () {
$.getJSON(
- "/api/poll",
+ "/stage/api/poll",
function (data, status) {
OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item ||
=== added file 'openlp/plugins/remotes/lib/httpauth.py'
--- openlp/plugins/remotes/lib/httpauth.py 1970-01-01 00:00:00 +0000
+++ openlp/plugins/remotes/lib/httpauth.py 2013-03-11 21:06:21 +0000
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2013 Raoul Snyman #
+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+
+"""
+The :mod:`http` module manages the HTTP authorisation logic. This code originates from
+http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
+
+"""
+
+import cherrypy
+import logging
+
+from openlp.core.lib import Settings
+
+SESSION_KEY = '_cp_openlp'
+
+log = logging.getLogger(__name__)
+
+
+def check_credentials(user_name, password):
+ """
+ Verifies credentials for username and password.
+ Returns None on success or a string describing the error on failure
+ """
+ if user_name == Settings().value(u'remotes/user id') and password == Settings().value(u'remotes/password'):
+ return None
+ else:
+ return u"Incorrect username or password."
+
+
+def check_auth(*args, **kwargs):
+ """
+ A tool that looks in config for 'auth.require'. If found and it
+ is not None, a login is required and the entry is evaluated as a list of
+ conditions that the user must fulfill
+ """
+ conditions = cherrypy.request.config.get('auth.require', None)
+ if not Settings().value(u'remotes/authentication enabled'):
+ return None
+ if conditions is not None:
+ username = cherrypy.session.get(SESSION_KEY)
+ if username:
+ cherrypy.request.login = username
+ for condition in conditions:
+ # A condition is just a callable that returns true or false
+ if not condition():
+ raise cherrypy.HTTPRedirect("/auth/login")
+ else:
+ raise cherrypy.HTTPRedirect("/auth/login")
+
+cherrypy.tools.auth = cherrypy.Tool('before_handler', check_auth)
+
+
+def require_auth(*conditions):
+ """
+ A decorator that appends conditions to the auth.require config variable.
+ """
+ def decorate(f):
+ """
+ Lets process a decoration.
+ """
+ if not hasattr(f, '_cp_config'):
+ f._cp_config = dict()
+ if 'auth.require' not in f._cp_config:
+ f._cp_config['auth.require'] = []
+ f._cp_config['auth.require'].extend(conditions)
+ return f
+ return decorate
+
+
+class AuthController(object):
+
+ def on_login(self, username):
+ """
+ Called on successful login
+ """
+
+ def on_logout(self, username):
+ """
+ Called on logout
+ """
+
+ def get_loginform(self, username, msg="Enter login information", from_page="/"):
+ """
+ Provides a login form
+ """
+ return """<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
+ <title>User Login</title>
+ <link rel="stylesheet" href="/files/jquery.mobile.css" />
+ <link rel="stylesheet" href="/files/openlp.css" />
+ <link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
+ <script type="text/javascript" src="/files/jquery.js"></script>
+ <script type="text/javascript" src="/files/openlp.js"></script>
+ <script type="text/javascript" src="/files/jquery.mobile.js"></script>
+ </head>
+ <body>
+ <form method="post" action="/auth/login">
+ <input type="hidden" name="from_page" value="%(from_page)s" />
+ %(msg)s<br/>
+ Username: <input type="text" name="username" value="%(username)s" /><br />
+ Password: <input type="password" name="password" /><br />
+ <input type="submit" value="Log in" />
+ </body></html>""" % locals()
+
+ @cherrypy.expose
+ def login(self, username=None, password=None, from_page="/"):
+ """
+ Provides the actual login control
+ """
+ if username is None or password is None:
+ return self.get_loginform("", from_page=from_page)
+
+ error_msg = check_credentials(username, password)
+ if error_msg:
+ return self.get_loginform(username, error_msg, from_page)
+ else:
+ cherrypy.session[SESSION_KEY] = cherrypy.request.login = username
+ self.on_login(username)
+ raise cherrypy.HTTPRedirect(from_page or "/")
+
+ @cherrypy.expose
+ def logout(self, from_page="/"):
+ """
+ Provides the actual logout functions
+ """
+ sess = cherrypy.session
+ username = sess.get(SESSION_KEY, None)
+ sess[SESSION_KEY] = None
+ if username:
+ cherrypy.request.login = None
+ self.on_logout(username)
+ raise cherrypy.HTTPRedirect(from_page or "/")
+
=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py 2013-03-01 17:36:43 +0000
+++ openlp/plugins/remotes/lib/httpserver.py 2013-03-11 21:06:21 +0000
@@ -119,41 +119,23 @@
import re
import urllib
import urlparse
+import cherrypy
-from PyQt4 import QtCore, QtNetwork
from mako.template import Template
+from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent
-
from openlp.core.utils import AppLocation, translate
+from openlp.plugins.remotes.lib.httpauth import AuthController, require_auth
log = logging.getLogger(__name__)
-class HttpResponse(object):
- """
- A simple object to encapsulate a pseudo-http response.
- """
- code = '200 OK'
- content = ''
- headers = {
- 'Content-Type': 'text/html; charset="utf-8"\r\n'
- }
-
- def __init__(self, content='', headers=None, code=None):
- if headers is None:
- headers = {}
- self.content = content
- for key, value in headers.iteritems():
- self.headers[key] = value
- if code:
- self.code = code
-
-
class HttpServer(object):
"""
Ability to control OpenLP via a web browser.
"""
+
def __init__(self, plugin):
"""
Initialise the httpserver, and start the server.
@@ -164,22 +146,37 @@
self.connections = []
self.current_item = None
self.current_slide = None
- self.start_tcp()
+ self.conf = {'/files': {u'tools.staticdir.on': True,
+ u'tools.staticdir.dir': self.html_dir}}
+ self.start_server()
- def start_tcp(self):
+ def start_server(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 = Settings().value(self.plugin.settingsSection + u'/port')
- address = Settings().value(self.plugin.settingsSection + u'/ip address')
- self.server = QtNetwork.QTcpServer()
- self.server.listen(QtNetwork.QHostAddress(address), port)
+ log.debug(u'Start CherryPy server')
+ if Settings().value(self.plugin.settingsSection + u'/https enabled'):
+ port = Settings().value(self.plugin.settingsSection + u'/https port')
+ address = Settings().value(self.plugin.settingsSection + u'/ip address')
+ shared_data = AppLocation.get_directory(AppLocation.SharedData)
+ server_config = {u'server.socket_host': str(address),
+ u'server.socket_port': port,
+ u'server.ssl_certificate': os.path.join(shared_data, u'openlp.crt'),
+ u'server.ssl_private_key': os.path.join(shared_data, u'openlp.key')}
+ else:
+ port = Settings().value(self.plugin.settingsSection + u'/port')
+ address = Settings().value(self.plugin.settingsSection + u'/ip address')
+ server_config = {u'server.socket_host': str(address),
+ u'server.socket_port': port}
+ cherrypy.config.update(server_config)
+ cherrypy.config.update({'environment': 'embedded'})
+ cherrypy.config.update({'engine.autoreload_on': False})
+ cherrypy.tree.mount(HttpConnection(self), '/', config=self.conf)
+ cherrypy.engine.start()
Registry().register_function(u'slidecontroller_live_changed', self.slide_change)
Registry().register_function(u'slidecontroller_live_started', self.item_change)
- self.server.newConnection.connect(self.new_connection)
log.debug(u'TCP listening on port %d' % port)
def slide_change(self, row):
@@ -194,51 +191,41 @@
"""
self.current_item = items[0]
- 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')
- if connection in self.connections:
- self.connections.remove(connection)
-
def close(self):
"""
Close down the http server.
"""
log.debug(u'close http server')
- self.server.close()
+ cherrypy.engine.exit()
+ cherrypy.engine.stop()
class HttpConnection(object):
"""
- A single connection, this handles communication between the server
- and the client.
+ A single connection, this handles communication between the server and the client.
"""
- def __init__(self, parent, socket):
+ _cp_config = {
+ 'tools.sessions.on': True,
+ 'tools.auth.on': True
+ }
+
+ auth = AuthController()
+
+ def __init__(self, parent):
"""
Initialise the http connection. Listen out for socket signals.
"""
- log.debug(u'Initialise HttpConnection: %s' % socket.peerAddress())
- self.socket = socket
self.parent = parent
self.routes = [
(u'^/$', self.serve_file),
(u'^/(stage)$', self.serve_file),
(r'^/files/(.*)$', self.serve_file),
(r'^/api/poll$', self.poll),
+ (r'^/stage/api/poll$', self.poll),
(r'^/api/controller/(live|preview)/(.*)$', self.controller),
+ (r'^/stage/api/controller/(live|preview)/(.*)$', self.controller),
(r'^/api/service/(.*)$', self.service),
+ (r'^/stage/api/service/(.*)$', self.service),
(r'^/api/display/(hide|show|blank|theme|desktop)$', self.display),
(r'^/api/alert$', self.alert),
(r'^/api/plugin/(search)$', self.pluginInfo),
@@ -246,11 +233,62 @@
(r'^/api/(.*)/live$', self.go_live),
(r'^/api/(.*)/add$', self.add_to_service)
]
- self.socket.readyRead.connect(self.ready_read)
- self.socket.disconnected.connect(self.disconnected)
self.translate()
+ @cherrypy.expose
+ @require_auth()
+ def default(self, *args, **kwargs):
+ """
+ Handles the requests for the main url. This is secure depending on settings in config.
+ """
+ url = urlparse.urlparse(cherrypy.url())
+ self.url_params = urlparse.parse_qs(url.query)
+ # Loop through the routes we set up earlier and execute them
+ return self._process_http_request(args, kwargs)
+
+ @cherrypy.expose
+ def stage(self, *args, **kwargs):
+ """
+ Handles the requests for the stage url. This is not secure.
+ """
+ url = urlparse.urlparse(cherrypy.url())
+ self.url_params = urlparse.parse_qs(url.query)
+ return self._process_http_request(args, kwargs)
+
+ @cherrypy.expose
+ def files(self, *args, **kwargs):
+ """
+ Handles the requests for the files url. This is not secure.
+ """
+ url = urlparse.urlparse(cherrypy.url())
+ self.url_params = urlparse.parse_qs(url.query)
+ return self._process_http_request(args, kwargs)
+
+ def _process_http_request(self, args, kwargs):
+ """
+ Common function to process HTTP requests where secure or insecure
+ """
+ url = urlparse.urlparse(cherrypy.url())
+ self.url_params = urlparse.parse_qs(url.query)
+ response = None
+ for route, func in self.routes:
+ match = re.match(route, url.path)
+ if match:
+ log.debug('Route "%s" matched "%s"', route, url.path)
+ args = []
+ for param in match.groups():
+ args.append(param)
+ response = func(*args)
+ break
+ if response:
+ return response
+ else:
+ return self._http_not_found()
+
def _get_service_items(self):
+ """
+ Read the service item in use and return the data as a json object
+ """
service_items = []
if self.parent.current_item:
current_unique_identifier = self.parent.current_item.unique_identifier
@@ -297,40 +335,6 @@
'slides': translate('RemotePlugin.Mobile', 'Slides')
}
- 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 = str(self.socket.readLine())
- try:
- log.debug(u'received: ' + data)
- except UnicodeDecodeError:
- # Malicious request containing non-ASCII characters.
- self.close()
- return
- words = data.split(' ')
- response = None
- if words[0] == u'GET':
- url = urlparse.urlparse(words[1])
- self.url_params = urlparse.parse_qs(url.query)
- # Loop through the routes we set up earlier and execute them
- for route, func in self.routes:
- match = re.match(route, url.path)
- if match:
- log.debug('Route "%s" matched "%s"', route, url.path)
- args = []
- for param in match.groups():
- args.append(param)
- response = func(*args)
- break
- if response:
- self.send_response(response)
- else:
- self.send_response(HttpResponse(code='404 Not Found'))
- self.close()
-
def serve_file(self, filename=None):
"""
Send a file to the socket. For now, just a subset of file types
@@ -347,7 +351,7 @@
filename = u'stage.html'
path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
if not path.startswith(self.parent.html_dir):
- return HttpResponse(code=u'404 Not Found')
+ return self._http_not_found()
ext = os.path.splitext(filename)[1]
html = None
if ext == u'.html':
@@ -376,11 +380,12 @@
content = file_handle.read()
except IOError:
log.exception(u'Failed to open %s' % path)
- return HttpResponse(code=u'404 Not Found')
+ return self._http_not_found()
finally:
if file_handle:
file_handle.close()
- return HttpResponse(content, {u'Content-Type': mimetype})
+ cherrypy.response.headers['Content-Type'] = mimetype
+ return content
def poll(self):
"""
@@ -395,18 +400,20 @@
u'theme': self.live_controller.theme_screen.isChecked(),
u'display': self.live_controller.desktop_screen.isChecked()
}
- return HttpResponse(json.dumps({u'results': result}), {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': result})
def display(self, action):
"""
Hide or show the display screen.
+ This is a cross Thread call and UI is updated so Events need to be used.
``action``
This is the action, either ``hide`` or ``show``.
"""
- Registry().execute(u'slidecontroller_toggle_display', action)
- return HttpResponse(json.dumps({u'results': {u'success': True}}),
- {u'Content-Type': u'application/json'})
+ self.live_controller.emit(QtCore.SIGNAL(u'slidecontroller_toggle_display'), action)
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': {u'success': True}})
def alert(self):
"""
@@ -417,14 +424,14 @@
try:
text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
except KeyError, ValueError:
- return HttpResponse(code=u'400 Bad Request')
+ return self._http_bad_request()
text = urllib.unquote(text)
Registry().execute(u'alerts_text', [text])
success = True
else:
success = False
- return HttpResponse(json.dumps({u'results': {u'success': success}}),
- {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': {u'success': success}})
def controller(self, display_type, action):
"""
@@ -464,15 +471,14 @@
try:
data = json.loads(self.url_params[u'data'][0])
except KeyError, ValueError:
- return HttpResponse(code=u'400 Bad Request')
+ return self._http_bad_request()
log.info(data)
# This slot expects an int within a list.
id = data[u'request'][u'id']
Registry().execute(event, [id])
- else:
- Registry().execute(event)
json_data = {u'results': {u'success': True}}
- return HttpResponse(json.dumps(json_data), {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps(json_data)
def service(self, action):
"""
@@ -483,19 +489,20 @@
"""
event = u'servicemanager_%s' % action
if action == u'list':
- return HttpResponse(json.dumps({u'results': {u'items': self._get_service_items()}}),
- {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': {u'items': self._get_service_items()}})
else:
event += u'_item'
if self.url_params and self.url_params.get(u'data'):
try:
data = json.loads(self.url_params[u'data'][0])
except KeyError, ValueError:
- return HttpResponse(code=u'400 Bad Request')
+ return self._http_bad_request()
Registry().execute(event, data[u'request'][u'id'])
else:
Registry().execute(event)
- return HttpResponse(json.dumps({u'results': {u'success': True}}), {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': {u'success': True}})
def pluginInfo(self, action):
"""
@@ -510,7 +517,8 @@
for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch:
searches.append([plugin.name, unicode(plugin.textStrings[StringContent.Name][u'plural'])])
- return HttpResponse(json.dumps({u'results': {u'items': searches}}), {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': {u'items': searches}})
def search(self, plugin_name):
"""
@@ -522,14 +530,15 @@
try:
text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
except KeyError, ValueError:
- return HttpResponse(code=u'400 Bad Request')
+ return self._http_bad_request()
text = urllib.unquote(text)
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch:
results = plugin.mediaItem.search(text, False)
else:
results = []
- return HttpResponse(json.dumps({u'results': {u'items': results}}), {u'Content-Type': u'application/json'})
+ cherrypy.response.headers['Content-Type'] = u'application/json'
+ return json.dumps({u'results': {u'items': results}})
def go_live(self, plugin_name):
"""
@@ -538,11 +547,11 @@
try:
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
except KeyError, ValueError:
- return HttpResponse(code=u'400 Bad Request')
- plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
+ return self._http_bad_request()
+ plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem:
plugin.mediaItem.goLive(id, remote=True)
- return HttpResponse(code=u'200 OK')
+ return self._http_success()
def add_to_service(self, plugin_name):
"""
@@ -551,38 +560,22 @@
try:
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
except KeyError, ValueError:
- return HttpResponse(code=u'400 Bad Request')
- plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
+ return self._http_bad_request()
+ plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem:
item_id = plugin.mediaItem.createItemFromId(id)
plugin.mediaItem.addToService(item_id, remote=True)
- return HttpResponse(code=u'200 OK')
-
- def send_response(self, response):
- http = u'HTTP/1.1 %s\r\n' % response.code
- for header, value in response.headers.iteritems():
- http += '%s: %s\r\n' % (header, value)
- http += '\r\n'
- self.socket.write(http)
- self.socket.write(response.content)
-
- 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')
- self.socket.close()
- self.socket = None
- self.parent.close_connection(self)
+ self._http_success()
+
+ def _http_success(self):
+ cherrypy.response.status = 200
+
+ def _http_bad_request(self):
+ cherrypy.response.status = 400
+
+ def _http_not_found(self):
+ cherrypy.response.status = 404
+ cherrypy.response.body = ["<html><body>Sorry, an error occurred </body></html>"]
def _get_service_manager(self):
"""
=== modified file 'openlp/plugins/remotes/lib/remotetab.py'
--- openlp/plugins/remotes/lib/remotetab.py 2013-03-06 17:46:19 +0000
+++ openlp/plugins/remotes/lib/remotetab.py 2013-03-11 21:06:21 +0000
@@ -27,9 +27,12 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
+import os.path
+
from PyQt4 import QtCore, QtGui, QtNetwork
from openlp.core.lib import Registry, Settings, SettingsTab, translate
+from openlp.core.utils import AppLocation
ZERO_URL = u'0.0.0.0'
@@ -53,32 +56,84 @@
self.address_label.setObjectName(u'address_label')
self.address_edit = QtGui.QLineEdit(self.server_settings_group_box)
self.address_edit.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
- self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(
- u'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), self))
+ self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(u'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
+ self))
self.address_edit.setObjectName(u'address_edit')
self.server_settings_layout.addRow(self.address_label, self.address_edit)
self.twelve_hour_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.twelve_hour_check_box.setObjectName(u'twelve_hour_check_box')
self.server_settings_layout.addRow(self.twelve_hour_check_box)
- self.port_label = QtGui.QLabel(self.server_settings_group_box)
+ self.leftLayout.addWidget(self.server_settings_group_box)
+ self.http_settings_group_box = QtGui.QGroupBox(self.leftColumn)
+ self.http_settings_group_box.setObjectName(u'http_settings_group_box')
+ self.http_setting_layout = QtGui.QFormLayout(self.http_settings_group_box)
+ self.http_setting_layout.setObjectName(u'http_setting_layout')
+ self.port_label = QtGui.QLabel(self.http_settings_group_box)
self.port_label.setObjectName(u'port_label')
- self.port_spin_box = QtGui.QSpinBox(self.server_settings_group_box)
+ self.port_spin_box = QtGui.QSpinBox(self.http_settings_group_box)
self.port_spin_box.setMaximum(32767)
self.port_spin_box.setObjectName(u'port_spin_box')
- self.server_settings_layout.addRow(self.port_label, self.port_spin_box)
- self.remote_url_label = QtGui.QLabel(self.server_settings_group_box)
+ self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
+ self.remote_url_label = QtGui.QLabel(self.http_settings_group_box)
self.remote_url_label.setObjectName(u'remote_url_label')
- self.remote_url = QtGui.QLabel(self.server_settings_group_box)
+ self.remote_url = QtGui.QLabel(self.http_settings_group_box)
self.remote_url.setObjectName(u'remote_url')
self.remote_url.setOpenExternalLinks(True)
- self.server_settings_layout.addRow(self.remote_url_label, self.remote_url)
- self.stage_url_label = QtGui.QLabel(self.server_settings_group_box)
+ self.http_setting_layout.addRow(self.remote_url_label, self.remote_url)
+ self.stage_url_label = QtGui.QLabel(self.http_settings_group_box)
self.stage_url_label.setObjectName(u'stage_url_label')
- self.stage_url = QtGui.QLabel(self.server_settings_group_box)
+ self.stage_url = QtGui.QLabel(self.http_settings_group_box)
self.stage_url.setObjectName(u'stage_url')
self.stage_url.setOpenExternalLinks(True)
- self.server_settings_layout.addRow(self.stage_url_label, self.stage_url)
- self.leftLayout.addWidget(self.server_settings_group_box)
+ self.http_setting_layout.addRow(self.stage_url_label, self.stage_url)
+ self.leftLayout.addWidget(self.http_settings_group_box)
+ self.https_settings_group_box = QtGui.QGroupBox(self.leftColumn)
+ self.https_settings_group_box.setCheckable(True)
+ self.https_settings_group_box.setChecked(False)
+ self.https_settings_group_box.setObjectName(u'https_settings_group_box')
+ self.https_settings_layout = QtGui.QFormLayout(self.https_settings_group_box)
+ self.https_settings_layout.setObjectName(u'https_settings_layout')
+ self.https_error_label = QtGui.QLabel(self.https_settings_group_box)
+ self.https_error_label.setVisible(False)
+ self.https_error_label.setWordWrap(True)
+ self.https_error_label.setObjectName(u'https_error_label')
+ self.https_settings_layout.addRow(self.https_error_label)
+ self.https_port_label = QtGui.QLabel(self.https_settings_group_box)
+ self.https_port_label.setObjectName(u'https_port_label')
+ self.https_port_spin_box = QtGui.QSpinBox(self.https_settings_group_box)
+ self.https_port_spin_box.setMaximum(32767)
+ self.https_port_spin_box.setObjectName(u'https_port_spin_box')
+ self.https_settings_layout.addRow(self.https_port_label, self.https_port_spin_box)
+ self.remote_https_url = QtGui.QLabel(self.https_settings_group_box)
+ self.remote_https_url.setObjectName(u'remote_http_url')
+ self.remote_https_url.setOpenExternalLinks(True)
+ self.remote_https_url_label = QtGui.QLabel(self.https_settings_group_box)
+ self.remote_https_url_label.setObjectName(u'remote_http_url_label')
+ self.https_settings_layout.addRow(self.remote_https_url_label, self.remote_https_url)
+ self.stage_https_url_label = QtGui.QLabel(self.http_settings_group_box)
+ self.stage_https_url_label.setObjectName(u'stage_https_url_label')
+ self.stage_https_url = QtGui.QLabel(self.https_settings_group_box)
+ self.stage_https_url.setObjectName(u'stage_https_url')
+ self.stage_https_url.setOpenExternalLinks(True)
+ self.https_settings_layout.addRow(self.stage_https_url_label, self.stage_https_url)
+ self.leftLayout.addWidget(self.https_settings_group_box)
+ self.user_login_group_box = QtGui.QGroupBox(self.leftColumn)
+ self.user_login_group_box.setCheckable(True)
+ self.user_login_group_box.setChecked(False)
+ self.user_login_group_box.setObjectName(u'user_login_group_box')
+ self.user_login_layout = QtGui.QFormLayout(self.user_login_group_box)
+ self.user_login_layout.setObjectName(u'user_login_layout')
+ self.user_id_label = QtGui.QLabel(self.user_login_group_box)
+ self.user_id_label.setObjectName(u'user_id_label')
+ self.user_id = QtGui.QLineEdit(self.user_login_group_box)
+ self.user_id.setObjectName(u'user_id')
+ self.user_login_layout.addRow(self.user_id_label, self.user_id)
+ self.password_label = QtGui.QLabel(self.user_login_group_box)
+ self.password_label.setObjectName(u'password_label')
+ self.password = QtGui.QLineEdit(self.user_login_group_box)
+ self.password.setObjectName(u'password')
+ self.user_login_layout.addRow(self.password_label, self.password)
+ self.leftLayout.addWidget(self.user_login_group_box)
self.android_app_group_box = QtGui.QGroupBox(self.rightColumn)
self.android_app_group_box.setObjectName(u'android_app_group_box')
self.rightLayout.addWidget(self.android_app_group_box)
@@ -96,9 +151,10 @@
self.qr_layout.addWidget(self.qr_description_label)
self.leftLayout.addStretch()
self.rightLayout.addStretch()
- self.twelve_hour_check_box.stateChanged.connect(self.onTwelveHourCheckBoxChanged)
+ self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.connect(self.set_urls)
+ self.https_port_spin_box.valueChanged.connect(self.set_urls)
def retranslateUi(self):
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
@@ -112,6 +168,16 @@
'Scan the QR code or click <a href="https://play.google.com/store/'
'apps/details?id=org.openlp.android">download</a> to install the '
'Android app from Google Play.'))
+ self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
+ self.https_error_label.setText(translate('RemotePlugin.RemoteTab',
+ 'Could not find an SSL certificate. The HTTPS server will not be available unless an SSL certificate '
+ 'is found. Please see the manual for more information.'))
+ self.https_port_label.setText(self.port_label.text())
+ self.remote_https_url_label.setText(self.remote_url_label.text())
+ self.stage_https_url_label.setText(self.stage_url_label.text())
+ self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
+ self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
+ self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
def set_urls(self):
ip_address = u'localhost'
@@ -129,30 +195,56 @@
break
else:
ip_address = self.address_edit.text()
- url = u'http://%s:%s/' % (ip_address, self.port_spin_box.value())
- self.remote_url.setText(u'<a href="%s">%s</a>' % (url, url))
- url += u'stage'
- self.stage_url.setText(u'<a href="%s">%s</a>' % (url, url))
+ http_url = u'http://%s:%s/' % (ip_address, self.port_spin_box.value())
+ https_url = u'https://%s:%s/' % (ip_address, self.https_port_spin_box.value())
+ self.remote_url.setText(u'<a href="%s">%s</a>' % (http_url, http_url))
+ self.remote_https_url.setText(u'<a href="%s">%s</a>' % (https_url, https_url))
+ http_url += u'stage'
+ https_url += u'stage'
+ self.stage_url.setText(u'<a href="%s">%s</a>' % (http_url, http_url))
+ self.stage_https_url.setText(u'<a href="%s">%s</a>' % (https_url, https_url))
def load(self):
self.port_spin_box.setValue(Settings().value(self.settingsSection + u'/port'))
+ self.https_port_spin_box.setValue(Settings().value(self.settingsSection + u'/https port'))
self.address_edit.setText(Settings().value(self.settingsSection + u'/ip address'))
self.twelve_hour = Settings().value(self.settingsSection + u'/twelve hour')
self.twelve_hour_check_box.setChecked(self.twelve_hour)
+ shared_data = AppLocation.get_directory(AppLocation.SharedData)
+ if not os.path.exists(os.path.join(shared_data, u'openlp.crt')) or \
+ not os.path.exists(os.path.join(shared_data, u'openlp.key')):
+ self.https_settings_group_box.setChecked(False)
+ self.https_settings_group_box.setEnabled(False)
+ self.https_error_label.setVisible(True)
+ else:
+ self.https_settings_group_box.setChecked(Settings().value(self.settingsSection + u'/https enabled'))
+ self.https_settings_group_box.setEnabled(True)
+ self.https_error_label.setVisible(False)
+ self.user_login_group_box.setChecked(Settings().value(self.settingsSection + u'/authentication enabled'))
+ self.user_id.setText(Settings().value(self.settingsSection + u'/user id'))
+ self.password.setText(Settings().value(self.settingsSection + u'/password'))
self.set_urls()
def save(self):
changed = False
if Settings().value(self.settingsSection + u'/ip address') != self.address_edit.text() or \
- Settings().value(self.settingsSection + u'/port') != self.port_spin_box.value():
+ Settings().value(self.settingsSection + u'/port') != self.port_spin_box.value() or \
+ Settings().value(self.settingsSection + u'/https port') != self.https_port_spin_box.value() or \
+ Settings().value(self.settingsSection + u'/https enabled') != self.https_settings_group_box.isChecked():
changed = True
Settings().setValue(self.settingsSection + u'/port', self.port_spin_box.value())
+ Settings().setValue(self.settingsSection + u'/https port', self.https_port_spin_box.value())
+ Settings().setValue(self.settingsSection + u'/https enabled', self.https_settings_group_box.isChecked())
Settings().setValue(self.settingsSection + u'/ip address', self.address_edit.text())
Settings().setValue(self.settingsSection + u'/twelve hour', self.twelve_hour)
+ Settings().setValue(self.settingsSection + u'/authentication enabled', self.user_login_group_box.isChecked())
+ Settings().setValue(self.settingsSection + u'/user id', self.user_id.text())
+ Settings().setValue(self.settingsSection + u'/password', self.password.text())
if changed:
- Registry().register_function(u'remotes_config_updated')
-
- def onTwelveHourCheckBoxChanged(self, check_state):
+ Registry().execute(u'remotes_config_updated')
+
+
+ def on_twelve_hour_check_box_changed(self, check_state):
self.twelve_hour = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:
=== modified file 'openlp/plugins/remotes/remoteplugin.py'
--- openlp/plugins/remotes/remoteplugin.py 2013-02-20 19:31:51 +0000
+++ openlp/plugins/remotes/remoteplugin.py 2013-03-11 21:06:21 +0000
@@ -37,6 +37,11 @@
__default_settings__ = {
u'remotes/twelve hour': True,
u'remotes/port': 4316,
+ u'remotes/https port': 4317,
+ u'remotes/https enabled': False,
+ u'remotes/user id': u'openlp',
+ u'remotes/password': u'password',
+ u'remotes/authentication enabled': False,
u'remotes/ip address': u'0.0.0.0'
}
=== modified file 'scripts/check_dependencies.py'
--- scripts/check_dependencies.py 2013-02-28 21:36:55 +0000
+++ scripts/check_dependencies.py 2013-03-11 21:06:21 +0000
@@ -81,6 +81,7 @@
'enchant',
'BeautifulSoup',
'mako',
+ 'cherrypy',
'migrate',
'uno',
]
Follow ups