move remote to api and add waitress

This commit is contained in:
Tim Bentley 2016-06-05 22:16:13 +01:00
parent fad8b35660
commit 32fad35f79
26 changed files with 110 additions and 83 deletions

View File

@ -178,29 +178,3 @@ class Registry(object):
""" """
if key in self.working_flags: if key in self.working_flags:
del self.working_flags[key] del self.working_flags[key]
def remote_api(self, path, function, secure=False):
"""
Sets a working_flag based on the key passed in.
:param path: The working_flag to be created this is usually a major class like "renderer" or "main_window" .
:param function: The data to be saved.
:param secure: The data to be saved.
"""
self.remote_apis[path] = {'function': function, 'secure': secure}
def remote_execute(self, url_path):
"""
Execute all the handlers associated with the event and return an array of results.
:param url_path: The url path to be found
"""
for url, funcs in self.remote_apis.items():
a = url
match = re.match(url, url_path)
if match:
log.debug('Route "{route}" matched "{path}"'.format(route=url_path, path=url_path))
args = []
for param in match.groups():
args.append(param)
return funcs, args

View File

@ -22,6 +22,7 @@
from .poll import OpenLPPoll from .poll import OpenLPPoll
from .wsserver import OpenWSServer from .wsserver import OpenWSServer
from .httpserver import OpenLPHttpServer
from .remotecontroller import RemoteController from .remotecontroller import RemoteController
__all__ = ['OpenLPPoll', 'RemoteController'] __all__ = ['OpenLPPoll', 'RemoteController', 'OpenLPHttpServer']

View File

@ -0,0 +1,72 @@
# -*- 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 #
###############################################################################
"""
The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact
with OpenLP. It uses JSON to communicate with the remotes.
"""
import logging
from waitress import serve
from PyQt5 import QtCore
from openlp.core.common import RegistryProperties, OpenLPMixin
log = logging.getLogger(__name__)
class HttpThread(QtCore.QThread):
"""
A special Qt thread class to allow the HTTP server to run at the same time as the UI.
"""
def __init__(self, server):
"""
Constructor for the thread class.
:param server: The http server class.
"""
super(HttpThread, self).__init__(None)
self.http_server = server
def run(self):
"""
Run the thread.
"""
wsgiapp = object()
serve(wsgiapp, host='0.0.0.0', port=4317)
def stop(self):
self.http_server.stop = True
class OpenLPHttpServer(RegistryProperties, OpenLPMixin):
"""
Wrapper round a server instance
"""
def __init__(self, secure=False):
"""
Initialise the http server, and start the server of the correct type http / https
"""
super(OpenLPHttpServer, self).__init__()
self.http_thread = HttpThread(self)
self.http_thread.start()

View File

@ -22,7 +22,7 @@
import logging import logging
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties
from openlp.core.lib.remote import OpenWSServer, OpenLPPoll from openlp.core.lib.api import OpenWSServer, OpenLPPoll, OpenLPHttpServer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -54,3 +54,4 @@ class RemoteController(RegistryMixin, OpenLPMixin, RegistryProperties):
self.poll = OpenLPPoll() self.poll = OpenLPPoll()
Registry().register('OpenLPPoll', self.poll) Registry().register('OpenLPPoll', self.poll)
self.wsserver = OpenWSServer() self.wsserver = OpenWSServer()
self.httpserver = OpenLPHttpServer()

View File

