openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #24235
[Merge] lp:~tomasgroth/openlp/better-remote into lp:openlp
Tomas Groth has proposed merging lp:~tomasgroth/openlp/better-remote into lp:openlp.
Requested reviews:
Raoul Snyman (raoul-snyman)
Tim Bentley (trb143)
For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/better-remote/+merge/234283
Continuation of Felipes work.
Changes to remote control:
- Displays the title of the slide (presentations)
- Displays the presenter's notes (presentations)
- Displays a thumbnail for each slide (presentations, images)
- Fixed bug that was preventing the remote to be updated with correct slide (presentations)
- Added thumbnails and notes to stage view when available
- Displays the service notes (general)
Changes to the main display:
- Display the title of the slide on each item on the slide controller (presentations)
Added option of remote setting tab to choose if thumbnails are displayed
--
https://code.launchpad.net/~tomasgroth/openlp/better-remote/+merge/234283
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2014-05-01 17:49:43 +0000
+++ openlp/core/lib/__init__.py 2014-09-11 10:15:15 +0000
@@ -145,11 +145,13 @@
return button_icon
-def image_to_byte(image):
+def image_to_byte(image, base_64=True):
"""
Resize an image to fit on the current screen for the web and returns it as a byte stream.
:param image: The image to converted.
+ :param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
+ To preserve original intention, this defaults to True
"""
log.debug('image_to_byte - start')
byte_array = QtCore.QByteArray()
@@ -158,6 +160,8 @@
buffie.open(QtCore.QIODevice.WriteOnly)
image.save(buffie, "PNG")
log.debug('image_to_byte - end')
+ if not base_64:
+ return byte_array
# convert to base64 encoding so does not get missed!
return bytes(byte_array.toBase64()).decode('utf-8')
=== modified file 'openlp/core/lib/imagemanager.py'
--- openlp/core/lib/imagemanager.py 2014-03-20 19:10:31 +0000
+++ openlp/core/lib/imagemanager.py 2014-09-11 10:15:15 +0000
@@ -106,7 +106,7 @@
"""
secondary_priority = 0
- def __init__(self, path, source, background):
+ def __init__(self, path, source, background, width=-1, height=-1):
"""
Create an image for the :class:`ImageManager`'s cache.
@@ -115,7 +115,8 @@
:class:`~openlp.core.lib.ImageSource` class.
:param background: A ``QtGui.QColor`` object specifying the colour to be used to fill the gabs if the image's
ratio does not match with the display ratio.
-
+ :param width: The width of the image, defaults to -1 meaning that the screen width will be used.
+ :param height: The height of the image, defaults to -1 meaning that the screen height will be used.
"""
self.path = path
self.image = None
@@ -124,6 +125,8 @@
self.source = source
self.background = background
self.timestamp = 0
+ self.width = width
+ self.height = height
# FIXME: We assume that the path exist. The caller has to take care that it exists!
if os.path.exists(path):
self.timestamp = os.stat(path).st_mtime
@@ -210,13 +213,13 @@
image.background = background
self._reset_image(image)
- def update_image_border(self, path, source, background):
+ def update_image_border(self, path, source, background, width=-1, height=-1):
"""
Border has changed so update the image affected.
"""
log.debug('update_image_border')
# Mark the image as dirty for a rebuild by setting the image and byte stream to None.
- image = self._cache[(path, source)]
+ image = self._cache[(path, source, width, height)]
if image.source == source:
image.background = background
self._reset_image(image)
@@ -237,12 +240,12 @@
if not self.image_thread.isRunning():
self.image_thread.start()
- def get_image(self, path, source):
+ def get_image(self, path, source, width=-1, height=-1):
"""
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
"""
log.debug('getImage %s' % path)
- image = self._cache[(path, source)]
+ image = self._cache[(path, source, width, height)]
if image.image is None:
self._conversion_queue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick
@@ -257,12 +260,12 @@
self._conversion_queue.modify_priority(image, Priority.Low)
return image.image
- def get_image_bytes(self, path, source):
+ def get_image_bytes(self, path, source, width=-1, height=-1):
"""
Returns the byte string for an image. If not present wait for the background thread to process it.
"""
log.debug('get_image_bytes %s' % path)
- image = self._cache[(path, source)]
+ image = self._cache[(path, source, width, height)]
if image.image_bytes is None:
self._conversion_queue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick
@@ -272,14 +275,14 @@
time.sleep(0.1)
return image.image_bytes
- def add_image(self, path, source, background):
+ def add_image(self, path, source, background, width=-1, height=-1):
"""
Add image to cache if it is not already there.
"""
log.debug('add_image %s' % path)
- if not (path, source) in self._cache:
- image = Image(path, source, background)
- self._cache[(path, source)] = image
+ if not (path, source, width, height) in self._cache:
+ image = Image(path, source, background, width, height)
+ self._cache[(path, source, width, height)] = image
self._conversion_queue.put((image.priority, image.secondary_priority, image))
# Check if the there are any images with the same path and check if the timestamp has changed.
for image in list(self._cache.values()):
@@ -308,7 +311,10 @@
image = self._conversion_queue.get()[2]
# Generate the QImage for the image.
if image.image is None:
- image.image = resize_image(image.path, self.width, self.height, image.background)
+ # Let's see if the image was requested with specific dimensions
+ width = self.width if image.width == -1 else image.width
+ height = self.height if image.height == -1 else image.height
+ image.image = resize_image(image.path, width, height, image.background)
# Set the priority to Lowest and stop here as we need to process more important images first.
if image.priority == Priority.Normal:
self._conversion_queue.modify_priority(image, Priority.Lowest)
=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py 2014-06-17 07:27:12 +0000
+++ openlp/core/lib/serviceitem.py 2014-09-11 10:15:15 +0000
@@ -39,8 +39,8 @@
from PyQt4 import QtGui
-from openlp.core.common import RegistryProperties, Settings, translate
-from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags
+from openlp.core.common import RegistryProperties, Settings, translate, AppLocation
+from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
log = logging.getLogger(__name__)
@@ -112,7 +112,17 @@
The capability to edit the title of the item
``IsOptical``
- .Determines is the service_item is based on an optical device
+ Determines is the service_item is based on an optical device
+
+ ``HasDisplayTitle``
+ The item contains 'displaytitle' on every frame which should be
+ preferred over 'title' when displaying the item
+
+ ``HasNotes``
+ The item contains 'notes'
+
+ ``HasThumbnails``
+ The item has related thumbnails available
"""
CanPreview = 1
@@ -133,6 +143,9 @@
CanAutoStartForLive = 16
CanEditTitle = 17
IsOptical = 18
+ HasDisplayTitle = 19
+ HasNotes = 20
+ HasThumbnails = 21
class ServiceItem(RegistryProperties):
@@ -272,18 +285,23 @@
self.raw_footer = []
self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
- def add_from_image(self, path, title, background=None):
+ def add_from_image(self, path, title, background=None, thumbnail=None):
"""
Add an image slide to the service item.
:param path: The directory in which the image file is located.
:param title: A title for the slide in the service item.
:param background:
+ :param thumbnail: Optional alternative thumbnail, used for remote thumbnails.
"""
if background:
self.image_border = background
self.service_item_type = ServiceItemType.Image
- self._raw_frames.append({'title': title, 'path': path})
+ # If no thumbnail was given we create one
+ if not thumbnail:
+ thumbnail = os.path.join(AppLocation.get_section_data_path('images'), 'thumbnails', os.path.split(path)[1])
+ create_thumb(path, thumbnail, False)
+ self._raw_frames.append({'title': title, 'path': path, 'image': thumbnail})
self.image_manager.add_image(path, ImageSource.ImagePlugin, self.image_border)
self._new_item()
@@ -301,16 +319,22 @@
self._raw_frames.append({'title': title, 'raw_slide': raw_slide, 'verseTag': verse_tag})
self._new_item()
- def add_from_command(self, path, file_name, image):
+ def add_from_command(self, path, file_name, image, display_title=None, notes=None):
"""
Add a slide from a command.
:param path: The title of the slide in the service item.
:param file_name: The title of the slide in the service item.
:param image: The command of/for the slide.
+ :param display_title: Title to show in gui/webinterface, optional.
+ :param notes: Notes to show in the webinteface, optional.
"""
self.service_item_type = ServiceItemType.Command
- self._raw_frames.append({'title': file_name, 'image': image, 'path': path})
+ # If the item should have a display title but this frame doesn't have one, we make one up
+ if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
+ display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
+ self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
+ 'display_title': display_title, 'notes': notes})
self._new_item()
def get_service_repr(self, lite_save):
@@ -354,7 +378,8 @@
service_data = [slide['title'] for slide in self._raw_frames]
elif self.service_item_type == ServiceItemType.Command:
for slide in self._raw_frames:
- service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path']})
+ service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path'],
+ 'display_title': slide['display_title'], 'notes': slide['notes']})
return {'header': service_header, 'data': service_data}
def set_from_service(self, service_item, path=None):
@@ -425,7 +450,8 @@
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
elif path:
self.has_original_files = False
- self.add_from_command(path, text_image['title'], text_image['image'])
+ self.add_from_command(path, text_image['title'], text_image['image'],
+ text_image.get('display_title', ''), text_image.get('notes', ''))
else:
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
self._new_item()
=== modified file 'openlp/core/ui/listpreviewwidget.py'
--- openlp/core/ui/listpreviewwidget.py 2014-05-19 19:19:05 +0000
+++ openlp/core/ui/listpreviewwidget.py 2014-09-11 10:15:15 +0000
@@ -94,8 +94,8 @@
Displays the given slide.
"""
self.service_item = service_item
+ self.setRowCount(0)
self.clear()
- self.setRowCount(0)
self.setColumnWidth(0, width)
row = 0
text = []
=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py 2014-08-23 20:42:10 +0000
+++ openlp/core/ui/servicemanager.py 2014-09-11 10:15:15 +0000
@@ -1281,7 +1281,11 @@
# Add the children to their parent tree_widget_item.
for count, frame in enumerate(service_item_from_item.get_frames()):
child = QtGui.QTreeWidgetItem(tree_widget_item)
- text = frame['title'].replace('\n', ' ')
+ # prefer to use a display_title
+ if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle):
+ text = frame['display_title'].replace('\n', ' ')
+ else:
+ text = frame['title'].replace('\n', ' ')
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, count)
if service_item == item_count:
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2014-07-12 23:47:53 +0000
+++ openlp/core/ui/slidecontroller.py 2014-09-11 10:15:15 +0000
@@ -873,6 +873,7 @@
if self.service_item.is_command():
Registry().execute('%s_slide' % self.service_item.name.lower(), [self.service_item, self.is_live, index])
self.update_preview()
+ self.selected_row = index
else:
self.preview_widget.change_slide(index)
self.slide_selected()
@@ -1042,8 +1043,8 @@
self.display.image(to_display)
# reset the store used to display first image
self.service_item.bg_image_bytes = None
- self.update_preview()
self.selected_row = row
+ self.update_preview()
self.preview_widget.change_slide(row)
self.display.setFocus()
@@ -1055,6 +1056,7 @@
"""
self.preview_widget.change_slide(row)
self.update_preview()
+ self.selected_row = row
def update_preview(self):
"""
=== modified file 'openlp/plugins/bibles/lib/manager.py'
--- openlp/plugins/bibles/lib/manager.py 2014-05-06 19:53:08 +0000
+++ openlp/plugins/bibles/lib/manager.py 2014-09-11 10:15:15 +0000
@@ -85,7 +85,7 @@
BibleFormat.CSV,
BibleFormat.OpenSong,
BibleFormat.WebDownload,
- BibleFormar.Zefania,
+ BibleFormat.Zefania,
]
=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py 2014-04-24 02:57:02 +0000
+++ openlp/plugins/images/lib/mediaitem.py 2014-09-11 10:15:15 +0000
@@ -551,6 +551,7 @@
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.CanEditTitle)
+ service_item.add_capability(ItemCapabilities.HasThumbnails)
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
@@ -589,7 +590,7 @@
# Continue with the existing images.
for filename in images_file_names:
name = os.path.split(filename)[1]
- service_item.add_from_image(filename, name, background)
+ service_item.add_from_image(filename, name, background, os.path.join(self.service_path, name))
return True
def check_group_exists(self, new_group):
=== modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
--- openlp/plugins/presentations/lib/impresscontroller.py 2014-08-27 23:18:06 +0000
+++ openlp/plugins/presentations/lib/impresscontroller.py 2014-09-11 10:15:15 +0000
@@ -65,7 +65,7 @@
from openlp.core.lib import ScreenList
from openlp.core.utils import delete_file, get_uno_command, get_uno_instance
-from .presentationcontroller import PresentationController, PresentationDocument
+from .presentationcontroller import PresentationController, PresentationDocument, TextType
log = logging.getLogger(__name__)
@@ -257,6 +257,7 @@
self.presentation.Display = ScreenList().current['number'] + 1
self.control = None
self.create_thumbnails()
+ self.create_titles_and_notes()
return True
def create_thumbnails(self):
@@ -450,22 +451,46 @@
:param slide_no: The slide the notes are required for, starting at 1
"""
- return self.__get_text_from_page(slide_no, True)
+ return self.__get_text_from_page(slide_no, TextType.Notes)
- def __get_text_from_page(self, slide_no, notes=False):
+ 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 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 = []
pages = self.document.getDrawPages()
- page = pages.getByIndex(slide_no - 1)
- if notes:
- page = page.getNotesPage()
- for index in range(page.getCount()):
- shape = page.getByIndex(index)
- if shape.supportsService("com.sun.star.drawing.Text"):
- text += shape.getString() + '\n'
- return text
+ 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)
+ self.save_titles_and_notes(titles, notes)
=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py 2014-06-05 16:25:37 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py 2014-09-11 10:15:15 +0000
@@ -288,13 +288,14 @@
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
- image_file = 'mainslide%03d.png' % i
- image = os.path.join(doc.get_temp_folder(), image_file)
+ image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
+ thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
while os.path.isfile(image):
- service_item.add_from_image(image, name)
+ service_item.add_from_image(image, name, thumbnail=thumbnail)
i += 1
- image_file = 'mainslide%03d.png' % i
- image = os.path.join(doc.get_temp_folder(), image_file)
+ image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
+ thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
+ service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation()
return True
else:
@@ -323,8 +324,21 @@
i = 1
img = doc.get_thumbnail_path(i, True)
if img:
+ # Get titles and notes
+ titles, notes = doc.get_titles_and_notes()
+ service_item.add_capability(ItemCapabilities.HasDisplayTitle)
+ if notes.count('') != len(notes):
+ service_item.add_capability(ItemCapabilities.HasNotes)
+ service_item.add_capability(ItemCapabilities.HasThumbnails)
while img:
- service_item.add_from_command(path, name, img)
+ # Use title and note if available
+ title = ''
+ if titles and len(titles) >= i:
+ title = titles[i - 1]
+ note = ''
+ if notes and len(notes) >= i:
+ note = notes[i - 1]
+ service_item.add_from_command(path, name, img, title, note)
i += 1
img = doc.get_thumbnail_path(i, True)
doc.close_presentation()
=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
--- openlp/plugins/presentations/lib/powerpointcontroller.py 2014-08-27 23:18:06 +0000
+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2014-09-11 10:15:15 +0000
@@ -27,7 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-This modul is for controlling powerpiont. PPT API documentation:
+This module is for controlling powerpoint. PPT API documentation:
`http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
"""
import os
@@ -37,16 +37,17 @@
if is_win():
from win32com.client import Dispatch
+ import win32com
import winreg
import win32ui
import pywintypes
from openlp.core.lib import ScreenList
+from openlp.core.common import Registry
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
from openlp.core.common import trace_error_handler
from .presentationcontroller import PresentationController, PresentationDocument
-
log = logging.getLogger(__name__)
@@ -136,6 +137,7 @@
self.controller.process.Presentations.Open(self.file_path, False, False, True)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
self.create_thumbnails()
+ self.create_titles_and_notes()
# Powerpoint 2013 pops up when loading a file, so we minimize it again
if self.presentation.Application.Version == u'15.0':
try:
@@ -392,6 +394,28 @@
"""
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
+ 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 = []
+ for slide in self.presentation.Slides:
+ try:
+ text = slide.Shapes.Title.TextFrame.TextRange.Text
+ except Exception as e:
+ log.exception(e)
+ text = ''
+ titles.append(text.replace('\n', ' ').replace('\x0b', ' ') + '\n')
+ note = _get_text_from_shapes(slide.NotesPage.Shapes)
+ if len(note) == 0:
+ note = ' '
+ notes.append(note)
+ self.save_titles_and_notes(titles, notes)
+
def show_error_msg(self):
"""
Stop presentation and display an error message.
@@ -410,8 +434,8 @@
:param shapes: A set of shapes to search for text.
"""
text = ''
- for index in range(shapes.Count):
- shape = shapes(index + 1)
- if shape.HasTextFrame:
- text += shape.TextFrame.TextRange.Text + '\n'
+ for shape in shapes:
+ if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
+ if shape.HasTextFrame and shape.TextFrame.HasText:
+ text += shape.TextFrame.TextRange.Text + '\n'
return text
=== modified file 'openlp/plugins/presentations/lib/pptviewcontroller.py'
--- openlp/plugins/presentations/lib/pptviewcontroller.py 2014-08-27 23:18:06 +0000
+++ openlp/plugins/presentations/lib/pptviewcontroller.py 2014-09-11 10:15:15 +0000
@@ -29,6 +29,11 @@
import logging
import os
+import logging
+import zipfile
+import re
+from xml.etree import ElementTree
+
from openlp.core.common import is_win
@@ -127,14 +132,14 @@
temp_folder = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
- file_path = os.path.normpath(self.file_path)
+ self.file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
- file_path = file_path.encode('utf-16-le') + b'\0'
+ self.file_path = self.file_path.encode('utf-16-le') + b'\0'
preview_path = preview_path.encode('utf-16-le') + b'\0'
if not os.path.isdir(temp_folder):
os.makedirs(temp_folder)
- self.ppt_id = self.controller.process.OpenPPT(file_path, None, rect, preview_path)
+ self.ppt_id = self.controller.process.OpenPPT(self.file_path, None, rect, preview_path)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()
@@ -154,6 +159,68 @@
path = '%s\\slide%s.bmp' % (self.get_temp_folder(), str(idx + 1))
self.convert_thumbnail(path, idx + 1)
+ def create_titles_and_notes(self):
+ """
+ Extracts the titles and notes from the zipped file
+ and writes the list of titles (one per slide)
+ to 'titles.txt'
+ and the notes to 'slideNotes[x].txt'
+ in the thumbnails directory
+ """
+ titles = None
+ notes = None
+ filename = os.path.normpath(self.file_path)
+ # let's make sure we have a valid zipped presentation
+ if os.path.exists(filename) and zipfile.is_zipfile(filename):
+ namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
+ "a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
+ # open the file
+ with zipfile.ZipFile(filename) as zip_file:
+ # find the presentation.xml to get the slide count
+ with zip_file.open('ppt/presentation.xml') as pres:
+ tree = ElementTree.parse(pres)
+ nodes = tree.getroot().findall(".//p:sldIdLst/p:sldId", namespaces=namespaces)
+ # initialize the lists
+ titles = ['' for i in range(len(nodes))]
+ notes = ['' for i in range(len(nodes))]
+ # loop thru the file list to find slides and notes
+ for zip_info in zip_file.infolist():
+ node_type = ''
+ index = -1
+ list_to_add = None
+ # check if it is a slide
+ match = re.search("slides/slide(.+)\.xml", zip_info.filename)
+ if match:
+ index = int(match.group(1))-1
+ node_type = 'ctrTitle'
+ list_to_add = titles
+ # or a note
+ match = re.search("notesSlides/notesSlide(.+)\.xml", zip_info.filename)
+ if match:
+ index = int(match.group(1))-1
+ node_type = 'body'
+ list_to_add = notes
+ # if it is one of our files, index shouldn't be -1
+ if index >= 0:
+ with zip_file.open(zip_info) as zipped_file:
+ tree = ElementTree.parse(zipped_file)
+ text = ''
+ nodes = tree.getroot().findall(".//p:ph[@type='" + node_type + "']../../..//p:txBody//a:t",
+ namespaces=namespaces)
+ # if we found any content
+ if nodes and len(nodes) > 0:
+ for node in nodes:
+ if len(text) > 0:
+ text += '\n'
+ text += node.text
+ # Let's remove the \n from the titles and
+ # just add one at the end
+ if node_type == 'ctrTitle':
+ text = text.replace('\n', ' ').replace('\x0b', ' ') + '\n'
+ list_to_add[index] = text
+ # now let's write the files
+ 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
=== added file 'openlp/plugins/presentations/lib/pptviewlib/test.pptx'
Binary files openlp/plugins/presentations/lib/pptviewlib/test.pptx 1970-01-01 00:00:00 +0000 and openlp/plugins/presentations/lib/pptviewlib/test.pptx 2014-09-11 10:15:15 +0000 differ
=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
--- openlp/plugins/presentations/lib/presentationcontroller.py 2014-05-02 06:42:17 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py 2014-09-11 10:15:15 +0000
@@ -293,6 +293,49 @@
"""
return ''
+ def get_titles_and_notes(self):
+ """
+ Reads the titles from the titles file and
+ the notes files and returns the content in two lists
+ """
+ titles = []
+ notes = []
+ titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
+ if os.path.exists(titles_file):
+ try:
+ with open(titles_file) as fi:
+ titles = fi.read().splitlines()
+ except:
+ log.exception('Failed to open/read existing titles file')
+ titles = []
+ for slide_no, title in enumerate(titles, 1):
+ notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
+ note = ''
+ if os.path.exists(notes_file):
+ try:
+ with open(notes_file) as fn:
+ note = fn.read()
+ except:
+ log.exception('Failed to open/read notes file')
+ note = ''
+ notes.append(note)
+ return titles, notes
+
+ def save_titles_and_notes(self, titles, notes):
+ """
+ Performs the actual persisting of titles to the titles.txt
+ and notes to the slideNote%.txt
+ """
+ if titles:
+ titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
+ with open(titles_file, mode='w') as fo:
+ fo.writelines(titles)
+ if notes:
+ for slide_no, note in enumerate(notes, 1):
+ notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
+ with open(notes_file, mode='w') as fn:
+ fn.write(note)
+
class PresentationController(object):
"""
@@ -427,3 +470,12 @@
def close_presentation(self):
pass
+
+
+class TextType(object):
+ """
+ Type Enumeration for Types of Text to request
+ """
+ Title = 0
+ SlideText = 1
+ Notes = 2
=== modified file 'openlp/plugins/remotes/html/openlp.js'
--- openlp/plugins/remotes/html/openlp.js 2013-12-24 08:56:50 +0000
+++ openlp/plugins/remotes/html/openlp.js 2014-09-11 10:15:15 +0000
@@ -87,16 +87,30 @@
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
- var text = data.results.slides[idx]["tag"];
- if (text != "") text = text + ": ";
- text = text + data.results.slides[idx]["text"];
+ var indexInt = parseInt(idx,10);
+ var slide = data.results.slides[idx];
+ var text = slide["tag"];
+ if (text != "") {
+ text = text + ": ";
+ }
+ if (slide["title"]) {
+ text += slide["title"]
+ } else {
+ text += slide["text"];
+ }
+ if (slide["notes"]) {
+ text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["notes"] + "</div>");
+ }
text = text.replace(/\n/g, '<br />');
- var li = $("<li data-icon=\"false\">").append(
- $("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(text));
- if (data.results.slides[idx]["selected"]) {
+ if (slide["img"]) {
+ text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails88x88/") + "'>";
+ }
+ var li = $("<li data-icon=\"false\">").append($("<a href=\"#\">").html(text));
+ if (slide["selected"]) {
li.attr("data-theme", "e");
}
li.children("a").click(OpenLP.setSlide);
+ li.find("*").attr("value", indexInt );
ul.append(li);
}
OpenLP.currentItem = data.results.item;
=== modified file 'openlp/plugins/remotes/html/stage.js'
--- openlp/plugins/remotes/html/stage.js 2013-12-24 08:56:50 +0000
+++ openlp/plugins/remotes/html/stage.js 2014-09-11 10:15:15 +0000
@@ -102,7 +102,21 @@
$("#verseorder span").removeClass("currenttag");
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
- var text = slide["text"];
+ var text = "";
+ // use title if available
+ if (slide["title"]) {
+ text = slide["title"];
+ } else {
+ text = slide["text"];
+ }
+ // use thumbnail if available
+ if (slide["img"]) {
+ text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
+ }
+ // use notes if available
+ if (slide["notes"]) {
+ text += '<br />' + slide["notes"];
+ }
text = text.replace(/\n/g, "<br />");
$("#currentslide").html(text);
text = "";
@@ -110,7 +124,11 @@
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "<p class=\"nextslide\">";
- text = text + OpenLP.currentSlides[idx]["text"];
+ if (OpenLP.currentSlides[idx]["title"]) {
+ text = text + OpenLP.currentSlides[idx]["title"];
+ } else {
+ text = text + OpenLP.currentSlides[idx]["text"];
+ }
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "</p>";
else
=== modified file 'openlp/plugins/remotes/lib/httprouter.py'
--- openlp/plugins/remotes/lib/httprouter.py 2014-04-19 05:09:54 +0000
+++ openlp/plugins/remotes/lib/httprouter.py 2014-09-11 10:15:15 +0000
@@ -125,7 +125,7 @@
from PyQt4 import QtCore
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, translate
-from openlp.core.lib import PluginStatus, StringContent, image_to_byte
+from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities
log = logging.getLogger(__name__)
FILE_TYPES = {
@@ -159,6 +159,7 @@
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
+ (r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
(r'^/main/image$', {'function': self.main_image, 'secure': False}),
@@ -328,7 +329,8 @@
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
- 'slides': translate('RemotePlugin.Mobile', 'Slides')
+ 'slides': translate('RemotePlugin.Mobile', 'Slides'),
+ 'settings': translate('RemotePlugin.Mobile', 'Settings'),
}
def serve_file(self, file_name=None):
@@ -380,6 +382,42 @@
content_type = FILE_TYPES.get(ext, 'text/plain')
return ext, content_type
+ def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
+ """
+ Serve an image file. If not found return 404.
+ """
+ log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name))
+ supported_controllers = ['presentations', 'images']
+ # -1 means use the default dimension in ImageManager
+ width = -1
+ height = -1
+ if dimensions:
+ match = re.search('(\d+)x(\d+)', dimensions)
+ if match:
+ # let's make sure that the dimensions are within reason
+ width = sorted([10, int(match.group(1)), 1000])[1]
+ height = sorted([10, int(match.group(2)), 1000])[1]
+ content = ''
+ content_type = None
+ if controller_name and file_name:
+ if controller_name in supported_controllers:
+ full_path = urllib.parse.unquote(file_name)
+ if '..' not in full_path: # no hacking please
+ full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name),
+ 'thumbnails/' + full_path))
+ if os.path.exists(full_path):
+ path, just_file_name = os.path.split(full_path)
+ self.image_manager.add_image(full_path, just_file_name, None, width, height)
+ ext, content_type = self.get_content_type(full_path)
+ image = self.image_manager.get_image(full_path, just_file_name, width, height)
+ content = image_to_byte(image, False)
+ if len(content) == 0:
+ return self.do_not_found()
+ self.send_response(200)
+ self.send_header('Content-type', content_type)
+ self.end_headers()
+ return content
+
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
@@ -458,6 +496,7 @@
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
+ # Handle text (songs, custom)
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
@@ -465,11 +504,33 @@
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
+ # Handle images, unless a custom thumbnail is given or if thumbnails is disabled
+ elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
+ item['tag'] = str(index + 1)
+ thumbnail_path = os.path.sep + os.path.join('images', 'thumbnails', frame['title'])
+ item['img'] = urllib.request.pathname2url(thumbnail_path)
+ item['text'] = str(frame['title'])
+ item['html'] = str(frame['title'])
else:
+ # Handle presentation etc.
item['tag'] = str(index + 1)
+ if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
+ item['title'] = str(frame['display_title'])
+ if current_item.is_capable(ItemCapabilities.HasNotes):
+ item['notes'] = str(frame['notes'])
+ if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
+ Settings().value('remotes/thumbnails'):
+ # If the file is under our app directory tree send the portion after the match
+ data_path = AppLocation.get_data_path()
+ print(frame)
+ if frame['image'][0:len(data_path)] == data_path:
+ item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
+ if current_item.notes:
+ item['notes'] = item.get('notes', '') + '\n' + current_item.notes
+ print(item)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py 2014-04-19 05:26:49 +0000
+++ openlp/plugins/remotes/lib/httpserver.py 2014-09-11 10:15:15 +0000
@@ -144,6 +144,7 @@
try:
self.httpd = server_class((address, port), CustomHandler)
log.debug("Server started for class %s %s %d" % (server_class, address, port))
+ break
except OSError:
log.debug("failed to start http server thread state %d %s" %
(loop, self.http_thread.isRunning()))
@@ -151,6 +152,8 @@
time.sleep(0.1)
except:
log.error('Failed to start server ')
+ loop += 1
+ time.sleep(0.1)
def stop_server(self):
"""
=== modified file 'openlp/plugins/remotes/lib/remotetab.py'
--- openlp/plugins/remotes/lib/remotetab.py 2014-04-20 20:16:08 +0000
+++ openlp/plugins/remotes/lib/remotetab.py 2014-09-11 10:15:15 +0000
@@ -62,6 +62,9 @@
self.twelve_hour_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.twelve_hour_check_box.setObjectName('twelve_hour_check_box')
self.server_settings_layout.addRow(self.twelve_hour_check_box)
+ self.thumbnails_check_box = QtGui.QCheckBox(self.server_settings_group_box)
+ self.thumbnails_check_box.setObjectName('thumbnails_check_box')
+ self.server_settings_layout.addRow(self.thumbnails_check_box)
self.left_layout.addWidget(self.server_settings_group_box)
self.http_settings_group_box = QtGui.QGroupBox(self.left_column)
self.http_settings_group_box.setObjectName('http_settings_group_box')
@@ -163,6 +166,7 @@
self.left_layout.addStretch()
self.right_layout.addStretch()
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
+ self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.connect(self.set_urls)
self.https_port_spin_box.valueChanged.connect(self.set_urls)
@@ -176,6 +180,8 @@
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
+ self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
+ 'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="https://play.google.com/store/'
@@ -240,6 +246,8 @@
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
self.twelve_hour_check_box.setChecked(self.twelve_hour)
+ self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
+ self.thumbnails_check_box.setChecked(self.thumbnails)
local_data = AppLocation.get_directory(AppLocation.DataDir)
if not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.crt')) or \
not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.key')):
@@ -271,6 +279,7 @@
Settings().setValue(self.settings_section + '/https enabled', self.https_settings_group_box.isChecked())
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
+ Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
Settings().setValue(self.settings_section + '/password', self.password.text())
@@ -285,6 +294,15 @@
if check_state == QtCore.Qt.Checked:
self.twelve_hour = True
+ def on_thumbnails_check_box_changed(self, check_state):
+ """
+ Toggle the thumbnail check box.
+ """
+ self.thumbnails = False
+ # we have a set value convert to True/False
+ if check_state == QtCore.Qt.Checked:
+ self.thumbnails = True
+
def https_changed(self):
"""
Invert the HTTP group box based on Https group settings
=== modified file 'openlp/plugins/remotes/remoteplugin.py'
--- openlp/plugins/remotes/remoteplugin.py 2014-04-20 20:16:08 +0000
+++ openlp/plugins/remotes/remoteplugin.py 2014-09-11 10:15:15 +0000
@@ -44,7 +44,8 @@
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
- 'remotes/ip address': '0.0.0.0'
+ 'remotes/ip address': '0.0.0.0',
+ 'remotes/thumbnails': True
}
=== modified file 'tests/functional/openlp_core_common/test_applocation.py'
--- tests/functional/openlp_core_common/test_applocation.py 2014-04-02 18:51:21 +0000
+++ tests/functional/openlp_core_common/test_applocation.py 2014-09-11 10:15:15 +0000
@@ -162,9 +162,9 @@
patch('openlp.core.common.applocation.os.path.abspath') as mocked_abspath, \
patch('openlp.core.common.applocation.os.path.split') as mocked_split, \
patch('openlp.core.common.applocation.sys') as mocked_sys:
- mocked_abspath.return_value = 'plugins/dir'
+ mocked_abspath.return_value = os.path.join('plugins', 'dir')
mocked_split.return_value = ['openlp']
- mocked_get_frozen_path.return_value = 'plugins/dir'
+ mocked_get_frozen_path.return_value = os.path.join('plugins', 'dir')
mocked_sys.frozen = 1
mocked_sys.argv = ['openlp']
@@ -172,7 +172,7 @@
directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN: The correct directory should be returned
- self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"')
+ self.assertEqual(os.path.join('plugins', 'dir'), directory, 'Directory should be "plugins/dir"')
def get_frozen_path_in_unfrozen_app_test(self):
"""
=== modified file 'tests/functional/openlp_core_lib/test_image_manager.py'
--- tests/functional/openlp_core_lib/test_image_manager.py 2014-06-04 04:54:44 +0000
+++ tests/functional/openlp_core_lib/test_image_manager.py 2014-09-11 10:15:15 +0000
@@ -69,16 +69,17 @@
Test the Image Manager setup basic functionality
"""
# GIVEN: the an image add to the image manager
- self.image_manager.add_image(TEST_PATH, 'church.jpg', None)
+ full_path = os.path.normpath(os.path.join(TEST_PATH, 'church.jpg'))
+ self.image_manager.add_image(full_path, 'church.jpg', None)
# WHEN the image is retrieved
- image = self.image_manager.get_image(TEST_PATH, 'church.jpg')
+ image = self.image_manager.get_image(full_path, 'church.jpg')
# THEN returned record is a type of image
self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage')
# WHEN: The image bytes are requested.
- byte_array = self.image_manager.get_image_bytes(TEST_PATH, 'church.jpg')
+ byte_array = self.image_manager.get_image_bytes(full_path, 'church.jpg')
# THEN: Type should be a str.
self.assertEqual(isinstance(byte_array, str), True, 'The returned object should be a str')
@@ -89,6 +90,38 @@
self.image_manager.get_image(TEST_PATH, 'church1.jpg')
self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing image')
+ def different_dimension_image_test(self):
+ """
+ Test the Image Manager with dimensions
+ """
+ # GIVEN: add an image with specific dimensions
+ full_path = os.path.normpath(os.path.join(TEST_PATH, 'church.jpg'))
+ self.image_manager.add_image(full_path, 'church.jpg', None, 80, 80)
+
+ # WHEN: the image is retrieved
+ image = self.image_manager.get_image(full_path, 'church.jpg', 80, 80)
+
+ # THEN: The return should be of type image
+ self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage')
+
+ # WHEN: adding the same image with different dimensions
+ self.image_manager.add_image(full_path, 'church.jpg', None, 100, 100)
+
+ # THEN: the cache should contain two pictures
+ self.assertEqual(len(self.image_manager._cache), 2,
+ 'Image manager should consider two dimensions of the same picture as different')
+
+ # WHEN: adding the same image with first dimensions
+ self.image_manager.add_image(full_path, 'church.jpg', None, 80, 80)
+
+ # THEN: the cache should still contain only two pictures
+ self.assertEqual(len(self.image_manager._cache), 2, 'Same dimensions should not be added again')
+
+ # WHEN: calling with correct image, but wrong dimensions
+ with self.assertRaises(KeyError) as context:
+ self.image_manager.get_image(full_path, 'church.jpg', 120, 120)
+ self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing dimension')
+
def process_cache_test(self):
"""
Test the process_cache method
@@ -151,7 +184,7 @@
:param image: The name of the image. E. g. ``image1``
"""
- return self.image_manager._cache[(TEST_PATH, image)].priority
+ return self.image_manager._cache[(TEST_PATH, image, -1, -1)].priority
def mocked_resize_image(self, *args):
"""
=== modified file 'tests/functional/openlp_core_lib/test_serviceitem.py'
--- tests/functional/openlp_core_lib/test_serviceitem.py 2014-03-17 20:59:58 +0000
+++ tests/functional/openlp_core_lib/test_serviceitem.py 2014-09-11 10:15:15 +0000
@@ -32,13 +32,11 @@
import os
from unittest import TestCase
-
from tests.functional import MagicMock, patch
from tests.utils import assert_length, convert_file_service_item
from openlp.core.common import Registry
-from openlp.core.lib import ItemCapabilities, ServiceItem
-
+from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
@@ -113,20 +111,25 @@
# GIVEN: A new service item and a mocked add icon function
image_name = 'image_1.jpg'
test_file = os.path.join(TEST_PATH, image_name)
- frame_array = {'path': test_file, 'title': image_name}
+ thumb_file = os.path.normpath(os.path.join('/path/thumbnails', image_name))
+ frame_array = {'path': test_file, 'title': image_name, 'image': thumb_file}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj')
- with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
+ with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists,\
+ patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb,\
+ patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
+ mocked_get_section_data_path:
mocked_exists.return_value = True
+ mocked_get_section_data_path.return_value = os.path.normpath('/path/')
service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
- self.assertEqual(test_file, service_item.get_rendered_frame(0),
+ self.assertEqual(os.path.normpath(test_file), os.path.normpath(service_item.get_rendered_frame(0)),
'The first frame should match the path to the image')
self.assertEqual(frame_array, service_item.get_frames()[0],
'The return should match frame array1')
@@ -153,10 +156,12 @@
# GIVEN: A new service item and a mocked add icon function
image_name1 = 'image_1.jpg'
image_name2 = 'image_2.jpg'
- test_file1 = os.path.join('/home/openlp', image_name1)
- test_file2 = os.path.join('/home/openlp', image_name2)
- frame_array1 = {'path': test_file1, 'title': image_name1}
- frame_array2 = {'path': test_file2, 'title': image_name2}
+ test_file1 = os.path.normpath(os.path.join('/home/openlp', image_name1))
+ test_file2 = os.path.normpath(os.path.join('/home/openlp', image_name2))
+ thumb_file1 = os.path.normpath(os.path.join('/path/thumbnails', image_name1))
+ thumb_file2 = os.path.normpath(os.path.join('/path/thumbnails', image_name2))
+ frame_array1 = {'path': test_file1, 'title': image_name1, 'image': thumb_file1}
+ frame_array2 = {'path': test_file2, 'title': image_name2, 'image': thumb_file2}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
@@ -168,8 +173,12 @@
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
- with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
+ with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists, \
+ patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb, \
+ patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
+ mocked_get_section_data_path:
mocked_exists.return_value = True
+ mocked_get_section_data_path.return_value = os.path.normpath('/path/')
service_item2.set_from_service(line2)
service_item.set_from_service(line)
@@ -207,6 +216,44 @@
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')
+ def add_from_command_for_a_presentation_test(self):
+ """
+ Test the Service Item - adding a presentation
+ """
+ # GIVEN: A service item, a mocked icon and presentation data
+ service_item = ServiceItem(None)
+ presentation_name = 'test.pptx'
+ image = MagicMock()
+ display_title = 'DisplayTitle'
+ notes = 'Note1\nNote2\n'
+ frame = {'title': presentation_name, 'image': image, 'path': TEST_PATH,
+ 'display_title': display_title, 'notes': notes}
+
+ # WHEN: adding presentation to service_item
+ service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes)
+
+ # THEN: verify that it is setup as a Command and that the frame data matches
+ self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
+ self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
+
+ def add_from_comamnd_without_display_title_and_notes_test(self):
+ """
+ Test the Service Item - add from command, but not presentation
+ """
+ # GIVEN: A new service item, a mocked icon and image data
+ service_item = ServiceItem(None)
+ image_name = 'test.img'
+ image = MagicMock()
+ frame = {'title': image_name, 'image': image, 'path': TEST_PATH,
+ 'display_title': None, 'notes': None}
+
+ # WHEN: adding image to service_item
+ service_item.add_from_command(TEST_PATH, image_name, image)
+
+ # THEN: verify that it is setup as a Command and that the frame data matches
+ self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
+ self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
+
def service_item_load_optical_media_from_service_test(self):
"""
Test the Service Item - load an optical media item
=== modified file 'tests/functional/openlp_core_ui/test_thememanager.py'
--- tests/functional/openlp_core_ui/test_thememanager.py 2014-08-21 13:07:02 +0000
+++ tests/functional/openlp_core_ui/test_thememanager.py 2014-09-11 10:15:15 +0000
@@ -57,17 +57,18 @@
# GIVEN: A new ThemeManager instance.
theme_manager = ThemeManager()
theme_manager.path = os.path.join(TEST_RESOURCES_PATH, 'themes')
- zipfile.ZipFile.__init__ = MagicMock()
- zipfile.ZipFile.__init__.return_value = None
- zipfile.ZipFile.write = MagicMock()
-
- # WHEN: The theme is exported
- theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
-
- # THEN: The zipfile should be created at the given path
- zipfile.ZipFile.__init__.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
- zipfile.ZipFile.write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes', 'Default', 'Default.xml'),
- os.path.join('Default', 'Default.xml'))
+ with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \
+ patch('zipfile.ZipFile.write') as mocked_zipfile_write:
+ mocked_zipfile_init.return_value = None
+
+ # WHEN: The theme is exported
+ theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
+
+ # THEN: The zipfile should be created at the given path
+ mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
+ mocked_zipfile_write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes',
+ 'Default', 'Default.xml'),
+ os.path.join('Default', 'Default.xml'))
def initial_theme_manager_test(self):
"""
=== added file 'tests/functional/openlp_plugins/presentations/test_impresscontroller.py'
--- tests/functional/openlp_plugins/presentations/test_impresscontroller.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/presentations/test_impresscontroller.py 2014-09-11 10:15:15 +0000
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# 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; version 2 of the License. #
+# #
+# 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, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+Functional tests to test the Impress class and related methods.
+"""
+from unittest import TestCase
+import os
+import shutil
+from tempfile import mkdtemp
+
+from tests.functional import patch, MagicMock
+from tests.utils.constants import TEST_RESOURCES_PATH
+from tests.helpers.testmixin import TestMixin
+
+from openlp.plugins.presentations.lib.impresscontroller import \
+ ImpressController, ImpressDocument, TextType
+
+
+class TestImpressController(TestCase, TestMixin):
+ """
+ Test the ImpressController Class
+ """
+
+ def setUp(self):
+ """
+ Set up the patches and mocks need for all tests.
+ """
+ self.get_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)
+
+ def constructor_test(self):
+ """
+ Test the Constructor from the ImpressController
+ """
+ # GIVEN: No presentation controller
+ controller = None
+
+ # WHEN: The presentation controller object is created
+ controller = ImpressController(plugin=self.mock_plugin)
+
+ # THEN: The name of the presentation controller should be correct
+ self.assertEqual('Impress', controller.name,
+ 'The name of the presentation controller should be correct')
+
+
+class TestImpressDocumnt(TestCase):
+ """
+ Test the ImpressDocument Class
+ """
+ def setUp(self):
+ mocked_plugin = MagicMock()
+ mocked_plugin.settings_section = 'presentations'
+ self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
+ self.ppc = ImpressController(mocked_plugin)
+ self.doc = ImpressDocument(self.ppc, self.file_name)
+
+ def create_titles_and_notes_test(self):
+ """
+ Test ImpressDocument.create_titles_and_notes
+ """
+ # GIVEN: mocked PresentationController.save_titles_and_notes with
+ # 0 pages and the LibreOffice Document
+ self.doc.save_titles_and_notes = MagicMock()
+ self.doc.document = MagicMock()
+ self.doc.document.getDrawPages.return_value = MagicMock()
+ self.doc.document.getDrawPages().getCount.return_value = 0
+
+ # WHEN reading the titles and notes
+ self.doc.create_titles_and_notes()
+
+ # THEN save_titles_and_notes should have been called with empty arrays
+ self.doc.save_titles_and_notes.assert_called_once_with([], [])
+
+ # GIVEN: reset mock and set it to 2 pages
+ self.doc.save_titles_and_notes.reset_mock()
+ self.doc.document.getDrawPages().getCount.return_value = 2
+
+ # WHEN: a new call to create_titles_and_notes
+ self.doc.create_titles_and_notes()
+
+ # THEN: save_titles_and_notes should have been called once with
+ # two arrays of two elements
+ self.doc.save_titles_and_notes.assert_called_once_with(['\n', '\n'], [' ', ' '])
+
+ def get_text_from_page_out_of_bound_test(self):
+ """
+ Test ImpressDocument.__get_text_from_page with out-of-bounds index
+ """
+ # GIVEN: mocked LibreOffice Document with one slide,
+ # two notes and three texts
+ self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
+
+ # WHEN: __get_text_from_page is called with an index of 0x00
+ result = self.doc._ImpressDocument__get_text_from_page(0, TextType.Notes)
+
+ # THEN: the result should be an empty string
+ self.assertEqual(result, '', 'Result should be an empty string')
+
+ # WHEN: regardless of the type of text, index 0x00 is out of bounds
+ result = self.doc._ImpressDocument__get_text_from_page(0, TextType.Title)
+
+ # THEN: result should be an empty string
+ self.assertEqual(result, '', 'Result should be an empty string')
+
+ # WHEN: when called with 2, it should also be out of bounds
+ result = self.doc._ImpressDocument__get_text_from_page(2, TextType.SlideText)
+
+ # THEN: result should be an empty string ... and, getByIndex should
+ # have never been called
+ self.assertEqual(result, '', 'Result should be an empty string')
+ self.assertEqual(self.doc.document.getDrawPages().getByIndex.call_count, 0,
+ 'There should be no call to getByIndex')
+
+ def get_text_from_page_wrong_type_test(self):
+ """
+ Test ImpressDocument.__get_text_from_page with wrong TextType
+ """
+ # GIVEN: mocked LibreOffice Document with one slide, two notes and
+ # three texts
+ self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
+
+ # WHEN: called with TextType 3
+ result = self.doc._ImpressDocument__get_text_from_page(1, 3)
+
+ # THEN: result should be an empty string
+ self.assertEqual(result, '', 'Result should be and empty string')
+ self.assertEqual(self.doc.document.getDrawPages().getByIndex.call_count, 0,
+ 'There should be no call to getByIndex')
+
+ def get_text_from_page_valid_params_test(self):
+ """
+ Test ImpressDocument.__get_text_from_page with valid parameters
+ """
+ # GIVEN: mocked LibreOffice Document with one slide,
+ # two notes and three texts
+ self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
+
+ # WHEN: __get_text_from_page is called to get the Notes
+ result = self.doc._ImpressDocument__get_text_from_page(1, TextType.Notes)
+
+ # THEN: result should be 'Note\nNote\n'
+ self.assertEqual(result, 'Note\nNote\n', 'Result should be \'Note\\n\' times the count of notes in the page')
+
+ # WHEN: get the Title
+ result = self.doc._ImpressDocument__get_text_from_page(1, TextType.Title)
+
+ # THEN: result should be 'Title\n'
+ self.assertEqual(result, 'Title\n', 'Result should be exactly \'Title\\n\'')
+
+ # WHEN: get all text
+ result = self.doc._ImpressDocument__get_text_from_page(1, TextType.SlideText)
+
+ # THEN: result should be 'Title\nString\nString\n'
+ self.assertEqual(result, 'Title\nString\nString\n', 'Result should be exactly \'Title\\nString\\nString\\n\'')
+
+ def _mock_a_LibreOffice_document(self, page_count, note_count, text_count):
+ """
+ Helper function, creates a mock libreoffice document.
+
+ :param page_count: Number of pages in the document
+ :param note_count: Number of note pages in the document
+ :param text_count: Number of text pages in the document
+ """
+ pages = MagicMock()
+ page = MagicMock()
+ pages.getByIndex.return_value = page
+ notes_page = MagicMock()
+ notes_page.getCount.return_value = note_count
+ shape = MagicMock()
+ shape.supportsService.return_value = True
+ shape.getString.return_value = 'Note'
+ notes_page.getByIndex.return_value = shape
+ page.getNotesPage.return_value = notes_page
+ page.getCount.return_value = text_count
+ page.getByIndex.side_effect = self._get_page_shape_side_effect
+ pages.getCount.return_value = page_count
+ document = MagicMock()
+ document.getDrawPages.return_value = pages
+ document.getByIndex.return_value = page
+ return document
+
+ def _get_page_shape_side_effect(*args):
+ """
+ Helper function.
+ """
+ page_shape = MagicMock()
+ page_shape.supportsService.return_value = True
+ if args[1] == 0:
+ page_shape.getShapeType.return_value = 'com.sun.star.presentation.TitleTextShape'
+ page_shape.getString.return_value = 'Title'
+ else:
+ page_shape.getString.return_value = 'String'
+ return page_shape
=== modified file 'tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py'
--- tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2014-07-03 11:21:12 +0000
+++ tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2014-09-11 10:15:15 +0000
@@ -30,16 +30,20 @@
Functional tests to test the PowerPointController class and related methods.
"""
import os
-if os.name == 'nt':
- import pywintypes
import shutil
from unittest import TestCase
from tempfile import mkdtemp
from tests.functional import patch, MagicMock
from tests.helpers.testmixin import TestMixin
-
-from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument
+from tests.utils.constants import TEST_RESOURCES_PATH
+
+from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument,\
+ _get_text_from_shapes
+from openlp.core.common import is_win
+
+if is_win():
+ import pywintypes
class TestPowerpointController(TestCase, TestMixin):
@@ -79,7 +83,7 @@
'The name of the presentation controller should be correct')
-class TestPowerpointDocument(TestCase):
+class TestPowerpointDocument(TestCase, TestMixin):
"""
Test the PowerpointDocument Class
"""
@@ -88,6 +92,11 @@
"""
Set up the patches and mocks need for all tests.
"""
+ self.get_application()
+ self.build_settings()
+ self.mock_plugin = MagicMock()
+ self.temp_folder = mkdtemp()
+ self.mock_plugin.settings_section = self.temp_folder
self.powerpoint_document_stop_presentation_patcher = patch(
'openlp.plugins.presentations.lib.powerpointcontroller.PowerpointDocument.stop_presentation')
self.presentation_document_get_temp_folder_patcher = patch(
@@ -100,6 +109,8 @@
self.mock_controller = MagicMock()
self.mock_presentation = MagicMock()
self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
+ self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
+ self.real_controller = PowerpointController(self.mock_plugin)
def tearDown(self):
"""
@@ -108,12 +119,14 @@
self.powerpoint_document_stop_presentation_patcher.stop()
self.presentation_document_get_temp_folder_patcher.stop()
self.presentation_document_setup_patcher.stop()
+ self.destroy_settings()
+ shutil.rmtree(self.temp_folder)
def show_error_msg_test(self):
"""
Test the PowerpointDocument.show_error_msg() method gets called on com exception
"""
- if os.name == 'nt':
+ if is_win():
# GIVEN: A PowerpointDocument with mocked controller and presentation
with patch('openlp.plugins.presentations.lib.powerpointcontroller.critical_error_message_box') as \
mocked_critical_error_message_box:
@@ -129,3 +142,95 @@
'integration and the presentation will be stopped.'
' Restart the presentation if you wish to '
'present it.')
+
+ # add _test to the following if necessary
+ def verify_loading_document(self):
+ """
+ Test loading a document in PowerPoint
+ """
+ if is_win() and self.real_controller.check_available():
+ # GIVEN: A PowerpointDocument and a presentation
+ doc = PowerpointDocument(self.real_controller, self.file_name)
+
+ # WHEN: loading the filename
+ doc.load_presentation()
+ result = doc.is_loaded()
+
+ # THEN: result should be true
+ self.assertEqual(result, True, 'The result should be True')
+ else:
+ self.skipTest('Powerpoint not available, skipping test.')
+
+ def create_titles_and_notes_test(self):
+ """
+ Test creating the titles from PowerPoint
+ """
+ if is_win() and self.real_controller.check_available():
+ # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
+ self.doc = PowerpointDocument(self.real_controller, self.file_name)
+ self.doc.save_titles_and_notes = MagicMock()
+ self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
+ slide = MagicMock()
+ slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
+ pres = MagicMock()
+ pres.Slides = [slide, slide]
+ self.doc.presentation = pres
+
+ # WHEN reading the titles and notes
+ self.doc.create_titles_and_notes()
+
+ # THEN the save should have been called exactly once with 2 titles and 2 notes
+ self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
+ else:
+ self.skipTest('Powerpoint not available, skipping test.')
+
+ def create_titles_and_notes_with_no_slides_test(self):
+ """
+ Test creating the titles from PowerPoint when it returns no slides
+ """
+ if is_win() and self.real_controller.check_available():
+ # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
+ doc = PowerpointDocument(self.real_controller, self.file_name)
+ doc.save_titles_and_notes = MagicMock()
+ doc._PowerpointDocument__get_text_from_shapes = MagicMock()
+ pres = MagicMock()
+ pres.Slides = []
+ doc.presentation = pres
+
+ # WHEN reading the titles and notes
+ doc.create_titles_and_notes()
+
+ # THEN the save should have been called exactly once with empty titles and notes
+ doc.save_titles_and_notes.assert_called_once_with([], [])
+ else:
+ self.skipTest('Powerpoint not available, skipping test.')
+
+ def get_text_from_shapes_test(self):
+ """
+ Test getting text from powerpoint shapes
+ """
+ # GIVEN: mocked shapes
+ shape = MagicMock()
+ shape.PlaceholderFormat.Type = 2
+ shape.HasTextFrame = shape.TextFrame.HasText = True
+ shape.TextFrame.TextRange.Text = 'slideText'
+ shapes = [shape, shape]
+
+ # WHEN: getting the text
+ result = _get_text_from_shapes(shapes)
+
+ # THEN: it should return the text
+ self.assertEqual(result, 'slideText\nslideText\n', 'result should match \'slideText\nslideText\n\'')
+
+ def get_text_from_shapes_with_no_shapes_test(self):
+ """
+ Test getting text from powerpoint shapes with no shapes
+ """
+ # GIVEN: empty shapes array
+ shapes = []
+
+ # WHEN: getting the text
+ result = _get_text_from_shapes(shapes)
+
+ # THEN: it should not fail but return empty string
+ self.assertEqual(result, '', 'result should be empty')
=== modified file 'tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py'
--- tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py 2014-04-20 20:19:21 +0000
+++ tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py 2014-09-11 10:15:15 +0000
@@ -4,8 +4,8 @@
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2013 Raoul Snyman #
-# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
@@ -31,16 +31,19 @@
"""
import os
import shutil
-if os.name == 'nt':
- from ctypes import cdll
from tempfile import mkdtemp
from unittest import TestCase
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
+from tests.utils.constants import TEST_RESOURCES_PATH
from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
+from openlp.core.common import is_win
+
+if is_win():
+ from ctypes import cdll
class TestPptviewController(TestCase, TestMixin):
@@ -98,7 +101,7 @@
available = controller.check_available()
# THEN: On windows it should return True, on other platforms False
- if os.name == 'nt':
+ if is_win():
self.assertTrue(available, 'check_available should return True on windows.')
else:
self.assertFalse(available, 'check_available should return False when not on windows.')
@@ -130,7 +133,7 @@
"""
Set up the patches and mocks need for all tests.
"""
- self.os_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.os')
+ self.os_isdir_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.isdir')
self.pptview_document_create_thumbnails_patcher = patch(
'openlp.plugins.presentations.lib.pptviewcontroller.PptviewDocument.create_thumbnails')
self.pptview_document_stop_presentation_patcher = patch(
@@ -141,46 +144,45 @@
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup')
self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList')
self.rect_patcher = MagicMock()
-
- self.mock_os = self.os_patcher.start()
+ self.mock_os_isdir = self.os_isdir_patcher.start()
self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start()
self.mock_pptview_document_stop_presentation = self.pptview_document_stop_presentation_patcher.start()
self.mock_presentation_document_get_temp_folder = self.presentation_document_get_temp_folder_patcher.start()
self.mock_presentation_document_setup = self.presentation_document_setup_patcher.start()
self.mock_rect = self.rect_patcher.start()
self.mock_screen_list = self.screen_list_patcher.start()
-
self.mock_controller = MagicMock()
self.mock_presentation = MagicMock()
-
- self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
+ self.temp_folder = mkdtemp()
+ self.mock_presentation_document_get_temp_folder.return_value = self.temp_folder
def tearDown(self):
"""
Stop the patches
"""
- self.os_patcher.stop()
+ self.os_isdir_patcher.stop()
self.pptview_document_create_thumbnails_patcher.stop()
self.pptview_document_stop_presentation_patcher.stop()
self.presentation_document_get_temp_folder_patcher.stop()
self.presentation_document_setup_patcher.stop()
self.rect_patcher.stop()
self.screen_list_patcher.stop()
+ shutil.rmtree(self.temp_folder)
def load_presentation_succesfull_test(self):
"""
Test the PptviewDocument.load_presentation() method when the PPT is successfully opened
"""
# GIVEN: A reset mocked_os
- self.mock_os.reset()
+ self.mock_os_isdir.reset()
# WHEN: The temporary directory exists and OpenPPT returns successfully (not -1)
- self.mock_os.path.isdir.return_value = True
+ self.mock_os_isdir.return_value = True
self.mock_controller.process.OpenPPT.return_value = 0
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
- if os.name == 'nt':
+ if is_win():
result = instance.load_presentation()
# THEN: PptviewDocument.load_presentation should return True
@@ -191,17 +193,78 @@
Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is
not successfully opened
"""
- # GIVEN: A reset mocked_os
- self.mock_os.reset()
+ # GIVEN: A reset mock_os_isdir
+ self.mock_os_isdir.reset()
# WHEN: The temporary directory does not exist and OpenPPT returns unsuccessfully (-1)
- self.mock_os.path.isdir.return_value = False
- self.mock_controller.process.OpenPPT.return_value = -1
- instance = PptviewDocument(self.mock_controller, self.mock_presentation)
- instance.file_path = 'test\path.ppt'
- if os.name == 'nt':
- result = instance.load_presentation()
-
- # THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False
- self.mock_os.makedirs.assert_called_once_with('temp folder')
- self.assertFalse(result)
+ with patch('openlp.plugins.presentations.lib.pptviewcontroller.os.makedirs') as mock_makedirs:
+ self.mock_os_isdir.return_value = False
+ self.mock_controller.process.OpenPPT.return_value = -1
+ instance = PptviewDocument(self.mock_controller, self.mock_presentation)
+ instance.file_path = 'test\path.ppt'
+ if is_win():
+ result = instance.load_presentation()
+
+ # THEN: The temp folder should be created and PptviewDocument.load_presentation should return False
+ mock_makedirs.assert_called_once_with(self.temp_folder)
+ self.assertFalse(result)
+
+ def create_titles_and_notes_test(self):
+ """
+ Test PowerpointController.create_titles_and_notes
+ """
+ # GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
+ doc = PptviewDocument(self.mock_controller, self.mock_presentation)
+ doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
+ doc.save_titles_and_notes = MagicMock()
+
+ # WHEN reading the titles and notes
+ doc.create_titles_and_notes()
+
+ # THEN save_titles_and_notes should have been called once with empty arrays
+ doc.save_titles_and_notes.assert_called_once_with(['Test 1\n', '\n', 'Test 2\n', 'Test 4\n', 'Test 3\n'],
+ ['Notes for slide 1', 'Inserted', 'Notes for slide 2',
+ 'Notes \nfor slide 4', 'Notes for slide 3'])
+
+ def create_titles_and_notes_nonexistent_file_test(self):
+ """
+ Test PowerpointController.create_titles_and_notes with nonexistent file
+ """
+ # GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
+ with patch('builtins.open') as mocked_open, \
+ patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.exists') as mocked_exists, \
+ patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
+ mocked_dir_exists:
+ mocked_exists.return_value = False
+ mocked_dir_exists.return_value = False
+ doc = PptviewDocument(self.mock_controller, self.mock_presentation)
+ doc.file_path = 'Idontexist.pptx'
+ doc.save_titles_and_notes = MagicMock()
+
+ # WHEN: Reading the titles and notes
+ doc.create_titles_and_notes()
+
+ # THEN: File existens should have been checked, and not have been opened.
+ doc.save_titles_and_notes.assert_called_once_with(None, None)
+ mocked_exists.assert_any_call('Idontexist.pptx')
+ self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
+
+ def create_titles_and_notes_invalid_file_test(self):
+ """
+ Test PowerpointController.create_titles_and_notes with invalid file
+ """
+ # GIVEN: mocked PresentationController.save_titles_and_notes and an invalid file
+ with patch('builtins.open') as mocked_open, \
+ patch('openlp.plugins.presentations.lib.pptviewcontroller.zipfile.is_zipfile') as mocked_is_zf:
+ mocked_is_zf.return_value = False
+ mocked_open.filesize = 10
+ doc = PptviewDocument(self.mock_controller, self.mock_presentation)
+ doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
+ doc.save_titles_and_notes = MagicMock()
+
+ # WHEN: reading the titles and notes
+ doc.create_titles_and_notes()
+
+ # THEN:
+ doc.save_titles_and_notes.assert_called_once_with(None, None)
+ self.assertEqual(mocked_is_zf.call_count, 1, 'is_zipfile should have been called once')
=== modified file 'tests/functional/openlp_plugins/presentations/test_presentationcontroller.py'
--- tests/functional/openlp_plugins/presentations/test_presentationcontroller.py 2014-03-13 20:59:10 +0000
+++ tests/functional/openlp_plugins/presentations/test_presentationcontroller.py 2014-09-11 10:15:15 +0000
@@ -1,158 +1,166 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2014 Raoul Snyman #
-# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
-# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
-# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
-# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
-# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
-# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
-# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
-# --------------------------------------------------------------------------- #
-# 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; version 2 of the License. #
-# #
-# 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, write to the Free Software Foundation, Inc., 59 #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
-###############################################################################
-"""
-This module contains tests for the Presentation Controller.
-"""
-from unittest import TestCase
-
-from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
-from tests.functional import MagicMock, patch
-
-
-class TestPresentationController(TestCase):
- """
- Test the PresentationController.
- """
- # TODO: Items left to test
- # PresentationController
- # __init__
- # enabled
- # is_available
- # check_available
- # start_process
- # kill
- # add_document
- # remove_doc
- # close_presentation
- # _get_plugin_manager
-
- def constructor_test(self):
- """
- Test the Constructor
- """
- # GIVEN: No presentation controller
- controller = None
-
- # WHEN: The presentation controller object is created
- mock_plugin = MagicMock()
- mock_plugin.settings_section = ''
- controller = PresentationController(plugin=mock_plugin)
-
- # THEN: The name of the presentation controller should be correct
- self.assertEqual('PresentationController', controller.name,
- 'The name of the presentation controller should be correct')
-
-
-class TestPresentationDocument(TestCase):
- """
- Test the PresentationDocument Class
- """
- # TODO: Items left to test
- # PresentationDocument
- # __init__
- # load_presentation
- # presentation_deleted
- # get_file_name
- # get_thumbnail_folder
- # get_temp_folder
- # check_thumbnails
- # close_presentation
- # is_active
- # is_loaded
- # blank_screen
- # unblank_screen
- # is_blank
- # stop_presentation
- # start_presentation
- # get_slide_number
- # get_slide_count
- # goto_slide
- # next_step
- # previous_step
- # convert_thumbnail
- # get_thumbnail_path
- # poll_slidenumber
- # get_slide_text
- # get_slide_notes
-
- def setUp(self):
- """
- Set up the patches and mocks need for all tests.
- """
- self.check_directory_exists_patcher = \
- patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
- self.get_thumbnail_folder_patcher = \
- patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
- self._setup_patcher = \
- patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
-
- self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
- self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
- self.mock_setup = self._setup_patcher.start()
-
- self.mock_controller = MagicMock()
-
- self.mock_get_thumbnail_folder.return_value = 'returned/path/'
-
- def tearDown(self):
- """
- Stop the patches
- """
- self.check_directory_exists_patcher.stop()
- self.get_thumbnail_folder_patcher.stop()
- self._setup_patcher.stop()
-
- def initialise_presentation_document_test(self):
- """
- Test the PresentationDocument __init__ method when initialising the PresentationDocument Class
- """
- # GIVEN: A reset mock_setup and mocked controller
- self.mock_setup.reset()
-
- # WHEN: Creating an instance of PresentationDocument
- PresentationDocument(self.mock_controller, 'Name')
-
- # THEN: PresentationDocument.__init__ should have been called with the correct arguments
- self.mock_setup.assert_called_once_with('Name')
-
- def presentation_document_setup_test(self):
- """
- Test the PresentationDocument _setup method when initialising the PresentationDocument Class
- """
- self._setup_patcher.stop()
-
- # GIVEN: A mocked controller, patched check_directory_exists_patcher and patched get_thumbnail_folder method
-
- # WHEN: Creating an instance of PresentationDocument
- PresentationDocument(self.mock_controller, 'Name')
-
- # THEN: check_directory_exists should have been called with the correct arguments
- self.mock_check_directory_exists.assert_called_once_with('returned/path/')
-
- self._setup_patcher.start()
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# 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; version 2 of the License. #
+# #
+# 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, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+Functional tests to test the PresentationController and PresentationDocument
+classes and related methods.
+"""
+from unittest import TestCase
+import os
+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
+from tests.functional import MagicMock, patch, mock_open
+
+FOLDER_TO_PATCH = 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder'
+
+
+class TestPresentationController(TestCase):
+ """
+ Test the PresentationController.
+ """
+ def setUp(self):
+ mocked_plugin = MagicMock()
+ mocked_plugin.settings_section = 'presentations'
+ self.presentation = PresentationController(mocked_plugin)
+ self.document = PresentationDocument(self.presentation, '')
+
+ def constructor_test(self):
+ """
+ Test the Constructor
+ """
+ # GIVEN: A mocked plugin
+
+ # WHEN: The PresentationController is created
+
+ # THEN: The name of the presentation controller should be correct
+ self.assertEqual('PresentationController', self.presentation.name,
+ 'The name of the presentation controller should be correct')
+
+ def save_titles_and_notes_test(self):
+ """
+ Test PresentationDocument.save_titles_and_notes method with two valid lists
+ """
+ # GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
+ mocked_open = mock_open()
+ with patch('builtins.open', mocked_open), patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
+ titles = ['uno', 'dos']
+ notes = ['one', 'two']
+
+ # WHEN: calling save_titles_and_notes
+ mocked_get_thumbnail_folder.return_value = 'test'
+ self.document.save_titles_and_notes(titles, notes)
+
+ # THEN: the last call to open should have been for slideNotes2.txt
+ mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), mode='w')
+ mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), mode='w')
+ mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), mode='w')
+ self.assertEqual(mocked_open.call_count, 3, 'There should be exactly three files opened')
+ mocked_open().writelines.assert_called_once_with(['uno', 'dos'])
+ mocked_open().write.assert_called_any('one')
+ mocked_open().write.assert_called_any('two')
+
+ def save_titles_and_notes_with_None_test(self):
+ """
+ Test PresentationDocument.save_titles_and_notes method with no data
+ """
+ # GIVEN: None and an empty list and a mocked open and get_thumbnail_folder
+ with patch('builtins.open') as mocked_open, patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
+ titles = None
+ notes = None
+
+ # WHEN: calling save_titles_and_notes
+ mocked_get_thumbnail_folder.return_value = 'test'
+ self.document.save_titles_and_notes(titles, notes)
+
+ # THEN: No file should have been created
+ self.assertEqual(mocked_open.call_count, 0, 'No file should be created')
+
+ def get_titles_and_notes_test(self):
+ """
+ Test PresentationDocument.get_titles_and_notes method
+ """
+ # GIVEN: A mocked open, get_thumbnail_folder and exists
+
+ with patch('builtins.open', mock_open(read_data='uno\ndos\n')) as mocked_open, \
+ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
+ patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
+ mocked_get_thumbnail_folder.return_value = 'test'
+ mocked_exists.return_value = True
+
+ # WHEN: calling get_titles_and_notes
+ result_titles, result_notes = self.document.get_titles_and_notes()
+
+ # THEN: it should return two items for the titles and two empty strings for the notes
+ self.assertIs(type(result_titles), list, 'result_titles should be of type list')
+ self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
+ self.assertIs(type(result_notes), list, 'result_notes should be of type list')
+ self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
+ self.assertEqual(mocked_open.call_count, 3, 'Three files should be opened')
+ mocked_open.assert_any_call(os.path.join('test', 'titles.txt'))
+ mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'))
+ mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'))
+ self.assertEqual(mocked_exists.call_count, 3, 'Three files should have been checked')
+
+ def get_titles_and_notes_with_file_not_found_test(self):
+ """
+ Test PresentationDocument.get_titles_and_notes method with file not found
+ """
+ # GIVEN: A mocked open, get_thumbnail_folder and exists
+ with patch('builtins.open') as mocked_open, \
+ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
+ patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
+ mocked_get_thumbnail_folder.return_value = 'test'
+ mocked_exists.return_value = False
+
+ # WHEN: calling get_titles_and_notes
+ result_titles, result_notes = self.document.get_titles_and_notes()
+
+ # THEN: it should return two empty lists
+ self.assertIs(type(result_titles), list, 'result_titles should be of type list')
+ self.assertEqual(len(result_titles), 0, 'there be no titles')
+ self.assertIs(type(result_notes), list, 'result_notes should be a list')
+ self.assertEqual(len(result_notes), 0, 'but the list should be empty')
+ self.assertEqual(mocked_open.call_count, 0, 'No calls to open files')
+ self.assertEqual(mocked_exists.call_count, 1, 'There should be one call to file exists')
+
+ def get_titles_and_notes_with_file_error_test(self):
+ """
+ Test PresentationDocument.get_titles_and_notes method with file errors
+ """
+ # GIVEN: A mocked open, get_thumbnail_folder and exists
+ with patch('builtins.open') as mocked_open, \
+ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
+ patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
+ mocked_get_thumbnail_folder.return_value = 'test'
+ mocked_exists.return_value = True
+ mocked_open.side_effect = IOError()
+
+ # WHEN: calling get_titles_and_notes
+ result_titles, result_notes = self.document.get_titles_and_notes()
+
+ # THEN: it should return two empty lists
+ self.assertIs(type(result_titles), list, 'result_titles should be a list')
=== modified file 'tests/functional/openlp_plugins/remotes/test_remotetab.py'
--- tests/functional/openlp_plugins/remotes/test_remotetab.py 2014-03-14 22:08:44 +0000
+++ tests/functional/openlp_plugins/remotes/test_remotetab.py 2014-09-11 10:15:15 +0000
@@ -48,7 +48,8 @@
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
- 'remotes/ip address': '0.0.0.0'
+ 'remotes/ip address': '0.0.0.0',
+ 'remotes/thumbnails': True
}
ZERO_URL = '0.0.0.0'
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
=== modified file 'tests/functional/openlp_plugins/remotes/test_router.py'
--- tests/functional/openlp_plugins/remotes/test_router.py 2014-05-07 20:38:34 +0000
+++ tests/functional/openlp_plugins/remotes/test_router.py 2014-09-11 10:15:15 +0000
@@ -30,10 +30,12 @@
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
+import urllib.request
from unittest import TestCase
from openlp.core.common import Settings, Registry
from openlp.plugins.remotes.lib.httpserver import HttpRouter
+from urllib.parse import urlparse
from tests.functional import MagicMock, patch, mock_open
from tests.helpers.testmixin import TestMixin
@@ -186,3 +188,86 @@
self.router.send_response.assert_called_once_with(200)
self.router.send_header.assert_called_once_with('Content-type', 'text/html')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
+
+ def serve_thumbnail_without_params_test(self):
+ """
+ Test the serve_thumbnail routine without params
+ """
+ self.router.send_response = MagicMock()
+ self.router.send_header = MagicMock()
+ self.router.end_headers = MagicMock()
+ self.router.wfile = MagicMock()
+ self.router.serve_thumbnail()
+ self.router.send_response.assert_called_once_with(404)
+ self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
+ self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
+
+ def serve_thumbnail_with_invalid_params_test(self):
+ """
+ Test the serve_thumbnail routine with invalid params
+ """
+ # GIVEN: Mocked send_header, send_response, end_headers and wfile
+ self.router.send_response = MagicMock()
+ self.router.send_header = MagicMock()
+ self.router.end_headers = MagicMock()
+ self.router.wfile = MagicMock()
+
+ # WHEN: pass a bad controller
+ self.router.serve_thumbnail('badcontroller', 'tecnologia 1.pptx/slide1.png')
+
+ # THEN: a 404 should be returned
+ self.assertEqual(len(self.router.send_header.mock_calls), 1, 'One header')
+ self.assertEqual(len(self.router.send_response.mock_calls), 1, 'One response')
+ self.assertEqual(len(self.router.wfile.mock_calls), 1, 'Once call to write to the socket')
+ self.router.send_response.assert_called_once_with(404)
+
+ # WHEN: pass a bad filename
+ self.router.send_response.reset_mock()
+ self.router.serve_thumbnail('presentations', 'tecnologia 1.pptx/badfilename.png')
+
+ # THEN: return a 404
+ self.router.send_response.assert_called_once_with(404)
+
+ # WHEN: a dangerous URL is passed
+ self.router.send_response.reset_mock()
+ self.router.serve_thumbnail('presentations', '../tecnologia 1.pptx/slide1.png')
+
+ # THEN: return a 404
+ self.router.send_response.assert_called_once_with(404)
+
+ def serve_thumbnail_with_valid_params_test(self):
+ """
+ Test the serve_thumbnail routine with valid params
+ """
+ # GIVEN: Mocked send_header, send_response, end_headers and wfile
+ self.router.send_response = MagicMock()
+ self.router.send_header = MagicMock()
+ self.router.end_headers = MagicMock()
+ self.router.wfile = MagicMock()
+ mocked_image_manager = MagicMock()
+ Registry.create()
+ Registry().register('image_manager', mocked_image_manager)
+ file_name = 'another%20test/slide1.png'
+ full_path = os.path.normpath(os.path.join('thumbnails', file_name))
+ width = 120
+ height = 90
+ with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
+ patch('builtins.open', mock_open(read_data='123')), \
+ patch('openlp.plugins.remotes.lib.httprouter.AppLocation') as mocked_location, \
+ patch('openlp.plugins.remotes.lib.httprouter.image_to_byte') as mocked_image_to_byte:
+ mocked_exists.return_value = True
+ mocked_image_to_byte.return_value = '123'
+ mocked_location.get_section_data_path.return_value = ''
+
+ # WHEN: pass good controller and filename
+ result = self.router.serve_thumbnail('presentations', '{0}x{1}'.format(width, height), file_name)
+
+ # THEN: a file should be returned
+ self.assertEqual(self.router.send_header.call_count, 1, 'One header')
+ self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
+ self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
+ mocked_exists.assert_called_with(urllib.parse.unquote(full_path))
+ self.assertEqual(mocked_image_to_byte.call_count, 1, 'Called once')
+ mocked_image_manager.assert_called_any(os.path.normpath('thumbnails\\another test'),
+ 'slide1.png', None, '120x90')
+ mocked_image_manager.assert_called_any(os.path.normpath('thumbnails\\another test'), 'slide1.png', '120x90')
=== added file 'tests/resources/presentations/test.ppt'
Binary files tests/resources/presentations/test.ppt 1970-01-01 00:00:00 +0000 and tests/resources/presentations/test.ppt 2014-09-11 10:15:15 +0000 differ
=== added file 'tests/resources/presentations/test.pptx'
Binary files tests/resources/presentations/test.pptx 1970-01-01 00:00:00 +0000 and tests/resources/presentations/test.pptx 2014-09-11 10:15:15 +0000 differ
Follow ups