Initial Cherrypy implementation

This commit is contained in:
Tim Bentley 2013-03-06 21:53:29 +00:00
parent 44ebc1fe27
commit f1aadde13c
9 changed files with 534 additions and 242 deletions

View File

@ -69,6 +69,11 @@ try:
MAKO_VERSION = mako.__version__ MAKO_VERSION = mako.__version__
except ImportError: except ImportError:
MAKO_VERSION = u'-' MAKO_VERSION = u'-'
try:
import cherrypy
CHERRYPY_VERSION = cherrypy.__version__
except ImportError:
CHERRYPY_VERSION = u'-'
try: try:
import uno import uno
arg = uno.createUnoStruct(u'com.sun.star.beans.PropertyValue') arg = uno.createUnoStruct(u'com.sun.star.beans.PropertyValue')
@ -138,6 +143,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
u'PyEnchant: %s\n' % ENCHANT_VERSION + \ u'PyEnchant: %s\n' % ENCHANT_VERSION + \
u'PySQLite: %s\n' % SQLITE_VERSION + \ u'PySQLite: %s\n' % SQLITE_VERSION + \
u'Mako: %s\n' % MAKO_VERSION + \ u'Mako: %s\n' % MAKO_VERSION + \
u'CherryPy: %s\n' % CHERRYPY_VERSION + \
u'pyUNO bridge: %s\n' % UNO_VERSION u'pyUNO bridge: %s\n' % UNO_VERSION
if platform.system() == u'Linux': if platform.system() == u'Linux':
if os.environ.get(u'KDE_FULL_SESSION') == u'true': if os.environ.get(u'KDE_FULL_SESSION') == u'true':

View File

@ -362,8 +362,9 @@ class SlideController(DisplayController):
# Signals # Signals
QtCore.QObject.connect(self.previewListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected) QtCore.QObject.connect(self.previewListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected)
if self.isLive: if self.isLive:
# 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_live_spin_delay', self.receive_spin_delay)
Registry().register_function(u'slidecontroller_toggle_display', self.toggle_display)
self.toolbar.setWidgetVisible(self.loopList, False) self.toolbar.setWidgetVisible(self.loopList, False)
self.toolbar.setWidgetVisible(self.wideMenu, False) self.toolbar.setWidgetVisible(self.wideMenu, False)
else: else:
@ -867,9 +868,9 @@ class SlideController(DisplayController):
""" """
Go to the requested slide Go to the requested slide
""" """
index = int(message[0]) if not self.serviceItem or not message[0]:
if not self.serviceItem:
return return
index = int(message[0])
if self.serviceItem.is_command(): if self.serviceItem.is_command():
Registry().execute(u'%s_slide' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive, index]) Registry().execute(u'%s_slide' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive, index])
self.updatePreview() self.updatePreview()

View File

@ -90,6 +90,7 @@ class AppLocation(object):
VersionDir = 5 VersionDir = 5
CacheDir = 6 CacheDir = 6
LanguageDir = 7 LanguageDir = 7
SharedData = 8
# Base path where data/config/cache dir is located # Base path where data/config/cache dir is located
BaseDir = None BaseDir = None
@ -150,18 +151,18 @@ def _get_os_dir_path(dir_type):
if sys.platform == u'win32': if sys.platform == u'win32':
if dir_type == AppLocation.DataDir: if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp', u'data') 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.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp') return os.path.join(unicode(os.getenv(u'APPDATA'), encoding), u'openlp')
elif sys.platform == u'darwin': elif sys.platform == u'darwin':
if dir_type == AppLocation.DataDir: if dir_type == AppLocation.DataDir:
return os.path.join(unicode(os.getenv(u'HOME'), encoding), return os.path.join(unicode(os.getenv(u'HOME'), encoding),
u'Library', u'Application Support', u'openlp', u'Data') 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.split(openlp.__file__)[0]
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp') return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp')
else: else:
if dir_type == AppLocation.LanguageDir: if dir_type == AppLocation.LanguageDir or dir_type == AppLocation.SharedData:
prefixes = [u'/usr/local', u'/usr'] prefixes = [u'/usr/local', u'/usr']
for prefix in prefixes: for prefix in prefixes:
directory = os.path.join(prefix, u'share', u'openlp') directory = os.path.join(prefix, u'share', u'openlp')

View File