@ -453,7 +453,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
:param service_item: The service Item to be processed :param service_item: The service Item to be processed
:param item: The database item to be used to build the service item :param item: The database item to be used to build the service item
:param xml_version: :param xml_version:
:param remote: Was this remote triggered (False) :param remote: Was this api triggered (False)
:param context: The service context :param context: The service context
""" """
raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin') raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin')
@ -580,7 +580,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
:param item: Item to be processed :param item: Item to be processed
:param replace: Replace the existing item :param replace: Replace the existing item
:param remote: Triggered from remote :param remote: Triggered from api
:param position: Position to place item :param position: Position to place item
""" """
service_item = self.build_service_item(item, True, remote=remote, context=ServiceItemContext.Service) service_item = self.build_service_item(item, True, remote=remote, context=ServiceItemContext.Service)
@ -607,7 +607,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
self.generate_slide_data(service_item) self.generate_slide_data(service_item)
self.service_manager.add_service_item(service_item, replace=True) self.service_manager.add_service_item(service_item, replace=True)
else: else:
# Turn off the remote edit update message indicator # Turn off the api edit update message indicator
QtWidgets.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'), QtWidgets.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'),
translate('OpenLP.MediaManagerItem', translate('OpenLP.MediaManagerItem',
'You must select a {title} ' 'You must select a {title} '

View File

@ -229,7 +229,7 @@ ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants', E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'The connection was refused by the peer (or timed out)'), 'The connection was refused by the peer (or timed out)'),
E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants', E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
'The remote host closed the connection'), 'The api host closed the connection'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'), E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants', E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
'The socket operation failed because the application ' 'The socket operation failed because the application '

View File

@ -396,7 +396,7 @@ class PJLink1(QTcpSocket):
return return
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
if data.upper().startswith('PJLINK'): if data.upper().startswith('PJLINK'):
# Reconnected from remote host disconnect ? # Reconnected from api host disconnect ?
self.check_login(data) self.check_login(data)
self.send_busy = False self.send_busy = False
self.projectorReceivedData.emit() self.projectorReceivedData.emit()

View File

@ -286,7 +286,7 @@ class ServiceItem(RegistryProperties):
:param path: The directory in which the image file is located. :param path: The directory in which the image file is located.
:param title: A title for the slide in the service item. :param title: A title for the slide in the service item.
:param background: :param background:
:param thumbnail: Optional alternative thumbnail, used for remote thumbnails. :param thumbnail: Optional alternative thumbnail, used for api thumbnails.
""" """
if background: if background:
self.image_border = background self.image_border = background

View File

@ -40,7 +40,7 @@ from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.versionchecker import get_application_version 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 import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
from openlp.core.lib.ui import UiStrings, create_action from openlp.core.lib.ui import UiStrings, create_action
from openlp.core.lib.remote import RemoteController from openlp.core.lib.api import RemoteController
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \ from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
ShortcutListForm, FormattingTagForm, PreviewController ShortcutListForm, FormattingTagForm, PreviewController
from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimeform import FirstTimeForm

View File

@ -344,7 +344,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
""" """
Setter for property "modified". Sets whether or not the current service has been modified. Setter for property "modified". Sets whether or not the current service has been modified.
:param modified: Indicates if the service has new or removed items. Used to trigger a remote update. :param modified: Indicates if the service has new or removed items. Used to trigger a api update.
""" """
if modified: if modified:
self.service_id += 1 self.service_id += 1
@ -1079,7 +1079,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
def on_set_item(self, message, field=None): def on_set_item(self, message, field=None):
""" """
Called by a signal to select a specific item and make it live usually from remote. Called by a signal to select a specific item and make it live usually from api.
:param field: :param field:
:param message: The data passed in from a remove message :param message: The data passed in from a remove message
@ -1523,7 +1523,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
def remote_edit(self, field=None): def remote_edit(self, field=None):
""" """
Triggers a remote edit to a plugin to allow item to be edited. Triggers a api edit to a plugin to allow item to be edited.
:param field: :param field:
""" """
item = self.find_service_item()[0] item = self.find_service_item()[0]

View File

@ -531,7 +531,7 @@ class SlideController(DisplayController, RegistryProperties):
def toggle_display(self, action): def toggle_display(self, action):
""" """
Toggle the display settings triggered from remote messages. Toggle the display settings triggered from api messages.
:param action: The blank action to be processed. :param action: The blank action to be processed.
""" """
@ -797,7 +797,7 @@ class SlideController(DisplayController, RegistryProperties):
def replace_service_manager_item(self, item): def replace_service_manager_item(self, item):
""" """
Replacement item following a remote edit Replacement item following a api edit
:param item: The current service item :param item: The current service item
""" """
@ -841,7 +841,7 @@ class SlideController(DisplayController, RegistryProperties):
""" """
self.on_stop_loop() self.on_stop_loop()
old_item = self.service_item old_item = self.service_item
# rest to allow the remote pick up verse 1 if large imaged # rest to allow the api pick up verse 1 if large imaged
self.selected_row = 0 self.selected_row = 0
# take a copy not a link to the servicemanager copy. # take a copy not a link to the servicemanager copy.
self.service_item = copy.copy(service_item) self.service_item = copy.copy(service_item)
@ -931,7 +931,7 @@ class SlideController(DisplayController, RegistryProperties):
""" """
Go to the requested slide Go to the requested slide
:param message: remote message to be processed. :param message: api message to be processed.
""" """
index = int(message[0]) index = int(message[0])
if not self.service_item: if not self.service_item:

View File

@ -798,7 +798,7 @@ class BibleMediaItem(MediaManagerItem):
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used) :param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from api
:param context: Why is it being generated :param context: Why is it being generated
""" """
log.debug('generating slide data') log.debug('generating slide data')

