forked from openlp/openlp
Merge branch 'websockets' into 'master'
Upgrade and cleanup WebSocket code See merge request openlp/openlp!208
This commit is contained in:
commit
d117430175
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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()
|
||||
# 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'
|
||||
|
Loading…
Reference in New Issue
Block a user