forked from openlp/openlp
Initial Cherrypy implementation
This commit is contained in:
parent
44ebc1fe27
commit
f1aadde13c
openlp
core
plugins/remotes
scripts
@ -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':
|
||||||
|
@ -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()
|
||||||
|
@ -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')
|
||||||
|
@ -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 ||
|
||||||
|
192
openlp/plugins/remotes/lib/httpauth.py
Normal file
192
openlp/plugins/remotes/lib/httpauth.py
Normal 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 "/")
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ MODULES = [
|
|||||||
'enchant',
|
'enchant',
|
||||||
'BeautifulSoup',
|
'BeautifulSoup',
|
||||||
'mako',
|
'mako',
|
||||||
|
'cherrypy',
|
||||||
'migrate',
|
'migrate',
|
||||||
'uno',
|
'uno',
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user