api and router work, move settings and tab
58
openlp/core/api/__init__.py
Normal file
@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
from .errors import NotFound, ServerError
|
||||
from .httprouter import WSGIApplication
|
||||
|
||||
application = WSGIApplication('api')
|
||||
|
||||
|
||||
def _route_from_url(url_prefix, url):
|
||||
"""
|
||||
Create a route from the URL
|
||||
"""
|
||||
url_prefix = '/{prefix}/'.format(prefix=url_prefix.strip('/'))
|
||||
if not url:
|
||||
url = url_prefix[:-1]
|
||||
else:
|
||||
url = url_prefix + url
|
||||
url = url.replace('//', '/')
|
||||
return url
|
||||
|
||||
|
||||
def register_endpoint(endpoint):
|
||||
"""
|
||||
Register an endpoint with the app
|
||||
"""
|
||||
print("ep", endpoint)
|
||||
for url, view_func, method, secure in endpoint.routes:
|
||||
route = _route_from_url(endpoint.url_prefix, url)
|
||||
application.add_route(route, view_func, method, secure)
|
||||
|
||||
from .endpoint import Endpoint
|
||||
from .apitab import ApiTab
|
||||
from .poll import OpenLPPoll
|
||||
from .wsserver import OpenWSServer
|
||||
from .httpserver import OpenLPHttpServer
|
||||
from .apicontroller import ApiController
|
||||
|
||||
__all__ = ['OpenLPPoll', 'RemoteController', 'OpenLPHttpServer', 'application']
|
@ -21,8 +21,9 @@
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from openlp.core.api import OpenWSServer, OpenLPPoll, OpenLPHttpServer
|
||||
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties
|
||||
from openlp.core.lib.api import OpenWSServer, OpenLPPoll, OpenLPHttpServer
|
||||
from openlp.core.api.uiinterfaces import stage_endpoint
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -45,7 +46,7 @@ class ApiController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
Constructor
|
||||
"""
|
||||
super(ApiController, self).__init__(parent)
|
||||
# Registry().register_function('playbackPlay', self.media_play_msg)
|
||||
print("apic")
|
||||
|
||||
def bootstrap_post_set_up(self):
|
||||
"""
|
266
openlp/core/api/apitab.py
Normal file
@ -0,0 +1,266 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
||||
|
||||
from openlp.core.common import Settings, translate
|
||||
from openlp.core.lib import SettingsTab
|
||||
|
||||
ZERO_URL = '0.0.0.0'
|
||||
|
||||
|
||||
class ApiTab(SettingsTab):
|
||||
"""
|
||||
RemoteTab is the Remotes settings tab in the settings dialog.
|
||||
"""
|
||||
def __init__(self, parent):
|
||||
self.icon_path = ':/plugins/plugin_remote.png'
|
||||
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
|
||||
super(ApiTab, self).__init__(parent, 'remotes', advanced_translated)
|
||||
|
||||
def setupUi(self):
|
||||
self.setObjectName('ApiTab')
|
||||
super(ApiTab, self).setupUi()
|
||||
self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.server_settings_group_box.setObjectName('server_settings_group_box')
|
||||
self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
|
||||
self.server_settings_layout.setObjectName('server_settings_layout')
|
||||
self.address_label = QtWidgets.QLabel(self.server_settings_group_box)
|
||||
self.address_label.setObjectName('address_label')
|
||||
self.address_edit = QtWidgets.QLineEdit(self.server_settings_group_box)
|
||||
self.address_edit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
|
||||
self))
|
||||
self.address_edit.setObjectName('address_edit')
|
||||
self.server_settings_layout.addRow(self.address_label, self.address_edit)
|
||||
self.twelve_hour_check_box = QtWidgets.QCheckBox(self.server_settings_group_box)
|
||||
self.twelve_hour_check_box.setObjectName('twelve_hour_check_box')
|
||||
self.server_settings_layout.addRow(self.twelve_hour_check_box)
|
||||
self.thumbnails_check_box = QtWidgets.QCheckBox(self.server_settings_group_box)
|
||||
self.thumbnails_check_box.setObjectName('thumbnails_check_box')
|
||||
self.server_settings_layout.addRow(self.thumbnails_check_box)
|
||||
self.left_layout.addWidget(self.server_settings_group_box)
|
||||
self.http_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.http_settings_group_box.setObjectName('http_settings_group_box')
|
||||
self.http_setting_layout = QtWidgets.QFormLayout(self.http_settings_group_box)
|
||||
self.http_setting_layout.setObjectName('http_setting_layout')
|
||||
self.port_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.port_label.setObjectName('port_label')
|
||||
self.port_spin_box = QtWidgets.QSpinBox(self.http_settings_group_box)
|
||||
self.port_spin_box.setMaximum(32767)
|
||||
self.port_spin_box.setObjectName('port_spin_box')
|
||||
self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
|
||||
self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.remote_url_label.setObjectName('remote_url_label')
|
||||
self.remote_url = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.remote_url.setObjectName('remote_url')
|
||||
self.remote_url.setOpenExternalLinks(True)
|
||||
self.http_setting_layout.addRow(self.remote_url_label, self.remote_url)
|
||||
self.stage_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.stage_url_label.setObjectName('stage_url_label')
|
||||
self.stage_url = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.stage_url.setObjectName('stage_url')
|
||||
self.stage_url.setOpenExternalLinks(True)
|
||||
self.http_setting_layout.addRow(self.stage_url_label, self.stage_url)
|
||||
self.live_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.live_url_label.setObjectName('live_url_label')
|
||||
self.live_url = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.live_url.setObjectName('live_url')
|
||||
self.live_url.setOpenExternalLinks(True)
|
||||
self.http_setting_layout.addRow(self.live_url_label, self.live_url)
|
||||
self.left_layout.addWidget(self.http_settings_group_box)
|
||||
self.user_login_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.user_login_group_box.setCheckable(True)
|
||||
self.user_login_group_box.setChecked(False)
|
||||
self.user_login_group_box.setObjectName('user_login_group_box')
|
||||
self.user_login_layout = QtWidgets.QFormLayout(self.user_login_group_box)
|
||||
self.user_login_layout.setObjectName('user_login_layout')
|
||||
self.user_id_label = QtWidgets.QLabel(self.user_login_group_box)
|
||||
self.user_id_label.setObjectName('user_id_label')
|
||||
self.user_id = QtWidgets.QLineEdit(self.user_login_group_box)
|
||||
self.user_id.setObjectName('user_id')
|
||||
self.user_login_layout.addRow(self.user_id_label, self.user_id)
|
||||
self.password_label = QtWidgets.QLabel(self.user_login_group_box)
|
||||
self.password_label.setObjectName('password_label')
|
||||
self.password = QtWidgets.QLineEdit(self.user_login_group_box)
|
||||
self.password.setObjectName('password')
|
||||
self.user_login_layout.addRow(self.password_label, self.password)
|
||||
self.left_layout.addWidget(self.user_login_group_box)
|
||||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
||||
self.right_layout.addWidget(self.android_app_group_box)
|
||||
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
|
||||
self.android_qr_layout.setObjectName('android_qr_layout')
|
||||
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
|
||||
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.android_qr_code_label.setObjectName('android_qr_code_label')
|
||||
self.android_qr_layout.addWidget(self.android_qr_code_label)
|
||||
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_description_label.setObjectName('android_qr_description_label')
|
||||
self.android_qr_description_label.setOpenExternalLinks(True)
|
||||
self.android_qr_description_label.setWordWrap(True)
|
||||
self.android_qr_layout.addWidget(self.android_qr_description_label)
|
||||
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.ios_app_group_box.setObjectName('ios_app_group_box')
|
||||
self.right_layout.addWidget(self.ios_app_group_box)
|
||||
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
|
||||
self.ios_qr_layout.setObjectName('ios_qr_layout')
|
||||
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
|
||||
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
|
||||
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
|
||||
self.ios_qr_description_label.setOpenExternalLinks(True)
|
||||
self.ios_qr_description_label.setWordWrap(True)
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
|
||||
self.left_layout.addStretch()
|
||||
self.right_layout.addStretch()
|
||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
|
||||
self.address_edit.textChanged.connect(self.set_urls)
|
||||
self.port_spin_box.valueChanged.connect(self.set_urls)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface')
|
||||
|
||||
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
|
||||
self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
|
||||
self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
|
||||
self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
|
||||
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
|
||||
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
|
||||
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
||||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
|
||||
self.android_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab',
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
|
||||
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
|
||||
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
|
||||
self.ios_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab',
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
||||
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
||||
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
||||
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
|
||||
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
|
||||
|
||||
def set_urls(self):
|
||||
"""
|
||||
Update the display based on the data input on the screen
|
||||
"""
|
||||
ip_address = self.get_ip_address(self.address_edit.text())
|
||||
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value())
|
||||
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
||||
http_url_temp = http_url + 'stage'
|
||||
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
http_url_temp = http_url + 'main'
|
||||
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
|
||||
def get_ip_address(self, ip_address):
|
||||
"""
|
||||
returns the IP address in dependency of the passed address
|
||||
ip_address == 0.0.0.0: return the IP address of the first valid interface
|
||||
else: return ip_address
|
||||
"""
|
||||
if ip_address == ZERO_URL:
|
||||
interfaces = QtNetwork.QNetworkInterface.allInterfaces()
|
||||
for interface in interfaces:
|
||||
if not interface.isValid():
|
||||
continue
|
||||
if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)):
|
||||
continue
|
||||
for address in interface.addressEntries():
|
||||
ip = address.ip()
|
||||
if ip.protocol() == QtNetwork.QAbstractSocket.IPv4Protocol and \
|
||||
ip != QtNetwork.QHostAddress.LocalHost:
|
||||
return ip.toString()
|
||||
return ip_address
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load the configuration and update the server configuration if necessary
|
||||
"""
|
||||
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
|
||||
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
||||
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
|
||||
self.twelve_hour_check_box.setChecked(self.twelve_hour)
|
||||
self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
|
||||
self.thumbnails_check_box.setChecked(self.thumbnails)
|
||||
self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
|
||||
self.user_id.setText(Settings().value(self.settings_section + '/user id'))
|
||||
self.password.setText(Settings().value(self.settings_section + '/password'))
|
||||
self.set_urls()
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the configuration and update the server configuration if necessary
|
||||
"""
|
||||
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \
|
||||
Settings().value(self.settings_section + '/port') != self.port_spin_box.value():
|
||||
self.settings_form.register_post_process('remotes_config_updated')
|
||||
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
|
||||
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
|
||||
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
|
||||
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
|
||||
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
|
||||
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
||||
Settings().setValue(self.settings_section + '/password', self.password.text())
|
||||
self.generate_icon()
|
||||
|
||||
def on_twelve_hour_check_box_changed(self, check_state):
|
||||
"""
|
||||
Toggle the 12 hour check box.
|
||||
"""
|
||||
self.twelve_hour = False
|
||||
# we have a set value convert to True/False
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.twelve_hour = True
|
||||
|
||||
def on_thumbnails_check_box_changed(self, check_state):
|
||||
"""
|
||||
Toggle the thumbnail check box.
|
||||
"""
|
||||
self.thumbnails = False
|
||||
# we have a set value convert to True/False
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.thumbnails = True
|
||||
|
||||
def generate_icon(self):
|
||||
"""
|
||||
Generate icon for main window
|
||||
"""
|
||||
self.remote_server_icon.hide()
|
||||
icon = QtGui.QImage(':/remote/network_server.png')
|
||||
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
if Settings().value(self.settings_section + '/authentication enabled'):
|
||||
overlay = QtGui.QImage(':/remote/network_auth.png')
|
||||
overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
painter = QtGui.QPainter(icon)
|
||||
painter.drawImage(20, 0, overlay)
|
||||
painter.end()
|
||||
self.remote_server_icon.setPixmap(QtGui.QPixmap.fromImage(icon))
|
||||
self.remote_server_icon.show()
|
34
openlp/core/api/endpoint.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
openlp/core/api/endpoint.py: Endpoint stuff
|
||||
"""
|
||||
|
||||
|
||||
class Endpoint(object):
|
||||
"""
|
||||
This is an endpoint for the API
|
||||
"""
|
||||
def __init__(self, url_prefix):
|
||||
"""
|
||||
Create an endpoint with a URL prefix
|
||||
"""
|
||||
print("init")
|
||||
self.url_prefix = url_prefix
|
||||
self.routes = []
|
||||
|
||||
def add_url_route(self, url, view_func, method, secure):
|
||||
"""
|
||||
Add a url route to the list of routes
|
||||
"""
|
||||
self.routes.append((url, view_func, method, secure))
|
||||
|
||||
def route(self, rule, method='GET', secure=False):
|
||||
"""
|
||||
Set up a URL route
|
||||
"""
|
||||
def decorator(func):
|
||||
"""
|
||||
Make this a decorator
|
||||
"""
|
||||
self.add_url_route(rule, func, method, secure)
|
||||
return func
|
||||
return decorator
|
67
openlp/core/api/errors.py
Normal file
@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
HTTP Error classes
|
||||
"""
|
||||
|
||||
|
||||
class HttpError(Exception):
|
||||
"""
|
||||
A base HTTP error (aka status code)
|
||||
"""
|
||||
def __init__(self, status, message):
|
||||
"""
|
||||
Initialise the exception
|
||||
"""
|
||||
super(HttpError, self).__init__(message)
|
||||
self.status = status
|
||||
self.message = message
|
||||
|
||||
def to_response(self):
|
||||
"""
|
||||
Convert this exception to a Response object
|
||||
"""
|
||||
return self.message, self.status
|
||||
|
||||
|
||||
class NotFound(HttpError):
|
||||
"""
|
||||
A 404
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Make this a 404
|
||||
"""
|
||||
super(NotFound, self).__init__(404, 'Not Found')
|
||||
|
||||
|
||||
class ServerError(HttpError):
|
||||
"""
|
||||
A 500
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Make this a 500
|
||||
"""
|
||||
super(ServerError, self).__init__(500, 'Server Error')
|
||||
|
||||
|
9404
openlp/core/api/html/assets/jquery.js
vendored
Normal file
4
openlp/core/api/html/assets/jquery.min.js
vendored
Normal file
9357
openlp/core/api/html/assets/jquery.mobile.js
Normal file
2
openlp/core/api/html/assets/jquery.mobile.min.css
vendored
Normal file
2
openlp/core/api/html/assets/jquery.mobile.min.js
vendored
Normal file
32
openlp/core/api/html/css/main.css
Normal file
@ -0,0 +1,32 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
body {
|
||||
background-color: black;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.size {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
31
openlp/core/api/html/css/openlp.css
Normal file
@ -0,0 +1,31 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
|
||||
.ui-icon-blank {
|
||||
background-image: url(../images/ui-icon-blank.png);
|
||||
}
|
||||
|
||||
.ui-icon-unblank {
|
||||
background-image: url(../images/ui-icon-unblank.png);
|
||||
}
|
||||
|
||||
/* Overwrite style from jquery-mobile.min.css */
|
||||
.ui-li .ui-btn-text a.ui-link-inherit{
|
||||
white-space: normal;
|
||||
}
|
64
openlp/core/api/html/css/stage.css
Normal file
@ -0,0 +1,64 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
|
||||
body {
|
||||
background-color: black;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#currentslide {
|
||||
font-size: 40pt;
|
||||
color: white;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#nextslide {
|
||||
font-size: 40pt;
|
||||
color: grey;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#clock {
|
||||
font-size: 30pt;
|
||||
color: yellow;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#notes {
|
||||
font-size: 36pt;
|
||||
color: salmon;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#verseorder {
|
||||
font-size: 30pt;
|
||||
color: green;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.currenttag {
|
||||
color: lightgreen;
|
||||
font-weight: bold;
|
||||
}
|
BIN
openlp/core/api/html/images/ajax-loader.gif
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
openlp/core/api/html/images/ajax-loader.png
Normal file
After Width: | Height: | Size: 340 B |
BIN
openlp/core/api/html/images/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
openlp/core/api/html/images/form-check-off.png
Normal file
After Width: | Height: | Size: 364 B |
BIN
openlp/core/api/html/images/form-check-on.png
Normal file
After Width: | Height: | Size: 460 B |
BIN
openlp/core/api/html/images/form-radio-off.png
Normal file
After Width: | Height: | Size: 453 B |
BIN
openlp/core/api/html/images/form-radio-on.png
Normal file
After Width: | Height: | Size: 519 B |
BIN
openlp/core/api/html/images/icon-search-black.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
openlp/core/api/html/images/icons-18-black.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
openlp/core/api/html/images/icons-18-white.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
openlp/core/api/html/images/icons-36-black.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
openlp/core/api/html/images/icons-36-white.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
openlp/core/api/html/images/ui-icon-blank.png
Normal file
After Width: | Height: | Size: 225 B |
BIN
openlp/core/api/html/images/ui-icon-unblank.png
Normal file
After Width: | Height: | Size: 231 B |
177
openlp/core/api/html/index.html
Normal file
@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
|
||||
<title>${app_title}</title>
|
||||
<link rel="stylesheet" href="/assets/jquery.mobile.min.css" />
|
||||
<link rel="stylesheet" href="/css/openlp.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/openlp.js"></script>
|
||||
<script type="text/javascript" src="/assets/jquery.mobile.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
translationStrings = {
|
||||
"go_live": "${go_live}",
|
||||
"add_to_service": "${add_to_service}",
|
||||
"no_results": "${no_results}",
|
||||
"home": "${home}"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div data-role="page" id="home">
|
||||
<div data-role="header">
|
||||
<h1>${app_title}</h1>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<div data-role="controlgroup">
|
||||
<a href="#service-manager" data-role="button" data-icon="arrow-r" data-iconpos="right">${service_manager}</a>
|
||||
<a href="#slide-controller" data-role="button" data-icon="arrow-r" data-iconpos="right">${slide_controller}</a>
|
||||
<a href="#alerts" data-role="button" data-icon="arrow-r" data-iconpos="right">${alerts}</a>
|
||||
<a href="#search" data-role="button" data-icon="arrow-r" data-iconpos="right">${search}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="service-manager">
|
||||
<div data-role="header" data-position="fixed">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${service_manager}</h1>
|
||||
<a href="#" id="service-refresh" data-role="button" data-icon="refresh">${refresh}</a>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager" data-theme="e">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<ul data-role="listview" data-inset="true">
|
||||
</ul>
|
||||
</div>
|
||||
<div data-role="footer" data-theme="b" class="ui-bar" data-position="fixed">
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="service-blank" data-role="button" data-icon="blank">${blank}</a>
|
||||
<a href="#" id="service-theme" data-role="button">${theme}</a>
|
||||
<a href="#" id="service-desktop" data-role="button">${desktop}</a>
|
||||
<a href="#" id="service-show" data-role="button" data-icon="unblank" data-iconpos="right">${show}</a>
|
||||
</div>
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="service-previous" data-role="button" data-icon="arrow-l">${prev}</a>
|
||||
<a href="#" id="service-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="slide-controller">
|
||||
<div data-role="header" data-position="fixed">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${slide_controller}</h1>
|
||||
<a href="#" id="controller-refresh" data-role="button" data-icon="refresh">${refresh}</a>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller" data-theme="e">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<ul data-role="listview" data-inset="true">
|
||||
</ul>
|
||||
</div>
|
||||
<div data-role="footer" data-theme="b" class="ui-bar" data-position="fixed">
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="controller-blank" data-role="button" data-icon="blank">${blank}</a>
|
||||
<a href="#" id="controller-theme" data-role="button">${theme}</a>
|
||||
<a href="#" id="controller-desktop" data-role="button">${desktop}</a>
|
||||
<a href="#" id="controller-show" data-role="button" data-icon="unblank" data-iconpos="right">${show}</a>
|
||||
</div>
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">${prev}</a>
|
||||
<a href="#" id="controller-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="alerts">
|
||||
<div data-role="header">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${alerts}</h1>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts" data-theme="e">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<div data-role="fieldcontain">
|
||||
<label for="alert-text">${text}:</label>
|
||||
<input type="text" name="alert-text" id="alert-text" value="" />
|
||||
</div>
|
||||
<a href="#" id="alert-submit" data-role="button">${show_alert}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="search">
|
||||
<div data-role="header" data-position="fixed">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${search}</h1>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search" data-theme="e">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<div data-role="fieldcontain">
|
||||
<label for="search-plugin">${search}:</label>
|
||||
<select name="search-plugin" id="search-plugin" data-native-menu="false"></select>
|
||||
</div>
|
||||
<div data-role="fieldcontain">
|
||||
<label for="search-text">${text}:</label>
|
||||
<input type="search" name="search-text" id="search-text" value="" />
|
||||
</div>
|
||||
<a href="#" id="search-submit" data-role="button">${search}</a>
|
||||
<ul data-role="listview" data-inset="true"/>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="options">
|
||||
<div data-role="header" data-position="inline" data-theme="b">
|
||||
<h1>${options}</h1>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<input type="hidden" id="selected-item" value="" />
|
||||
<a href="#" id="go-live" data-role="button">${go_live}</a>
|
||||
<a href="#" id="add-to-service" data-role="button">${add_to_service}</a>
|
||||
<a href="#" id="add-and-go-to-service" data-role="button">${add_and_go_to_service}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
50
openlp/core/api/html/js/main.js
Normal file
@ -0,0 +1,50 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
window.OpenLP = {
|
||||
loadSlide: function (event) {
|
||||
$.getJSON(
|
||||
"/main/image",
|
||||
function (data, status) {
|
||||
var img = document.getElementById('image');
|
||||
img.src = data.results.slide_image;
|
||||
img.style.display = 'block';
|
||||
}
|
||||
);
|
||||
},
|
||||
pollServer: function () {
|
||||
if ("WebSocket" in window) {
|
||||
// Let us open a web socket
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4317/main_poll');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onmessage = function (evt) {
|
||||
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
|
||||
if (OpenLP.slideCount != msg.results.slide_count) {
|
||||
OpenLP.slideCount = msg.results.slide_count;
|
||||
OpenLP.loadSlide();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The browser doesn't support WebSocket
|
||||
alert("WebSocket NOT supported by your Browser!");
|
||||
}
|
||||
}
|
||||
};
|
||||
$.ajaxSetup({ cache: false });
|
||||
OpenLP.pollServer();
|
||||
|
390
openlp/core/api/html/js/openlp.js
Normal file
@ -0,0 +1,390 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
|
||||
window.OpenLP = {
|
||||
getElement: function(event) {
|
||||
var targ;
|
||||
if (!event) {
|
||||
var event = window.event;
|
||||
}
|
||||
if (event.target) {
|
||||
targ = event.target;
|
||||
}
|
||||
else if (event.srcElement) {
|
||||
targ = event.srcElement;
|
||||
}
|
||||
if (targ.nodeType == 3) {
|
||||
// defeat Safari bug
|
||||
targ = targ.parentNode;
|
||||
}
|
||||
var isSecure = false;
|
||||
var isAuthorised = false;
|
||||
return $(targ);
|
||||
},
|
||||
getSearchablePlugins: function () {
|
||||
$.getJSON(
|
||||
"/api/plugin/search",
|
||||
function (data, status) {
|
||||
var select = $("#search-plugin");
|
||||
select.html("");
|
||||
$.each(data.results.items, function (idx, value) {
|
||||
select.append("<option value='" + value[0] + "'>" + value[1] + "</option>");
|
||||
});
|
||||
select.selectmenu("refresh");
|
||||
}
|
||||
);
|
||||
},
|
||||
loadService: function (event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
$.getJSON(
|
||||
"/api/service/list",
|
||||
function (data, status) {
|
||||
var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]");
|
||||
ul.html("");
|
||||
$.each(data.results.items, function (idx, value) {
|
||||
var text = value["title"];
|
||||
if (value["notes"]) {
|
||||
text += ' - ' + value["notes"];
|
||||
}
|
||||
var li = $("<li data-icon=\"false\">").append(
|
||||
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).text(text));
|
||||
li.attr("uuid", value["id"])
|
||||
li.children("a").click(OpenLP.setItem);
|
||||
ul.append(li);
|
||||
});
|
||||
ul.listview("refresh");
|
||||
}
|
||||
);
|
||||
},
|
||||
loadController: function (event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
$.getJSON(
|
||||
"/api/controller/live/text",
|
||||
function (data, status) {
|
||||
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
|
||||
ul.html("");
|
||||
for (idx in data.results.slides) {
|
||||
var indexInt = parseInt(idx,10);
|
||||
var slide = data.results.slides[idx];
|
||||
var text = slide["tag"];
|
||||
if (text != "") {
|
||||
text = text + ": ";
|
||||
}
|
||||
if (slide["title"]) {
|
||||
text += slide["title"]
|
||||
} else {
|
||||
text += slide["text"];
|
||||
}
|
||||
if (slide["slide_notes"]) {
|
||||
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["slide_notes"] + "</div>");
|
||||
}
|
||||
text = text.replace(/\n/g, '<br />');
|
||||
if (slide["img"]) {
|
||||
text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails88x88/") + "'>";
|
||||
}
|
||||
var li = $("<li data-icon=\"false\">").append($("<a href=\"#\">").html(text));
|
||||
if (slide["selected"]) {
|
||||
li.attr("data-theme", "e");
|
||||
}
|
||||
li.children("a").click(OpenLP.setSlide);
|
||||
li.find("*").attr("value", indexInt );
|
||||
ul.append(li);
|
||||
}
|
||||
OpenLP.currentItem = data.results.item;
|
||||
ul.listview("refresh");
|
||||
}
|
||||
);
|
||||
},
|
||||
setItem: function (event) {
|
||||
event.preventDefault();
|
||||
var item = OpenLP.getElement(event);
|
||||
var id = item.attr("value");
|
||||
if (typeof id !== "number") {
|
||||
id = "\"" + id + "\"";
|
||||
}
|
||||
var text = "{\"request\": {\"id\": " + id + "}}";
|
||||
$.getJSON(
|
||||
"/api/service/set",
|
||||
{"data": text},
|
||||
function (data, status) {
|
||||
$.mobile.changePage("#slide-controller");
|
||||
$("#service-manager > div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
|
||||
while (item[0].tagName != "LI") {
|
||||
item = item.parent();
|
||||
}
|
||||
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
|
||||
$("#service-manager > div[data-role=content] ul[data-role=listview]").listview("refresh");
|
||||
}
|
||||
);
|
||||
},
|
||||
setSlide: function (event) {
|
||||
event.preventDefault();
|
||||
var slide = OpenLP.getElement(event);
|
||||
var id = slide.attr("value");
|
||||
if (typeof id !== "number") {
|
||||
id = "\"" + id + "\"";
|
||||
}
|
||||
var text = "{\"request\": {\"id\": " + id + "}}";
|
||||
$.getJSON(
|
||||
"/api/controller/live/set",
|
||||
{"data": text},
|
||||
function (data, status) {
|
||||
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
|
||||
while (slide[0].tagName != "LI") {
|
||||
slide = slide.parent();
|
||||
}
|
||||
slide.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
|
||||
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
|
||||
}
|
||||
);
|
||||
},
|
||||
pollServer: function () {
|
||||
if ("WebSocket" in window) {
|
||||
// Let us open a web socket
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onmessage = function (evt) {
|
||||
var data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
|
||||
|
||||
var prevItem = OpenLP.currentItem;
|
||||
OpenLP.currentSlide = data.results.slide;
|
||||
OpenLP.currentItem = data.results.item;
|
||||
OpenLP.isSecure = data.results.isSecure;
|
||||
OpenLP.isAuthorised = data.results.isAuthorised;
|
||||
if ($("#service-manager").is(":visible")) {
|
||||
if (OpenLP.currentService != data.results.service) {
|
||||
OpenLP.currentService = data.results.service;
|
||||
OpenLP.loadService();
|
||||
}
|
||||
$("#service-manager div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
|
||||
$("#service-manager div[data-role=content] ul[data-role=listview] li a").each(function () {
|
||||
var item = $(this);
|
||||
while (item[0].tagName != "LI") {
|
||||
item = item.parent();
|
||||
}
|
||||
if (item.attr("uuid") == OpenLP.currentItem) {
|
||||
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
$("#service-manager div[data-role=content] ul[data-role=listview]").listview("refresh");
|
||||
}
|
||||
if ($("#slide-controller").is(":visible")) {
|
||||
if (prevItem != OpenLP.currentItem) {
|
||||
OpenLP.loadController();
|
||||
return;
|
||||
}
|
||||
var idx = 0;
|
||||
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
|
||||
$("#slide-controller div[data-role=content] ul[data-role=listview] li a").each(function () {
|
||||
var item = $(this);
|
||||
if (idx == OpenLP.currentSlide) {
|
||||
while (item[0].tagName != "LI") {
|
||||
item = item.parent();
|
||||
}
|
||||
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
|
||||
return false;
|
||||
}
|
||||
idx++;
|
||||
});
|
||||
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The browser doesn't support WebSocket
|
||||
alert("WebSocket NOT supported by your Browser!");
|
||||
}
|
||||
},
|
||||
nextItem: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/service/next");
|
||||
},
|
||||
previousItem: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/service/previous");
|
||||
},
|
||||
nextSlide: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/controller/live/next");
|
||||
},
|
||||
previousSlide: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/controller/live/previous");
|
||||
},
|
||||
blankDisplay: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/display/blank");
|
||||
},
|
||||
themeDisplay: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/display/theme");
|
||||
},
|
||||
desktopDisplay: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/display/desktop");
|
||||
},
|
||||
showDisplay: function (event) {
|
||||
event.preventDefault();
|
||||
$.getJSON("/api/display/show");
|
||||
},
|
||||
showAlert: function (event) {
|
||||
event.preventDefault();
|
||||
var alert = OpenLP.escapeString($("#alert-text").val())
|
||||
var text = "{\"request\": {\"text\": \"" + alert + "\"}}";
|
||||
$.getJSON(
|
||||
"/api/alert",
|
||||
{"data": text},
|
||||
function () {
|
||||
$("#alert-text").val("");
|
||||
}
|
||||
);
|
||||
},
|
||||
search: function (event) {
|
||||
event.preventDefault();
|
||||
var query = OpenLP.escapeString($("#search-text").val())
|
||||
var text = "{\"request\": {\"text\": \"" + query + "\"}}";
|
||||
$.getJSON(
|
||||
"/api/" + $("#search-plugin").val() + "/search",
|
||||
{"data": text},
|
||||
function (data, status) {
|
||||
var ul = $("#search > div[data-role=content] > ul[data-role=listview]");
|
||||
ul.html("");
|
||||
if (data.results.items.length == 0) {
|
||||
var li = $("<li data-icon=\"false\">").text(translationStrings["no_results"]);
|
||||
ul.append(li);
|
||||
}
|
||||
else {
|
||||
$.each(data.results.items, function (idx, value) {
|
||||
if (typeof value[0] !== "number"){
|
||||
value[0] = OpenLP.escapeString(value[0])
|
||||
}
|
||||
var txt = "";
|
||||
if (value.length > 2) {
|
||||
txt = value[1] + " ( " + value[2] + " )";
|
||||
} else {
|
||||
txt = value[1];
|
||||
}
|
||||
ul.append($("<li>").append($("<a>").attr("href", "#options")
|
||||
.attr("data-rel", "dialog").attr("value", value[0])
|
||||
.click(OpenLP.showOptions).text(txt)));
|
||||
});
|
||||
}
|
||||
ul.listview("refresh");
|
||||
}
|
||||
);
|
||||
},
|
||||
showOptions: function (event) {
|
||||
event.preventDefault();
|
||||
var element = OpenLP.getElement(event);
|
||||
$("#selected-item").val(element.attr("value"));
|
||||
},
|
||||
goLive: function (event) {
|
||||
event.preventDefault();
|
||||
var id = $("#selected-item").val();
|
||||
if (typeof id !== "number") {
|
||||
id = "\"" + id + "\"";
|
||||
}
|
||||
var text = "{\"request\": {\"id\": " + id + "}}";
|
||||
$.getJSON(
|
||||
"/api/" + $("#search-plugin").val() + "/live",
|
||||
{"data": text}
|
||||
);
|
||||
$.mobile.changePage("#slide-controller");
|
||||
},
|
||||
addToService: function (event) {
|
||||
event.preventDefault();
|
||||
var id = $("#selected-item").val();
|
||||
if (typeof id !== "number") {
|
||||
id = "\"" + id + "\"";
|
||||
}
|
||||
var text = "{\"request\": {\"id\": " + id + "}}";
|
||||
$.getJSON(
|
||||
"/api/" + $("#search-plugin").val() + "/add",
|
||||
{"data": text},
|
||||
function () {
|
||||
$("#options").dialog("close");
|
||||
}
|
||||
);
|
||||
},
|
||||
addAndGoToService: function (event) {
|
||||
event.preventDefault();
|
||||
var id = $("#selected-item").val();
|
||||
if (typeof id !== "number") {
|
||||
id = "\"" + id + "\"";
|
||||
}
|
||||
var text = "{\"request\": {\"id\": " + id + "}}";
|
||||
$.getJSON(
|
||||
"/api/" + $("#search-plugin").val() + "/add",
|
||||
{"data": text},
|
||||
function () {
|
||||
//$("#options").dialog("close");
|
||||
$.mobile.changePage("#service-manager");
|
||||
}
|
||||
);
|
||||
},
|
||||
escapeString: function (string) {
|
||||
return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")
|
||||
}
|
||||
}
|
||||
// Initial jQueryMobile options
|
||||
$(document).bind("mobileinit", function(){
|
||||
$.mobile.defaultDialogTransition = "none";
|
||||
$.mobile.defaultPageTransition = "none";
|
||||
});
|
||||
// Service Manager
|
||||
$("#service-manager").live("pagebeforeshow", OpenLP.loadService);
|
||||
$("#service-refresh").live("click", OpenLP.loadService);
|
||||
$("#service-next").live("click", OpenLP.nextItem);
|
||||
$("#service-previous").live("click", OpenLP.previousItem);
|
||||
$("#service-blank").live("click", OpenLP.blankDisplay);
|
||||
$("#service-theme").live("click", OpenLP.themeDisplay);
|
||||
$("#service-desktop").live("click", OpenLP.desktopDisplay);
|
||||
$("#service-show").live("click", OpenLP.showDisplay);
|
||||
// Slide Controller
|
||||
$("#slide-controller").live("pagebeforeshow", OpenLP.loadController);
|
||||
$("#controller-refresh").live("click", OpenLP.loadController);
|
||||
$("#controller-next").live("click", OpenLP.nextSlide);
|
||||
$("#controller-previous").live("click", OpenLP.previousSlide);
|
||||
$("#controller-blank").live("click", OpenLP.blankDisplay);
|
||||
$("#controller-theme").live("click", OpenLP.themeDisplay);
|
||||
$("#controller-desktop").live("click", OpenLP.desktopDisplay);
|
||||
$("#controller-show").live("click", OpenLP.showDisplay);
|
||||
// Alerts
|
||||
$("#alert-submit").live("click", OpenLP.showAlert);
|
||||
// Search
|
||||
$("#search-submit").live("click", OpenLP.search);
|
||||
$("#search-text").live("keypress", function(event) {
|
||||
if (event.which == 13)
|
||||
{
|
||||
OpenLP.search(event);
|
||||
}
|
||||
});
|
||||
$("#go-live").live("click", OpenLP.goLive);
|
||||
$("#add-to-service").live("click", OpenLP.addToService);
|
||||
$("#add-and-go-to-service").live("click", OpenLP.addAndGoToService);
|
||||
// Poll the server twice a second to get any updates.
|
||||
$.ajaxSetup({cache: false});
|
||||
$("#search").live("pageinit", function (event) {
|
||||
OpenLP.getSearchablePlugins();
|
||||
});
|
||||
OpenLP.pollServer();
|
178
openlp/core/api/html/js/stage.js
Normal file
@ -0,0 +1,178 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* 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 *
|
||||
******************************************************************************/
|
||||
window.OpenLP = {
|
||||
loadService: function (event) {
|
||||
$.getJSON(
|
||||
"/api/service/list",
|
||||
function (data, status) {
|
||||
OpenLP.nextSong = "";
|
||||
$("#notes").html("");
|
||||
for (idx in data.results.items) {
|
||||
idx = parseInt(idx, 10);
|
||||
if (data.results.items[idx]["selected"]) {
|
||||
$("#notes").html(data.results.items[idx]["notes"].replace(/\n/g, "<br />"));
|
||||
if (data.results.items.length > idx + 1) {
|
||||
OpenLP.nextSong = data.results.items[idx + 1]["title"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
OpenLP.updateSlide();
|
||||
}
|
||||
);
|
||||
},
|
||||
loadSlides: function (event) {
|
||||
$.getJSON(
|
||||
"/api/controller/live/text",
|
||||
function (data, status) {
|
||||
OpenLP.currentSlides = data.results.slides;
|
||||
OpenLP.currentSlide = 0;
|
||||
OpenLP.currentTags = Array();
|
||||
var div = $("#verseorder");
|
||||
div.html("");
|
||||
var tag = "";
|
||||
var tags = 0;
|
||||
var lastChange = 0;
|
||||
$.each(data.results.slides, function(idx, slide) {
|
||||
var prevtag = tag;
|
||||
tag = slide["tag"];
|
||||
if (tag != prevtag) {
|
||||
// If the tag has changed, add new one to the list
|
||||
lastChange = idx;
|
||||
tags = tags + 1;
|
||||
div.append(" <span>");
|
||||
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
|
||||
}
|
||||
else {
|
||||
if ((slide["text"] == data.results.slides[lastChange]["text"]) &&
|
||||
(data.results.slides.length >= idx + (idx - lastChange))) {
|
||||
// If the tag hasn't changed, check to see if the same verse
|
||||
// has been repeated consecutively. Note the verse may have been
|
||||
// split over several slides, so search through. If so, repeat the tag.
|
||||
var match = true;
|
||||
for (var idx2 = 0; idx2 < idx - lastChange; idx2++) {
|
||||
if(data.results.slides[lastChange + idx2]["text"] != data.results.slides[idx + idx2]["text"]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
lastChange = idx;
|
||||
tags = tags + 1;
|
||||
div.append(" <span>");
|
||||
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenLP.currentTags[idx] = tags;
|
||||
if (slide["selected"])
|
||||
OpenLP.currentSlide = idx;
|
||||
})
|
||||
OpenLP.loadService();
|
||||
}
|
||||
);
|
||||
},
|
||||
updateSlide: function() {
|
||||
// Show the current slide on top. Any trailing slides for the same verse
|
||||
// are shown too underneath in grey.
|
||||
// Then leave a blank line between following verses
|
||||
$("#verseorder span").removeClass("currenttag");
|
||||
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
|
||||
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
|
||||
var text = "";
|
||||
// use title if available
|
||||
if (slide["title"]) {
|
||||
text = slide["title"];
|
||||
} else {
|
||||
text = slide["text"];
|
||||
}
|
||||
// use thumbnail if available
|
||||
if (slide["img"]) {
|
||||
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
|
||||
}
|
||||
// use notes if available
|
||||
if (slide["slide_notes"]) {
|
||||
text += '<br />' + slide["slide_notes"];
|
||||
}
|
||||
text = text.replace(/\n/g, "<br />");
|
||||
$("#currentslide").html(text);
|
||||
text = "";
|
||||
if (OpenLP.currentSlide < OpenLP.currentSlides.length - 1) {
|
||||
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
|
||||
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
|
||||
text = text + "<p class=\"nextslide\">";
|
||||
if (OpenLP.currentSlides[idx]["title"]) {
|
||||
text = text + OpenLP.currentSlides[idx]["title"];
|
||||
} else {
|
||||
text = text + OpenLP.currentSlides[idx]["text"];
|
||||
}
|
||||
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
|
||||
text = text + "</p>";
|
||||
else
|
||||
text = text + "<br />";
|
||||
}
|
||||
text = text.replace(/\n/g, "<br />");
|
||||
$("#nextslide").html(text);
|
||||
}
|
||||
else {
|
||||
text = "<p class=\"nextslide\">" + $("#next-text").val() + ": " + OpenLP.nextSong + "</p>";
|
||||
$("#nextslide").html(text);
|
||||
}
|
||||
},
|
||||
updateClock: function() {
|
||||
var div = $("#clock");
|
||||
var t = new Date();
|
||||
var h = t.getHours();
|
||||
if (OpenLP.twelve && h > 12)
|
||||
h = h - 12;
|
||||
var m = t.getMinutes();
|
||||
if (m < 10)
|
||||
m = '0' + m + '';
|
||||
div.html(h + ":" + m);
|
||||
},
|
||||
pollServer: function () {
|
||||
if ("WebSocket" in window) {
|
||||
// Let us open a web socket
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onmessage = function (evt) {
|
||||
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
|
||||
OpenLP.twelve = msg.results.twelve;
|
||||
OpenLP.updateClock();
|
||||
if (OpenLP.currentItem != msg.results.item ||
|
||||
OpenLP.currentService != msg.results.service) {
|
||||
OpenLP.currentItem = msg.results.item;
|
||||
OpenLP.currentService = msg.results.service;
|
||||
OpenLP.loadSlides();
|
||||
}
|
||||
else if (OpenLP.currentSlide != msg.results.slide) {
|
||||
OpenLP.currentSlide = parseInt(msg.results.slide, 10);
|
||||
OpenLP.updateSlide();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The browser doesn't support WebSocket
|
||||
alert("WebSocket NOT supported by your Browser!");
|
||||
}
|
||||
},
|
||||
};
|
||||
$.ajaxSetup({ cache: false });
|
||||
setInterval("OpenLP.updateClock();", 1000);
|
||||
OpenLP.pollServer();
|
||||
OpenLP.updateClock();
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
@ -19,10 +19,16 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
from .poll import OpenLPPoll
|
||||
from .wsserver import OpenWSServer
|
||||
from .httpserver import OpenLPHttpServer
|
||||
from .apicontroller import ApiController
|
||||
|
||||
__all__ = ['OpenLPPoll', 'RemoteController', 'OpenLPHttpServer']
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${live_title}</title>
|
||||
<link rel="stylesheet" href="/css/main.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img id="image" class="size"/>
|
||||
</body>
|
||||
</html>
|
41
openlp/core/api/html/stage.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${stage_title}</title>
|
||||
<link rel="stylesheet" href="/css/stage.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/stage.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" id="next-text" value="${next}" />
|
||||
<div id="right">
|
||||
<div id="clock"></div>
|
||||
<div id="notes"></div>
|
||||
</div>
|
||||
<div id="verseorder"></div>
|
||||
<div id="currentslide"></div>
|
||||
<div id="nextslide"></div>
|
||||
</body>
|
||||
</html>
|
121
openlp/core/api/httprouter.py
Normal file
@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
App stuff
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
|
||||
from webob import Request, Response
|
||||
|
||||
from .errors import HttpError, NotFound, ServerError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _make_response(view_result):
|
||||
"""
|
||||
Create a Response object from response
|
||||
"""
|
||||
if isinstance(view_result, Response):
|
||||
return view_result
|
||||
elif isinstance(view_result, tuple):
|
||||
content_type = 'text/html'
|
||||
body = view_result[0]
|
||||
if isinstance(body, dict):
|
||||
content_type = 'application/json'
|
||||
body = json.dumps(body)
|
||||
response = Response(body=body, status=view_result[1],
|
||||
content_type=content_type, charset='utf8')
|
||||
if len(view_result) >= 3:
|
||||
response.headers = view_result[2]
|
||||
return response
|
||||
elif isinstance(view_result, dict):
|
||||
return Response(body=json.dumps(view_result), status=200,
|
||||
content_type='application/json', charset='utf8')
|
||||
elif isinstance(view_result, str):
|
||||
return Response(body=view_result, status=200,
|
||||
content_type='text/html', charset='utf8')
|
||||
|
||||
|
||||
def _handle_exception(error):
|
||||
"""
|
||||
Handle exceptions
|
||||
"""
|
||||
log.exception(error)
|
||||
if isinstance(error, HttpError):
|
||||
return error.to_response()
|
||||
else:
|
||||
return ServerError().to_response()
|
||||
|
||||
|
||||
class WSGIApplication(object):
|
||||
"""
|
||||
This is the core of the API, the WSGI app
|
||||
"""
|
||||
def __init__(self, name):
|
||||
"""
|
||||
Create the app object
|
||||
"""
|
||||
self.name = name
|
||||
self.route_map = {}
|
||||
|
||||
def add_route(self, route, view_func, method, secure):
|
||||
"""
|
||||
Add a route
|
||||
"""
|
||||
if route not in self.route_map:
|
||||
self.route_map[route] = {}
|
||||
self.route_map[route][method.upper()] = {'function': view_func, 'secure': secure}
|
||||
|
||||
def dispatch(self, request):
|
||||
"""
|
||||
Find the appropriate URL and run the view function
|
||||
"""
|
||||
print(request.path)
|
||||
print(self.route_map.items())
|
||||
for route, views in self.route_map.items():
|
||||
if re.match(route, request.path):
|
||||
if request.method.upper() in views:
|
||||
log.debug('Found {method} {url}'.format(method=request.method, url=request.path))
|
||||
view_func = views[request.method.upper()]['function']
|
||||
return _make_response(view_func(request))
|
||||
raise NotFound()
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
"""
|
||||
The actual WSGI application.
|
||||
"""
|
||||
request = Request(environ)
|
||||
try:
|
||||
response = self.dispatch(request)
|
||||
except Exception as e:
|
||||
response = _make_response(_handle_exception(e))
|
||||
return response(environ, start_response)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""
|
||||
Shortcut for wsgi_app.
|
||||
"""
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
@ -26,10 +26,11 @@ with OpenLP. It uses JSON to communicate with the remotes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from waitress import serve
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from waitress import serve
|
||||
|
||||
from openlp.core.api import application
|
||||
from openlp.core.common import RegistryProperties, OpenLPMixin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -51,8 +52,8 @@ class HttpThread(QtCore.QObject):
|
||||
"""
|
||||
Run the thread.
|
||||
"""
|
||||
wsgiapp = object()
|
||||
serve(wsgiapp, host='0.0.0.0', port=4317)
|
||||
print("start")
|
||||
serve(application, host='0.0.0.0', port=4318)
|
||||
|
||||
def stop(self):
|
||||
pass
|
97
openlp/core/api/uiinterfaces.py
Normal file
@ -0,0 +1,97 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from openlp.core.api import Endpoint, register_endpoint
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
stage_endpoint = Endpoint('')
|
||||
|
||||
|
||||
@stage_endpoint.route('$')
|
||||
@stage_endpoint.route('(stage)$')
|
||||
@stage_endpoint.route('(main)$')
|
||||
def file_access(request):
|
||||
"""
|
||||
Get a list of songs`
|
||||
"""
|
||||
#songs = db.query(Song).get()
|
||||
#return {'songs': [dictify(song) for song in songs]}
|
||||
print("AAA")
|
||||
|
||||
|
||||
@stage_endpoint.route('(stage)/(.*)$')
|
||||
def bespoke_file_access(request):
|
||||
"""
|
||||
Allow Stage view to be delivered with custom views.
|
||||
|
||||
:param url_path: base path of the URL. Not used but passed by caller
|
||||
:param file_name: file name with path
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
# log.debug('serve file request {name}'.format(name=file_name))
|
||||
# parts = file_name.split('/')
|
||||
# if len(parts) == 1:
|
||||
# file_name = os.path.join(parts[0], 'stage.html')
|
||||
# elif len(parts) == 3:
|
||||
# file_name = os.path.join(parts[1], parts[2])
|
||||
# path = os.path.normpath(os.path.join(self.config_dir, file_name))
|
||||
# if not path.startswith(self.config_dir):
|
||||
# return self.do_not_found()
|
||||
# return _process_file(path)
|
||||
|
||||
|
||||
@stage_endpoint.route('main/image$')
|
||||
def main_image(request):
|
||||
"""
|
||||
Return the latest display image as a byte stream.
|
||||
"""
|
||||
# result = {
|
||||
# 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
|
||||
# }
|
||||
# self.do_json_header()
|
||||
# return json.dumps({'results': result}).encode()
|
||||
pass
|
||||
|
||||
@stage_endpoint.route(r'^/(\w+)/thumbnails([^/]+)?/(.*)$')
|
||||
def main_image(request):
|
||||
"""
|
||||
Get a list of songs
|
||||
"""
|
||||
# songs = db.query(Song).get()
|
||||
# return {'songs': [dictify(song) for song in songs]}
|
||||
print("AAA")
|
||||
|
||||
|
||||
def _process_file(self, path):
|
||||
"""
|
||||
Common file processing code
|
||||
|
||||
:param path: path to file to be loaded
|
||||
:return: web resource to be loaded
|
||||
"""
|
||||
# content = None
|
||||
# ext, content_type = self.get_content_type(path)
|
||||
# file_handle = None
|
||||
# try:
|
||||
# if ext == '.html':
|
||||
# variables = self.template_vars
|
||||
# content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
|
||||
# else:
|
||||
# file_handle = open(path, 'rb')
|
||||
# log.debug('Opened {path}'.format(path=path))
|
||||
# content = file_handle.read()
|
||||
# except IOError:
|
||||
# log.exception('Failed to open {path}'.format(path=path))
|
||||
# return self.do_not_found()
|
||||
# finally:
|
||||
# if file_handle:
|
||||
# file_handle.close()
|
||||
# self.send_response(200)
|
||||
# self.send_header('Content-type', content_type)
|
||||
# self.end_headers()
|
||||
# return content
|
||||
pass
|
||||
|
||||
register_endpoint(stage_endpoint)
|
@ -29,7 +29,6 @@ import asyncio
|
||||
import websockets
|
||||
import logging
|
||||
import time
|
||||
import sys
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
@ -82,7 +81,7 @@ class OpenWSServer(RegistryProperties, OpenLPMixin):
|
||||
Start the correct server and save the handler
|
||||
"""
|
||||
address = Settings().value(self.settings_section + '/ip address')
|
||||
port = '4318'
|
||||
port = '4317'
|
||||
self.start_websocket_instance(address, port)
|
||||
# If web socket server start listening
|
||||
if hasattr(self, 'ws_server') and self.ws_server:
|
@ -34,21 +34,21 @@ from tempfile import gettempdir
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.api import ApiController
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \
|
||||
check_directory_exists, translate, is_win, is_macosx, add_actions
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
||||
from openlp.core.lib.ui import UiStrings, create_action
|
||||
from openlp.core.lib.api import ApiController
|
||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||
ShortcutListForm, FormattingTagForm, PreviewController
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||
from openlp.core.ui.media import MediaController
|
||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
||||
from openlp.core.ui.projector.manager import ProjectorManager
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -26,6 +26,7 @@ import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.api import ApiTab
|
||||
from openlp.core.common import Registry, RegistryProperties
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||
@ -55,6 +56,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
self.projector_tab = None
|
||||
self.advanced_tab = None
|
||||
self.player_tab = None
|
||||
self.api_tab = None
|
||||
|
||||
def exec(self):
|
||||
"""
|
||||
@ -71,6 +73,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
self.insert_tab(self.advanced_tab)
|
||||
self.insert_tab(self.player_tab)
|
||||
self.insert_tab(self.projector_tab)
|
||||
self.insert_tab(self.api_tab)
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
if plugin.settings_tab:
|
||||
self.insert_tab(plugin.settings_tab, plugin.is_active())
|
||||
@ -153,10 +156,13 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
self.advanced_tab = AdvancedTab(self)
|
||||
# Advanced tab
|
||||
self.player_tab = PlayerTab(self)
|
||||
# Api tab
|
||||
self.api_tab = ApiTab(self)
|
||||
self.general_tab.post_set_up()
|
||||
self.themes_tab.post_set_up()
|
||||
self.advanced_tab.post_set_up()
|
||||
self.player_tab.post_set_up()
|
||||
self.api_tab.post_set_up()
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
if plugin.settings_tab:
|
||||
plugin.settings_tab.post_set_up()
|
||||
|
@ -30,7 +30,7 @@ window.OpenLP = {
|
||||
pollServer: function () {
|
||||
if ("WebSocket" in window) {
|
||||
// Let us open a web socket
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4318/main_poll');
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4317/main_poll');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onmessage = function (evt) {
|
||||
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
|
||||
|
@ -161,7 +161,7 @@ window.OpenLP = {
|
||||
pollServer: function () {
|
||||
if ("WebSocket" in window) {
|
||||
// Let us open a web socket
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4318/poll');
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onmessage = function (evt) {
|
||||
var data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
|
||||
|
@ -149,7 +149,7 @@ window.OpenLP = {
|
||||
pollServer: function () {
|
||||
if ("WebSocket" in window) {
|
||||
// Let us open a web socket
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4318/poll');
|
||||
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onmessage = function (evt) {
|
||||
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
|
||||
|
@ -33,7 +33,7 @@ log = logging.getLogger(__name__)
|
||||
__default_settings__ = {
|
||||
'remotes/twelve hour': True,
|
||||
'remotes/port': 4316,
|
||||
'remotes/websocket port': 4318,
|
||||
'remotes/websocket port': 4317,
|
||||
'remotes/user id': 'openlp',
|
||||
'remotes/password': 'password',
|
||||
'remotes/authentication enabled': False,
|
||||
|
@ -26,7 +26,7 @@ import websockets
|
||||
import random
|
||||
|
||||
async def tester():
|
||||
async with websockets.connect('ws://localhost:4318/poll') as websocket:
|
||||
async with websockets.connect('ws://localhost:4317/poll') as websocket:
|
||||
|
||||
while True:
|
||||
greeting = await websocket.recv()
|
||||
|
@ -26,9 +26,9 @@ import os
|
||||
import urllib.request
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.api.api import OpenLPPoll
|
||||
from openlp.core.common import Settings, Registry
|
||||
from openlp.core.ui import ServiceManager
|
||||
from openlp.core.lib.api import OpenLPPoll
|
||||
from openlp.plugins.remotes.lib.httpserver import HttpRouter
|
||||
from tests.functional import MagicMock, patch, mock_open
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|