@ -26,7 +26,7 @@
window.OpenLP = { window.OpenLP = {
loadService: function (event) { loadService: function (event) {
$.getJSON( $.getJSON(
"/api/service/list", "/stage/api/service/list",
function (data, status) { function (data, status) {
OpenLP.nextSong = ""; OpenLP.nextSong = "";
$("#notes").html(""); $("#notes").html("");
@ -46,7 +46,7 @@ window.OpenLP = {
}, },
loadSlides: function (event) { loadSlides: function (event) {
$.getJSON( $.getJSON(
"/api/controller/live/text", "/stage/api/controller/live/text",
function (data, status) { function (data, status) {
OpenLP.currentSlides = data.results.slides; OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0; OpenLP.currentSlide = 0;
@ -137,7 +137,7 @@ window.OpenLP = {
}, },
pollServer: function () { pollServer: function () {
$.getJSON( $.getJSON(
"/api/poll", "/stage/api/poll",
function (data, status) { function (data, status) {
OpenLP.updateClock(data); OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item || if (OpenLP.currentItem != data.results.item ||

View File

@ -0,0 +1,192 @@
# -*- 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 urlparse
SESSION_KEY = '_cp_openlp'
def check_credentials(user_name, password):
"""
Verifies credentials for username and password.
Returns None on success or a string describing the error on failure
"""
# @todo make from config
if user_name == 'openlp' and password == 'openlp':
return None
else:
return u"Incorrect username or password."
# if u.password != md5.new(password).hexdigest():
# return u"Incorrect 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
"""
print "check"
conditions = cherrypy.request.config.get('auth.require', None)
print conditions
print args, kwargs
print urlparse.urlparse(cherrypy.url())
url = urlparse.urlparse(cherrypy.url())
print urlparse.parse_qs(url.query)
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.
"""
print conditions
def decorate(f):
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)
print "a ", [f]
return f
return decorate
# Conditions are callables that return True
# if the user fulfills the conditions they define, False otherwise
#
# They can access the current username as cherrypy.request.login
#
# Define those at will however suits the application.
#def member_of(groupname):
# def check():
# # replace with actual check if <username> is in <groupname>
# return cherrypy.request.login == 'joe' and groupname == 'admin'
# return check
#def name_is(reqd_username):
# return lambda: reqd_username == cherrypy.request.login
#def any_of(*conditions):
# """
# Returns True if any of the conditions match
# """
# def check():
# for c in conditions:
# if c():
# return True
# return False
# return check
# By default all conditions are required, but this might still be
# needed if you want to use it inside of an any_of(...) condition
#def all_of(*conditions):
# """
# Returns True if all of the conditions match
# """
# def check():
# for c in conditions:
# if not c():
# return False
# return True
# return check
# Controller to provide login and logout actions
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><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="/"):
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 "/")

View File

@ -119,40 +119,23 @@ import os
import re import re
import urllib import urllib
import urlparse import urlparse
import cherrypy
from PyQt4 import QtCore, QtNetwork
from mako.template import Template from mako.template import Template
from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent from openlp.core.lib import Registry, Settings, PluginStatus, StringContent
from openlp.core.utils import AppLocation, translate from openlp.core.utils import AppLocation, translate
from openlp.plugins.remotes.lib.httpauth import AuthController, require_auth
log = logging.getLogger(__name__) 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): class HttpServer(object):
""" """
Ability to control OpenLP via a web browser. Ability to control OpenLP via a web browser.
""" """
def __init__(self, plugin): def __init__(self, plugin):
""" """
Initialise the httpserver, and start the server. Initialise the httpserver, and start the server.
@ -163,22 +146,28 @@ class HttpServer(object):
self.connections = [] self.connections = []
self.current_item = None self.current_item = None
self.current_slide = 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. 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 Listen out for slide and song changes so they can be broadcast to
clients. Listen out for socket connections. clients. Listen out for socket connections.
""" """
log.debug(u'Start TCP server') log.debug(u'Start CherryPy server')
port = Settings().value(self.plugin.settingsSection + u'/port') port = Settings().value(self.plugin.settingsSection + u'/port')
address = Settings().value(self.plugin.settingsSection + u'/ip address') address = Settings().value(self.plugin.settingsSection + u'/ip address')
self.server = QtNetwork.QTcpServer() server_config = {u'server.socket_host': str(address),
self.server.listen(QtNetwork.QHostAddress(address), port) 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_changed', self.slide_change)
Registry().register_function(u'slidecontroller_live_started', self.item_change) Registry().register_function(u'slidecontroller_live_started', self.item_change)
QtCore.QObject.connect(self.server, QtCore.SIGNAL(u'newConnection()'), self.new_connection)
log.debug(u'TCP listening on port %d' % port) log.debug(u'TCP listening on port %d' % port)
def slide_change(self, row): def slide_change(self, row):
@ -193,50 +182,41 @@ class HttpServer(object):
""" """
self.current_item = items[0] 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): def close(self):
""" """
Close down the http server. Close down the http server.
""" """
log.debug(u'close http server') log.debug(u'close http server')
self.server.close() cherrypy.engine.exit()
cherrypy.engine.stop()
class HttpConnection(object): class HttpConnection(object):
""" """
A single connection, this handles communication between the server A single connection, this handles communication between the server and the client.
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. Initialise the http connection. Listen out for socket signals.
""" """
log.debug(u'Initialise HttpConnection: %s' % socket.peerAddress()) #log.debug(u'Initialise HttpConnection: %s' % socket.peerAddress())
self.socket = socket #self.socket = socket
self.parent = parent self.parent = parent
self.routes = [ self.routes = [
(u'^/$', self.serve_file), (u'^/$', self.serve_file),
(u'^/(stage)$', self.serve_file), (u'^/(stage)$', self.serve_file),
(r'^/files/(.*)$', self.serve_file), (r'^/files/(.*)$', self.serve_file),
(r'^/api/poll$', self.poll), (r'^/api/poll$', self.poll),
(r'^/stage/api/poll$', self.poll),
(r'^/api/controller/(live|preview)/(.*)$', self.controller), (r'^/api/controller/(live|preview)/(.*)$', self.controller),
(r'^/stage/api/controller/live/(.*)$', self.controller),
(r'^/api/service/(.*)$', self.service), (r'^/api/service/(.*)$', self.service),
(r'^/api/display/(hide|show|blank|theme|desktop)$', self.display), (r'^/api/display/(hide|show|blank|theme|desktop)$', self.display),
(r'^/api/alert$', self.alert), (r'^/api/alert$', self.alert),
@ -245,17 +225,79 @@ class HttpConnection(object):
(r'^/api/(.*)/live$', self.go_live), (r'^/api/(.*)/live$', self.go_live),
(r'^/api/(.*)/add$', self.add_to_service) (r'^/api/(.*)/add$', self.add_to_service)
] ]
QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'), self.ready_read)
QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'), self.disconnected)
self.translate() self.translate()
@cherrypy.expose
#@require_auth(auth)
def default(self, *args, **kwargs):
"""
Handles the requests for the main url. This is secure depending on settings.
"""
# 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.
"""
print "Stage"
url = urlparse.urlparse(cherrypy.url())
self.url_params = urlparse.parse_qs(url.query)
print url
print [self.url_params]
#return self.serve_file(u'stage')
return self._process_http_request(args, kwargs)
@cherrypy.expose
def files(self, *args, **kwargs):
"""
Handles the requests for the stage url. This is not secure.
"""
print "files"
url = urlparse.urlparse(cherrypy.url())
self.url_params = urlparse.parse_qs(url.query)
print url
print [self.url_params]
print args
#return self.serve_file(args)
return self._process_http_request(args, kwargs)
def _process_http_request(self, args, kwargs):
"""
Common function to process HTTP requests where secure or insecure
"""
print "common handler"
url = urlparse.urlparse(cherrypy.url())
self.url_params = urlparse.parse_qs(url.query)
print url
print [self.url_params]
response = None
for route, func in self.routes:
match = re.match(route, url.path)
if match:
print 'Route "%s" matched "%s"', route, url.path
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): def _get_service_items(self):
"""
Read the service item in use and return the data as a json object
"""
service_items = [] service_items = []
if self.parent.current_item: if self.parent.current_item:
current_unique_identifier = self.parent.current_item.unique_identifier current_unique_identifier = self.parent.current_item.unique_identifier
else: else:
current_unique_identifier = None current_unique_identifier = None
for item in self.service_manager.serviceItems: for item in self.service_manager.service_items:
service_item = item[u'service_item'] service_item = item[u'service_item']
service_items.append({ service_items.append({
u'id': unicode(service_item.unique_identifier), u'id': unicode(service_item.unique_identifier),
@ -296,40 +338,6 @@ class HttpConnection(object):
'slides': translate('RemotePlugin.Mobile', 'Slides') '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): def serve_file(self, filename=None):
""" """
Send a file to the socket. For now, just a subset of file types Send a file to the socket. For now, just a subset of file types
@ -339,6 +347,7 @@ class HttpConnection(object):
Ultimately for i18n, this could first look for xx/file.html before 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' falling back to file.html... where xx is the language, e.g. 'en'
""" """
print "serve_file", filename
log.debug(u'serve file request %s' % filename) log.debug(u'serve file request %s' % filename)
if not filename: if not filename:
filename = u'index.html' filename = u'index.html'
@ -346,7 +355,7 @@ class HttpConnection(object):
filename = u'stage.html' filename = u'stage.html'
path = os.path.normpath(os.path.join(self.parent.html_dir, filename)) path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
if not path.startswith(self.parent.html_dir): 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] ext = os.path.splitext(filename)[1]
html = None html = None
if ext == u'.html': if ext == u'.html':
@ -375,11 +384,12 @@ class HttpConnection(object):
content = file_handle.read() content = file_handle.read()
except IOError: except IOError:
log.exception(u'Failed to open %s' % path) log.exception(u'Failed to open %s' % path)
return HttpResponse(code=u'404 Not Found') return self._http_not_found()
finally: finally:
if file_handle: if file_handle:
file_handle.close() file_handle.close()
return HttpResponse(content, {u'Content-Type': mimetype}) cherrypy.response.headers['Content-Type'] = mimetype
return content
def poll(self): def poll(self):
""" """
@ -389,24 +399,25 @@ class HttpConnection(object):
u'service': self.service_manager.service_id, u'service': self.service_manager.service_id,
u'slide': self.parent.current_slide or 0, u'slide': self.parent.current_slide or 0,
u'item': self.parent.current_item.unique_identifier if self.parent.current_item else u'', u'item': self.parent.current_item.unique_identifier if self.parent.current_item else u'',
u'twelve':Settings().value(u'remotes/twelve hour'), u'twelve': Settings().value(u'remotes/twelve hour'),
u'blank': self.live_controller.blankScreen.isChecked(), u'blank': self.live_controller.blankScreen.isChecked(),
u'theme': self.live_controller.themeScreen.isChecked(), u'theme': self.live_controller.themeScreen.isChecked(),
u'display': self.live_controller.desktopScreen.isChecked() u'display': self.live_controller.desktopScreen.isChecked()
} }
return HttpResponse(json.dumps({u'results': result}), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps({u'results': result})
def display(self, action): def display(self, action):
""" """
Hide or show the display screen. Hide or show the display screen.
This is a cross Thread call and UI is updated so Events need to be used.
``action`` ``action``
This is the action, either ``hide`` or ``show``. This is the action, either ``hide`` or ``show``.
""" """
Registry().execute(u'slidecontroller_toggle_display', action) self.live_controller.emit(QtCore.SIGNAL(u'slidecontroller_toggle_display'), action)
return HttpResponse(json.dumps({u'results': {u'success': True}}), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps({u'results': {u'success': True}})
def alert(self): def alert(self):
""" """
@ -417,14 +428,14 @@ class HttpConnection(object):
try: try:
text = json.loads(self.url_params[u'data'][0])[u'request'][u'text'] text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
except KeyError, ValueError: except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request') return self._http_bad_request()
text = urllib.unquote(text) text = urllib.unquote(text)
Registry().execute(u'alerts_text', [text]) Registry().execute(u'alerts_text', [text])
success = True success = True
else: else:
success = False success = False
return HttpResponse(json.dumps({u'results': {u'success': success}}), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps({u'results': {u'success': success}})
def controller(self, type, action): def controller(self, type, action):
""" """
@ -465,34 +476,37 @@ class HttpConnection(object):
try: try:
data = json.loads(self.url_params[u'data'][0]) data = json.loads(self.url_params[u'data'][0])
except KeyError, ValueError: except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request') return self._http_bad_request()
log.info(data) log.info(data)
# This slot expects an int within a list. # This slot expects an int within a list.
id = data[u'request'][u'id'] id = data[u'request'][u'id']
Registry().execute(event, [id]) Registry().execute(event, [id])
else: else:
Registry().execute(event) Registry().execute(event, [0])
json_data = {u'results': {u'success': True}} json_data = {u'results': {u'success': True}}
return HttpResponse(json.dumps(json_data), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps(json_data)
def service(self, action): def service(self, action):
"""
List details of the Service and update the UI
"""
event = u'servicemanager_%s' % action event = u'servicemanager_%s' % action
if action == u'list': if action == u'list':
return HttpResponse(json.dumps({u'results': {u'items': self._get_service_items()}}), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps({u'results': {u'items': self._get_service_items()}})
else: else:
event += u'_item' event += u'_item'
if self.url_params and self.url_params.get(u'data'): if self.url_params and self.url_params.get(u'data'):
try: try:
data = json.loads(self.url_params[u'data'][0]) data = json.loads(self.url_params[u'data'][0])
except KeyError, ValueError: except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request') return self._http_bad_request()
Registry().execute(event, data[u'request'][u'id']) Registry().execute(event, data[u'request'][u'id'])
else: else:
Registry().execute(event) Registry().execute(event)
return HttpResponse(json.dumps({u'results': {u'success': True}}), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps({u'results': {u'success': True}})
def pluginInfo(self, action): def pluginInfo(self, action):
""" """
@ -507,9 +521,8 @@ class HttpConnection(object):
for plugin in self.plugin_manager.plugins: for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch: if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch:
searches.append([plugin.name, unicode(plugin.textStrings[StringContent.Name][u'plural'])]) searches.append([plugin.name, unicode(plugin.textStrings[StringContent.Name][u'plural'])])
return HttpResponse( cherrypy.response.headers['Content-Type'] = u'application/json'
json.dumps({u'results': {u'items': searches}}), return json.dumps({u'results': {u'items': searches}})
{u'Content-Type': u'application/json'})
def search(self, type): def search(self, type):
""" """
@ -521,15 +534,15 @@ class HttpConnection(object):
try: try:
text = json.loads(self.url_params[u'data'][0])[u'request'][u'text'] text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
except KeyError, ValueError: except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request') return self._http_bad_request()
text = urllib.unquote(text) text = urllib.unquote(text)
plugin = self.plugin_manager.get_plugin_by_name(type) plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch: if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch:
results = plugin.mediaItem.search(text, False) results = plugin.mediaItem.search(text, False)
else: else:
results = [] results = []
return HttpResponse(json.dumps({u'results': {u'items': results}}), cherrypy.response.headers['Content-Type'] = u'application/json'
{u'Content-Type': u'application/json'}) return json.dumps({u'results': {u'items': results}})
def go_live(self, type): def go_live(self, type):
""" """
@ -538,11 +551,11 @@ class HttpConnection(object):
try: try:
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id'] id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
except KeyError, ValueError: except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request') return self._http_bad_request()
plugin = self.plugin_manager.get_plugin_by_name(type) plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem: if plugin.status == PluginStatus.Active and plugin.mediaItem:
plugin.mediaItem.goLive(id, remote=True) plugin.mediaItem.goLive(id, remote=True)
return HttpResponse(code=u'200 OK') return self._http_success()
def add_to_service(self, type): def add_to_service(self, type):
""" """
@ -551,38 +564,22 @@ class HttpConnection(object):
try: try:
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id'] id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
except KeyError, ValueError: except KeyError, ValueError:
return HttpResponse(code=u'400 Bad Request') return self._http_bad_request()
plugin = self.plugin_manager.get_plugin_by_name(type) plugin = self.plugin_manager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem: if plugin.status == PluginStatus.Active and plugin.mediaItem:
item_id = plugin.mediaItem.createItemFromId(id) item_id = plugin.mediaItem.createItemFromId(id)
plugin.mediaItem.addToService(item_id, remote=True) plugin.mediaItem.addToService(item_id, remote=True)
return HttpResponse(code=u'200 OK') self._http_success()
def send_response(self, response): def _http_success(self):
http = u'HTTP/1.1 %s\r\n' % response.code cherrypy.response.status = 200
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): def _http_bad_request(self):
""" cherrypy.response.status = 400
The client has disconnected. Tidy up
"""
log.debug(u'socket disconnected')
self.close()
def close(self): def _http_not_found(self):
""" cherrypy.response.status = 404
The server has closed the connection. Tidy up cherrypy.response.body = ["<html><body>Sorry, an error occured</body></html>"]
"""
if not self.socket:
return
log.debug(u'close socket')
self.socket.close()
self.socket = None
self.parent.close_connection(self)
def _get_service_manager(self): def _get_service_manager(self):
""" """

View File

@ -27,9 +27,12 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import os.path
from PyQt4 import QtCore, QtGui, QtNetwork from PyQt4 import QtCore, QtGui, QtNetwork
from openlp.core.lib import Registry, Settings, SettingsTab, translate from openlp.core.lib import Registry, Settings, SettingsTab, translate
from openlp.core.utils import AppLocation
ZERO_URL = u'0.0.0.0' ZERO_URL = u'0.0.0.0'
@ -45,117 +48,203 @@ class RemoteTab(SettingsTab):
def setupUi(self): def setupUi(self):
self.setObjectName(u'RemoteTab') self.setObjectName(u'RemoteTab')
SettingsTab.setupUi(self) SettingsTab.setupUi(self)
self.serverSettingsGroupBox = QtGui.QGroupBox(self.leftColumn) self.server_settings_group_box = QtGui.QGroupBox(self.leftColumn)
self.serverSettingsGroupBox.setObjectName(u'serverSettingsGroupBox') self.server_settings_group_box.setObjectName(u'server_settings_group_box')
self.serverSettingsLayout = QtGui.QFormLayout(self.serverSettingsGroupBox) self.server_settings_layout = QtGui.QFormLayout(self.server_settings_group_box)
self.serverSettingsLayout.setObjectName(u'serverSettingsLayout') self.server_settings_layout.setObjectName(u'server_settings_layout')
self.addressLabel = QtGui.QLabel(self.serverSettingsGroupBox) self.address_label = QtGui.QLabel(self.server_settings_group_box)
self.addressLabel.setObjectName(u'addressLabel') self.address_label.setObjectName(u'address_label')
self.addressEdit = QtGui.QLineEdit(self.serverSettingsGroupBox) self.address_edit = QtGui.QLineEdit(self.server_settings_group_box)
self.addressEdit.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) self.address_edit.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
self.addressEdit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp( self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(u'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
u'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), self)) self))
self.addressEdit.setObjectName(u'addressEdit') self.address_edit.setObjectName(u'address_edit')
QtCore.QObject.connect(self.addressEdit, QtCore.SIGNAL(u'textChanged(const QString&)'), self.setUrls) self.server_settings_layout.addRow(self.address_label, self.address_edit)
self.serverSettingsLayout.addRow(self.addressLabel, self.addressEdit) self.twelve_hour_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.twelveHourCheckBox = QtGui.QCheckBox(self.serverSettingsGroupBox) self.twelve_hour_check_box.setObjectName(u'twelve_hour_check_box')
self.twelveHourCheckBox.setObjectName(u'twelveHourCheckBox') self.server_settings_layout.addRow(self.twelve_hour_check_box)
self.serverSettingsLayout.addRow(self.twelveHourCheckBox) self.leftLayout.addWidget(self.server_settings_group_box)
self.portLabel = QtGui.QLabel(self.serverSettingsGroupBox) self.http_settings_group_box = QtGui.QGroupBox(self.leftColumn)
self.portLabel.setObjectName(u'portLabel') self.http_settings_group_box.setObjectName(u'http_settings_group_box')
self.portSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox) self.http_setting_layout = QtGui.QFormLayout(self.http_settings_group_box)
self.portSpinBox.setMaximum(32767) self.http_setting_layout.setObjectName(u'http_setting_layout')
self.portSpinBox.setObjectName(u'portSpinBox') self.port_label = QtGui.QLabel(self.http_settings_group_box)
QtCore.QObject.connect(self.portSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls) self.port_label.setObjectName(u'port_label')
self.serverSettingsLayout.addRow(self.portLabel, self.portSpinBox) self.port_spin_box = QtGui.QSpinBox(self.http_settings_group_box)
self.remoteUrlLabel = QtGui.QLabel(self.serverSettingsGroupBox) self.port_spin_box.setMaximum(32767)
self.remoteUrlLabel.setObjectName(u'remoteUrlLabel') self.port_spin_box.setObjectName(u'port_spin_box')
self.remoteUrl = QtGui.QLabel(self.serverSettingsGroupBox) self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
self.remoteUrl.setObjectName(u'remoteUrl') self.remote_url_label = QtGui.QLabel(self.http_settings_group_box)
self.remoteUrl.setOpenExternalLinks(True) self.remote_url_label.setObjectName(u'remote_url_label')
self.serverSettingsLayout.addRow(self.remoteUrlLabel, self.remoteUrl) self.remote_url = QtGui.QLabel(self.http_settings_group_box)
self.stageUrlLabel = QtGui.QLabel(self.serverSettingsGroupBox) self.remote_url.setObjectName(u'remote_url')
self.stageUrlLabel.setObjectName(u'stageUrlLabel') self.remote_url.setOpenExternalLinks(True)
self.stageUrl = QtGui.QLabel(self.serverSettingsGroupBox) self.http_setting_layout.addRow(self.remote_url_label, self.remote_url)
self.stageUrl.setObjectName(u'stageUrl') self.stage_url_label = QtGui.QLabel(self.http_settings_group_box)
self.stageUrl.setOpenExternalLinks(True) self.stage_url_label.setObjectName(u'stage_url_label')
self.serverSettingsLayout.addRow(self.stageUrlLabel, self.stageUrl) self.stage_url = QtGui.QLabel(self.http_settings_group_box)
self.leftLayout.addWidget(self.serverSettingsGroupBox) self.stage_url.setObjectName(u'stage_url')
self.androidAppGroupBox = QtGui.QGroupBox(self.rightColumn) self.stage_url.setOpenExternalLinks(True)
self.androidAppGroupBox.setObjectName(u'androidAppGroupBox') self.http_setting_layout.addRow(self.stage_url_label, self.stage_url)
self.rightLayout.addWidget(self.androidAppGroupBox) self.leftLayout.addWidget(self.http_settings_group_box)
self.qrLayout = QtGui.QVBoxLayout(self.androidAppGroupBox) self.https_settings_group_box = QtGui.QGroupBox(self.leftColumn)
self.qrLayout.setObjectName(u'qrLayout') self.https_settings_group_box.setCheckable(True)
self.qrCodeLabel = QtGui.QLabel(self.androidAppGroupBox) self.https_settings_group_box.setChecked(False)
self.qrCodeLabel.setPixmap(QtGui.QPixmap(u':/remotes/android_app_qr.png')) self.https_settings_group_box.setObjectName(u'https_settings_group_box')
self.qrCodeLabel.setAlignment(QtCore.Qt.AlignCenter) self.https_settings_layout = QtGui.QFormLayout(self.https_settings_group_box)
self.qrCodeLabel.setObjectName(u'qrCodeLabel') self.https_settings_layout.setObjectName(u'https_settings_layout')
self.qrLayout.addWidget(self.qrCodeLabel) self.https_error_label = QtGui.QLabel(self.https_settings_group_box)
self.qrDescriptionLabel = QtGui.QLabel(self.androidAppGroupBox) self.https_error_label.setVisible(False)
self.qrDescriptionLabel.setObjectName(u'qrDescriptionLabel') self.https_error_label.setWordWrap(True)
self.qrDescriptionLabel.setOpenExternalLinks(True) self.https_error_label.setObjectName(u'https_error_label')
self.qrDescriptionLabel.setWordWrap(True) self.https_settings_layout.addRow(self.https_error_label)
self.qrLayout.addWidget(self.qrDescriptionLabel) 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)
self.qr_layout = QtGui.QVBoxLayout(self.android_app_group_box)
self.qr_layout.setObjectName(u'qr_layout')
self.qr_code_label = QtGui.QLabel(self.android_app_group_box)
self.qr_code_label.setPixmap(QtGui.QPixmap(u':/remotes/android_app_qr.png'))
self.qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.qr_code_label.setObjectName(u'qr_code_label')
self.qr_layout.addWidget(self.qr_code_label)
self.qr_description_label = QtGui.QLabel(self.android_app_group_box)
self.qr_description_label.setObjectName(u'qr_description_label')
self.qr_description_label.setOpenExternalLinks(True)
self.qr_description_label.setWordWrap(True)
self.qr_layout.addWidget(self.qr_description_label)
self.leftLayout.addStretch() self.leftLayout.addStretch()
self.rightLayout.addStretch() self.rightLayout.addStretch()
QtCore.QObject.connect(self.twelveHourCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
self.onTwelveHourCheckBoxChanged) 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): def retranslateUi(self):
self.serverSettingsGroupBox.setTitle( self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
translate('RemotePlugin.RemoteTab', 'Server Settings')) self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
self.addressLabel.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:')) self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
self.portLabel.setText(translate('RemotePlugin.RemoteTab', 'Port number:')) self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
self.remoteUrlLabel.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:')) self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:')) self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
self.twelveHourCheckBox.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format')) self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.androidAppGroupBox.setTitle(translate('RemotePlugin.RemoteTab', 'Android App')) self.qr_description_label.setText(translate('RemotePlugin.RemoteTab',
self.qrDescriptionLabel.setText(translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="https://play.google.com/store/' 'Scan the QR code or click <a href="https://play.google.com/store/'
'apps/details?id=org.openlp.android">download</a> to install the ' 'apps/details?id=org.openlp.android">download</a> to install the '
'Android app from Google Play.')) '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 setUrls(self): def set_urls(self):
ipAddress = u'localhost' ipAddress = u'localhost'
if self.addressEdit.text() == ZERO_URL: if self.address_edit.text() == ZERO_URL:
ifaces = QtNetwork.QNetworkInterface.allInterfaces() interfaces = QtNetwork.QNetworkInterface.allInterfaces()
for iface in ifaces: for interface in interfaces:
if not iface.isValid(): if not interface.isValid():
continue continue
if not (iface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)): if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)):
continue continue
for addr in iface.addressEntries(): for address in interface.addressEntries():
ip = addr.ip() ip = address.ip()
if ip.protocol() == 0 and ip != QtNetwork.QHostAddress.LocalHost: if ip.protocol() == 0 and ip != QtNetwork.QHostAddress.LocalHost:
ipAddress = ip ipAddress = ip
break break
else: else:
ipAddress = self.addressEdit.text() ipAddress = self.address_edit.text()
url = u'http://%s:%s/' % (ipAddress, self.portSpinBox.value()) http_url = u'http://%s:%s/' % (ipAddress, self.port_spin_box.value())
self.remoteUrl.setText(u'<a href="%s">%s</a>' % (url, url)) https_url = u'https://%s:%s/' % (ipAddress, self.https_port_spin_box.value())
url += u'stage' self.remote_url.setText(u'<a href="%s">%s</a>' % (http_url, http_url))
self.stageUrl.setText(u'<a href="%s">%s</a>' % (url, 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): def load(self):
self.portSpinBox.setValue(Settings().value(self.settingsSection + u'/port')) self.port_spin_box.setValue(Settings().value(self.settingsSection + u'/port'))
self.addressEdit.setText(Settings().value(self.settingsSection + u'/ip address')) self.https_port_spin_box.setValue(Settings().value(self.settingsSection + u'/https port'))
self.twelveHour = Settings().value(self.settingsSection + u'/twelve hour') self.address_edit.setText(Settings().value(self.settingsSection + u'/ip address'))
self.twelveHourCheckBox.setChecked(self.twelveHour) self.twelve_hour = Settings().value(self.settingsSection + u'/twelve hour')
self.setUrls() 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): def save(self):
changed = False changed = False
if Settings().value(self.settingsSection + u'/ip address') != self.addressEdit.text() or \ if Settings().value(self.settingsSection + u'/ip address') != self.address_edit.text() or \
Settings().value(self.settingsSection + u'/port') != self.portSpinBox.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 changed = True
Settings().setValue(self.settingsSection + u'/port', self.portSpinBox.value()) Settings().setValue(self.settingsSection + u'/port', self.port_spin_box.value())
Settings().setValue(self.settingsSection + u'/ip address', self.addressEdit.text()) Settings().setValue(self.settingsSection + u'/https port', self.https_port_spin_box.value())
Settings().setValue(self.settingsSection + u'/twelve hour', self.twelveHour) 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: if changed:
Registry().register_function(u'remotes_config_updated') Registry().register_function(u'remotes_config_updated')
def onTwelveHourCheckBoxChanged(self, check_state): def on_twelve_hour_check_box_changed(self, check_state):
self.twelveHour = False self.twelve_hour = False
# we have a set value convert to True/False # we have a set value convert to True/False
if check_state == QtCore.Qt.Checked: if check_state == QtCore.Qt.Checked:
self.twelveHour = True self.twelve_hour = True

View File

@ -37,6 +37,11 @@ log = logging.getLogger(__name__)
__default_settings__ = { __default_settings__ = {
u'remotes/twelve hour': True, u'remotes/twelve hour': True,
u'remotes/port': 4316, 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' u'remotes/ip address': u'0.0.0.0'
} }

View File

@ -81,6 +81,7 @@ MODULES = [
'enchant', 'enchant',
'BeautifulSoup', 'BeautifulSoup',
'mako', 'mako',
'cherrypy',
'migrate', 'migrate',
'uno', 'uno',
] ]