Merge branch 'fix_http_poll' into 'master'

Fix http poll routes

Closes #707

See merge request openlp/openlp!283
This commit is contained in:
Tim Bentley 2021-01-08 15:01:54 +00:00
commit 965dd70567
6 changed files with 138 additions and 42 deletions

View File

@ -24,16 +24,19 @@ from openlp.core.common.mixins import RegistryProperties
class Poller(RegistryProperties):
"""
Accessed by the web layer to get status type information from the application
WARNING:
This class is DEPRECATED, if you need the state of the program, use the registry to access variables.
Used only for the deprecated V1 HTTP API.
"""
def __init__(self):
"""
Constructor for the poll builder class.
"""
super(Poller, self).__init__()
self.previous = {}
def raw_poll(self):
return {
def poll(self):
return {'results': {
'counter': self.live_controller.slide_count if self.live_controller.slide_count else 0,
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
@ -45,21 +48,4 @@ class Poller(RegistryProperties):
'version': 3,
'isSecure': self.settings.value('api/authentication enabled'),
'chordNotation': self.settings.value('songs/chord notation')
}
def poll(self):
"""
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()}
}}

View File

@ -30,11 +30,6 @@ core = Blueprint('core', __name__)
log = logging.getLogger(__name__)
@core.route('/poll')
def poll():
return jsonify(Registry().get('poller').poll())
@core.route('/display', methods=['POST'])
@login_required
def toggle_display():

View File

@ -19,8 +19,8 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
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.
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.
"""
import asyncio
import json
@ -32,8 +32,10 @@ from websockets import serve
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.threading import ThreadWorker, run_thread
from openlp.core.api.websocketspoll import WebSocketPoller
USERS = set()
poller = WebSocketPoller()
log = logging.getLogger(__name__)
@ -52,7 +54,7 @@ async def handle_websocket(websocket, path):
"""
log.debug('WebSocket handle_websocket connection')
await register(websocket)
reply = Registry().get('poller').poll_first_time()
reply = poller.get_state()
if reply:
json_reply = json.dumps(reply).encode()
await websocket.send(json_reply)
@ -93,7 +95,7 @@ async def notify_users():
:return:
"""
if USERS: # asyncio.wait doesn't accept an empty list
reply = Registry().get('poller').poll()
reply = poller.get_state_if_changed()
if reply:
json_reply = json.dumps(reply).encode()
await asyncio.wait([user.send(json_reply) for user in USERS])

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, 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/>. #
##########################################################################
from openlp.core.common.mixins import RegistryProperties
class WebSocketPoller(RegistryProperties):
"""
Accessed by web sockets to get status type information from the application
"""
def __init__(self):
"""
Constructor for the web sockets poll builder class.
"""
super(WebSocketPoller, self).__init__()
self._previous = {}
def get_state(self):
return {'results': {
'counter': self.live_controller.slide_count if self.live_controller.slide_count else 0,
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
'twelve': self.settings.value('api/twelve hour'),
'blank': self.live_controller.blank_screen.isChecked(),
'theme': self.live_controller.theme_screen.isChecked(),
'display': self.live_controller.desktop_screen.isChecked(),
'version': 3,
'isSecure': self.settings.value('api/authentication enabled'),
'chordNotation': self.settings.value('songs/chord notation')
}}
def get_state_if_changed(self):
"""
Poll OpenLP to determine current state if it has changed.
This must only be used by web sockets or else we could miss a state change.
:return: The current application state or None if unchanged since last call
"""
current = self.get_state()
if self._previous != current:
self._previous = current
return current
else:
return None

View File

@ -24,14 +24,14 @@ Functional tests to test the Http Server Class.
import pytest
from unittest.mock import MagicMock, patch
from openlp.core.api.poll import Poller
from openlp.core.api.websocketspoll import WebSocketPoller
from openlp.core.api.websockets import WebSocketServer
from openlp.core.common.registry import Registry
@pytest.fixture
def poller(settings):
poll = Poller()
poll = WebSocketPoller()
yield poll
@ -67,9 +67,9 @@ 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_poll(poller, settings):
def test_poller_get_state(poller, settings):
"""
Test the poll function returns the correct JSON
Test the get_state function returns the correct JSON
"""
# GIVEN: the system is configured with a set of data
mocked_service_manager = MagicMock()
@ -84,7 +84,7 @@ def test_poll(poller, settings):
Registry().register('live_controller', mocked_live_controller)
Registry().register('service_manager', mocked_service_manager)
# WHEN: The poller polls
poll_json = poller.poll()
poll_json = poller.get_state()
# 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'
@ -95,3 +95,28 @@ def test_poll(poller, settings):
assert poll_json['results']['slide'] == 5, 'The slide return value should be 5'
assert poll_json['results']['service'] == 21, 'The version return value should be 21'
assert poll_json['results']['item'] == '23-34-45', 'The item return value should match 23-34-45'
def test_poller_get_state_if_changed(poller, settings):
"""
Test the get_state_if_changed function returns None if the state has not changed
"""
# GIVEN: the system is configured with a set of data
poller._previous = {}
mocked_service_manager = MagicMock()
mocked_service_manager.service_id = 21
mocked_live_controller = MagicMock()
mocked_live_controller.selected_row = 5
mocked_live_controller.service_item = MagicMock()
mocked_live_controller.service_item.unique_identifier = '23-34-45'
mocked_live_controller.blank_screen.isChecked.return_value = True
mocked_live_controller.theme_screen.isChecked.return_value = False
mocked_live_controller.desktop_screen.isChecked.return_value = False
Registry().register('live_controller', mocked_live_controller)
Registry().register('service_manager', mocked_service_manager)
# WHEN: The poller polls twice
poll_json = poller.get_state_if_changed()
poll_json2 = poller.get_state_if_changed()
# THEN: The get_state_if_changed function should return None on the second call because the state has not changed
assert poll_json is not None, 'The first get_state_if_changed function call should have not returned None'
assert poll_json2 is None, 'The second get_state_if_changed function should return None'

View File

@ -18,7 +18,10 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
from unittest.mock import MagicMock
from openlp.core.common.registry import Registry
from openlp.core.api.poll import Poller
from openlp.core.state import State
from openlp.core.lib.plugin import PluginStatus, StringContent
@ -53,13 +56,35 @@ def test_system_information(flask_client, settings):
assert not res['login_required']
def test_poll(flask_client):
class FakePoller:
def poll(self):
return {'foo': 'bar'}
Registry.create().register('poller', FakePoller())
res = flask_client.get('/api/v2/core/poll').get_json()
assert res['foo'] == 'bar'
def test_poll_backend(settings):
"""
Test the raw poll function returns the correct JSON
"""
# GIVEN: the system is configured with a set of data
poller = Poller()
mocked_service_manager = MagicMock()
mocked_service_manager.service_id = 21
mocked_live_controller = MagicMock()
mocked_live_controller.selected_row = 5
mocked_live_controller.service_item = MagicMock()
mocked_live_controller.service_item.unique_identifier = '23-34-45'
mocked_live_controller.blank_screen.isChecked.return_value = True
mocked_live_controller.theme_screen.isChecked.return_value = False
mocked_live_controller.desktop_screen.isChecked.return_value = False
Registry().register('live_controller', mocked_live_controller)
Registry().register('service_manager', mocked_service_manager)
# WHEN: The poller polls
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']['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'
assert poll_json['results']['service'] == 21, 'The version return value should be 21'
assert poll_json['results']['item'] == '23-34-45', 'The item return value should match 23-34-45'
def test_login_get_is_refused(flask_client):