View File

@ -129,7 +129,7 @@ class CustomMediaItem(MediaManagerItem):
self.list_view.setCurrentItem(custom_name) self.list_view.setCurrentItem(custom_name)
self.auto_select_id = -1 self.auto_select_id = -1
# Called to redisplay the custom list screen edith from a search # Called to redisplay the custom list screen edith from a search
# or from the exit of the Custom edit dialog. If remote editing is # or from the exit of the Custom edit dialog. If api editing is
# active trigger it and clean up so it will not update again. # active trigger it and clean up so it will not update again.
self.check_search_result() self.check_search_result()

View File

@ -543,7 +543,7 @@ class ImageMediaItem(MediaManagerItem):
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used) :param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from api
:param context: Why is it being generated :param context: Why is it being generated
""" """
background = QtGui.QColor(Settings().value(self.settings_section + '/background color')) background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))

View File

@ -242,7 +242,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used) :param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from api
:param context: Why is it being generated :param context: Why is it being generated
""" """
if item is None: if item is None:

View File

@ -266,7 +266,7 @@ class PresentationMediaItem(MediaManagerItem):
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used) :param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from api
:param context: Why is it being generated :param context: Why is it being generated
""" """
if item: if item:

View File

@ -7158,7 +7158,7 @@ jQuery.fn.extend({
var self = this; var self = this;
// Request the remote document // Request the api document
jQuery.ajax({ jQuery.ajax({
url: url, url: url,
type: type, type: type,

View File

@ -151,7 +151,7 @@ class RemoteTab(SettingsTab):
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live 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.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab', self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.')) 'Show thumbnails of non-text slides in api and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App')) self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.android_qr_description_label.setText( self.android_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', translate('RemotePlugin.RemoteTab',
@ -252,10 +252,10 @@ class RemoteTab(SettingsTab):
Generate icon for main window Generate icon for main window
""" """
self.remote_server_icon.hide() self.remote_server_icon.hide()
icon = QtGui.QImage(':/remote/network_server.png') icon = QtGui.QImage(':/api/network_server.png')
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
if Settings().value(self.settings_section + '/authentication enabled'): if Settings().value(self.settings_section + '/authentication enabled'):
overlay = QtGui.QImage(':/remote/network_auth.png') overlay = QtGui.QImage(':/api/network_auth.png')
overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
painter = QtGui.QPainter(icon) painter = QtGui.QPainter(icon)
painter.drawImage(20, 0, overlay) painter.drawImage(20, 0, overlay)

View File

@ -92,9 +92,9 @@ class RemotesPlugin(Plugin, OpenLPMixin):
Information about this plugin Information about this plugin
""" """
about_text = translate('RemotePlugin', '<strong>Remote Plugin</strong>' about_text = translate('RemotePlugin', '<strong>Remote Plugin</strong>'
'<br />The remote plugin provides the ability to send messages to ' '<br />The api plugin provides the ability to send messages to '
'a running version of OpenLP on a different computer via a web ' 'a running version of OpenLP on a different computer via a web '
'browser or through the remote API.') 'browser or through the api API.')
return about_text return about_text
def set_plugin_text_strings(self): def set_plugin_text_strings(self):
@ -115,7 +115,7 @@ class RemotesPlugin(Plugin, OpenLPMixin):
""" """
Called when Config is changed to requests a restart with the server on new address or port Called when Config is changed to requests a restart with the server on new address or port
""" """
log.debug('remote config changed') log.debug('api config changed')
QtWidgets.QMessageBox.information(self.main_window, QtWidgets.QMessageBox.information(self.main_window,
translate('RemotePlugin', 'Server Config Change'), translate('RemotePlugin', 'Server Config Change'),
translate('RemotePlugin', translate('RemotePlugin',

View File

@ -242,11 +242,11 @@ class SongMediaItem(MediaManagerItem):
def on_song_list_load(self): def on_song_list_load(self):
""" """
Handle the exit from the edit dialog and trigger remote updates of songs Handle the exit from the edit dialog and trigger api updates of songs
""" """
log.debug('on_song_list_load - start') log.debug('on_song_list_load - start')
# Called to redisplay the song list screen edit from a search or from the exit of the Song edit dialog. If # Called to redisplay the song list screen edit from a search or from the exit of the Song edit dialog. If
# remote editing is active Trigger it and clean up so it will not update again. Push edits to the service # api editing is active Trigger it and clean up so it will not update again. Push edits to the service
# manager to update items # manager to update items
if self.edit_item and self.update_service_on_edit and not self.remote_triggered: if self.edit_item and self.update_service_on_edit and not self.remote_triggered:
item = self.build_service_item(self.edit_item) item = self.build_service_item(self.edit_item)
@ -555,7 +555,7 @@ class SongMediaItem(MediaManagerItem):
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used) :param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from api
:param context: Why is it being generated :param context: Why is it being generated
""" """
log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item, log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item,

View File

@ -171,7 +171,7 @@ def get_repo_name():
# Determine the branch's name # Determine the branch's name
repo_name = '' repo_name = ''
for line in output_list: for line in output_list:
# Check if it is remote branch. # Check if it is api branch.
if 'push branch' in line: if 'push branch' in line:
match = re.match(REPO_REGEX, line) match = re.match(REPO_REGEX, line)
if match: if match:

View File

@ -149,24 +149,3 @@ class TestRegistry(TestCase):
def dummy_function_2(self): def dummy_function_2(self):
return "function_2" return "function_2"
def test_registry_remote_apis(self):
"""
Test the registry working flags creation and its usage
"""
# GIVEN: A new registry
Registry.create()
# WHEN: I register a API to function
func = MagicMock()
func1 = MagicMock()
Registry().remote_api(r'^/api/poll$', func)
Registry().remote_api(r'^/main/poll$', func1, True)
# THEN: When I look to a function
call_structure, args = Registry().remote_execute('/api/poll')
self.assertEqual(call_structure['function'], func, 'Calling function should match')
self.assertEqual(call_structure['secure'], False, 'Calling function should not require security')
call_structure, args = Registry().remote_execute('/main/poll')
self.assertEqual(call_structure['function'], func1, 'Calling function should match')
self.assertEqual(call_structure['secure'], True, 'Calling function should not require security')

View File

@ -28,7 +28,7 @@ from unittest import TestCase
from openlp.core.common import Settings, Registry from openlp.core.common import Settings, Registry
from openlp.core.ui import ServiceManager from openlp.core.ui import ServiceManager
from openlp.core.lib.remote import OpenLPPoll from openlp.core.lib.api import OpenLPPoll
from openlp.plugins.remotes.lib.httpserver import HttpRouter from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock, patch, mock_open from tests.functional import MagicMock, patch, mock_open
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -327,7 +327,7 @@ class TestRouter(TestCase, TestMixin):
def remote_next_test(self): def remote_next_test(self):
""" """
Test service manager receives remote next click properly (bug 1407445) Test service manager receives api next click properly (bug 1407445)
""" """
# GIVEN: initial setup and mocks # GIVEN: initial setup and mocks
self.router.routes = [(r'^/api/service/(.*)$', {'function': self.router.service, 'secure': False})] self.router.routes = [(r'^/api/service/(.*)$', {'function': self.router.service, 'secure': False})]
@ -348,7 +348,7 @@ class TestRouter(TestCase, TestMixin):
def remote_previous_test(self): def remote_previous_test(self):
""" """
Test service manager receives remote previous click properly (bug 1407445) Test service manager receives api previous click properly (bug 1407445)
""" """
# GIVEN: initial setup and mocks # GIVEN: initial setup and mocks
self.router.routes = [(r'^/api/service/(.*)$', {'function': self.router.service, 'secure': False})] self.router.routes = [(r'^/api/service/(.*)$', {'function': self.router.service, 'secure': False})]

View File

@ -502,7 +502,7 @@ class TestMediaItem(TestCase, TestMixin):
def build_remote_search_test(self): def build_remote_search_test(self):
""" """
Test results for the remote search api Test results for the api search api
""" """
# GIVEN: A Song and a search a JSON array should be returned. # GIVEN: A Song and a search a JSON array should be returned.
mock_song = MagicMock() mock_song = MagicMock()