forked from openlp/openlp
merge trunk
This commit is contained in:
commit
9bf639a3cb
67
.bzrignore
67
.bzrignore
@ -1,57 +1,48 @@
|
|||||||
*.*~
|
*.*~
|
||||||
*.~\?~
|
|
||||||
\#*\#
|
|
||||||
build
|
|
||||||
.cache
|
|
||||||
cover
|
|
||||||
.coverage
|
|
||||||
coverage
|
|
||||||
.directory
|
|
||||||
.vscode
|
|
||||||
dist
|
|
||||||
*.dll
|
*.dll
|
||||||
documentation/build/doctrees
|
|
||||||
documentation/build/html
|
|
||||||
*.e4*
|
*.e4*
|
||||||
*eric[1-9]project
|
|
||||||
.git
|
|
||||||
env
|
|
||||||
# Git files
|
|
||||||
.gitignore
|
|
||||||
htmlcov
|
|
||||||
.idea
|
|
||||||
*.kate-swp
|
*.kate-swp
|
||||||
*.kdev4
|
*.kdev4
|
||||||
.kdev4
|
|
||||||
*.komodoproject
|
*.komodoproject
|
||||||
.komodotools
|
|
||||||
list
|
|
||||||
*.log*
|
*.log*
|
||||||
*.nja
|
*.nja
|
||||||
openlp.cfg
|
|
||||||
openlp/core/resources.py.old
|
|
||||||
OpenLP.egg-info
|
|
||||||
openlp.org 2.0.e4*
|
|
||||||
openlp.pro
|
|
||||||
openlp-test-projectordb.sqlite
|
|
||||||
*.orig
|
*.orig
|
||||||
output
|
|
||||||
*.pyc
|
*.pyc
|
||||||
__pycache__
|
|
||||||
.pylint.d
|
|
||||||
.pytest_cache
|
|
||||||
*.qm
|
*.qm
|
||||||
*.rej
|
*.rej
|
||||||
# Rejected diff's
|
|
||||||
resources/innosetup/Output
|
|
||||||
resources/windows/warnOpenLP.txt
|
|
||||||
*.ropeproject
|
*.ropeproject
|
||||||
tags
|
*.~\?~
|
||||||
output
|
*eric[1-9]project
|
||||||
|
.cache
|
||||||
|
.coverage
|
||||||
|
.directory
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.idea
|
||||||
|
.kdev4
|
||||||
|
.komodotools
|
||||||
|
.pylint.d
|
||||||
|
.pytest_cache
|
||||||
|
.vscode
|
||||||
|
OpenLP.egg-info
|
||||||
|
\#*\#
|
||||||
|
__pycache__
|
||||||
|
build
|
||||||
|
cover
|
||||||
|
coverage
|
||||||
|
dist
|
||||||
|
env
|
||||||
htmlcov
|
htmlcov
|
||||||
|
list
|
||||||
node_modules
|
node_modules
|
||||||
openlp-test-projectordb.sqlite
|
openlp-test-projectordb.sqlite
|
||||||
|
openlp.cfg
|
||||||
|
openlp.pro
|
||||||
|
openlp/core/resources.py.old
|
||||||
|
openlp/plugins/presentations/lib/vendor/Pyro4
|
||||||
|
openlp/plugins/presentations/lib/vendor/serpent.py
|
||||||
|
output
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.cache
|
tags
|
||||||
test
|
test
|
||||||
tests.kdev4
|
tests.kdev4
|
||||||
|
@ -78,6 +78,8 @@ def path_to_str(path=None):
|
|||||||
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
|
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
if isinstance(path, str):
|
||||||
|
return path
|
||||||
if not isinstance(path, Path) and path is not None:
|
if not isinstance(path, Path) and path is not None:
|
||||||
raise TypeError('parameter \'path\' must be of type Path or NoneType')
|
raise TypeError('parameter \'path\' must be of type Path or NoneType')
|
||||||
if path is None:
|
if path is None:
|
||||||
|
@ -104,6 +104,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
State().update_pre_conditions('mediacontroller', True)
|
State().update_pre_conditions('mediacontroller', True)
|
||||||
State().update_pre_conditions('media_live', True)
|
State().update_pre_conditions('media_live', True)
|
||||||
else:
|
else:
|
||||||
|
if hasattr(self.main_window, 'splash') and self.main_window.splash.isVisible():
|
||||||
|
self.main_window.splash.hide()
|
||||||
State().missing_text('media_live', translate('OpenLP.SlideController',
|
State().missing_text('media_live', translate('OpenLP.SlideController',
|
||||||
'VLC or pymediainfo are missing, so you are unable to play any media'))
|
'VLC or pymediainfo are missing, so you are unable to play any media'))
|
||||||
return True
|
return True
|
||||||
|
431
openlp/plugins/presentations/lib/libreofficeserver.py
Normal file
431
openlp/plugins/presentations/lib/libreofficeserver.py
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2019 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/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
This module runs a Pyro4 server using LibreOffice's version of Python
|
||||||
|
|
||||||
|
Please Note: This intentionally uses os.path over pathlib because we don't know which version of Python is shipped with
|
||||||
|
the version of LibreOffice on the user's computer.
|
||||||
|
"""
|
||||||
|
from subprocess import Popen
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
# Only make the log file on OS X when running as a server
|
||||||
|
logfile = os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp', 'libreofficeserver.log')
|
||||||
|
print('Setting up log file: {logfile}'.format(logfile=logfile))
|
||||||
|
logging.basicConfig(filename=logfile, level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
# Add the current directory to sys.path so that we can load the serializers
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__)))
|
||||||
|
# Add the vendor directory to sys.path so that we can load Pyro4
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), 'vendor'))
|
||||||
|
|
||||||
|
from serializers import register_classes
|
||||||
|
from Pyro4 import Daemon, expose
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Wrap these imports in a try so that we can run the tests on macOS
|
||||||
|
import uno
|
||||||
|
from com.sun.star.beans import PropertyValue
|
||||||
|
from com.sun.star.task import ErrorCodeIOException
|
||||||
|
except ImportError:
|
||||||
|
# But they need to be defined for mocking
|
||||||
|
uno = None
|
||||||
|
PropertyValue = None
|
||||||
|
ErrorCodeIOException = Exception
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
register_classes()
|
||||||
|
|
||||||
|
|
||||||
|
class TextType(object):
|
||||||
|
"""
|
||||||
|
Type Enumeration for Types of Text to request
|
||||||
|
"""
|
||||||
|
Title = 0
|
||||||
|
SlideText = 1
|
||||||
|
Notes = 2
|
||||||
|
|
||||||
|
|
||||||
|
class LibreOfficeException(Exception):
|
||||||
|
"""
|
||||||
|
A specific exception for LO
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@expose
|
||||||
|
class LibreOfficeServer(object):
|
||||||
|
"""
|
||||||
|
A Pyro4 server which controls LibreOffice
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Set up the server
|
||||||
|
"""
|
||||||
|
self._desktop = None
|
||||||
|
self._control = None
|
||||||
|
self._document = None
|
||||||
|
self._presentation = None
|
||||||
|
self._process = None
|
||||||
|
self._manager = None
|
||||||
|
|
||||||
|
def _create_property(self, name, value):
|
||||||
|
"""
|
||||||
|
Create an OOo style property object which are passed into some Uno methods.
|
||||||
|
"""
|
||||||
|
log.debug('create property')
|
||||||
|
property_object = PropertyValue()
|
||||||
|
property_object.Name = name
|
||||||
|
property_object.Value = value
|
||||||
|
return property_object
|
||||||
|
|
||||||
|
def _get_text_from_page(self, slide_no, text_type=TextType.SlideText):
|
||||||
|
"""
|
||||||
|
Return any text extracted from the presentation page.
|
||||||
|
|
||||||
|
:param slide_no: The slide the notes are required for, starting at 1
|
||||||
|
:param notes: A boolean. If set the method searches the notes of the slide.
|
||||||
|
:param text_type: A TextType. Enumeration of the types of supported text.
|
||||||
|
"""
|
||||||
|
text = ''
|
||||||
|
if TextType.Title <= text_type <= TextType.Notes:
|
||||||
|
pages = self._document.getDrawPages()
|
||||||
|
if 0 < slide_no <= pages.getCount():
|
||||||
|
page = pages.getByIndex(slide_no - 1)
|
||||||
|
if text_type == TextType.Notes:
|
||||||
|
page = page.getNotesPage()
|
||||||
|
for index in range(page.getCount()):
|
||||||
|
shape = page.getByIndex(index)
|
||||||
|
shape_type = shape.getShapeType()
|
||||||
|
if shape.supportsService('com.sun.star.drawing.Text'):
|
||||||
|
# if they requested title, make sure it is the title
|
||||||
|
if text_type != TextType.Title or shape_type == 'com.sun.star.presentation.TitleTextShape':
|
||||||
|
text += shape.getString() + '\n'
|
||||||
|
return text
|
||||||
|
|
||||||
|
def start_process(self):
|
||||||
|
"""
|
||||||
|
Initialise Impress
|
||||||
|
"""
|
||||||
|
uno_command = [
|
||||||
|
'/Applications/LibreOffice.app/Contents/MacOS/soffice',
|
||||||
|
'--nologo',
|
||||||
|
'--norestore',
|
||||||
|
'--minimized',
|
||||||
|
'--nodefault',
|
||||||
|
'--nofirststartwizard',
|
||||||
|
'--accept=pipe,name=openlp_maclo;urp;StarOffice.ServiceManager'
|
||||||
|
]
|
||||||
|
self._process = Popen(uno_command)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def desktop(self):
|
||||||
|
"""
|
||||||
|
Set up an UNO desktop instance
|
||||||
|
"""
|
||||||
|
if self._desktop is not None:
|
||||||
|
return self._desktop
|
||||||
|
uno_instance = None
|
||||||
|
context = uno.getComponentContext()
|
||||||
|
resolver = context.ServiceManager.createInstanceWithContext('com.sun.star.bridge.UnoUrlResolver', context)
|
||||||
|
loop = 0
|
||||||
|
while uno_instance is None and loop < 3:
|
||||||
|
try:
|
||||||
|
uno_instance = resolver.resolve('uno:pipe,name=openlp_maclo;urp;StarOffice.ComponentContext')
|
||||||
|
except Exception:
|
||||||
|
log.exception('Unable to find running instance, retrying...')
|
||||||
|
loop += 1
|
||||||
|
try:
|
||||||
|
self._manager = uno_instance.ServiceManager
|
||||||
|
log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop')
|
||||||
|
desktop = self._manager.createInstanceWithContext('com.sun.star.frame.Desktop', uno_instance)
|
||||||
|
if not desktop:
|
||||||
|
raise Exception('Failed to get UNO desktop')
|
||||||
|
self._desktop = desktop
|
||||||
|
return desktop
|
||||||
|
except Exception:
|
||||||
|
log.exception('Failed to get UNO desktop')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
"""
|
||||||
|
Shut down the server
|
||||||
|
"""
|
||||||
|
can_kill = True
|
||||||
|
if hasattr(self, '_docs'):
|
||||||
|
while self._docs:
|
||||||
|
self._docs[0].close_presentation()
|
||||||
|
docs = self.desktop.getComponents()
|
||||||
|
count = 0
|
||||||
|
if docs.hasElements():
|
||||||
|
list_elements = docs.createEnumeration()
|
||||||
|
while list_elements.hasMoreElements():
|
||||||
|
doc = list_elements.nextElement()
|
||||||
|
if doc.getImplementationName() != 'com.sun.star.comp.framework.BackingComp':
|
||||||
|
count += 1
|
||||||
|
if count > 0:
|
||||||
|
log.debug('LibreOffice not terminated as docs are still open')
|
||||||
|
can_kill = False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.desktop.terminate()
|
||||||
|
log.debug('LibreOffice killed')
|
||||||
|
except Exception:
|
||||||
|
log.exception('Failed to terminate LibreOffice')
|
||||||
|
if getattr(self, '_process') and can_kill:
|
||||||
|
self._process.kill()
|
||||||
|
|
||||||
|
def load_presentation(self, file_path, screen_number):
|
||||||
|
"""
|
||||||
|
Load a presentation
|
||||||
|
"""
|
||||||
|
self._file_path = file_path
|
||||||
|
url = uno.systemPathToFileUrl(file_path)
|
||||||
|
properties = (self._create_property('Hidden', True),)
|
||||||
|
self._document = None
|
||||||
|
loop_count = 0
|
||||||
|
while loop_count < 3:
|
||||||
|
try:
|
||||||
|
self._document = self.desktop.loadComponentFromURL(url, '_blank', 0, properties)
|
||||||
|
except Exception:
|
||||||
|
log.exception('Failed to load presentation {url}'.format(url=url))
|
||||||
|
if self._document:
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
loop_count += 1
|
||||||
|
if loop_count == 3:
|
||||||
|
log.error('Looped too many times')
|
||||||
|
return False
|
||||||
|
self._presentation = self._document.getPresentation()
|
||||||
|
self._presentation.Display = screen_number
|
||||||
|
self._control = None
|
||||||
|
return True
|
||||||
|
|
||||||
|
def extract_thumbnails(self, temp_folder):
|
||||||
|
"""
|
||||||
|
Create thumbnails for the presentation
|
||||||
|
"""
|
||||||
|
thumbnails = []
|
||||||
|
thumb_dir_url = uno.systemPathToFileUrl(temp_folder)
|
||||||
|
properties = (self._create_property('FilterName', 'impress_png_Export'),)
|
||||||
|
pages = self._document.getDrawPages()
|
||||||
|
if not pages:
|
||||||
|
return []
|
||||||
|
if not os.path.isdir(temp_folder):
|
||||||
|
os.makedirs(temp_folder)
|
||||||
|
for index in range(pages.getCount()):
|
||||||
|
page = pages.getByIndex(index)
|
||||||
|
self._document.getCurrentController().setCurrentPage(page)
|
||||||
|
url_path = '{path}/{name}.png'.format(path=thumb_dir_url, name=str(index + 1))
|
||||||
|
path = os.path.join(temp_folder, str(index + 1) + '.png')
|
||||||
|
try:
|
||||||
|
self._document.storeToURL(url_path, properties)
|
||||||
|
thumbnails.append(path)
|
||||||
|
except ErrorCodeIOException as exception:
|
||||||
|
log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode))
|
||||||
|
except Exception:
|
||||||
|
log.exception('{path} - Unable to store openoffice preview'.format(path=path))
|
||||||
|
return thumbnails
|
||||||
|
|
||||||
|
def get_titles_and_notes(self):
|
||||||
|
"""
|
||||||
|
Extract the titles and the notes from the slides.
|
||||||
|
"""
|
||||||
|
titles = []
|
||||||
|
notes = []
|
||||||
|
pages = self._document.getDrawPages()
|
||||||
|
for slide_no in range(1, pages.getCount() + 1):
|
||||||
|
titles.append(self._get_text_from_page(slide_no, TextType.Title).replace('\n', ' ') + '\n')
|
||||||
|
note = self._get_text_from_page(slide_no, TextType.Notes)
|
||||||
|
if len(note) == 0:
|
||||||
|
note = ' '
|
||||||
|
notes.append(note)
|
||||||
|
return titles, notes
|
||||||
|
|
||||||
|
def close_presentation(self):
|
||||||
|
"""
|
||||||
|
Close presentation and clean up objects.
|
||||||
|
"""
|
||||||
|
log.debug('close Presentation LibreOffice')
|
||||||
|
if self._document:
|
||||||
|
if self._presentation:
|
||||||
|
try:
|
||||||
|
self._presentation.end()
|
||||||
|
self._presentation = None
|
||||||
|
self._document.dispose()
|
||||||
|
except Exception:
|
||||||
|
log.exception("Closing presentation failed")
|
||||||
|
self._document = None
|
||||||
|
|
||||||
|
def is_loaded(self):
|
||||||
|
"""
|
||||||
|
Returns true if a presentation is loaded.
|
||||||
|
"""
|
||||||
|
log.debug('is loaded LibreOffice')
|
||||||
|
if self._presentation is None or self._document is None:
|
||||||
|
log.debug("is_loaded: no presentation or document")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
if self._document.getPresentation() is None:
|
||||||
|
log.debug("getPresentation failed to find a presentation")
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
log.exception("getPresentation failed to find a presentation")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
"""
|
||||||
|
Returns true if a presentation is active and running.
|
||||||
|
"""
|
||||||
|
log.debug('is active LibreOffice')
|
||||||
|
if not self.is_loaded():
|
||||||
|
return False
|
||||||
|
return self._control.isRunning() if self._control else False
|
||||||
|
|
||||||
|
def unblank_screen(self):
|
||||||
|
"""
|
||||||
|
Unblanks the screen.
|
||||||
|
"""
|
||||||
|
log.debug('unblank screen LibreOffice')
|
||||||
|
return self._control.resume()
|
||||||
|
|
||||||
|
def blank_screen(self):
|
||||||
|
"""
|
||||||
|
Blanks the screen.
|
||||||
|
"""
|
||||||
|
log.debug('blank screen LibreOffice')
|
||||||
|
self._control.blankScreen(0)
|
||||||
|
|
||||||
|
def is_blank(self):
|
||||||
|
"""
|
||||||
|
Returns true if screen is blank.
|
||||||
|
"""
|
||||||
|
log.debug('is blank LibreOffice')
|
||||||
|
if self._control and self._control.isRunning():
|
||||||
|
return self._control.isPaused()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def stop_presentation(self):
|
||||||
|
"""
|
||||||
|
Stop the presentation, remove from screen.
|
||||||
|
"""
|
||||||
|
log.debug('stop presentation LibreOffice')
|
||||||
|
self._presentation.end()
|
||||||
|
self._control = None
|
||||||
|
|
||||||
|
def start_presentation(self):
|
||||||
|
"""
|
||||||
|
Start the presentation from the beginning.
|
||||||
|
"""
|
||||||
|
log.debug('start presentation LibreOffice')
|
||||||
|
if self._control is None or not self._control.isRunning():
|
||||||
|
window = self._document.getCurrentController().getFrame().getContainerWindow()
|
||||||
|
window.setVisible(True)
|
||||||
|
self._presentation.start()
|
||||||
|
self._control = self._presentation.getController()
|
||||||
|
# start() returns before the Component is ready. Try for 15 seconds.
|
||||||
|
sleep_count = 1
|
||||||
|
while not self._control and sleep_count < 150:
|
||||||
|
time.sleep(0.1)
|
||||||
|
sleep_count += 1
|
||||||
|
self._control = self._presentation.getController()
|
||||||
|
window.setVisible(False)
|
||||||
|
else:
|
||||||
|
self._control.activate()
|
||||||
|
self.goto_slide(1)
|
||||||
|
|
||||||
|
def get_slide_number(self):
|
||||||
|
"""
|
||||||
|
Return the current slide number on the screen, from 1.
|
||||||
|
"""
|
||||||
|
return self._control.getCurrentSlideIndex() + 1
|
||||||
|
|
||||||
|
def get_slide_count(self):
|
||||||
|
"""
|
||||||
|
Return the total number of slides.
|
||||||
|
"""
|
||||||
|
return self._document.getDrawPages().getCount()
|
||||||
|
|
||||||
|
def goto_slide(self, slide_no):
|
||||||
|
"""
|
||||||
|
Go to a specific slide (from 1).
|
||||||
|
|
||||||
|
:param slide_no: The slide the text is required for, starting at 1
|
||||||
|
"""
|
||||||
|
self._control.gotoSlideIndex(slide_no - 1)
|
||||||
|
|
||||||
|
def next_step(self):
|
||||||
|
"""
|
||||||
|
Triggers the next effect of slide on the running presentation.
|
||||||
|
"""
|
||||||
|
is_paused = self._control.isPaused()
|
||||||
|
self._control.gotoNextEffect()
|
||||||
|
time.sleep(0.1)
|
||||||
|
if not is_paused and self._control.isPaused():
|
||||||
|
self._control.gotoPreviousEffect()
|
||||||
|
|
||||||
|
def previous_step(self):
|
||||||
|
"""
|
||||||
|
Triggers the previous slide on the running presentation.
|
||||||
|
"""
|
||||||
|
self._control.gotoPreviousEffect()
|
||||||
|
|
||||||
|
def get_slide_text(self, slide_no):
|
||||||
|
"""
|
||||||
|
Returns the text on the slide.
|
||||||
|
|
||||||
|
:param slide_no: The slide the text is required for, starting at 1
|
||||||
|
"""
|
||||||
|
return self._get_text_from_page(slide_no)
|
||||||
|
|
||||||
|
def get_slide_notes(self, slide_no):
|
||||||
|
"""
|
||||||
|
Returns the text in the slide notes.
|
||||||
|
|
||||||
|
:param slide_no: The slide the notes are required for, starting at 1
|
||||||
|
"""
|
||||||
|
return self._get_text_from_page(slide_no, TextType.Notes)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
The main function which runs the server
|
||||||
|
"""
|
||||||
|
daemon = Daemon(host='localhost', port=4310)
|
||||||
|
daemon.register(LibreOfficeServer, 'openlp.libreofficeserver')
|
||||||
|
try:
|
||||||
|
daemon.requestLoop()
|
||||||
|
finally:
|
||||||
|
daemon.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
266
openlp/plugins/presentations/lib/maclocontroller.py
Normal file
266
openlp/plugins/presentations/lib/maclocontroller.py
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2019 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/>. #
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from subprocess import Popen
|
||||||
|
|
||||||
|
from Pyro4 import Proxy
|
||||||
|
|
||||||
|
from openlp.core.common import delete_file, is_macosx
|
||||||
|
from openlp.core.common.applocation import AppLocation
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.core.common.registry import Registry
|
||||||
|
from openlp.core.display.screens import ScreenList
|
||||||
|
from openlp.plugins.presentations.lib.serializers import register_classes
|
||||||
|
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
||||||
|
|
||||||
|
|
||||||
|
LIBREOFFICE_PATH = Path('/Applications/LibreOffice.app')
|
||||||
|
LIBREOFFICE_PYTHON = LIBREOFFICE_PATH / 'Contents' / 'Resources' / 'python'
|
||||||
|
|
||||||
|
if is_macosx() and LIBREOFFICE_PATH.exists():
|
||||||
|
macuno_available = True
|
||||||
|
else:
|
||||||
|
macuno_available = False
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
register_classes()
|
||||||
|
|
||||||
|
|
||||||
|
class MacLOController(PresentationController):
|
||||||
|
"""
|
||||||
|
Class to control interactions with MacLO presentations on Mac OS X via Pyro4. It starts the Pyro4 nameserver,
|
||||||
|
starts the LibreOfficeServer, and then controls MacLO via Pyro4.
|
||||||
|
"""
|
||||||
|
log.info('MacLOController loaded')
|
||||||
|
|
||||||
|
def __init__(self, plugin):
|
||||||
|
"""
|
||||||
|
Initialise the class
|
||||||
|
"""
|
||||||
|
log.debug('Initialising')
|
||||||
|
super(MacLOController, self).__init__(plugin, 'maclo', MacLODocument, 'Impress on macOS')
|
||||||
|
self.supports = ['odp']
|
||||||
|
self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm']
|
||||||
|
self.server_process = None
|
||||||
|
self._client = None
|
||||||
|
self._start_server()
|
||||||
|
|
||||||
|
def _start_server(self):
|
||||||
|
"""
|
||||||
|
Start a LibreOfficeServer
|
||||||
|
"""
|
||||||
|
libreoffice_python = Path('/Applications/LibreOffice.app/Contents/Resources/python')
|
||||||
|
libreoffice_server = AppLocation.get_directory(AppLocation.PluginsDir).joinpath('presentations', 'lib',
|
||||||
|
'libreofficeserver.py')
|
||||||
|
if libreoffice_python.exists():
|
||||||
|
self.server_process = Popen([str(libreoffice_python), str(libreoffice_server)])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
"""
|
||||||
|
Set up a Pyro4 client so that we can talk to the LibreOfficeServer
|
||||||
|
"""
|
||||||
|
if not self._client:
|
||||||
|
self._client = Proxy('PYRO:openlp.libreofficeserver@localhost:4310')
|
||||||
|
if not self._client._pyroConnection:
|
||||||
|
self._client._pyroReconnect()
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def check_available(self):
|
||||||
|
"""
|
||||||
|
MacLO is able to run on this machine.
|
||||||
|
"""
|
||||||
|
log.debug('check_available')
|
||||||
|
return macuno_available
|
||||||
|
|
||||||
|
def start_process(self):
|
||||||
|
"""
|
||||||
|
Loads a running version of LibreOffice in the background. It is not displayed to the user but is available to
|
||||||
|
the UNO interface when required.
|
||||||
|
"""
|
||||||
|
log.debug('Started automatically by the Pyro server')
|
||||||
|
self.client.start_process()
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
"""
|
||||||
|
Called at system exit to clean up any running presentations.
|
||||||
|
"""
|
||||||
|
log.debug('Kill LibreOffice')
|
||||||
|
self.client.shutdown()
|
||||||
|
self.server_process.kill()
|
||||||
|
|
||||||
|
|
||||||
|
class MacLODocument(PresentationDocument):
|
||||||
|
"""
|
||||||
|
Class which holds information and controls a single presentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, controller, presentation):
|
||||||
|
"""
|
||||||
|
Constructor, store information about the file and initialise.
|
||||||
|
"""
|
||||||
|
log.debug('Init Presentation LibreOffice')
|
||||||
|
super(MacLODocument, self).__init__(controller, presentation)
|
||||||
|
self.client = controller.client
|
||||||
|
|
||||||
|
def load_presentation(self):
|
||||||
|
"""
|
||||||
|
Tell the LibreOfficeServer to start the presentation.
|
||||||
|
"""
|
||||||
|
log.debug('Load Presentation LibreOffice')
|
||||||
|
if not self.client.load_presentation(str(self.file_path), ScreenList().current.number + 1):
|
||||||
|
return False
|
||||||
|
self.create_thumbnails()
|
||||||
|
self.create_titles_and_notes()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_thumbnails(self):
|
||||||
|
"""
|
||||||
|
Create thumbnail images for presentation.
|
||||||
|
"""
|
||||||
|
log.debug('create thumbnails LibreOffice')
|
||||||
|
if self.check_thumbnails():
|
||||||
|
return
|
||||||
|
temp_thumbnails = self.client.extract_thumbnails(str(self.get_temp_folder()))
|
||||||
|
for index, temp_thumb in enumerate(temp_thumbnails):
|
||||||
|
temp_thumb = Path(temp_thumb)
|
||||||
|
self.convert_thumbnail(temp_thumb, index + 1)
|
||||||
|
delete_file(temp_thumb)
|
||||||
|
|
||||||
|
def create_titles_and_notes(self):
|
||||||
|
"""
|
||||||
|
Writes the list of titles (one per slide) to 'titles.txt' and the notes to 'slideNotes[x].txt'
|
||||||
|
in the thumbnails directory
|
||||||
|
"""
|
||||||
|
titles, notes = self.client.get_titles_and_notes()
|
||||||
|
self.save_titles_and_notes(titles, notes)
|
||||||
|
|
||||||
|
def close_presentation(self):
|
||||||
|
"""
|
||||||
|
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
|
||||||
|
shutdown.
|
||||||
|
"""
|
||||||
|
log.debug('close Presentation LibreOffice')
|
||||||
|
self.client.close_presentation()
|
||||||
|
self.controller.remove_doc(self)
|
||||||
|
|
||||||
|
def is_loaded(self):
|
||||||
|
"""
|
||||||
|
Returns true if a presentation is loaded.
|
||||||
|
"""
|
||||||
|
log.debug('is loaded LibreOffice')
|
||||||
|
return self.client.is_loaded()
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
"""
|
||||||
|
Returns true if a presentation is active and running.
|
||||||
|
"""
|
||||||
|
log.debug('is active LibreOffice')
|
||||||
|
return self.client.is_active()
|
||||||
|
|
||||||
|
def unblank_screen(self):
|
||||||
|
"""
|
||||||
|
Unblanks the screen.
|
||||||
|
"""
|
||||||
|
log.debug('unblank screen LibreOffice')
|
||||||
|
return self.client.unblank_screen()
|
||||||
|
|
||||||
|
def blank_screen(self):
|
||||||
|
"""
|
||||||
|
Blanks the screen.
|
||||||
|
"""
|
||||||
|
log.debug('blank screen LibreOffice')
|
||||||
|
self.client.blank_screen()
|
||||||
|
|
||||||
|
def is_blank(self):
|
||||||
|
"""
|
||||||
|
Returns true if screen is blank.
|
||||||
|
"""
|
||||||
|
log.debug('is blank LibreOffice')
|
||||||
|
return self.client.is_blank()
|
||||||
|
|
||||||
|
def stop_presentation(self):
|
||||||
|
"""
|
||||||
|
Stop the presentation, remove from screen.
|
||||||
|
"""
|
||||||
|
log.debug('stop presentation LibreOffice')
|
||||||
|
self.client.stop_presentation()
|
||||||
|
|
||||||
|
def start_presentation(self):
|
||||||
|
"""
|
||||||
|
Start the presentation from the beginning.
|
||||||
|
"""
|
||||||
|
log.debug('start presentation LibreOffice')
|
||||||
|
self.client.start_presentation()
|
||||||
|
# Make sure impress doesn't steal focus, unless we're on a single screen setup
|
||||||
|
if len(ScreenList()) > 1:
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
|
def get_slide_number(self):
|
||||||
|
"""
|
||||||
|
Return the current slide number on the screen, from 1.
|
||||||
|
"""
|
||||||
|
return self.client.get_slide_number()
|
||||||
|
|
||||||
|
def get_slide_count(self):
|
||||||
|
"""
|
||||||
|
Return the total number of slides.
|
||||||
|
"""
|
||||||
|
return self.client.get_slide_count()
|
||||||
|
|
||||||
|
def goto_slide(self, slide_no):
|
||||||
|
"""
|
||||||
|
Go to a specific slide (from 1).
|
||||||
|
|
||||||
|
:param slide_no: The slide the text is required for, starting at 1
|
||||||
|
"""
|
||||||
|
self.client.goto_slide(slide_no)
|
||||||
|
|
||||||
|
def next_step(self):
|
||||||
|
"""
|
||||||
|
Triggers the next effect of slide on the running presentation.
|
||||||
|
"""
|
||||||
|
self.client.next_step()
|
||||||
|
|
||||||
|
def previous_step(self):
|
||||||
|
"""
|
||||||
|
Triggers the previous slide on the running presentation.
|
||||||
|
"""
|
||||||
|
self.client.previous_step()
|
||||||
|
|
||||||
|
def get_slide_text(self, slide_no):
|
||||||
|
"""
|
||||||
|
Returns the text on the slide.
|
||||||
|
|
||||||
|
:param slide_no: The slide the text is required for, starting at 1
|
||||||
|
"""
|
||||||
|
return self.client.get_slide_text(slide_no)
|
||||||
|
|
||||||
|
def get_slide_notes(self, slide_no):
|
||||||
|
"""
|
||||||
|
Returns the text in the slide notes.
|
||||||
|
|
||||||
|
:param slide_no: The slide the notes are required for, starting at 1
|
||||||
|
"""
|
||||||
|
return self.client.get_slide_notes(slide_no)
|
@ -412,7 +412,8 @@ class PresentationController(object):
|
|||||||
"""
|
"""
|
||||||
log.info('PresentationController loaded')
|
log.info('PresentationController loaded')
|
||||||
|
|
||||||
def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument):
|
def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument,
|
||||||
|
display_name=None):
|
||||||
"""
|
"""
|
||||||
This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins
|
This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins
|
||||||
|
|
||||||
@ -432,6 +433,7 @@ class PresentationController(object):
|
|||||||
self.docs = []
|
self.docs = []
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.display_name = display_name if display_name is not None else name
|
||||||
self.document_class = document_class
|
self.document_class = document_class
|
||||||
self.settings_section = self.plugin.settings_section
|
self.settings_section = self.plugin.settings_section
|
||||||
self.available = None
|
self.available = None
|
||||||
|
@ -127,10 +127,10 @@ class PresentationTab(SettingsTab):
|
|||||||
|
|
||||||
def set_controller_text(self, checkbox, controller):
|
def set_controller_text(self, checkbox, controller):
|
||||||
if checkbox.isEnabled():
|
if checkbox.isEnabled():
|
||||||
checkbox.setText(controller.name)
|
checkbox.setText(controller.display_name)
|
||||||
else:
|
else:
|
||||||
checkbox.setText(translate('PresentationPlugin.PresentationTab',
|
checkbox.setText(translate('PresentationPlugin.PresentationTab',
|
||||||
'{name} (unavailable)').format(name=controller.name))
|
'{name} (unavailable)').format(name=controller.display_name))
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
|
52
openlp/plugins/presentations/lib/serializers.py
Normal file
52
openlp/plugins/presentations/lib/serializers.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2019 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/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
This module contains some helpers for serializing Path objects in Pyro4
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
except ImportError:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from Pyro4.util import SerializerBase
|
||||||
|
|
||||||
|
|
||||||
|
def path_class_to_dict(obj):
|
||||||
|
"""
|
||||||
|
Serialize a Path object for Pyro4
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'__class__': 'Path',
|
||||||
|
'parts': obj.parts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def path_dict_to_class(classname, d):
|
||||||
|
return Path(d['parts'])
|
||||||
|
|
||||||
|
|
||||||
|
def register_classes():
|
||||||
|
"""
|
||||||
|
Register the serializers
|
||||||
|
"""
|
||||||
|
SerializerBase.register_class_to_dict(Path, path_class_to_dict)
|
||||||
|
SerializerBase.register_dict_to_class('Path', path_dict_to_class)
|
5
openlp/plugins/presentations/lib/vendor/do_not_delete.txt
vendored
Normal file
5
openlp/plugins/presentations/lib/vendor/do_not_delete.txt
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Vendor Directory
|
||||||
|
================
|
||||||
|
|
||||||
|
Do not delete this directory, it is used on Mac OS to place Pyro4 and serpent for use with Impress.
|
||||||
|
|
@ -28,13 +28,13 @@ import os
|
|||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.state import State
|
|
||||||
from openlp.core.api.http import register_endpoint
|
from openlp.core.api.http import register_endpoint
|
||||||
from openlp.core.common import extension_loader
|
from openlp.core.common import extension_loader
|
||||||
from openlp.core.common.i18n import translate
|
from openlp.core.common.i18n import translate
|
||||||
from openlp.core.common.settings import Settings
|
from openlp.core.common.settings import Settings
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.plugin import Plugin, StringContent
|
from openlp.core.lib.plugin import Plugin, StringContent
|
||||||
|
from openlp.core.state import State
|
||||||
from openlp.core.ui.icons import UiIcons
|
from openlp.core.ui.icons import UiIcons
|
||||||
from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint
|
from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint
|
||||||
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController
|
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController
|
||||||
@ -45,9 +45,11 @@ from openlp.plugins.presentations.lib.presentationtab import PresentationTab
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
__default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
|
__default_settings__ = {
|
||||||
|
'presentations/override app': QtCore.Qt.Unchecked,
|
||||||
'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
|
'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
|
||||||
'presentations/pdf_program': None,
|
'presentations/pdf_program': None,
|
||||||
|
'presentations/maclo': QtCore.Qt.Checked,
|
||||||
'presentations/Impress': QtCore.Qt.Checked,
|
'presentations/Impress': QtCore.Qt.Checked,
|
||||||
'presentations/Powerpoint': QtCore.Qt.Checked,
|
'presentations/Powerpoint': QtCore.Qt.Checked,
|
||||||
'presentations/Pdf': QtCore.Qt.Checked,
|
'presentations/Pdf': QtCore.Qt.Checked,
|
||||||
@ -56,7 +58,7 @@ __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
|
|||||||
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
|
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
|
||||||
'presentations/powerpoint control window': QtCore.Qt.Unchecked,
|
'presentations/powerpoint control window': QtCore.Qt.Unchecked,
|
||||||
'presentations/last directory': None
|
'presentations/last directory': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PresentationPlugin(Plugin):
|
class PresentationPlugin(Plugin):
|
||||||
@ -100,7 +102,7 @@ class PresentationPlugin(Plugin):
|
|||||||
try:
|
try:
|
||||||
self.controllers[controller].start_process()
|
self.controllers[controller].start_process()
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning('Failed to start controller process')
|
log.exception('Failed to start controller process')
|
||||||
self.controllers[controller].available = False
|
self.controllers[controller].available = False
|
||||||
self.media_item.build_file_mask_string()
|
self.media_item.build_file_mask_string()
|
||||||
|
|
||||||
|
@ -160,6 +160,8 @@ def check_module(mod, text='', indent=' '):
|
|||||||
w('OK')
|
w('OK')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
w('FAIL')
|
w('FAIL')
|
||||||
|
except Exception:
|
||||||
|
w('ERROR')
|
||||||
w(os.linesep)
|
w(os.linesep)
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,7 +110,18 @@ class TestPath(TestCase):
|
|||||||
# WHEN: Calling `path_to_str` with an invalid Type
|
# WHEN: Calling `path_to_str` with an invalid Type
|
||||||
# THEN: A TypeError should have been raised
|
# THEN: A TypeError should have been raised
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
path_to_str(str())
|
path_to_str(57)
|
||||||
|
|
||||||
|
def test_path_to_str_wth_str(self):
|
||||||
|
"""
|
||||||
|
Test that `path_to_str` just returns a str when given a str
|
||||||
|
"""
|
||||||
|
# GIVEN: The `path_to_str` function
|
||||||
|
# WHEN: Calling `path_to_str` with a str
|
||||||
|
result = path_to_str('/usr/bin')
|
||||||
|
|
||||||
|
# THEN: The string should be returned
|
||||||
|
assert result == '/usr/bin'
|
||||||
|
|
||||||
def test_path_to_str_none(self):
|
def test_path_to_str_none(self):
|
||||||
"""
|
"""
|
||||||
|
@ -0,0 +1,948 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2019 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/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
Functional tests to test the LibreOffice Pyro server
|
||||||
|
"""
|
||||||
|
from unittest.mock import MagicMock, patch, call
|
||||||
|
|
||||||
|
from openlp.plugins.presentations.lib.libreofficeserver import LibreOfficeServer, TextType, main
|
||||||
|
|
||||||
|
|
||||||
|
def test_constructor():
|
||||||
|
"""
|
||||||
|
Test the Constructor from the server
|
||||||
|
"""
|
||||||
|
# GIVEN: No server
|
||||||
|
# WHEN: The server object is created
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# THEN: The server should have been set up correctly
|
||||||
|
assert server._control is None
|
||||||
|
# assert server._desktop is None
|
||||||
|
assert server._document is None
|
||||||
|
assert server._presentation is None
|
||||||
|
assert server._process is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.Popen')
|
||||||
|
def test_start_process(MockedPopen):
|
||||||
|
"""
|
||||||
|
Test that the correct command is issued to run LibreOffice
|
||||||
|
"""
|
||||||
|
# GIVEN: A LOServer
|
||||||
|
mocked_process = MagicMock()
|
||||||
|
MockedPopen.return_value = mocked_process
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: The start_process() method is run
|
||||||
|
server.start_process()
|
||||||
|
|
||||||
|
# THEN: The correct command line should run and the process should have started
|
||||||
|
MockedPopen.assert_called_with([
|
||||||
|
'/Applications/LibreOffice.app/Contents/MacOS/soffice',
|
||||||
|
'--nologo',
|
||||||
|
'--norestore',
|
||||||
|
'--minimized',
|
||||||
|
'--nodefault',
|
||||||
|
'--nofirststartwizard',
|
||||||
|
'--accept=pipe,name=openlp_maclo;urp;StarOffice.ServiceManager'
|
||||||
|
])
|
||||||
|
assert server._process is mocked_process
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
def test_desktop_already_has_desktop(mocked_uno):
|
||||||
|
"""
|
||||||
|
Test that setup_desktop() exits early when there's already a desktop
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
server._desktop = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: the desktop property is called
|
||||||
|
desktop = server.desktop
|
||||||
|
|
||||||
|
# THEN: setup_desktop() exits early
|
||||||
|
assert desktop is server._desktop
|
||||||
|
assert server._manager is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
def test_desktop_exception(mocked_uno):
|
||||||
|
"""
|
||||||
|
Test that setting up the desktop works correctly when an exception occurs
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_context = MagicMock()
|
||||||
|
mocked_resolver = MagicMock()
|
||||||
|
mocked_uno_instance = MagicMock()
|
||||||
|
MockedServiceManager = MagicMock()
|
||||||
|
mocked_uno.getComponentContext.return_value = mocked_context
|
||||||
|
mocked_context.ServiceManager.createInstanceWithContext.return_value = mocked_resolver
|
||||||
|
mocked_resolver.resolve.side_effect = [Exception, mocked_uno_instance]
|
||||||
|
mocked_uno_instance.ServiceManager = MockedServiceManager
|
||||||
|
MockedServiceManager.createInstanceWithContext.side_effect = Exception()
|
||||||
|
|
||||||
|
# WHEN: the desktop property is called
|
||||||
|
server.desktop
|
||||||
|
|
||||||
|
# THEN: A desktop object was created
|
||||||
|
mocked_uno.getComponentContext.assert_called_once_with()
|
||||||
|
mocked_context.ServiceManager.createInstanceWithContext.assert_called_once_with(
|
||||||
|
'com.sun.star.bridge.UnoUrlResolver', mocked_context)
|
||||||
|
expected_calls = [
|
||||||
|
call('uno:pipe,name=openlp_maclo;urp;StarOffice.ComponentContext'),
|
||||||
|
call('uno:pipe,name=openlp_maclo;urp;StarOffice.ComponentContext')
|
||||||
|
]
|
||||||
|
assert mocked_resolver.resolve.call_args_list == expected_calls
|
||||||
|
MockedServiceManager.createInstanceWithContext.assert_called_once_with(
|
||||||
|
'com.sun.star.frame.Desktop', mocked_uno_instance)
|
||||||
|
assert server._manager is MockedServiceManager
|
||||||
|
assert server._desktop is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
def test_desktop(mocked_uno):
|
||||||
|
"""
|
||||||
|
Test that setting up the desktop works correctly
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_context = MagicMock()
|
||||||
|
mocked_resolver = MagicMock()
|
||||||
|
mocked_uno_instance = MagicMock()
|
||||||
|
MockedServiceManager = MagicMock()
|
||||||
|
mocked_desktop = MagicMock()
|
||||||
|
mocked_uno.getComponentContext.return_value = mocked_context
|
||||||
|
mocked_context.ServiceManager.createInstanceWithContext.return_value = mocked_resolver
|
||||||
|
mocked_resolver.resolve.side_effect = [Exception, mocked_uno_instance]
|
||||||
|
mocked_uno_instance.ServiceManager = MockedServiceManager
|
||||||
|
MockedServiceManager.createInstanceWithContext.return_value = mocked_desktop
|
||||||
|
|
||||||
|
# WHEN: the desktop property is called
|
||||||
|
server.desktop
|
||||||
|
|
||||||
|
# THEN: A desktop object was created
|
||||||
|
mocked_uno.getComponentContext.assert_called_once_with()
|
||||||
|
mocked_context.ServiceManager.createInstanceWithContext.assert_called_once_with(
|
||||||
|
'com.sun.star.bridge.UnoUrlResolver', mocked_context)
|
||||||
|
expected_calls = [
|
||||||
|
call('uno:pipe,name=openlp_maclo;urp;StarOffice.ComponentContext'),
|
||||||
|
call('uno:pipe,name=openlp_maclo;urp;StarOffice.ComponentContext')
|
||||||
|
]
|
||||||
|
assert mocked_resolver.resolve.call_args_list == expected_calls
|
||||||
|
MockedServiceManager.createInstanceWithContext.assert_called_once_with(
|
||||||
|
'com.sun.star.frame.Desktop', mocked_uno_instance)
|
||||||
|
assert server._manager is MockedServiceManager
|
||||||
|
assert server._desktop is mocked_desktop
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.PropertyValue')
|
||||||
|
def test_create_property(MockedPropertyValue):
|
||||||
|
"""
|
||||||
|
Test that the _create_property() method works correctly
|
||||||
|
"""
|
||||||
|
# GIVEN: A server amnd property to set
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
name = 'Hidden'
|
||||||
|
value = True
|
||||||
|
|
||||||
|
# WHEN: The _create_property() method is called
|
||||||
|
prop = server._create_property(name, value)
|
||||||
|
|
||||||
|
# THEN: The property should have the correct attributes
|
||||||
|
assert prop.Name == name
|
||||||
|
assert prop.Value == value
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_text_from_page_slide_text():
|
||||||
|
"""
|
||||||
|
Test that the _get_text_from_page() method gives us nothing for slide text
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer object and some mocked objects
|
||||||
|
text_type = TextType.SlideText
|
||||||
|
slide_no = 1
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
server._document = MagicMock()
|
||||||
|
mocked_pages = MagicMock()
|
||||||
|
mocked_page = MagicMock()
|
||||||
|
mocked_shape = MagicMock()
|
||||||
|
server._document.getDrawPages.return_value = mocked_pages
|
||||||
|
mocked_pages.getCount.return_value = 1
|
||||||
|
mocked_pages.getByIndex.return_value = mocked_page
|
||||||
|
mocked_page.getByIndex.return_value = mocked_shape
|
||||||
|
mocked_shape.getShapeType.return_value = 'com.sun.star.presentation.TitleTextShape'
|
||||||
|
mocked_shape.supportsService.return_value = True
|
||||||
|
mocked_shape.getString.return_value = 'Page Text'
|
||||||
|
|
||||||
|
# WHEN: _get_text_from_page() is run for slide text
|
||||||
|
text = server._get_text_from_page(slide_no, text_type)
|
||||||
|
|
||||||
|
# THE: The text is correct
|
||||||
|
assert text == 'Page Text\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_text_from_page_title():
|
||||||
|
"""
|
||||||
|
Test that the _get_text_from_page() method gives us the text from the titles
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer object and some mocked objects
|
||||||
|
text_type = TextType.Title
|
||||||
|
slide_no = 1
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
server._document = MagicMock()
|
||||||
|
mocked_pages = MagicMock()
|
||||||
|
mocked_page = MagicMock()
|
||||||
|
mocked_shape = MagicMock()
|
||||||
|
server._document.getDrawPages.return_value = mocked_pages
|
||||||
|
mocked_pages.getCount.return_value = 1
|
||||||
|
mocked_pages.getByIndex.return_value = mocked_page
|
||||||
|
mocked_page.getByIndex.return_value = mocked_shape
|
||||||
|
mocked_shape.getShapeType.return_value = 'com.sun.star.presentation.TitleTextShape'
|
||||||
|
mocked_shape.supportsService.return_value = True
|
||||||
|
mocked_shape.getString.return_value = 'Page Title'
|
||||||
|
|
||||||
|
# WHEN: _get_text_from_page() is run for titles
|
||||||
|
text = server._get_text_from_page(slide_no, text_type)
|
||||||
|
|
||||||
|
# THEN: The text should be correct
|
||||||
|
assert text == 'Page Title\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_text_from_page_notes():
|
||||||
|
"""
|
||||||
|
Test that the _get_text_from_page() method gives us the text from the notes
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer object and some mocked objects
|
||||||
|
text_type = TextType.Notes
|
||||||
|
slide_no = 1
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
server._document = MagicMock()
|
||||||
|
mocked_pages = MagicMock()
|
||||||
|
mocked_page = MagicMock()
|
||||||
|
mocked_notes_page = MagicMock()
|
||||||
|
mocked_shape = MagicMock()
|
||||||
|
server._document.getDrawPages.return_value = mocked_pages
|
||||||
|
mocked_pages.getCount.return_value = 1
|
||||||
|
mocked_pages.getByIndex.return_value = mocked_page
|
||||||
|
mocked_page.getNotesPage.return_value = mocked_notes_page
|
||||||
|
mocked_notes_page.getByIndex.return_value = mocked_shape
|
||||||
|
mocked_shape.getShapeType.return_value = 'com.sun.star.presentation.TitleTextShape'
|
||||||
|
mocked_shape.supportsService.return_value = True
|
||||||
|
mocked_shape.getString.return_value = 'Page Notes'
|
||||||
|
|
||||||
|
# WHEN: _get_text_from_page() is run for titles
|
||||||
|
text = server._get_text_from_page(slide_no, text_type)
|
||||||
|
|
||||||
|
# THEN: The text should be correct
|
||||||
|
assert text == 'Page Notes\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_shutdown_other_docs():
|
||||||
|
"""
|
||||||
|
Test the shutdown method while other documents are open in LibreOffice
|
||||||
|
"""
|
||||||
|
def close_docs():
|
||||||
|
server._docs = []
|
||||||
|
|
||||||
|
# GIVEN: An up an running LibreOfficeServer
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_doc = MagicMock()
|
||||||
|
mocked_desktop = MagicMock()
|
||||||
|
mocked_docs = MagicMock()
|
||||||
|
mocked_list = MagicMock()
|
||||||
|
mocked_element_doc = MagicMock()
|
||||||
|
server._docs = [mocked_doc]
|
||||||
|
server._desktop = mocked_desktop
|
||||||
|
server._process = MagicMock()
|
||||||
|
mocked_doc.close_presentation.side_effect = close_docs
|
||||||
|
mocked_desktop.getComponents.return_value = mocked_docs
|
||||||
|
mocked_docs.hasElements.return_value = True
|
||||||
|
mocked_docs.createEnumeration.return_value = mocked_list
|
||||||
|
mocked_list.hasMoreElements.side_effect = [True, False]
|
||||||
|
mocked_list.nextElement.return_value = mocked_element_doc
|
||||||
|
mocked_element_doc.getImplementationName.side_effect = [
|
||||||
|
'org.openlp.Nothing',
|
||||||
|
'com.sun.star.comp.framework.BackingComp'
|
||||||
|
]
|
||||||
|
|
||||||
|
# WHEN: shutdown() is called
|
||||||
|
server.shutdown()
|
||||||
|
|
||||||
|
# THEN: The right methods are called and everything works
|
||||||
|
mocked_doc.close_presentation.assert_called_once_with()
|
||||||
|
mocked_desktop.getComponents.assert_called_once_with()
|
||||||
|
mocked_docs.hasElements.assert_called_once_with()
|
||||||
|
mocked_docs.createEnumeration.assert_called_once_with()
|
||||||
|
assert mocked_list.hasMoreElements.call_count == 2
|
||||||
|
mocked_list.nextElement.assert_called_once_with()
|
||||||
|
mocked_element_doc.getImplementationName.assert_called_once_with()
|
||||||
|
assert mocked_desktop.terminate.call_count == 0
|
||||||
|
assert server._process.kill.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_shutdown():
|
||||||
|
"""
|
||||||
|
Test the shutdown method
|
||||||
|
"""
|
||||||
|
def close_docs():
|
||||||
|
server._docs = []
|
||||||
|
|
||||||
|
# GIVEN: An up an running LibreOfficeServer
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_doc = MagicMock()
|
||||||
|
mocked_desktop = MagicMock()
|
||||||
|
mocked_docs = MagicMock()
|
||||||
|
mocked_list = MagicMock()
|
||||||
|
mocked_element_doc = MagicMock()
|
||||||
|
server._docs = [mocked_doc]
|
||||||
|
server._desktop = mocked_desktop
|
||||||
|
server._process = MagicMock()
|
||||||
|
mocked_doc.close_presentation.side_effect = close_docs
|
||||||
|
mocked_desktop.getComponents.return_value = mocked_docs
|
||||||
|
mocked_docs.hasElements.return_value = True
|
||||||
|
mocked_docs.createEnumeration.return_value = mocked_list
|
||||||
|
mocked_list.hasMoreElements.side_effect = [True, False]
|
||||||
|
mocked_list.nextElement.return_value = mocked_element_doc
|
||||||
|
mocked_element_doc.getImplementationName.return_value = 'com.sun.star.comp.framework.BackingComp'
|
||||||
|
|
||||||
|
# WHEN: shutdown() is called
|
||||||
|
server.shutdown()
|
||||||
|
|
||||||
|
# THEN: The right methods are called and everything works
|
||||||
|
mocked_doc.close_presentation.assert_called_once_with()
|
||||||
|
mocked_desktop.getComponents.assert_called_once_with()
|
||||||
|
mocked_docs.hasElements.assert_called_once_with()
|
||||||
|
mocked_docs.createEnumeration.assert_called_once_with()
|
||||||
|
assert mocked_list.hasMoreElements.call_count == 2
|
||||||
|
mocked_list.nextElement.assert_called_once_with()
|
||||||
|
mocked_element_doc.getImplementationName.assert_called_once_with()
|
||||||
|
mocked_desktop.terminate.assert_called_once_with()
|
||||||
|
server._process.kill.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
def test_load_presentation_exception(mocked_uno):
|
||||||
|
"""
|
||||||
|
Test the load_presentation() method when an exception occurs
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer object
|
||||||
|
presentation_file = '/path/to/presentation.odp'
|
||||||
|
screen_number = 1
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_desktop = MagicMock()
|
||||||
|
mocked_uno.systemPathToFileUrl.side_effect = lambda x: x
|
||||||
|
server._desktop = mocked_desktop
|
||||||
|
mocked_desktop.loadComponentFromURL.side_effect = Exception()
|
||||||
|
|
||||||
|
# WHEN: load_presentation() is called
|
||||||
|
with patch.object(server, '_create_property') as mocked_create_property:
|
||||||
|
mocked_create_property.side_effect = lambda x, y: {x: y}
|
||||||
|
result = server.load_presentation(presentation_file, screen_number)
|
||||||
|
|
||||||
|
# THEN: A presentation is loaded
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
def test_load_presentation(mocked_uno):
|
||||||
|
"""
|
||||||
|
Test the load_presentation() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer object
|
||||||
|
presentation_file = '/path/to/presentation.odp'
|
||||||
|
screen_number = 1
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_desktop = MagicMock()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_presentation = MagicMock()
|
||||||
|
mocked_uno.systemPathToFileUrl.side_effect = lambda x: x
|
||||||
|
server._desktop = mocked_desktop
|
||||||
|
mocked_desktop.loadComponentFromURL.return_value = mocked_document
|
||||||
|
mocked_document.getPresentation.return_value = mocked_presentation
|
||||||
|
|
||||||
|
# WHEN: load_presentation() is called
|
||||||
|
with patch.object(server, '_create_property') as mocked_create_property:
|
||||||
|
mocked_create_property.side_effect = lambda x, y: {x: y}
|
||||||
|
result = server.load_presentation(presentation_file, screen_number)
|
||||||
|
|
||||||
|
# THEN: A presentation is loaded
|
||||||
|
assert result is True
|
||||||
|
mocked_uno.systemPathToFileUrl.assert_called_once_with(presentation_file)
|
||||||
|
mocked_create_property.assert_called_once_with('Hidden', True)
|
||||||
|
mocked_desktop.loadComponentFromURL.assert_called_once_with(
|
||||||
|
presentation_file, '_blank', 0, ({'Hidden': True},))
|
||||||
|
assert server._document is mocked_document
|
||||||
|
mocked_document.getPresentation.assert_called_once_with()
|
||||||
|
assert server._presentation is mocked_presentation
|
||||||
|
assert server._presentation.Display == screen_number
|
||||||
|
assert server._control is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
def test_extract_thumbnails_no_pages(mocked_uno):
|
||||||
|
"""
|
||||||
|
Test the extract_thumbnails() method when there are no pages
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
temp_folder = '/tmp'
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
mocked_uno.systemPathToFileUrl.side_effect = lambda x: x
|
||||||
|
mocked_document.getDrawPages.return_value = None
|
||||||
|
|
||||||
|
# WHEN: The extract_thumbnails() method is called
|
||||||
|
with patch.object(server, '_create_property') as mocked_create_property:
|
||||||
|
mocked_create_property.side_effect = lambda x, y: {x: y}
|
||||||
|
thumbnails = server.extract_thumbnails(temp_folder)
|
||||||
|
|
||||||
|
# THEN: Thumbnails have been extracted
|
||||||
|
mocked_uno.systemPathToFileUrl.assert_called_once_with(temp_folder)
|
||||||
|
mocked_create_property.assert_called_once_with('FilterName', 'impress_png_Export')
|
||||||
|
mocked_document.getDrawPages.assert_called_once_with()
|
||||||
|
assert thumbnails == []
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.uno')
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.os')
|
||||||
|
def test_extract_thumbnails(mocked_os, mocked_uno):
|
||||||
|
"""
|
||||||
|
Test the extract_thumbnails() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
temp_folder = '/tmp'
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_pages = MagicMock()
|
||||||
|
mocked_page_1 = MagicMock()
|
||||||
|
mocked_page_2 = MagicMock()
|
||||||
|
mocked_controller = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
mocked_uno.systemPathToFileUrl.side_effect = lambda x: x
|
||||||
|
mocked_document.getDrawPages.return_value = mocked_pages
|
||||||
|
mocked_os.path.isdir.return_value = False
|
||||||
|
mocked_pages.getCount.return_value = 2
|
||||||
|
mocked_pages.getByIndex.side_effect = [mocked_page_1, mocked_page_2]
|
||||||
|
mocked_document.getCurrentController.return_value = mocked_controller
|
||||||
|
mocked_os.path.join.side_effect = lambda *x: '/'.join(x)
|
||||||
|
|
||||||
|
# WHEN: The extract_thumbnails() method is called
|
||||||
|
with patch.object(server, '_create_property') as mocked_create_property:
|
||||||
|
mocked_create_property.side_effect = lambda x, y: {x: y}
|
||||||
|
thumbnails = server.extract_thumbnails(temp_folder)
|
||||||
|
|
||||||
|
# THEN: Thumbnails have been extracted
|
||||||
|
mocked_uno.systemPathToFileUrl.assert_called_once_with(temp_folder)
|
||||||
|
mocked_create_property.assert_called_once_with('FilterName', 'impress_png_Export')
|
||||||
|
mocked_document.getDrawPages.assert_called_once_with()
|
||||||
|
mocked_pages.getCount.assert_called_once_with()
|
||||||
|
assert mocked_pages.getByIndex.call_args_list == [call(0), call(1)]
|
||||||
|
assert mocked_controller.setCurrentPage.call_args_list == \
|
||||||
|
[call(mocked_page_1), call(mocked_page_2)]
|
||||||
|
assert mocked_document.storeToURL.call_args_list == \
|
||||||
|
[call('/tmp/1.png', ({'FilterName': 'impress_png_Export'},)),
|
||||||
|
call('/tmp/2.png', ({'FilterName': 'impress_png_Export'},))]
|
||||||
|
assert thumbnails == ['/tmp/1.png', '/tmp/2.png']
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_titles_and_notes():
|
||||||
|
"""
|
||||||
|
Test the get_titles_and_notes() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer object and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_pages = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
mocked_document.getDrawPages.return_value = mocked_pages
|
||||||
|
mocked_pages.getCount.return_value = 2
|
||||||
|
|
||||||
|
# WHEN: get_titles_and_notes() is called
|
||||||
|
with patch.object(server, '_get_text_from_page') as mocked_get_text_from_page:
|
||||||
|
mocked_get_text_from_page.side_effect = [
|
||||||
|
'OpenLP on Mac OS X',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'Installing is a drag-and-drop affair'
|
||||||
|
]
|
||||||
|
titles, notes = server.get_titles_and_notes()
|
||||||
|
|
||||||
|
# THEN: The right calls are made and the right stuff returned
|
||||||
|
mocked_document.getDrawPages.assert_called_once_with()
|
||||||
|
mocked_pages.getCount.assert_called_once_with()
|
||||||
|
assert mocked_get_text_from_page.call_count == 4
|
||||||
|
expected_calls = [
|
||||||
|
call(1, TextType.Title), call(1, TextType.Notes),
|
||||||
|
call(2, TextType.Title), call(2, TextType.Notes),
|
||||||
|
]
|
||||||
|
assert mocked_get_text_from_page.call_args_list == expected_calls
|
||||||
|
assert titles == ['OpenLP on Mac OS X\n', '\n'], titles
|
||||||
|
assert notes == [' ', 'Installing is a drag-and-drop affair'], notes
|
||||||
|
|
||||||
|
|
||||||
|
def test_close_presentation():
|
||||||
|
"""
|
||||||
|
Test that closing the presentation cleans things up correctly
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_presentation = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
server._presentation = mocked_presentation
|
||||||
|
|
||||||
|
# WHEN: close_presentation() is called
|
||||||
|
server.close_presentation()
|
||||||
|
|
||||||
|
# THEN: The presentation and document should be closed
|
||||||
|
mocked_presentation.end.assert_called_once_with()
|
||||||
|
mocked_document.dispose.assert_called_once_with()
|
||||||
|
assert server._document is None
|
||||||
|
assert server._presentation is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_loaded_no_objects():
|
||||||
|
"""
|
||||||
|
Test the is_loaded() method when there's no document or presentation
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: The is_loaded() method is called
|
||||||
|
result = server.is_loaded()
|
||||||
|
|
||||||
|
# THEN: The result should be false
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_loaded_no_presentation():
|
||||||
|
"""
|
||||||
|
Test the is_loaded() method when there's no presentation
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
server._presentation = MagicMock()
|
||||||
|
mocked_document.getPresentation.return_value = None
|
||||||
|
|
||||||
|
# WHEN: The is_loaded() method is called
|
||||||
|
result = server.is_loaded()
|
||||||
|
|
||||||
|
# THEN: The result should be false
|
||||||
|
assert result is False
|
||||||
|
mocked_document.getPresentation.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_loaded_exception():
|
||||||
|
"""
|
||||||
|
Test the is_loaded() method when an exception is thrown
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
server._presentation = MagicMock()
|
||||||
|
mocked_document.getPresentation.side_effect = Exception()
|
||||||
|
|
||||||
|
# WHEN: The is_loaded() method is called
|
||||||
|
result = server.is_loaded()
|
||||||
|
|
||||||
|
# THEN: The result should be false
|
||||||
|
assert result is False
|
||||||
|
mocked_document.getPresentation.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_loaded():
|
||||||
|
"""
|
||||||
|
Test the is_loaded() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_presentation = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
server._presentation = mocked_presentation
|
||||||
|
mocked_document.getPresentation.return_value = mocked_presentation
|
||||||
|
|
||||||
|
# WHEN: The is_loaded() method is called
|
||||||
|
result = server.is_loaded()
|
||||||
|
|
||||||
|
# THEN: The result should be false
|
||||||
|
assert result is True
|
||||||
|
mocked_document.getPresentation.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_active_not_loaded():
|
||||||
|
"""
|
||||||
|
Test is_active() when is_loaded() returns False
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: is_active() is called with is_loaded() returns False
|
||||||
|
with patch.object(server, 'is_loaded') as mocked_is_loaded:
|
||||||
|
mocked_is_loaded.return_value = False
|
||||||
|
result = server.is_active()
|
||||||
|
|
||||||
|
# THEN: It should have returned False
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_active_no_control():
|
||||||
|
"""
|
||||||
|
Test is_active() when is_loaded() returns True but there's no control
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: is_active() is called with is_loaded() returns False
|
||||||
|
with patch.object(server, 'is_loaded') as mocked_is_loaded:
|
||||||
|
mocked_is_loaded.return_value = True
|
||||||
|
result = server.is_active()
|
||||||
|
|
||||||
|
# THEN: The result should be False
|
||||||
|
assert result is False
|
||||||
|
mocked_is_loaded.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_active():
|
||||||
|
"""
|
||||||
|
Test is_active()
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
mocked_control.isRunning.return_value = True
|
||||||
|
|
||||||
|
# WHEN: is_active() is called with is_loaded() returns False
|
||||||
|
with patch.object(server, 'is_loaded') as mocked_is_loaded:
|
||||||
|
mocked_is_loaded.return_value = True
|
||||||
|
result = server.is_active()
|
||||||
|
|
||||||
|
# THEN: The result should be False
|
||||||
|
assert result is True
|
||||||
|
mocked_is_loaded.assert_called_once_with()
|
||||||
|
mocked_control.isRunning.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unblank_screen():
|
||||||
|
"""
|
||||||
|
Test the unblank_screen() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: unblank_screen() is run
|
||||||
|
server.unblank_screen()
|
||||||
|
|
||||||
|
# THEN: The resume method should have been called
|
||||||
|
mocked_control.resume.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_blank_screen():
|
||||||
|
"""
|
||||||
|
Test the blank_screen() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: blank_screen() is run
|
||||||
|
server.blank_screen()
|
||||||
|
|
||||||
|
# THEN: The resume method should have been called
|
||||||
|
mocked_control.blankScreen.assert_called_once_with(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_blank_no_control():
|
||||||
|
"""
|
||||||
|
Test the is_blank() method when there's no control
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: is_blank() is called
|
||||||
|
result = server.is_blank()
|
||||||
|
|
||||||
|
# THEN: It should have returned False
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_blank_control_is_running():
|
||||||
|
"""
|
||||||
|
Test the is_blank() method when the control is running
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a bunch of mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
mocked_control.isRunning.return_value = True
|
||||||
|
mocked_control.isPaused.return_value = True
|
||||||
|
|
||||||
|
# WHEN: is_blank() is called
|
||||||
|
result = server.is_blank()
|
||||||
|
|
||||||
|
# THEN: It should have returned False
|
||||||
|
assert result is True
|
||||||
|
mocked_control.isRunning.assert_called_once_with()
|
||||||
|
mocked_control.isPaused.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_stop_presentation():
|
||||||
|
"""
|
||||||
|
Test the stop_presentation() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a mocked presentation
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_presentation = MagicMock()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._presentation = mocked_presentation
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: stop_presentation() is called
|
||||||
|
server.stop_presentation()
|
||||||
|
|
||||||
|
# THEN: The presentation is ended and the control is removed
|
||||||
|
mocked_presentation.end.assert_called_once_with()
|
||||||
|
assert server._control is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.time.sleep')
|
||||||
|
def test_start_presentation_no_control(mocked_sleep):
|
||||||
|
"""
|
||||||
|
Test the start_presentation() method when there's no control
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and some mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_presentation = MagicMock()
|
||||||
|
mocked_controller = MagicMock()
|
||||||
|
mocked_frame = MagicMock()
|
||||||
|
mocked_window = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
server._presentation = mocked_presentation
|
||||||
|
mocked_document.getCurrentController.return_value = mocked_controller
|
||||||
|
mocked_controller.getFrame.return_value = mocked_frame
|
||||||
|
mocked_frame.getContainerWindow.return_value = mocked_window
|
||||||
|
mocked_presentation.getController.side_effect = [None, mocked_control]
|
||||||
|
|
||||||
|
# WHEN: start_presentation() is called
|
||||||
|
server.start_presentation()
|
||||||
|
|
||||||
|
# THEN: The slide number should be correct
|
||||||
|
mocked_document.getCurrentController.assert_called_once_with()
|
||||||
|
mocked_controller.getFrame.assert_called_once_with()
|
||||||
|
mocked_frame.getContainerWindow.assert_called_once_with()
|
||||||
|
mocked_presentation.start.assert_called_once_with()
|
||||||
|
assert mocked_presentation.getController.call_count == 2
|
||||||
|
mocked_sleep.assert_called_once_with(0.1)
|
||||||
|
assert mocked_window.setVisible.call_args_list == [call(True), call(False)]
|
||||||
|
assert server._control is mocked_control
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_presentation():
|
||||||
|
"""
|
||||||
|
Test the start_presentation() method when there's a control
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and some mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: start_presentation() is called
|
||||||
|
with patch.object(server, 'goto_slide') as mocked_goto_slide:
|
||||||
|
server.start_presentation()
|
||||||
|
|
||||||
|
# THEN: The control should have been activated and the first slide selected
|
||||||
|
mocked_control.activate.assert_called_once_with()
|
||||||
|
mocked_goto_slide.assert_called_once_with(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_slide_number():
|
||||||
|
"""
|
||||||
|
Test the get_slide_number() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and some mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
mocked_control.getCurrentSlideIndex.return_value = 3
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: get_slide_number() is called
|
||||||
|
result = server.get_slide_number()
|
||||||
|
|
||||||
|
# THEN: The slide number should be correct
|
||||||
|
assert result == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_slide_count():
|
||||||
|
"""
|
||||||
|
Test the get_slide_count() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and some mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_document = MagicMock()
|
||||||
|
mocked_pages = MagicMock()
|
||||||
|
server._document = mocked_document
|
||||||
|
mocked_document.getDrawPages.return_value = mocked_pages
|
||||||
|
mocked_pages.getCount.return_value = 2
|
||||||
|
|
||||||
|
# WHEN: get_slide_count() is called
|
||||||
|
result = server.get_slide_count()
|
||||||
|
|
||||||
|
# THEN: The slide count should be correct
|
||||||
|
assert result == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_goto_slide():
|
||||||
|
"""
|
||||||
|
Test the goto_slide() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and some mocks
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: goto_slide() is called
|
||||||
|
server.goto_slide(1)
|
||||||
|
|
||||||
|
# THEN: The slide number should be correct
|
||||||
|
mocked_control.gotoSlideIndex.assert_called_once_with(0)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.time.sleep')
|
||||||
|
def test_next_step_when_paused(mocked_sleep):
|
||||||
|
"""
|
||||||
|
Test the next_step() method when paused
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a mocked control
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
mocked_control.isPaused.side_effect = [False, True]
|
||||||
|
|
||||||
|
# WHEN: next_step() is called
|
||||||
|
server.next_step()
|
||||||
|
|
||||||
|
# THEN: The correct call should be made
|
||||||
|
mocked_control.gotoNextEffect.assert_called_once_with()
|
||||||
|
mocked_sleep.assert_called_once_with(0.1)
|
||||||
|
assert mocked_control.isPaused.call_count == 2
|
||||||
|
mocked_control.gotoPreviousEffect.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.time.sleep')
|
||||||
|
def test_next_step(mocked_sleep):
|
||||||
|
"""
|
||||||
|
Test the next_step() method when paused
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a mocked control
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
mocked_control.isPaused.side_effect = [True, True]
|
||||||
|
|
||||||
|
# WHEN: next_step() is called
|
||||||
|
server.next_step()
|
||||||
|
|
||||||
|
# THEN: The correct call should be made
|
||||||
|
mocked_control.gotoNextEffect.assert_called_once_with()
|
||||||
|
mocked_sleep.assert_called_once_with(0.1)
|
||||||
|
assert mocked_control.isPaused.call_count == 1
|
||||||
|
assert mocked_control.gotoPreviousEffect.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_step():
|
||||||
|
"""
|
||||||
|
Test the previous_step() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance and a mocked control
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
mocked_control = MagicMock()
|
||||||
|
server._control = mocked_control
|
||||||
|
|
||||||
|
# WHEN: previous_step() is called
|
||||||
|
server.previous_step()
|
||||||
|
|
||||||
|
# THEN: The correct call should be made
|
||||||
|
mocked_control.gotoPreviousEffect.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_slide_text():
|
||||||
|
"""
|
||||||
|
Test the get_slide_text() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: get_slide_text() is called for a particular slide
|
||||||
|
with patch.object(server, '_get_text_from_page') as mocked_get_text_from_page:
|
||||||
|
mocked_get_text_from_page.return_value = 'OpenLP on Mac OS X'
|
||||||
|
result = server.get_slide_text(5)
|
||||||
|
|
||||||
|
# THEN: The text should be returned
|
||||||
|
mocked_get_text_from_page.assert_called_once_with(5)
|
||||||
|
assert result == 'OpenLP on Mac OS X'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_slide_notes():
|
||||||
|
"""
|
||||||
|
Test the get_slide_notes() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A LibreOfficeServer instance
|
||||||
|
server = LibreOfficeServer()
|
||||||
|
|
||||||
|
# WHEN: get_slide_notes() is called for a particular slide
|
||||||
|
with patch.object(server, '_get_text_from_page') as mocked_get_text_from_page:
|
||||||
|
mocked_get_text_from_page.return_value = 'Installing is a drag-and-drop affair'
|
||||||
|
result = server.get_slide_notes(3)
|
||||||
|
|
||||||
|
# THEN: The text should be returned
|
||||||
|
mocked_get_text_from_page.assert_called_once_with(3, TextType.Notes)
|
||||||
|
assert result == 'Installing is a drag-and-drop affair'
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.libreofficeserver.Daemon')
|
||||||
|
def test_main(MockedDaemon):
|
||||||
|
"""
|
||||||
|
Test the main() function
|
||||||
|
"""
|
||||||
|
# GIVEN: Mocked out Pyro objects
|
||||||
|
mocked_daemon = MagicMock()
|
||||||
|
MockedDaemon.return_value = mocked_daemon
|
||||||
|
|
||||||
|
# WHEN: main() is run
|
||||||
|
main()
|
||||||
|
|
||||||
|
# THEN: The correct calls are made
|
||||||
|
MockedDaemon.assert_called_once_with(host='localhost', port=4310)
|
||||||
|
mocked_daemon.register.assert_called_once_with(LibreOfficeServer, 'openlp.libreofficeserver')
|
||||||
|
mocked_daemon.requestLoop.assert_called_once_with()
|
||||||
|
mocked_daemon.close.assert_called_once_with()
|
@ -0,0 +1,453 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2019 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/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
Functional tests to test the Mac LibreOffice class and related methods.
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import MagicMock, patch, call
|
||||||
|
|
||||||
|
from openlp.core.common.settings import Settings
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.plugins.presentations.lib.maclocontroller import MacLOController, MacLODocument
|
||||||
|
from openlp.plugins.presentations.presentationplugin import __default_settings__
|
||||||
|
|
||||||
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
|
||||||
|
|
||||||
|
class TestMacLOController(TestCase, TestMixin):
|
||||||
|
"""
|
||||||
|
Test the MacLOController Class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Set up the patches and mocks need for all tests.
|
||||||
|
"""
|
||||||
|
self.setup_application()
|
||||||
|
self.build_settings()
|
||||||
|
self.mock_plugin = MagicMock()
|
||||||
|
self.temp_folder = mkdtemp()
|
||||||
|
self.mock_plugin.settings_section = self.temp_folder
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""
|
||||||
|
Stop the patches
|
||||||
|
"""
|
||||||
|
self.destroy_settings()
|
||||||
|
shutil.rmtree(self.temp_folder)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server')
|
||||||
|
def test_constructor(self, mocked_start_server):
|
||||||
|
"""
|
||||||
|
Test the Constructor from the MacLOController
|
||||||
|
"""
|
||||||
|
# GIVEN: No presentation controller
|
||||||
|
controller = None
|
||||||
|
|
||||||
|
# WHEN: The presentation controller object is created
|
||||||
|
controller = MacLOController(plugin=self.mock_plugin)
|
||||||
|
|
||||||
|
# THEN: The name of the presentation controller should be correct
|
||||||
|
assert controller.name == 'maclo', \
|
||||||
|
'The name of the presentation controller should be correct'
|
||||||
|
assert controller.display_name == 'Impress on macOS', \
|
||||||
|
'The display name of the presentation controller should be correct'
|
||||||
|
mocked_start_server.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server')
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.Proxy')
|
||||||
|
def test_client(self, MockedProxy, mocked_start_server):
|
||||||
|
"""
|
||||||
|
Test the client property of the Controller
|
||||||
|
"""
|
||||||
|
# GIVEN: A controller without a client and a mocked out Pyro
|
||||||
|
controller = MacLOController(plugin=self.mock_plugin)
|
||||||
|
mocked_client = MagicMock()
|
||||||
|
MockedProxy.return_value = mocked_client
|
||||||
|
mocked_client._pyroConnection = None
|
||||||
|
|
||||||
|
# WHEN: the client property is called the first time
|
||||||
|
client = controller.client
|
||||||
|
|
||||||
|
# THEN: a client is created
|
||||||
|
assert client == mocked_client
|
||||||
|
MockedProxy.assert_called_once_with('PYRO:openlp.libreofficeserver@localhost:4310')
|
||||||
|
mocked_client._pyroReconnect.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server')
|
||||||
|
def test_check_available(self, mocked_start_server):
|
||||||
|
"""
|
||||||
|
Test the check_available() method
|
||||||
|
"""
|
||||||
|
from openlp.plugins.presentations.lib.maclocontroller import macuno_available
|
||||||
|
|
||||||
|
# GIVEN: A controller
|
||||||
|
controller = MacLOController(plugin=self.mock_plugin)
|
||||||
|
|
||||||
|
# WHEN: check_available() is run
|
||||||
|
result = controller.check_available()
|
||||||
|
|
||||||
|
# THEN: it should return false
|
||||||
|
assert result == macuno_available
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server')
|
||||||
|
def test_start_process(self, mocked_start_server):
|
||||||
|
"""
|
||||||
|
Test the start_process() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A controller and a client
|
||||||
|
controller = MacLOController(plugin=self.mock_plugin)
|
||||||
|
controller._client = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: start_process() is called
|
||||||
|
controller.start_process()
|
||||||
|
|
||||||
|
# THEN: The client's start_process() should have been called
|
||||||
|
controller._client.start_process.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server')
|
||||||
|
def test_kill(self, mocked_start_server):
|
||||||
|
"""
|
||||||
|
Test the kill() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A controller and a client
|
||||||
|
controller = MacLOController(plugin=self.mock_plugin)
|
||||||
|
controller._client = MagicMock()
|
||||||
|
controller.server_process = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: start_process() is called
|
||||||
|
controller.kill()
|
||||||
|
|
||||||
|
# THEN: The client's start_process() should have been called
|
||||||
|
controller._client.shutdown.assert_called_once_with()
|
||||||
|
controller.server_process.kill.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
class TestMacLODocument(TestCase):
|
||||||
|
"""
|
||||||
|
Test the MacLODocument Class
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
mocked_plugin = MagicMock()
|
||||||
|
mocked_plugin.settings_section = 'presentations'
|
||||||
|
Settings().extend_default_settings(__default_settings__)
|
||||||
|
self.file_name = Path(TEST_RESOURCES_PATH) / 'presentations' / 'test.odp'
|
||||||
|
self.mocked_client = MagicMock()
|
||||||
|
with patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server'):
|
||||||
|
self.controller = MacLOController(mocked_plugin)
|
||||||
|
self.controller._client = self.mocked_client
|
||||||
|
self.document = MacLODocument(self.controller, self.file_name)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.ScreenList')
|
||||||
|
def test_load_presentation_cannot_load(self, MockedScreenList):
|
||||||
|
"""
|
||||||
|
Test the load_presentation() method when the server can't load the presentation
|
||||||
|
"""
|
||||||
|
# GIVEN: A document and a mocked client
|
||||||
|
mocked_screen_list = MagicMock()
|
||||||
|
MockedScreenList.return_value = mocked_screen_list
|
||||||
|
mocked_screen_list.current.number = 0
|
||||||
|
self.mocked_client.load_presentation.return_value = False
|
||||||
|
|
||||||
|
# WHEN: load_presentation() is called
|
||||||
|
result = self.document.load_presentation()
|
||||||
|
|
||||||
|
# THEN: Stuff should work right
|
||||||
|
self.mocked_client.load_presentation.assert_called_once_with(str(self.file_name), 1)
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.ScreenList')
|
||||||
|
def test_load_presentation(self, MockedScreenList):
|
||||||
|
"""
|
||||||
|
Test the load_presentation() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A document and a mocked client
|
||||||
|
mocked_screen_list = MagicMock()
|
||||||
|
MockedScreenList.return_value = mocked_screen_list
|
||||||
|
mocked_screen_list.current.number = 0
|
||||||
|
self.mocked_client.load_presentation.return_value = True
|
||||||
|
|
||||||
|
# WHEN: load_presentation() is called
|
||||||
|
with patch.object(self.document, 'create_thumbnails') as mocked_create_thumbnails, \
|
||||||
|
patch.object(self.document, 'create_titles_and_notes') as mocked_create_titles_and_notes:
|
||||||
|
result = self.document.load_presentation()
|
||||||
|
|
||||||
|
# THEN: Stuff should work right
|
||||||
|
self.mocked_client.load_presentation.assert_called_once_with(str(self.file_name), 1)
|
||||||
|
mocked_create_thumbnails.assert_called_once_with()
|
||||||
|
mocked_create_titles_and_notes.assert_called_once_with()
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_create_thumbnails_already_exist(self):
|
||||||
|
"""
|
||||||
|
Test the create_thumbnails() method when thumbnails already exist
|
||||||
|
"""
|
||||||
|
# GIVEN: thumbnails that exist and a mocked client
|
||||||
|
self.document.check_thumbnails = MagicMock(return_value=True)
|
||||||
|
|
||||||
|
# WHEN: create_thumbnails() is called
|
||||||
|
self.document.create_thumbnails()
|
||||||
|
|
||||||
|
# THEN: The method should exit early
|
||||||
|
assert self.mocked_client.extract_thumbnails.call_count == 0
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.delete_file')
|
||||||
|
def test_create_thumbnails(self, mocked_delete_file):
|
||||||
|
"""
|
||||||
|
Test the create_thumbnails() method
|
||||||
|
"""
|
||||||
|
# GIVEN: thumbnails that don't exist and a mocked client
|
||||||
|
self.document.check_thumbnails = MagicMock(return_value=False)
|
||||||
|
self.mocked_client.extract_thumbnails.return_value = ['thumb1.png', 'thumb2.png']
|
||||||
|
|
||||||
|
# WHEN: create_thumbnails() is called
|
||||||
|
with patch.object(self.document, 'convert_thumbnail') as mocked_convert_thumbnail, \
|
||||||
|
patch.object(self.document, 'get_temp_folder') as mocked_get_temp_folder:
|
||||||
|
mocked_get_temp_folder.return_value = 'temp'
|
||||||
|
self.document.create_thumbnails()
|
||||||
|
|
||||||
|
# THEN: The method should complete successfully
|
||||||
|
self.mocked_client.extract_thumbnails.assert_called_once_with('temp')
|
||||||
|
assert mocked_convert_thumbnail.call_args_list == [
|
||||||
|
call(Path('thumb1.png'), 1), call(Path('thumb2.png'), 2)]
|
||||||
|
assert mocked_delete_file.call_args_list == [call(Path('thumb1.png')), call(Path('thumb2.png'))]
|
||||||
|
|
||||||
|
def test_create_titles_and_notes(self):
|
||||||
|
"""
|
||||||
|
Test create_titles_and_notes() method
|
||||||
|
"""
|
||||||
|
# GIVEN: mocked client and mocked save_titles_and_notes() method
|
||||||
|
self.mocked_client.get_titles_and_notes.return_value = ('OpenLP', 'This is a note')
|
||||||
|
|
||||||
|
# WHEN: create_titles_and_notes() is called
|
||||||
|
with patch.object(self.document, 'save_titles_and_notes') as mocked_save_titles_and_notes:
|
||||||
|
self.document.create_titles_and_notes()
|
||||||
|
|
||||||
|
# THEN save_titles_and_notes should have been called
|
||||||
|
self.mocked_client.get_titles_and_notes.assert_called_once_with()
|
||||||
|
mocked_save_titles_and_notes.assert_called_once_with('OpenLP', 'This is a note')
|
||||||
|
|
||||||
|
def test_close_presentation(self):
|
||||||
|
"""
|
||||||
|
Test the close_presentation() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client and mocked remove_doc() method
|
||||||
|
# WHEN: close_presentation() is called
|
||||||
|
with patch.object(self.controller, 'remove_doc') as mocked_remove_doc:
|
||||||
|
self.document.close_presentation()
|
||||||
|
|
||||||
|
# THEN: The presentation should have been closed
|
||||||
|
self.mocked_client.close_presentation.assert_called_once_with()
|
||||||
|
mocked_remove_doc.assert_called_once_with(self.document)
|
||||||
|
|
||||||
|
def test_is_loaded(self):
|
||||||
|
"""
|
||||||
|
Test the is_loaded() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.is_loaded.return_value = True
|
||||||
|
|
||||||
|
# WHEN: is_loaded() is called
|
||||||
|
result = self.document.is_loaded()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_is_active(self):
|
||||||
|
"""
|
||||||
|
Test the is_active() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.is_active.return_value = True
|
||||||
|
|
||||||
|
# WHEN: is_active() is called
|
||||||
|
result = self.document.is_active()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_unblank_screen(self):
|
||||||
|
"""
|
||||||
|
Test the unblank_screen() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.unblank_screen.return_value = True
|
||||||
|
|
||||||
|
# WHEN: unblank_screen() is called
|
||||||
|
result = self.document.unblank_screen()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.unblank_screen.assert_called_once_with()
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_blank_screen(self):
|
||||||
|
"""
|
||||||
|
Test the blank_screen() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.blank_screen.return_value = True
|
||||||
|
|
||||||
|
# WHEN: blank_screen() is called
|
||||||
|
self.document.blank_screen()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.blank_screen.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_is_blank(self):
|
||||||
|
"""
|
||||||
|
Test the is_blank() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.is_blank.return_value = True
|
||||||
|
|
||||||
|
# WHEN: is_blank() is called
|
||||||
|
result = self.document.is_blank()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_stop_presentation(self):
|
||||||
|
"""
|
||||||
|
Test the stop_presentation() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.stop_presentation.return_value = True
|
||||||
|
|
||||||
|
# WHEN: stop_presentation() is called
|
||||||
|
self.document.stop_presentation()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.stop_presentation.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.ScreenList')
|
||||||
|
@patch('openlp.plugins.presentations.lib.maclocontroller.Registry')
|
||||||
|
def test_start_presentation(self, MockedRegistry, MockedScreenList):
|
||||||
|
"""
|
||||||
|
Test the start_presentation() method
|
||||||
|
"""
|
||||||
|
# GIVEN: a mocked client, and multiple screens
|
||||||
|
mocked_screen_list = MagicMock()
|
||||||
|
mocked_screen_list.__len__.return_value = 2
|
||||||
|
mocked_registry = MagicMock()
|
||||||
|
mocked_main_window = MagicMock()
|
||||||
|
MockedScreenList.return_value = mocked_screen_list
|
||||||
|
MockedRegistry.return_value = mocked_registry
|
||||||
|
mocked_screen_list.screen_list = [0, 1]
|
||||||
|
mocked_registry.get.return_value = mocked_main_window
|
||||||
|
|
||||||
|
# WHEN: start_presentation() is called
|
||||||
|
self.document.start_presentation()
|
||||||
|
|
||||||
|
# THEN: The presentation should be started
|
||||||
|
self.mocked_client.start_presentation.assert_called_once_with()
|
||||||
|
mocked_registry.get.assert_called_once_with('main_window')
|
||||||
|
mocked_main_window.activateWindow.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_get_slide_number(self):
|
||||||
|
"""
|
||||||
|
Test the get_slide_number() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.get_slide_number.return_value = 5
|
||||||
|
|
||||||
|
# WHEN: get_slide_number() is called
|
||||||
|
result = self.document.get_slide_number()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
assert result == 5
|
||||||
|
|
||||||
|
def test_get_slide_count(self):
|
||||||
|
"""
|
||||||
|
Test the get_slide_count() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.get_slide_count.return_value = 8
|
||||||
|
|
||||||
|
# WHEN: get_slide_count() is called
|
||||||
|
result = self.document.get_slide_count()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
assert result == 8
|
||||||
|
|
||||||
|
def test_goto_slide(self):
|
||||||
|
"""
|
||||||
|
Test the goto_slide() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
# WHEN: goto_slide() is called
|
||||||
|
self.document.goto_slide(3)
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.goto_slide.assert_called_once_with(3)
|
||||||
|
|
||||||
|
def test_next_step(self):
|
||||||
|
"""
|
||||||
|
Test the next_step() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
# WHEN: next_step() is called
|
||||||
|
self.document.next_step()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.next_step.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_previous_step(self):
|
||||||
|
"""
|
||||||
|
Test the previous_step() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
# WHEN: previous_step() is called
|
||||||
|
self.document.previous_step()
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.previous_step.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_get_slide_text(self):
|
||||||
|
"""
|
||||||
|
Test the get_slide_text() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.get_slide_text.return_value = 'Some slide text'
|
||||||
|
|
||||||
|
# WHEN: get_slide_text() is called
|
||||||
|
result = self.document.get_slide_text(1)
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.get_slide_text.assert_called_once_with(1)
|
||||||
|
assert result == 'Some slide text'
|
||||||
|
|
||||||
|
def test_get_slide_notes(self):
|
||||||
|
"""
|
||||||
|
Test the get_slide_notes() method
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked client
|
||||||
|
self.mocked_client.get_slide_notes.return_value = 'This is a note'
|
||||||
|
|
||||||
|
# WHEN: get_slide_notes() is called
|
||||||
|
result = self.document.get_slide_notes(2)
|
||||||
|
|
||||||
|
# THEN: Then the result should be correct
|
||||||
|
self.mocked_client.get_slide_notes.assert_called_once_with(2)
|
||||||
|
assert result == 'This is a note'
|
Loading…
Reference in New Issue
Block a user