Fix sequential presentations - proper fix

This commit is contained in:
robbie jackson 2021-04-28 07:10:27 +00:00 committed by Tomas Groth
parent b81d7e753c
commit 5793526bcb
4 changed files with 89 additions and 14 deletions

View File

@ -28,6 +28,7 @@ from openlp.core.common.registry import Registry
from openlp.core.lib import ServiceItemContext from openlp.core.lib import ServiceItemContext
from openlp.core.ui import HideMode from openlp.core.ui import HideMode
from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
from openlp.plugins.presentations.lib.presentationcontroller import PresentationList
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -49,21 +50,20 @@ class Controller(object):
self.hide_mode = None self.hide_mode = None
log.info('{name} controller loaded'.format(name=live)) log.info('{name} controller loaded'.format(name=live))
def add_handler(self, controller, file, hide_mode, slide_no): def add_handler(self, controller, file, hide_mode, slide_no, unique_id):
""" """
Add a handler, which is an instance of a presentation and slidecontroller combination. If the slidecontroller Add a handler, which is an instance of a presentation and slidecontroller combination. If the slidecontroller
has a display then load the presentation. has a display then load the presentation.
""" """
log.debug('Live = {live}, add_handler {handler}'.format(live=self.is_live, handler=file)) log.debug('Live = {live}, add_handler {handler}'.format(live=self.is_live, handler=file))
self.controller = controller self.controller = controller
if self.doc is not None:
self.shutdown()
self.doc = self.controller.add_document(file) self.doc = self.controller.add_document(file)
if not self.doc.load_presentation(): if not self.doc.load_presentation():
# Display error message to user # Display error message to user
# Inform slidecontroller that the action failed? # Inform slidecontroller that the action failed?
self.doc.slidenumber = 0 self.doc.slidenumber = 0
return return
PresentationList().add(self.doc, unique_id)
self.doc.slidenumber = slide_no self.doc.slidenumber = slide_no
self.hide_mode = hide_mode self.hide_mode = hide_mode
log.debug('add_handler, slide_number: {slide:d}'.format(slide=slide_no)) log.debug('add_handler, slide_number: {slide:d}'.format(slide=slide_no))
@ -205,15 +205,15 @@ class Controller(object):
self.poll() self.poll()
return ret return ret
def shutdown(self): def shutdown(self, unique_id):
""" """
Based on the handler passed at startup triggers slide show to shut down. Based on the handler passed at startup triggers slide show to shut down.
""" """
log.debug('Live = {live}, shutdown'.format(live=self.is_live)) log.debug('Live = {live}, shutdown'.format(live=self.is_live))
if not self.doc: presentation_to_close = PresentationList().get_presentation_by_id(unique_id)
return if presentation_to_close:
self.doc.close_presentation() presentation_to_close.close_presentation()
self.doc = None PresentationList().remove(unique_id)
def blank(self, hide_mode): def blank(self, hide_mode):
""" """
@ -365,7 +365,8 @@ class MessageListener(object):
if self.handler is None: if self.handler is None:
self.controller = controller self.controller = controller
else: else:
controller.add_handler(self.controllers[self.handler], file_path, hide_mode, message[3]) controller.add_handler(self.controllers[self.handler], file_path, hide_mode, message[3],
message[0].unique_identifier)
self.timer.start() self.timer.start()
def slide(self, message): def slide(self, message):
@ -443,10 +444,10 @@ class MessageListener(object):
""" """
is_live = message[1] is_live = message[1]
if is_live: if is_live:
self.live_handler.shutdown() self.live_handler.shutdown(message[0].unique_identifier)
self.timer.stop() self.timer.stop()
else: else:
self.preview_handler.shutdown() self.preview_handler.shutdown(message[0].unique_identifier)
def hide(self, message): def hide(self, message):
""" """

View File

@ -24,7 +24,7 @@ from pathlib import Path
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common import md5_hash, sha256_file_hash from openlp.core.common import Singleton, md5_hash, sha256_file_hash
from openlp.core.common.applocation import AppLocation from openlp.core.common.applocation import AppLocation
from openlp.core.common.path import create_paths from openlp.core.common.path import create_paths
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
@ -386,6 +386,37 @@ class PresentationDocument(object):
return self._sha256_file_hash return self._sha256_file_hash
class PresentationList(metaclass=Singleton):
"""
This is a singleton class which maintains a list of instances for presentations
which have been started.
The document load_presentation() method is called several times - for example, when the
presentation files are being loaded into the library - but a document is included in this
PresentationList only when the presentation is actually displayed.
In this case the loading is initiated by a Registry 'presentation_start' event, the message
includes the service item, and the unique_identifier from the service item is used as the id
to differentiate the presentation document instances within this PresentationList.
The purpose of this is so that the 'presentation_stop' event, which also includes the service
item and its unique identifier, can result in the correct presentation being stopped.
This fixes issue #700
"""
def __init__(self):
self._presentations = {}
def add(self, document, unique_id):
self._presentations[unique_id] = document
def remove(self, unique_id):
del self._presentations[unique_id]
def get_presentation_by_id(self, unique_id):
if unique_id in self._presentations:
return self._presentations[unique_id]
else:
return None
class PresentationController(object): class PresentationController(object):
""" """
This class is used to control interactions with presentation applications by creating a runtime environment. This class is used to control interactions with presentation applications by creating a runtime environment.

View File

@ -122,7 +122,7 @@ def test_add_handler_failure():
mocked_doc_controller.add_document.return_value = mocked_doc mocked_doc_controller.add_document.return_value = mocked_doc
# WHEN: calling add_handler that fails # WHEN: calling add_handler that fails
controller.add_handler(mocked_doc_controller, MagicMock(), True, 0) controller.add_handler(mocked_doc_controller, MagicMock(), True, 0, "uuid")
# THEN: slidenumber should be 0 # THEN: slidenumber should be 0
assert controller.doc.slidenumber == 0, 'doc.slidenumber should be 0' assert controller.doc.slidenumber == 0, 'doc.slidenumber should be 0'

View File

@ -26,7 +26,8 @@ import pytest
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call, patch
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
PresentationList
FOLDER_TO_PATCH = 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder' FOLDER_TO_PATCH = 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder'
@ -215,3 +216,45 @@ def test_load_presentation(get_thumbnail_folder):
# THEN: load_presentation should return false # THEN: load_presentation should return false
assert result is False, "PresentationDocument.load_presentation should return false." assert result is False, "PresentationDocument.load_presentation should return false."
def test_presentation_list_is_singleton():
"""
Test PresentationList is a singleton class
"""
# GIVEN: a PresentationList
presentation_list = PresentationList()
# WHEN: I try to create another instance
presentation_list_2 = PresentationList()
# THEN: I get the same instance returned
assert presentation_list_2 is presentation_list
def test_presentation_list_add_and_retrieve(document):
"""
Test adding a presentation document and later retrieving it
"""
# GIVEN: a fixture with a mocked document which is added to the PresentationList
PresentationList().add(document, "unique id")
# WHEN: I retrieve the presentation document
retrieved_presentation = PresentationList().get_presentation_by_id("unique id")
# THEN: I get the same instance returned
retrieved_presentation is document
def test_presentation_list_remove(document):
"""
Test removing a presentation document from the list
"""
# GIVEN: a fixture with a mocked document which is added to the PresentationList
PresentationList().add(document, "unique id")
# WHEN: I remove the presentation document
PresentationList().remove("unique id")
# THEN: That document shouldn't be in the list
PresentationList().get_presentation_by_id("unique id") is None