Upgrade and cleanup WebSocket code

This commit is contained in:
Tim 2020-06-17 14:49:31 +01:00
parent af1dd70589
commit e6fa02a75e
No known key found for this signature in database
GPG Key ID: 3D454289AF831A6D
5 changed files with 64 additions and 134 deletions

View File

@ -18,10 +18,6 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
import json
from openlp.core.common.registry import Registry
from openlp.core.common.httputils import get_web_page
from openlp.core.common.mixins import RegistryProperties
@ -34,13 +30,7 @@ class Poller(RegistryProperties):
Constructor for the poll builder class.
"""
super(Poller, self).__init__()
self.live_cache = None
self.stage_cache = None
self.chords_cache = None
settings = Registry().get('settings_thread')
self.address = settings.value('api/ip address')
self.ws_port = settings.value('api/websocket port')
self.http_port = settings.value('api/port')
self.previous = {}
def raw_poll(self):
return {
@ -53,81 +43,22 @@ class Poller(RegistryProperties):
'display': self.live_controller.desktop_screen.isChecked(),
'version': 3,
'isSecure': self.settings.value('api/authentication enabled'),
'isAuthorised': False,
'chordNotation': self.settings.value('songs/chord notation'),
'isStageActive': self.is_stage_active(),
'isLiveActive': self.is_live_active(),
'isChordsActive': self.is_chords_active()
'chordNotation': self.settings.value('songs/chord notation')
}
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
Poll OpenLP to determine current state if it has changed.
"""
current = self.raw_poll()
if self.previous != current:
self.previous = current
return {'results': current}
else:
return None
def poll_first_time(self):
"""
Poll OpenLP to determine the current state.
"""
return {'results': self.raw_poll()}
def main_poll(self):
"""
Poll OpenLP to determine the current slide count.
"""
result = {
'slide_count': self.live_controller.slide_count
}
return json.dumps({'results': result}).encode()
def reset_cache(self):
"""
Reset the caches as the web has changed
:return:
"""
self.stage_cache = None
self.live_cache = None
self.chords.cache = None
def is_stage_active(self):
"""
Is stage active - call it and see but only once
:return: if stage is active or not
"""
if self.stage_cache is None:
try:
page = get_web_page(f'http://{self.address}:{self.http_port}/stage')
except Exception:
page = None
if page:
self.stage_cache = True
else:
self.stage_cache = False
return self.stage_cache
def is_live_active(self):
"""
Is main active - call it and see but only once
:return: if live is active or not
"""
if self.live_cache is None:
try:
page = get_web_page(f'http://{self.address}:{self.http_port}/main')
except Exception:
page = None
if page:
self.live_cache = True
else:
self.live_cache = False
return self.live_cache
def is_chords_active(self):
"""
Is chords active - call it and see but only once
:return: if live is active or not
"""
if self.chords_cache is None:
try:
page = get_web_page(f'http://{self.address}:{self.http_port}/chords')
except Exception:
page = None
if page:
self.chords_cache = True
else:
self.chords_cache = False
return self.chords_cache

View File

@ -33,38 +33,64 @@ from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.threading import ThreadWorker, run_thread
USERS = set()
log = logging.getLogger(__name__)
async def handle_websocket(request, path):
async def handle_websocket(websocket, path):
"""
Handle web socket requests and return the poll information
Handle web socket requests and return the state information
Check every 0.2 seconds to get the latest position and send if it changed.
Check every 0.2 seconds to get the latest position and send if it changed. This only gets triggered when the first
client connects.
:param request: request from client
:param path: determines the endpoints supported
:param websocket: request from client
:param path: determines the endpoints supported - Not needed
"""
log.debug('WebSocket handler registered with client')
previous_poll = None
previous_main_poll = None
poller = Registry().get('poller')
if path == '/state':
log.debug('WebSocket handle_websocket connection')
await register(websocket)
reply = Registry().get('poller').poll_first_time()
if reply:
json_reply = json.dumps(reply).encode()
await websocket.send(json_reply)
try:
while True:
current_poll = poller.poll()
if current_poll != previous_poll:
await request.send(json.dumps(current_poll).encode())
previous_poll = current_poll
await asyncio.sleep(0.2)
elif path == '/live_changed':
while True:
main_poll = poller.main_poll()
if main_poll != previous_main_poll:
await request.send(main_poll)
previous_main_poll = main_poll
await notify_users()
await asyncio.sleep(0.2)
finally:
await unregister(websocket)
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
reply = Registry().get('poller').poll()
if reply:
json_reply = json.dumps(reply).encode()
await asyncio.wait([user.send(json_reply) for user in USERS])
class WebSocketWorker(ThreadWorker, RegistryProperties, LogMixin):

View File

@ -1037,11 +1037,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
event.ignore()
else:
event.accept()
# Included here as it is only needed to help force the logging rollover not for general logging use.
# Rollover require as when WebSocket exits having been used it will destroy the application log on exit.
import logging
log_man = logging.getLogger()
log_man.handlers[0].doRollover()
if event.isAccepted():
# Wait for all the threads to complete
self._wait_for_threads()

View File

@ -202,7 +202,6 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.update_slide_limits()
self.panel = QtWidgets.QWidget(self.main_window.control_splitter)
self.slide_list = {}
self.slide_count = 0
self.controller_width = -1
# Layout for holding panel
self.panel_layout = QtWidgets.QVBoxLayout(self.panel)
@ -1201,7 +1200,6 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.preview_display.set_single_image('#000', image_path)
else:
self.preview_display.go_to_slide(self.selected_row)
self.slide_count += 1
def display_maindisplay(self):
"""

View File

@ -67,19 +67,6 @@ def test_serverstart_not_required(mocked_run_thread, MockWebSocketWorker, regist
assert MockWebSocketWorker.call_count == 0, 'The http thread should not have been called'
def test_main_poll(poller):
"""
Test the main_poll function returns the correct JSON
"""
# WHEN: the live controller has 5 slides
mocked_live_controller = MagicMock()
mocked_live_controller.slide_count = 5
Registry().register('live_controller', mocked_live_controller)
# THEN: the live json should be generated
main_json = poller.main_poll()
assert b'{"results": {"slide_count": 5}}' == main_json, 'The return value should match the defined json'
def test_poll(poller, settings):
"""
Test the poll function returns the correct JSON
@ -97,19 +84,12 @@ def test_poll(poller, settings):
Registry().register('live_controller', mocked_live_controller)
Registry().register('service_manager', mocked_service_manager)
# WHEN: The poller polls
with patch.object(poller, 'is_stage_active') as mocked_is_stage_active, \
patch.object(poller, 'is_live_active') as mocked_is_live_active, \
patch.object(poller, 'is_chords_active') as mocked_is_chords_active:
mocked_is_stage_active.return_value = True
mocked_is_live_active.return_value = True
mocked_is_chords_active.return_value = True
poll_json = poller.poll()
poll_json = poller.poll()
# THEN: the live json should be generated and match expected results
assert poll_json['results']['blank'] is True, 'The blank return value should be True'
assert poll_json['results']['theme'] is False, 'The theme return value should be False'
assert poll_json['results']['display'] is False, 'The display return value should be False'
assert poll_json['results']['isSecure'] is False, 'The isSecure return value should be False'
assert poll_json['results']['isAuthorised'] is False, 'The isAuthorised return value should be False'
assert poll_json['results']['twelve'] is True, 'The twelve return value should be True'
assert poll_json['results']['version'] == 3, 'The version return value should be 3'
assert poll_json['results']['slide'] == 5, 'The slide return value should be 5'