2016-06-05 05:49:27 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2019-04-13 13:00:22 +00:00
|
|
|
##########################################################################
|
|
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
|
|
# ---------------------------------------------------------------------- #
|
2020-12-30 21:42:49 +00:00
|
|
|
# Copyright (c) 2008-2021 OpenLP Developers #
|
2019-04-13 13:00:22 +00:00
|
|
|
# ---------------------------------------------------------------------- #
|
|
|
|
# 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, either version 3 of the License, or #
|
|
|
|
# (at your option) any later version. #
|
|
|
|
# #
|
|
|
|
# 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, see <https://www.gnu.org/licenses/>. #
|
|
|
|
##########################################################################
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2021-01-08 15:01:53 +00:00
|
|
|
The :mod:`websockets` module contains the websockets server. This is a server used by remotes to listen for stage
|
|
|
|
changes from within OpenLP. It uses JSON to communicate with the remotes.
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
|
|
|
import asyncio
|
2016-09-30 19:50:48 +00:00
|
|
|
import json
|
2016-06-05 05:49:27 +00:00
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
|
2018-01-05 05:32:12 +00:00
|
|
|
from websockets import serve
|
2016-06-05 05:49:27 +00:00
|
|
|
|
2017-10-23 22:09:57 +00:00
|
|
|
from openlp.core.common.mixins import LogMixin, RegistryProperties
|
|
|
|
from openlp.core.common.registry import Registry
|
2017-12-20 14:17:07 +00:00
|
|
|
from openlp.core.threading import ThreadWorker, run_thread
|
2021-01-08 15:01:53 +00:00
|
|
|
from openlp.core.api.websocketspoll import WebSocketPoller
|
2016-06-05 05:49:27 +00:00
|
|
|
|
2020-06-17 13:49:31 +00:00
|
|
|
USERS = set()
|
2021-01-08 15:01:53 +00:00
|
|
|
poller = WebSocketPoller()
|
2020-06-17 13:49:31 +00:00
|
|
|
|
2018-10-02 04:39:42 +00:00
|
|
|
|
2016-06-05 05:49:27 +00:00
|
|
|
log = logging.getLogger(__name__)
|
2020-08-01 19:28:19 +00:00
|
|
|
# Disable DEBUG logs for the websockets lib
|
|
|
|
ws_logger = logging.getLogger('websockets')
|
|
|
|
ws_logger.setLevel(logging.ERROR)
|
2016-06-05 05:49:27 +00:00
|
|
|
|
|
|
|
|
2020-06-17 13:49:31 +00:00
|
|
|
async def handle_websocket(websocket, path):
|
2018-01-05 05:32:12 +00:00
|
|
|
"""
|
2020-06-17 13:49:31 +00:00
|
|
|
Handle web socket requests and return the state information
|
|
|
|
Check every 0.2 seconds to get the latest position and send if it changed.
|
2018-01-05 05:32:12 +00:00
|
|
|
|
2020-06-17 13:49:31 +00:00
|
|
|
:param websocket: request from client
|
|
|
|
:param path: determines the endpoints supported - Not needed
|
2018-01-05 05:32:12 +00:00
|
|
|
"""
|
2020-06-17 13:49:31 +00:00
|
|
|
log.debug('WebSocket handle_websocket connection')
|
|
|
|
await register(websocket)
|
2021-01-08 15:01:53 +00:00
|
|
|
reply = poller.get_state()
|
2020-06-17 13:49:31 +00:00
|
|
|
if reply:
|
|
|
|
json_reply = json.dumps(reply).encode()
|
|
|
|
await websocket.send(json_reply)
|
2020-08-01 19:28:19 +00:00
|
|
|
while True:
|
|
|
|
try:
|
2020-06-17 13:49:31 +00:00
|
|
|
await notify_users()
|
2020-08-01 19:28:19 +00:00
|
|
|
await asyncio.wait_for(websocket.recv(), 0.2)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
pass
|
|
|
|
except Exception:
|
|
|
|
await unregister(websocket)
|
|
|
|
break
|
2020-06-17 13:49:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def register(websocket):
|
|
|
|
"""
|
|
|
|
Register Clients
|
|
|
|
:param websocket: The client details
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
log.debug('WebSocket handler register')
|
|
|
|
USERS.add(websocket)
|
|
|
|
|
|
|
|
|
|
|
|
async def unregister(websocket):
|
|
|
|
"""
|
|
|
|
Unregister Clients
|
|
|
|
:param websocket: The client details
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
log.debug('WebSocket handler unregister')
|
|
|
|
USERS.remove(websocket)
|
|
|
|
|
|
|
|
|
|
|
|
async def notify_users():
|
|
|
|
"""
|
|
|
|
Dispatch state to all registered users if we have any changes
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if USERS: # asyncio.wait doesn't accept an empty list
|
2021-01-08 15:01:53 +00:00
|
|
|
reply = poller.get_state_if_changed()
|
2020-06-17 13:49:31 +00:00
|
|
|
if reply:
|
|
|
|
json_reply = json.dumps(reply).encode()
|
|
|
|
await asyncio.wait([user.send(json_reply) for user in USERS])
|
2018-01-05 05:32:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class WebSocketWorker(ThreadWorker, RegistryProperties, LogMixin):
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2016-06-13 19:51:46 +00:00
|
|
|
A special Qt thread class to allow the WebSockets server to run at the same time as the UI.
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2017-12-20 14:17:07 +00:00
|
|
|
def start(self):
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2017-12-20 14:17:07 +00:00
|
|
|
Run the worker.
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2020-04-02 18:48:27 +00:00
|
|
|
settings = Registry().get('settings_thread')
|
|
|
|
address = settings.value('api/ip address')
|
|
|
|
port = settings.value('api/websocket port')
|
2018-01-05 05:32:12 +00:00
|
|
|
# Start the event loop
|
2018-01-06 04:41:49 +00:00
|
|
|
self.event_loop = asyncio.new_event_loop()
|
|
|
|
asyncio.set_event_loop(self.event_loop)
|
2018-01-05 05:32:12 +00:00
|
|
|
# Create the websocker server
|
|
|
|
loop = 1
|
|
|
|
self.server = None
|
|
|
|
while not self.server:
|
|
|
|
try:
|
|
|
|
self.server = serve(handle_websocket, address, port)
|
|
|
|
log.debug('WebSocket server started on {addr}:{port}'.format(addr=address, port=port))
|
2018-01-07 17:50:29 +00:00
|
|
|
except Exception:
|
2018-01-05 05:32:12 +00:00
|
|
|
log.exception('Failed to start WebSocket server')
|
|
|
|
loop += 1
|
|
|
|
time.sleep(0.1)
|
|
|
|
if not self.server and loop > 3:
|
|
|
|
log.error('Unable to start WebSocket server {addr}:{port}, giving up'.format(addr=address, port=port))
|
|
|
|
if self.server:
|
|
|
|
# If the websocket server exists, start listening
|
2018-01-06 04:41:49 +00:00
|
|
|
self.event_loop.run_until_complete(self.server)
|
2020-08-01 19:28:19 +00:00
|
|
|
try:
|
|
|
|
self.event_loop.run_forever()
|
|
|
|
finally:
|
|
|
|
self.event_loop.close()
|
2017-12-20 14:17:07 +00:00
|
|
|
self.quit.emit()
|
2016-06-05 05:49:27 +00:00
|
|
|
|
|
|
|
def stop(self):
|
2017-12-20 14:17:07 +00:00
|
|
|
"""
|
|
|
|
Stop the websocket server
|
|
|
|
"""
|
2020-08-01 19:28:19 +00:00
|
|
|
self.event_loop.call_soon_threadsafe(self.event_loop.stop)
|
2016-06-05 05:49:27 +00:00
|
|
|
|
|
|
|
|
2017-10-23 22:09:57 +00:00
|
|
|
class WebSocketServer(RegistryProperties, LogMixin):
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
|
|
|
Wrapper round a server instance
|
|
|
|
"""
|
2016-06-06 19:56:38 +00:00
|
|
|
def __init__(self):
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2016-06-16 20:50:01 +00:00
|
|
|
Initialise and start the WebSockets server
|
2016-06-05 05:49:27 +00:00
|
|
|
"""
|
2016-06-14 20:56:50 +00:00
|
|
|
super(WebSocketServer, self).__init__()
|
2018-01-07 04:36:45 +00:00
|
|
|
if not Registry().get_flag('no_web_server'):
|
2018-01-05 05:32:12 +00:00
|
|
|
worker = WebSocketWorker()
|
2017-12-20 14:17:07 +00:00
|
|
|
run_thread(worker, 'websocket_server')
|