From 8ce073d83f6a78c5982f3106f560d1d460de205c Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 5 Jun 2016 06:49:27 +0100 Subject: [PATCH] Move Websockets to core from plugin --- openlp/core/lib/remote/__init__.py | 39 +++++ .../remotes/lib => core/lib/remote}/poll.py | 0 openlp/core/lib/remote/remotecontroller.py | 53 +++++++ openlp/core/lib/remote/wsserver.py | 149 ++++++++++++++++++ openlp/core/ui/mainwindow.py | 3 +- openlp/core/ui/media/mediacontroller.py | 1 - openlp/plugins/remotes/lib/__init__.py | 6 +- openlp/plugins/remotes/lib/httpserver.py | 80 +--------- openlp/plugins/remotes/remoteplugin.py | 1 - 9 files changed, 250 insertions(+), 82 deletions(-) create mode 100644 openlp/core/lib/remote/__init__.py rename openlp/{plugins/remotes/lib => core/lib/remote}/poll.py (100%) create mode 100644 openlp/core/lib/remote/remotecontroller.py create mode 100644 openlp/core/lib/remote/wsserver.py diff --git a/openlp/core/lib/remote/__init__.py b/openlp/core/lib/remote/__init__.py new file mode 100644 index 000000000..273e34677 --- /dev/null +++ b/openlp/core/lib/remote/__init__.py @@ -0,0 +1,39 @@ +# -*- 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 # +############################################################################### + +import os +from openlp.core.common import AppLocation +from .poll import OpenLPPoll +from .wsserver import OpenWSServer +from .remotecontroller import RemoteController + + +def get_cert_file(file_type): + """ + Helper method to get certificate files + :param file_type: file suffix key, cert or pem + :return: full path to file + """ + local_data = AppLocation.get_directory(AppLocation.DataDir) + return os.path.join(local_data, 'remotes', 'openlp.{type}'.format(type=file_type)) + +__all__ = ['OpenLPPoll', 'RemoteController', 'get_cert_file'] diff --git a/openlp/plugins/remotes/lib/poll.py b/openlp/core/lib/remote/poll.py similarity index 100% rename from openlp/plugins/remotes/lib/poll.py rename to openlp/core/lib/remote/poll.py diff --git a/openlp/core/lib/remote/remotecontroller.py b/openlp/core/lib/remote/remotecontroller.py new file mode 100644 index 000000000..ff5f64bc2 --- /dev/null +++ b/openlp/core/lib/remote/remotecontroller.py @@ -0,0 +1,53 @@ +# -*- 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 openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties +from openlp.core.lib.remote import OpenWSServer + + +class RemoteController(RegistryMixin, OpenLPMixin, RegistryProperties): + """ + The implementation of the Media Controller. The Media Controller adds an own class for every Player. + Currently these are QtWebkit, Phonon and Vlc. display_controllers are an array of controllers keyed on the + slidecontroller or plugin which built them. + + ControllerType is the class containing the key values. + + media_players are an array of media players keyed on player name. + + current_media_players is an array of player instances keyed on ControllerType. + + """ + def __init__(self, parent=None): + """ + Constructor + """ + super(RemoteController, self).__init__(parent) + self.media_players = {} + self.display_controllers = {} + self.current_media_players = {} + # Registry().register_function('playbackPlay', self.media_play_msg) + + def bootstrap_post_set_up(self): + """ + process the bootstrap post setup request + """ + self.wsserver = OpenWSServer() diff --git a/openlp/core/lib/remote/wsserver.py b/openlp/core/lib/remote/wsserver.py new file mode 100644 index 000000000..07d804feb --- /dev/null +++ b/openlp/core/lib/remote/wsserver.py @@ -0,0 +1,149 @@ +# -*- 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 asyncio +import websockets +import logging +import time + +from PyQt5 import QtCore + +from openlp.core.common import Settings, RegistryProperties, OpenLPMixin +from openlp.core.lib.remote import OpenLPPoll + + +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. + """ + self.http_server.start_server() + + def stop(self): + self.http_server.stop = True + + +class OpenWSServer(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(OpenWSServer, self).__init__() + self.settings_section = 'remotes' + self.secure = secure + self.http_thread = HttpThread(self) + self.http_thread.start() + + def start_server(self): + """ + Start the correct server and save the handler + """ + address = Settings().value(self.settings_section + '/ip address') + is_secure = Settings().value(self.settings_section + '/https enabled') + port = '4318' + self.start_websocket_instance(address, port) + # If web socket server start listening + if hasattr(self, 'ws_server') and self.ws_server: + event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(event_loop) + event_loop.run_until_complete(self.ws_server) + event_loop.run_forever() + else: + log.debug('Failed to start ws server on port {port}'.format(port=port)) + + def start_websocket_instance(self, address, port): + """ + Start the server + + :param address: The server address + :param port: The run port + """ + loop = 1 + while loop < 4: + try: + self.ws_server = websockets.serve(self.handle_websocket, address, port) + log.debug("Web Socket Server started for class {address} {port}".format(address=address, port=port)) + break + except Exception as e: + log.error('Failed to start ws server {why}'.format(why=e)) + loop += 1 + time.sleep(0.1) + + @staticmethod + async def handle_websocket(request, path): + """ + Handle web socket requests and return the poll information. + Check ever 0.5 seconds to get the latest postion and send if changed. + Only gets triggered when 1st client attaches + :param request: request from client + :param path: not used - future to register for a different end point + :return: + """ + log.debug("web socket handler registered with client") + previous_poll = None + previous_main_poll = None + if path == '/poll': + while True: + current_poll = OpenLPPoll().poll() + if current_poll != previous_poll: + await request.send(current_poll) + previous_poll = current_poll + await asyncio.sleep(0.2) + elif path == '/main_poll': + while True: + main_poll = OpenLPPoll().main_poll() + if main_poll != previous_main_poll: + await request.send(main_poll) + previous_main_poll = main_poll + await asyncio.sleep(0.2) + + def stop_server(self): + """ + Stop the server + """ + if self.http_thread.isRunning(): + self.http_thread.stop() + self.httpd = None + log.debug('Stopped the server.') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index ccd12727c..ba75f0171 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -40,13 +40,13 @@ 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.remote import RemoteController 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.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm from openlp.core.ui.projector.manager import ProjectorManager -from openlp.core.ui.lib.toolbar import OpenLPToolbar from openlp.core.ui.lib.dockwidget import OpenLPDockWidget from openlp.core.ui.lib.mediadockmanager import MediaDockManager @@ -529,6 +529,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): Settings().set_up_default_values() self.about_form = AboutForm(self) MediaController() + RemoteController() SettingsForm(self) self.formatting_tag_form = FormattingTagForm(self) self.shortcut_form = ShortcutListForm(self) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 021ea5281..887adcb30 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -38,7 +38,6 @@ from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\ parse_optical_path from openlp.core.ui.lib.toolbar import OpenLPToolbar -from openlp.core.ui.lib.dockwidget import OpenLPDockWidget log = logging.getLogger(__name__) diff --git a/openlp/plugins/remotes/lib/__init__.py b/openlp/plugins/remotes/lib/__init__.py index 53e7d70e3..18c781662 100644 --- a/openlp/plugins/remotes/lib/__init__.py +++ b/openlp/plugins/remotes/lib/__init__.py @@ -20,10 +20,8 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from .poll import OpenLPPoll -from .remotetab import RemoteTab from .httprouter import HttpRouter from .httpserver import OpenLPServer +from .remotetab import RemoteTab - -__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter', 'OpenLPPoll'] +__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter'] diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index efc9d3e43..0e5829a51 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -25,19 +25,17 @@ The :mod:`http` module contains the API web server. This is a lightweight web se with OpenLP. It uses JSON to communicate with the remotes. """ -import asyncio import ssl import socket -import websockets -import os import logging import time from PyQt5 import QtCore -from openlp.core.common import AppLocation, Settings, RegistryProperties, OpenLPMixin +from openlp.core.common import Settings, RegistryProperties, OpenLPMixin -from openlp.plugins.remotes.lib import HttpRouter, OpenLPPoll +from openlp.plugins.remotes.lib import HttpRouter +from openlp.core.lib.remote import get_cert_file from socketserver import BaseServer, ThreadingMixIn from http.server import BaseHTTPRequestHandler, HTTPServer @@ -97,14 +95,13 @@ class OpenLPServer(RegistryProperties, OpenLPMixin): """ Wrapper round a server instance """ - def __init__(self, websocket=False, secure=False): + def __init__(self, secure=False): """ Initialise the http server, and start the server of the correct type http / https """ super(OpenLPServer, self).__init__() self.settings_section = 'remotes' self.secure = secure - self.websocket = websocket self.http_thread = HttpThread(self) self.http_thread.start() @@ -124,23 +121,12 @@ class OpenLPServer(RegistryProperties, OpenLPMixin): if self.secure: self.start_server_instance(address, port, HTTPSServer) else: - if self.websocket: - self.start_websocket_instance(address, port) - else: - self.start_server_instance(address, port, ThreadingHTTPServer) + self.start_server_instance(address, port, ThreadingHTTPServer) # If HTTP server start listening if hasattr(self, 'httpd') and self.httpd: self.httpd.serve_forever() else: log.debug('Failed to start http server on port {port}'.format(port=port)) - # If web socket server start listening - if hasattr(self, 'ws_server') and self.ws_server: - event_loop = asyncio.new_event_loop() - asyncio.set_event_loop(event_loop) - event_loop.run_until_complete(self.ws_server) - event_loop.run_forever() - else: - log.debug('Failed to start ws server on port {port}'.format(port=port)) def start_server_instance(self, address, port, server_class): """ @@ -168,52 +154,6 @@ class OpenLPServer(RegistryProperties, OpenLPMixin): loop += 1 time.sleep(0.1) - def start_websocket_instance(self, address, port): - """ - Start the server - - :param address: The server address - :param port: The run port - """ - loop = 1 - while loop < 4: - try: - self.ws_server = websockets.serve(self.handle_websocket, address, '4318') - log.debug("Web Socket Server started for class {address} {port:d}".format(address=address, port=port)) - break - except Exception as e: - log.error('Failed to start ws server {why}'.format(why=e)) - loop += 1 - time.sleep(0.1) - - @staticmethod - async def handle_websocket(request, path): - """ - Handle web socket requests and return the poll information. - Check ever 0.5 seconds to get the latest postion and send if changed. - Only gets triggered when 1st client attaches - :param request: request from client - :param path: not used - future to register for a different end point - :return: - """ - log.debug("web socket handler registered with client") - previous_poll = None - previous_main_poll = None - if path == '/poll': - while True: - current_poll = OpenLPPoll().poll() - if current_poll != previous_poll: - await request.send(current_poll) - previous_poll = current_poll - await asyncio.sleep(0.2) - elif path == '/main_poll': - while True: - main_poll = OpenLPPoll().main_poll() - if main_poll != previous_main_poll: - await request.send(main_poll) - previous_main_poll = main_poll - await asyncio.sleep(0.2) - def stop_server(self): """ Stop the server @@ -237,13 +177,3 @@ class HTTPSServer(HTTPServer): server_side=True) self.server_bind() self.server_activate() - - -def get_cert_file(file_type): - """ - Helper method to get certificate files - :param file_type: file suffix key, cert or pem - :return: full path to file - """ - local_data = AppLocation.get_directory(AppLocation.DataDir) - return os.path.join(local_data, 'remotes', 'openlp.{type}'.format(type=file_type)) diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index de8defff7..d6e498362 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -63,7 +63,6 @@ class RemotesPlugin(Plugin, OpenLPMixin): """ super(RemotesPlugin, self).initialise() self.server = OpenLPServer() - self.server_ws = OpenLPServer(websocket=True) self.server_secure = OpenLPServer(secure=True) if not hasattr(self, 'remote_server_icon'): self.remote_server_icon = QtWidgets.QLabel(self.main_window.status_bar)