Moved most of the presentation plugin over to pathlib

This commit is contained in:
Philip Ridout 2017-09-15 20:01:09 +01:00
parent f0e7381f5c
commit 8ed5903ced
12 changed files with 278 additions and 266 deletions

View File

@ -29,9 +29,10 @@ import os
import re import re
import math import math
from PyQt5 import QtCore, QtGui, Qt, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import translate from openlp.core.common import translate
from openlp.core.common.path import Path
log = logging.getLogger(__name__ + '.__init__') log = logging.getLogger(__name__ + '.__init__')
@ -125,10 +126,11 @@ def build_icon(icon):
Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a
QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name. QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name.
:param icon: :param QtGui.QIcon | Path | QtGui.QIcon | str icon:
The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file path
location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string. location like ``Path(/path/to/file.png)``. However, the **recommended** way is to specify a resource string.
:return: The build icon. :return: The build icon.
:rtype: QtGui.QIcon
""" """
if isinstance(icon, QtGui.QIcon): if isinstance(icon, QtGui.QIcon):
return icon return icon
@ -136,6 +138,8 @@ def build_icon(icon):
button_icon = QtGui.QIcon() button_icon = QtGui.QIcon()
if isinstance(icon, str): if isinstance(icon, str):
pix_map = QtGui.QPixmap(icon) pix_map = QtGui.QPixmap(icon)
elif isinstance(icon, Path):
pix_map = QtGui.QPixmap(str(icon))
elif isinstance(icon, QtGui.QImage): elif isinstance(icon, QtGui.QImage):
pix_map = QtGui.QPixmap.fromImage(icon) pix_map = QtGui.QPixmap.fromImage(icon)
if pix_map: if pix_map:
@ -221,10 +225,12 @@ def validate_thumb(file_path, thumb_path):
:param thumb_path: The path to the thumb. :param thumb_path: The path to the thumb.
:return: True, False if the image has changed since the thumb was created. :return: True, False if the image has changed since the thumb was created.
""" """
if not os.path.exists(thumb_path): file_path = Path(file_path)
thumb_path = Path(thumb_path)
if not thumb_path.exists():
return False return False
image_date = os.stat(file_path).st_mtime image_date = file_path.stat().st_mtime
thumb_date = os.stat(thumb_path).st_mtime thumb_date = thumb_path.stat().st_mtime
return image_date <= thumb_date return image_date <= thumb_date

View File

@ -359,10 +359,8 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
:param files: The files to be loaded. :param files: The files to be loaded.
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
""" """
names = []
full_list = [] full_list = []
for count in range(self.list_view.count()): for count in range(self.list_view.count()):
names.append(self.list_view.item(count).text())
full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
duplicates_found = False duplicates_found = False
files_added = False files_added = False

View File

@ -95,3 +95,20 @@ def rmtree(*args, **kwargs):
args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),)) args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),))
return shutil.rmtree(*args, **kwargs) return shutil.rmtree(*args, **kwargs)
# TODO: Test and tidy
def which(*args, **kwargs):
"""
Wraps :func:shutil.rmtree` so that we can accept Path objects.
:param openlp.core.common.path.Path path: Takes a Path object which is then converted to a str object
:return: Passes the return from :func:`shutil.rmtree` back
:rtype: None
See the following link for more information on the other parameters:
https://docs.python.org/3/library/shutil.html#shutil.rmtree
"""
file_name = shutil.which(*args, **kwargs)
if file_name:
return str_to_path(file_name)
return None

View File

@ -32,11 +32,14 @@
# http://nxsy.org/comparing-documents-with-openoffice-and-python # http://nxsy.org/comparing-documents-with-openoffice-and-python
import logging import logging
import os
import time import time
from openlp.core.common import is_win, Registry, delete_file from PyQt5 import QtCore
from openlp.core.common.path import Path
from openlp.core.common import Registry, delete_file, get_uno_command, get_uno_instance, is_win
from openlp.core.lib import ScreenList
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
TextType
if is_win(): if is_win():
from win32com.client import Dispatch from win32com.client import Dispatch
@ -55,14 +58,6 @@ else:
except ImportError: except ImportError:
uno_available = False uno_available = False
from PyQt5 import QtCore
from openlp.core.lib import ScreenList
from openlp.core.common import get_uno_command, get_uno_instance
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
TextType
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -203,12 +198,15 @@ class ImpressDocument(PresentationDocument):
Class which holds information and controls a single presentation. Class which holds information and controls a single presentation.
""" """
def __init__(self, controller, presentation): def __init__(self, controller, document_path):
""" """
Constructor, store information about the file and initialise. Constructor, store information about the file and initialise.
:param openlp.core.common.path.Path document_path: File path for the document to load
:rtype: None
""" """
log.debug('Init Presentation OpenOffice') log.debug('Init Presentation OpenOffice')
super(ImpressDocument, self).__init__(controller, presentation) super().__init__(controller, document_path)
self.document = None self.document = None
self.presentation = None self.presentation = None
self.control = None self.control = None
@ -225,10 +223,10 @@ class ImpressDocument(PresentationDocument):
if desktop is None: if desktop is None:
self.controller.start_process() self.controller.start_process()
desktop = self.controller.get_com_desktop() desktop = self.controller.get_com_desktop()
url = 'file:///' + self.file_path.replace('\\', '/').replace(':', '|').replace(' ', '%20') url = self.file_path.as_uri()
else: else:
desktop = self.controller.get_uno_desktop() desktop = self.controller.get_uno_desktop()
url = uno.systemPathToFileUrl(self.file_path) url = uno.systemPathToFileUrl(str(self.file_path))
if desktop is None: if desktop is None:
return False return False
self.desktop = desktop self.desktop = desktop
@ -254,11 +252,11 @@ class ImpressDocument(PresentationDocument):
log.debug('create thumbnails OpenOffice') log.debug('create thumbnails OpenOffice')
if self.check_thumbnails(): if self.check_thumbnails():
return return
temp_folder_path = self.get_temp_folder()
if is_win(): if is_win():
thumb_dir_url = 'file:///' + str(self.get_temp_folder()).replace('\\', '/') \ thumb_dir_url = temp_folder_path.as_uri()
.replace(':', '|').replace(' ', '%20')
else: else:
thumb_dir_url = uno.systemPathToFileUrl(str(self.get_temp_folder())) thumb_dir_url = uno.systemPathToFileUrl(str(temp_folder_path))
properties = [] properties = []
properties.append(self.create_property('FilterName', 'impress_png_Export')) properties.append(self.create_property('FilterName', 'impress_png_Export'))
properties = tuple(properties) properties = tuple(properties)
@ -266,17 +264,16 @@ class ImpressDocument(PresentationDocument):
pages = doc.getDrawPages() pages = doc.getDrawPages()
if not pages: if not pages:
return return
temp_folder_path = self.get_temp_folder() if not temp_folder_path.is_dir():
if not temp_folder_path.isdir(): temp_folder_path.mkdir(parents=True)
temp_folder_path.mkdir()
for index in range(pages.getCount()): for index in range(pages.getCount()):
page = pages.getByIndex(index) page = pages.getByIndex(index)
doc.getCurrentController().setCurrentPage(page) doc.getCurrentController().setCurrentPage(page)
url_path = '{path}/{name}.png'.format(path=thumb_dir_url, name=str(index + 1)) url_path = '{path}/{name:d}.png'.format(path=thumb_dir_url, name=index + 1)
path = temp_folder_path / '{number).png'.format(number=index + 1) path = temp_folder_path / '{number:d}.png'.format(number=index + 1)
try: try:
doc.storeToURL(url_path, properties) doc.storeToURL(url_path, properties)
self.convert_thumbnail(str(path), index + 1) self.convert_thumbnail(path, index + 1)
delete_file(path) delete_file(path)
except ErrorCodeIOException as exception: except ErrorCodeIOException as exception:
log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode)) log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode))

View File

@ -19,15 +19,13 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import logging import logging
import os
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, Settings, UiStrings, translate from openlp.core.common import Registry, Settings, UiStrings, translate
from openlp.core.common.languagemanager import get_locale_key from openlp.core.common.languagemanager import get_locale_key
from openlp.core.common.path import path_to_str from openlp.core.common.path import Path, path_to_str, str_to_path
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext,\ from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext,\
build_icon, check_item_selected, create_thumb, validate_thumb build_icon, check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
@ -128,7 +126,7 @@ class PresentationMediaItem(MediaManagerItem):
""" """
self.list_view.setIconSize(QtCore.QSize(88, 50)) self.list_view.setIconSize(QtCore.QSize(88, 50))
file_paths = Settings().value(self.settings_section + '/presentations files') file_paths = Settings().value(self.settings_section + '/presentations files')
self.load_list([path_to_str(file) for file in file_paths], initial_load=True) self.load_list([path_to_str(path) for path in file_paths], initial_load=True)
self.populate_display_types() self.populate_display_types()
def populate_display_types(self): def populate_display_types(self):
@ -152,54 +150,57 @@ class PresentationMediaItem(MediaManagerItem):
else: else:
self.presentation_widget.hide() self.presentation_widget.hide()
def load_list(self, files, target_group=None, initial_load=False): def load_list(self, file_paths, target_group=None, initial_load=False):
""" """
Add presentations into the media manager. This is called both on initial load of the plugin to populate with Add presentations into the media manager. This is called both on initial load of the plugin to populate with
existing files, and when the user adds new files via the media manager. existing files, and when the user adds new files via the media manager.
:param list[openlp.core.common.path.Path] file_paths: List of file paths to add to the media manager.
""" """
current_list = self.get_file_list() file_paths = [str_to_path(filename) for filename in file_paths]
titles = [file_path.name for file_path in current_list] current_paths = self.get_file_list()
titles = [file_path.name for file_path in current_paths]
self.application.set_busy_cursor() self.application.set_busy_cursor()
if not initial_load: if not initial_load:
self.main_window.display_progress_bar(len(files)) self.main_window.display_progress_bar(len(file_paths))
# Sort the presentations by its filename considering language specific characters. # Sort the presentations by its filename considering language specific characters.
files.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1])) file_paths.sort(key=lambda file_path: get_locale_key(file_path.name))
for file in files: for file_path in file_paths:
if not initial_load: if not initial_load:
self.main_window.increment_progress_bar() self.main_window.increment_progress_bar()
if current_list.count(file) > 0: if current_paths.count(file_path) > 0:
continue continue
filename = os.path.split(file)[1] file_name = file_path.name
if not os.path.exists(file): if not file_path.exists():
item_name = QtWidgets.QListWidgetItem(filename) item_name = QtWidgets.QListWidgetItem(file_name)
item_name.setIcon(build_icon(ERROR_IMAGE)) item_name.setIcon(build_icon(ERROR_IMAGE))
item_name.setData(QtCore.Qt.UserRole, file) item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
item_name.setToolTip(file) item_name.setToolTip(str(file_path))
self.list_view.addItem(item_name) self.list_view.addItem(item_name)
else: else:
if titles.count(filename) > 0: if titles.count(file_name) > 0:
if not initial_load: if not initial_load:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'), critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.')) 'A presentation with that filename already exists.'))
continue continue
controller_name = self.find_controller_by_type(filename) controller_name = self.find_controller_by_type(file_path)
if controller_name: if controller_name:
controller = self.controllers[controller_name] controller = self.controllers[controller_name]
doc = controller.add_document(file) doc = controller.add_document(file_path)
thumb = str(doc.get_thumbnail_folder() / 'icon.png') thumbnail_path = doc.get_thumbnail_folder() / 'icon.png'
preview = doc.get_thumbnail_path(1, True) preview_path = doc.get_thumbnail_path(1, True)
if not preview and not initial_load: if not preview_path and not initial_load:
doc.load_presentation() doc.load_presentation()
preview = doc.get_thumbnail_path(1, True) preview_path = doc.get_thumbnail_path(1, True)
doc.close_presentation() doc.close_presentation()
if not (preview and os.path.exists(preview)): if not (preview_path and preview_path.exists()):
icon = build_icon(':/general/general_delete.png') icon = build_icon(':/general/general_delete.png')
else: else:
if validate_thumb(preview, thumb): if validate_thumb(preview_path, thumbnail_path):
icon = build_icon(thumb) icon = build_icon(thumbnail_path)
else: else:
icon = create_thumb(preview, thumb) icon = create_thumb(str(preview_path), str(thumbnail_path))
else: else:
if initial_load: if initial_load:
icon = build_icon(':/general/general_delete.png') icon = build_icon(':/general/general_delete.png')
@ -208,10 +209,10 @@ class PresentationMediaItem(MediaManagerItem):
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'This type of presentation is not supported.')) 'This type of presentation is not supported.'))
continue continue
item_name = QtWidgets.QListWidgetItem(filename) item_name = QtWidgets.QListWidgetItem(file_name)
item_name.setData(QtCore.Qt.UserRole, file) item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
item_name.setIcon(icon) item_name.setIcon(icon)
item_name.setToolTip(file) item_name.setToolTip(str(file_path))
self.list_view.addItem(item_name) self.list_view.addItem(item_name)
if not initial_load: if not initial_load:
self.main_window.finished_progress_bar() self.main_window.finished_progress_bar()
@ -228,8 +229,8 @@ class PresentationMediaItem(MediaManagerItem):
self.application.set_busy_cursor() self.application.set_busy_cursor()
self.main_window.display_progress_bar(len(row_list)) self.main_window.display_progress_bar(len(row_list))
for item in items: for item in items:
filepath = str(item.data(QtCore.Qt.UserRole)) file_path = str_to_path(item.data(QtCore.Qt.UserRole))
self.clean_up_thumbnails(filepath) self.clean_up_thumbnails(file_path)
self.main_window.increment_progress_bar() self.main_window.increment_progress_bar()
self.main_window.finished_progress_bar() self.main_window.finished_progress_bar()
for row in row_list: for row in row_list:
@ -237,30 +238,29 @@ class PresentationMediaItem(MediaManagerItem):
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
self.application.set_normal_cursor() self.application.set_normal_cursor()
def clean_up_thumbnails(self, filepath, clean_for_update=False): def clean_up_thumbnails(self, file_path, clean_for_update=False):
""" """
Clean up the files created such as thumbnails Clean up the files created such as thumbnails
:param filepath: File path of the presention to clean up after :param openlp.core.common.path.Path file_path: File path of the presention to clean up after
:param clean_for_update: Only clean thumbnails if update is needed :param bool clean_for_update: Only clean thumbnails if update is needed
:return: None :rtype: None
""" """
for cidx in self.controllers: for cidx in self.controllers:
root, file_ext = os.path.splitext(filepath) file_ext = file_path.suffix[1:]
file_ext = file_ext[1:]
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports: if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
doc = self.controllers[cidx].add_document(filepath) doc = self.controllers[cidx].add_document(file_path)
if clean_for_update: if clean_for_update:
thumb_path = doc.get_thumbnail_path(1, True) thumb_path = doc.get_thumbnail_path(1, True)
if not thumb_path or not os.path.exists(filepath) or os.path.getmtime( if not thumb_path or not file_path.exists() or \
thumb_path) < os.path.getmtime(filepath): thumb_path.stat().st_mtime < file_path.stat().st_mtime:
doc.presentation_deleted() doc.presentation_deleted()
else: else:
doc.presentation_deleted() doc.presentation_deleted()
doc.close_presentation() doc.close_presentation()
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service, presentation_file=None): context=ServiceItemContext.Service, file_path=None):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
@ -276,10 +276,9 @@ class PresentationMediaItem(MediaManagerItem):
items = self.list_view.selectedItems() items = self.list_view.selectedItems()
if len(items) > 1: if len(items) > 1:
return False return False
filename = presentation_file if file_path is None:
if filename is None: file_path = str_to_path(items[0].data(QtCore.Qt.UserRole))
filename = items[0].data(QtCore.Qt.UserRole) file_type = file_path.suffix.lower()[1:]
file_type = os.path.splitext(filename.lower())[1][1:]
if not self.display_type_combo_box.currentText(): if not self.display_type_combo_box.currentText():
return False return False
service_item.add_capability(ItemCapabilities.CanEditTitle) service_item.add_capability(ItemCapabilities.CanEditTitle)
@ -292,29 +291,28 @@ class PresentationMediaItem(MediaManagerItem):
# force a nonexistent theme # force a nonexistent theme
service_item.theme = -1 service_item.theme = -1
for bitem in items: for bitem in items:
filename = presentation_file if file_path is None:
if filename is None: file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
filename = bitem.data(QtCore.Qt.UserRole) path, file_name = file_path.parent, file_path.name
(path, name) = os.path.split(filename) service_item.title = file_name
service_item.title = name if file_path.exists():
if os.path.exists(filename): processor = self.find_controller_by_type(file_path)
processor = self.find_controller_by_type(filename)
if not processor: if not processor:
return False return False
controller = self.controllers[processor] controller = self.controllers[processor]
service_item.processor = None service_item.processor = None
doc = controller.add_document(filename) doc = controller.add_document(file_path)
if doc.get_thumbnail_path(1, True) is None or \ if doc.get_thumbnail_path(1, True) is None or \
not (doc.get_temp_folder() / 'mainslide001.png').is_file(): not (doc.get_temp_folder() / 'mainslide001.png').is_file():
doc.load_presentation() doc.load_presentation()
i = 1 i = 1
image = str(doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)) image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
thumbnail = str(doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)) thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
while os.path.isfile(image): while image_path.is_file():
service_item.add_from_image(image, name, thumbnail=thumbnail) service_item.add_from_image(str(image_path), file_name, thumbnail=str(thumbnail_path))
i += 1 i += 1
image = str(doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)) image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
thumbnail = str(doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)) thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
service_item.add_capability(ItemCapabilities.HasThumbnails) service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation() doc.close_presentation()
return True return True
@ -324,34 +322,34 @@ class PresentationMediaItem(MediaManagerItem):
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'The presentation {name} no longer exists.' 'The presentation {name} no longer exists.'
).format(name=filename)) ).format(name=file_path))
return False return False
else: else:
service_item.processor = self.display_type_combo_box.currentText() service_item.processor = self.display_type_combo_box.currentText()
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
for bitem in items: for bitem in items:
filename = bitem.data(QtCore.Qt.UserRole) file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
(path, name) = os.path.split(filename) path, file_name = file_path.parent, file_path.name
service_item.title = name service_item.title = file_name
if os.path.exists(filename): if file_path.exists:
if self.display_type_combo_box.itemData(self.display_type_combo_box.currentIndex()) == 'automatic': if self.display_type_combo_box.itemData(self.display_type_combo_box.currentIndex()) == 'automatic':
service_item.processor = self.find_controller_by_type(filename) service_item.processor = self.find_controller_by_type(file_path)
if not service_item.processor: if not service_item.processor:
return False return False
controller = self.controllers[service_item.processor] controller = self.controllers[service_item.processor]
doc = controller.add_document(filename) doc = controller.add_document(file_path)
if doc.get_thumbnail_path(1, True) is None: if doc.get_thumbnail_path(1, True) is None:
doc.load_presentation() doc.load_presentation()
i = 1 i = 1
img = doc.get_thumbnail_path(i, True) thumbnail_path = doc.get_thumbnail_path(i, True)
if img: if thumbnail_path:
# Get titles and notes # Get titles and notes
titles, notes = doc.get_titles_and_notes() titles, notes = doc.get_titles_and_notes()
service_item.add_capability(ItemCapabilities.HasDisplayTitle) service_item.add_capability(ItemCapabilities.HasDisplayTitle)
if notes.count('') != len(notes): if notes.count('') != len(notes):
service_item.add_capability(ItemCapabilities.HasNotes) service_item.add_capability(ItemCapabilities.HasNotes)
service_item.add_capability(ItemCapabilities.HasThumbnails) service_item.add_capability(ItemCapabilities.HasThumbnails)
while img: while thumbnail_path:
# Use title and note if available # Use title and note if available
title = '' title = ''
if titles and len(titles) >= i: if titles and len(titles) >= i:
@ -359,9 +357,9 @@ class PresentationMediaItem(MediaManagerItem):
note = '' note = ''
if notes and len(notes) >= i: if notes and len(notes) >= i:
note = notes[i - 1] note = notes[i - 1]
service_item.add_from_command(path, name, img, title, note) service_item.add_from_command(str(path), file_name, str(thumbnail_path), title, note)
i += 1 i += 1
img = doc.get_thumbnail_path(i, True) thumbnail_path = doc.get_thumbnail_path(i, True)
doc.close_presentation() doc.close_presentation()
return True return True
else: else:
@ -371,7 +369,7 @@ class PresentationMediaItem(MediaManagerItem):
'Missing Presentation'), 'Missing Presentation'),
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'The presentation {name} is incomplete, ' 'The presentation {name} is incomplete, '
'please reload.').format(name=filename)) 'please reload.').format(name=file_path))
return False return False
else: else:
# File is no longer present # File is no longer present
@ -379,18 +377,20 @@ class PresentationMediaItem(MediaManagerItem):
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'The presentation {name} no longer exists.' 'The presentation {name} no longer exists.'
).format(name=filename)) ).format(name=file_path))
return False return False
def find_controller_by_type(self, filename): def find_controller_by_type(self, file_path):
""" """
Determine the default application controller to use for the selected file type. This is used if "Automatic" is Determine the default application controller to use for the selected file type. This is used if "Automatic" is
set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension. set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
If none found, then look for a controller which "also supports" it instead. If none found, then look for a controller which "also supports" it instead.
:param filename: The file name :param openlp.core.common.path.Path file_path: The file path
:return: The default application controller for this file type, or None if not supported
:rtype: PresentationController
""" """
file_type = os.path.splitext(filename)[1][1:] file_type = file_path.suffix[1:]
if not file_type: if not file_type:
return None return None
for controller in self.controllers: for controller in self.controllers:

View File

@ -19,16 +19,15 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import logging
import copy import copy
import os import logging
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common import Registry, Settings from openlp.core.common import Registry, Settings
from openlp.core.ui import HideMode from openlp.core.common.path import Path
from openlp.core.lib import ServiceItemContext from openlp.core.lib import ServiceItemContext
from openlp.core.ui import HideMode
from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -325,21 +324,25 @@ class MessageListener(object):
is_live = message[1] is_live = message[1]
item = message[0] item = message[0]
hide_mode = message[2] hide_mode = message[2]
file = item.get_frame_path() file_path = Path(item.get_frame_path())
self.handler = item.processor self.handler = item.processor
# When starting presentation from the servicemanager we convert # When starting presentation from the servicemanager we convert
# PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager # PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager
# the conversion has already been done at this point. # the conversion has already been done at this point.
file_type = os.path.splitext(file.lower())[1][1:] file_type = file_path.suffix.lower()[1:]
if file_type in PDF_CONTROLLER_FILETYPES: if file_type in PDF_CONTROLLER_FILETYPES:
log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file)) log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file_path))
# Create a copy of the original item, and then clear the original item so it can be filled with images # Create a copy of the original item, and then clear the original item so it can be filled with images
item_cpy = copy.copy(item) item_cpy = copy.copy(item)
item.__init__(None) item.__init__(None)
if is_live: if is_live:
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file) # TODO: To Path object
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live,
str(file_path))
else: else:
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file) # TODO: To Path object
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview,
str(file_path))
# Some of the original serviceitem attributes is needed in the new serviceitem # Some of the original serviceitem attributes is needed in the new serviceitem
item.footer = item_cpy.footer item.footer = item_cpy.footer
item.from_service = item_cpy.from_service item.from_service = item_cpy.from_service
@ -352,13 +355,13 @@ class MessageListener(object):
self.handler = None self.handler = None
else: else:
if self.handler == self.media_item.automatic: if self.handler == self.media_item.automatic:
self.handler = self.media_item.find_controller_by_type(file) self.handler = self.media_item.find_controller_by_type(file_path)
if not self.handler: if not self.handler:
return return
else: else:
# the saved handler is not present so need to use one based on file suffix. # the saved handler is not present so need to use one based on file_path suffix.
if not self.controllers[self.handler].available: if not self.controllers[self.handler].available:
self.handler = self.media_item.find_controller_by_type(file) self.handler = self.media_item.find_controller_by_type(file_path)
if not self.handler: if not self.handler:
return return
if is_live: if is_live:
@ -370,7 +373,7 @@ class MessageListener(object):
if self.handler is None: if self.handler is None:
self.controller = controller self.controller = controller
else: else:
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) controller.add_handler(self.controllers[self.handler], file_path, hide_mode, message[3])
self.timer.start() self.timer.start()
def slide(self, message): def slide(self, message):

View File

@ -23,13 +23,13 @@
import os import os
import logging import logging
import re import re
from shutil import which
from subprocess import check_output, CalledProcessError from subprocess import check_output, CalledProcessError
from openlp.core.common import AppLocation, check_binary_exists from openlp.core.common import AppLocation, check_binary_exists
from openlp.core.common import Settings, is_win from openlp.core.common import Settings, is_win
from openlp.core.common.path import Path, path_to_str from openlp.core.common.path import Path, path_to_str
from openlp.core.lib import ScreenList from openlp.core.lib import ScreenList
from openlp.core.lib.shutil import which
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
if is_win(): if is_win():
@ -66,11 +66,12 @@ class PdfController(PresentationController):
Function that checks whether a binary is either ghostscript or mudraw or neither. Function that checks whether a binary is either ghostscript or mudraw or neither.
Is also used from presentationtab.py Is also used from presentationtab.py
:param program_path:The full path to the binary to check. :param openlp.core.common.path.Path program_path: The full path to the binary to check.
:return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid. :return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid.
:rtype: str | None
""" """
program_type = None program_type = None
runlog = check_binary_exists(Path(program_path)) runlog = check_binary_exists(program_path)
# Analyse the output to see it the program is mudraw, ghostscript or neither # Analyse the output to see it the program is mudraw, ghostscript or neither
for line in runlog.splitlines(): for line in runlog.splitlines():
decoded_line = line.decode() decoded_line = line.decode()
@ -107,30 +108,29 @@ class PdfController(PresentationController):
:return: True if program to open PDF-files was found, otherwise False. :return: True if program to open PDF-files was found, otherwise False.
""" """
log.debug('check_installed Pdf') log.debug('check_installed Pdf')
self.mudrawbin = '' self.mudrawbin = None
self.mutoolbin = '' self.mutoolbin = None
self.gsbin = '' self.gsbin = None
self.also_supports = [] self.also_supports = []
# Use the user defined program if given # Use the user defined program if given
if Settings().value('presentations/enable_pdf_program'): if Settings().value('presentations/enable_pdf_program'):
pdf_program = path_to_str(Settings().value('presentations/pdf_program')) program_path = Settings().value('presentations/pdf_program')
program_type = self.process_check_binary(pdf_program) program_type = self.process_check_binary(program_path)
if program_type == 'gs': if program_type == 'gs':
self.gsbin = pdf_program self.gsbin = program_path
elif program_type == 'mudraw': elif program_type == 'mudraw':
self.mudrawbin = pdf_program self.mudrawbin = program_path
elif program_type == 'mutool': elif program_type == 'mutool':
self.mutoolbin = pdf_program self.mutoolbin = program_path
else: else:
# Fallback to autodetection # Fallback to autodetection
application_path = str(AppLocation.get_directory(AppLocation.AppDir)) application_path = AppLocation.get_directory(AppLocation.AppDir)
if is_win(): if is_win():
# for windows we only accept mudraw.exe or mutool.exe in the base folder # for windows we only accept mudraw.exe or mutool.exe in the base folder
application_path = str(AppLocation.get_directory(AppLocation.AppDir)) if (application_path / 'mudraw.exe').is_file():
if os.path.isfile(os.path.join(application_path, 'mudraw.exe')): self.mudrawbin = application_path / 'mudraw.exe'
self.mudrawbin = os.path.join(application_path, 'mudraw.exe') elif (application_path / 'mutool.exe').is_file():
elif os.path.isfile(os.path.join(application_path, 'mutool.exe')): self.mutoolbin = application_path / 'mutool.exe'
self.mutoolbin = os.path.join(application_path, 'mutool.exe')
else: else:
DEVNULL = open(os.devnull, 'wb') DEVNULL = open(os.devnull, 'wb')
# First try to find mudraw # First try to find mudraw
@ -143,11 +143,11 @@ class PdfController(PresentationController):
self.gsbin = which('gs') self.gsbin = which('gs')
# Last option: check if mudraw or mutool is placed in OpenLP base folder # Last option: check if mudraw or mutool is placed in OpenLP base folder
if not self.mudrawbin and not self.mutoolbin and not self.gsbin: if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
application_path = str(AppLocation.get_directory(AppLocation.AppDir)) application_path = AppLocation.get_directory(AppLocation.AppDir)
if os.path.isfile(os.path.join(application_path, 'mudraw')): if (application_path / 'mudraw').is_file():
self.mudrawbin = os.path.join(application_path, 'mudraw') self.mudrawbin = application_path / 'mudraw'
elif os.path.isfile(os.path.join(application_path, 'mutool')): elif (application_path / 'mutool').is_file():
self.mutoolbin = os.path.join(application_path, 'mutool') self.mutoolbin = application_path / 'mutool'
if self.mudrawbin or self.mutoolbin: if self.mudrawbin or self.mutoolbin:
self.also_supports = ['xps', 'oxps'] self.also_supports = ['xps', 'oxps']
return True return True
@ -172,12 +172,15 @@ class PdfDocument(PresentationDocument):
image-serviceitem on the fly and present as such. Therefore some of the 'playback' image-serviceitem on the fly and present as such. Therefore some of the 'playback'
functions is not implemented. functions is not implemented.
""" """
def __init__(self, controller, presentation): def __init__(self, controller, document_path):
""" """
Constructor, store information about the file and initialise. Constructor, store information about the file and initialise.
:param openlp.core.common.path.Path document_path: Path to the document to load
:rtype: None
""" """
log.debug('Init Presentation Pdf') log.debug('Init Presentation Pdf')
PresentationDocument.__init__(self, controller, presentation) super().__init__(controller, document_path)
self.presentation = None self.presentation = None
self.blanked = False self.blanked = False
self.hidden = False self.hidden = False
@ -200,13 +203,13 @@ class PdfDocument(PresentationDocument):
:return: The resolution dpi to be used. :return: The resolution dpi to be used.
""" """
# Use a postscript script to get size of the pdf. It is assumed that all pages have same size # Use a postscript script to get size of the pdf. It is assumed that all pages have same size
gs_resolution_script = str(AppLocation.get_directory( gs_resolution_script = AppLocation.get_directory(
AppLocation.PluginsDir)) + '/presentations/lib/ghostscript_get_resolution.ps' AppLocation.PluginsDir) / 'presentations' / 'lib' / 'ghostscript_get_resolution.ps'
# Run the script on the pdf to get the size # Run the script on the pdf to get the size
runlog = [] runlog = []
try: try:
runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', runlog = check_output([str(self.controller.gsbin), '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
'-sFile=' + self.file_path, gs_resolution_script], '-sFile={file_path}'.format(file_path=self.file_path), str(gs_resolution_script)],
startupinfo=self.startupinfo) startupinfo=self.startupinfo)
except CalledProcessError as e: except CalledProcessError as e:
log.debug(' '.join(e.cmd)) log.debug(' '.join(e.cmd))
@ -246,7 +249,7 @@ class PdfDocument(PresentationDocument):
created_files = sorted(temp_dir_path.glob('*')) created_files = sorted(temp_dir_path.glob('*'))
for image_path in created_files: for image_path in created_files:
if image_path.is_file(): if image_path.is_file():
self.image_files.append(str(image_path)) self.image_files.append(image_path)
self.num_pages = len(self.image_files) self.num_pages = len(self.image_files)
return True return True
size = ScreenList().current['size'] size = ScreenList().current['size']
@ -258,27 +261,27 @@ class PdfDocument(PresentationDocument):
# The %03d in the file name is handled by each binary # The %03d in the file name is handled by each binary
if self.controller.mudrawbin: if self.controller.mudrawbin:
log.debug('loading presentation using mudraw') log.debug('loading presentation using mudraw')
runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()), runlog = check_output([str(self.controller.mudrawbin), '-w', str(size.width()), '-h', str(size.height()),
'-o', str(temp_dir_path / 'mainslide%03d.png'), self.file_path], '-o', str(temp_dir_path / 'mainslide%03d.png'), str(self.file_path)],
startupinfo=self.startupinfo) startupinfo=self.startupinfo)
elif self.controller.mutoolbin: elif self.controller.mutoolbin:
log.debug('loading presentation using mutool') log.debug('loading presentation using mutool')
runlog = check_output([self.controller.mutoolbin, 'draw', '-w', str(size.width()), '-h', runlog = check_output([str(self.controller.mutoolbin), 'draw', '-w', str(size.width()),
str(size.height()), '-h', str(size.height()), '-o', str(temp_dir_path / 'mainslide%03d.png'),
'-o', str(temp_dir_path / 'mainslide%03d.png'), self.file_path], str(self.file_path)],
startupinfo=self.startupinfo) startupinfo=self.startupinfo)
elif self.controller.gsbin: elif self.controller.gsbin:
log.debug('loading presentation using gs') log.debug('loading presentation using gs')
resolution = self.gs_get_resolution(size) resolution = self.gs_get_resolution(size)
runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', runlog = check_output([str(self.controller.gsbin), '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
'-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-r{res}'.format(res=resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
'-sOutputFile=' + str(temp_dir_path / 'mainslide%03d.png'), '-sOutputFile={output}'.format(output=temp_dir_path / 'mainslide%03d.png'),
self.file_path], startupinfo=self.startupinfo) str(self.file_path)], startupinfo=self.startupinfo)
created_files = sorted(temp_dir_path.glob('*')) created_files = sorted(temp_dir_path.glob('*'))
for image_path in created_files: for image_path in created_files:
if image_path.is_file(): if image_path.is_file():
self.image_files.append(str(image_path)) self.image_files.append(image_path)
except Exception: except Exception as e:
log.exception(runlog) log.exception(runlog)
return False return False
self.num_pages = len(self.image_files) self.num_pages = len(self.image_files)

View File

@ -120,15 +120,16 @@ class PowerpointDocument(PresentationDocument):
Class which holds information and controls a single presentation. Class which holds information and controls a single presentation.
""" """
def __init__(self, controller, presentation): def __init__(self, controller, document_path):
""" """
Constructor, store information about the file and initialise. Constructor, store information about the file and initialise.
:param controller: :param controller:
:param presentation: :param openlp.core.common.path.Path document_path: Path to the document to load
:rtype: None
""" """
log.debug('Init Presentation Powerpoint') log.debug('Init Presentation Powerpoint')
super(PowerpointDocument, self).__init__(controller, presentation) super().__init__(controller, document_path)
self.presentation = None self.presentation = None
self.index_map = {} self.index_map = {}
self.slide_count = 0 self.slide_count = 0
@ -145,7 +146,7 @@ class PowerpointDocument(PresentationDocument):
try: try:
if not self.controller.process: if not self.controller.process:
self.controller.start_process() self.controller.start_process()
self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False) self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count) self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
self.create_thumbnails() self.create_thumbnails()
self.create_titles_and_notes() self.create_titles_and_notes()
@ -363,9 +364,8 @@ class PowerpointDocument(PresentationDocument):
width=size.width(), width=size.width(),
horizontal=(right - left))) horizontal=(right - left)))
log.debug('window title: {title}'.format(title=window_title)) log.debug('window title: {title}'.format(title=window_title))
filename_root, filename_ext = os.path.splitext(os.path.basename(self.file_path))
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \ if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
size.width() == (right - left) and filename_root in window_title: size.width() == (right - left) and self.file_path.stem in window_title:
log.debug('Found a match and will save the handle') log.debug('Found a match and will save the handle')
self.presentation_hwnd = hwnd self.presentation_hwnd = hwnd
# Stop powerpoint from flashing in the taskbar # Stop powerpoint from flashing in the taskbar

View File

@ -85,9 +85,9 @@ class PptviewController(PresentationController):
if self.process: if self.process:
return return
log.debug('start PPTView') log.debug('start PPTView')
dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), dll_path = AppLocation.get_directory(AppLocation.AppDir) \
'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll') / 'plugins' / 'presentations' / 'lib' / 'pptviewlib' / 'pptviewlib.dll'
self.process = cdll.LoadLibrary(dll_path) self.process = cdll.LoadLibrary(str(dll_path))
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
self.process.SetDebug(1) self.process.SetDebug(1)
@ -104,12 +104,15 @@ class PptviewDocument(PresentationDocument):
""" """
Class which holds information and controls a single presentation. Class which holds information and controls a single presentation.
""" """
def __init__(self, controller, presentation): def __init__(self, controller, document_path):
""" """
Constructor, store information about the file and initialise. Constructor, store information about the file and initialise.
:param openlp.core.common.path.Path document_path: File path to the document to load
:rtype: None
""" """
log.debug('Init Presentation PowerPoint') log.debug('Init Presentation PowerPoint')
super(PptviewDocument, self).__init__(controller, presentation) super().__init__(controller, document_path)
self.presentation = None self.presentation = None
self.ppt_id = None self.ppt_id = None
self.blanked = False self.blanked = False
@ -121,17 +124,16 @@ class PptviewDocument(PresentationDocument):
the background PptView task started earlier. the background PptView task started earlier.
""" """
log.debug('LoadPresentation') log.debug('LoadPresentation')
temp_dir_path = self.get_temp_folder() temp_path = self.get_temp_folder()
size = ScreenList().current['size'] size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom()) rect = RECT(size.x(), size.y(), size.right(), size.bottom())
self.file_path = os.path.normpath(self.file_path) preview_path = temp_path / 'slide'
preview_path = temp_dir_path / 'slide'
# Ensure that the paths are null terminated # Ensure that the paths are null terminated
byte_file_path = self.file_path.encode('utf-16-le') + b'\0' file_path_utf16 = str(self.file_path).encode('utf-16-le') + b'\0'
preview_file_name = str(preview_path).encode('utf-16-le') + b'\0' preview_path_utf16 = str(preview_path).encode('utf-16-le') + b'\0'
if not temp_dir_path: if not temp_path.is_dir():
temp_dir_path.mkdir(parents=True) temp_path.mkdir(parents=True)
self.ppt_id = self.controller.process.OpenPPT(byte_file_path, None, rect, preview_file_name) self.ppt_id = self.controller.process.OpenPPT(file_path_utf16, None, rect, preview_path_utf16)
if self.ppt_id >= 0: if self.ppt_id >= 0:
self.create_thumbnails() self.create_thumbnails()
self.stop_presentation() self.stop_presentation()
@ -148,7 +150,7 @@ class PptviewDocument(PresentationDocument):
return return
log.debug('create_thumbnails proceeding') log.debug('create_thumbnails proceeding')
for idx in range(self.get_slide_count()): for idx in range(self.get_slide_count()):
path = '{folder}\\slide{index}.bmp'.format(folder=self.get_temp_folder(), index=str(idx + 1)) path = self.get_temp_folder() / 'slide{index:d}.bmp'.format(index=idx + 1)
self.convert_thumbnail(path, idx + 1) self.convert_thumbnail(path, idx + 1)
def create_titles_and_notes(self): def create_titles_and_notes(self):
@ -161,13 +163,12 @@ class PptviewDocument(PresentationDocument):
""" """
titles = None titles = None
notes = None notes = None
filename = os.path.normpath(self.file_path)
# let's make sure we have a valid zipped presentation # let's make sure we have a valid zipped presentation
if os.path.exists(filename) and zipfile.is_zipfile(filename): if self.file_path.exists() and zipfile.is_zipfile(str(self.file_path)):
namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main", namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"} "a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
# open the file # open the file
with zipfile.ZipFile(filename) as zip_file: with zipfile.ZipFile(str(self.file_path)) as zip_file:
# find the presentation.xml to get the slide count # find the presentation.xml to get the slide count
with zip_file.open('ppt/presentation.xml') as pres: with zip_file.open('ppt/presentation.xml') as pres:
tree = ElementTree.parse(pres) tree = ElementTree.parse(pres)

View File

@ -19,10 +19,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import logging import logging
import os
import shutil
from PyQt5 import QtCore from PyQt5 import QtCore
@ -87,19 +84,26 @@ class PresentationDocument(object):
Returns a path to an image containing a preview for the requested slide Returns a path to an image containing a preview for the requested slide
""" """
def __init__(self, controller, name): def __init__(self, controller, document_path):
""" """
Constructor for the PresentationController class Constructor for the PresentationController class
:param controller:
:param openlp.core.common.path.Path document_path: Path to the document to load.
:rtype: None
""" """
self.controller = controller self.controller = controller
self._setup(name) self._setup(document_path)
def _setup(self, name): def _setup(self, document_path):
""" """
Run some initial setup. This method is separate from __init__ in order to mock it out in tests. Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
:param openlp.core.common.path.Path document_path: Path to the document to load.
:rtype: None
""" """
self.slide_number = 0 self.slide_number = 0
self.file_path = name self.file_path = document_path
check_directory_exists(self.get_thumbnail_folder()) check_directory_exists(self.get_thumbnail_folder())
def load_presentation(self): def load_presentation(self):
@ -126,12 +130,6 @@ class PresentationDocument(object):
except OSError: except OSError:
log.exception('Failed to delete presentation controller files') log.exception('Failed to delete presentation controller files')
def get_file_name(self):
"""
Return just the filename of the presentation, without the directory
"""
return os.path.split(self.file_path)[1]
def get_thumbnail_folder(self): def get_thumbnail_folder(self):
""" """
The location where thumbnail images will be stored The location where thumbnail images will be stored
@ -141,9 +139,9 @@ class PresentationDocument(object):
""" """
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
if Settings().value('presentations/thumbnail_scheme') == 'md5': if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash(self.file_path.encode('utf-8')) folder = md5_hash(bytes(self.file_path))
else: else:
folder = self.get_file_name() folder = self.file_path.name
return Path(self.controller.thumbnail_folder, folder) return Path(self.controller.thumbnail_folder, folder)
def get_temp_folder(self): def get_temp_folder(self):
@ -155,19 +153,22 @@ class PresentationDocument(object):
""" """
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
if Settings().value('presentations/thumbnail_scheme') == 'md5': if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash(self.file_path.encode('utf-8')) folder = md5_hash(bytes(self.file_path))
else: else:
folder = self.get_file_name() folder = self.file_path.name
return Path(self.controller.temp_folder, folder) return Path(self.controller.temp_folder, folder)
def check_thumbnails(self): def check_thumbnails(self):
""" """
Returns ``True`` if the thumbnail images exist and are more recent than the powerpoint file. Check that the last thumbnail image exists and is valid and are more recent than the powerpoint file.
:return: If the thumbnail is valid
:rtype: bool
""" """
last_image = self.get_thumbnail_path(self.get_slide_count(), True) last_image_path = self.get_thumbnail_path(self.get_slide_count(), True)
if not (last_image and os.path.isfile(last_image)): if not (last_image_path and last_image_path.is_file()):
return False return False
return validate_thumb(self.file_path, last_image) return validate_thumb(self.file_path, last_image_path)
def close_presentation(self): def close_presentation(self):
""" """
@ -250,24 +251,28 @@ class PresentationDocument(object):
""" """
pass pass
def convert_thumbnail(self, file, idx): def convert_thumbnail(self, image_path, index):
""" """
Convert the slide image the application made to a scaled 360px height .png image. Convert the slide image the application made to a scaled 360px height .png image.
:param openlp.core.common.path.Path image_path: Path to the image to create a thumb nail of
:param int index: The index of the slide to create the thumbnail for.
:rtype: None
""" """
if self.check_thumbnails(): if self.check_thumbnails():
return return
if os.path.isfile(file): if image_path.is_file():
thumb_path = self.get_thumbnail_path(idx, False) thumb_path = self.get_thumbnail_path(index, False)
create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360)) create_thumb(str(image_path), str(thumb_path), False, QtCore.QSize(-1, 360))
def get_thumbnail_path(self, slide_no, check_exists=True): def get_thumbnail_path(self, slide_no, check_exists=False):
""" """
Returns an image path containing a preview for the requested slide Returns an image path containing a preview for the requested slide
:param int slide_no: The slide an image is required for, starting at 1 :param int slide_no: The slide an image is required for, starting at 1
:param bool check_exists: Check if the generated path exists :param bool check_exists: Check if the generated path exists
:return: The path, or None if the :param:`check_exists` is True and the file does not exist :return: The path, or None if the :param:`check_exists` is True and the file does not exist
:rtype: openlp.core.common.path.Path, None :rtype: openlp.core.common.path.Path | None
""" """
path = self.get_thumbnail_folder() / (self.controller.thumbnail_prefix + str(slide_no) + '.png') path = self.get_thumbnail_folder() / (self.controller.thumbnail_prefix + str(slide_no) + '.png')
if path.is_file() or not check_exists: if path.is_file() or not check_exists:
@ -313,23 +318,17 @@ class PresentationDocument(object):
Reads the titles from the titles file and Reads the titles from the titles file and
the notes files and returns the content in two lists the notes files and returns the content in two lists
""" """
titles = []
notes = [] notes = []
titles_file = str(self.get_thumbnail_folder() / 'titles.txt') titles_path = self.get_thumbnail_folder() / 'titles.txt'
if os.path.exists(titles_file):
try: try:
with open(titles_file, encoding='utf-8') as fi: titles = titles_path.read_text().splitlines()
titles = fi.read().splitlines()
except: except:
log.exception('Failed to open/read existing titles file') log.exception('Failed to open/read existing titles file')
titles = [] titles = []
for slide_no, title in enumerate(titles, 1): for slide_no, title in enumerate(titles, 1):
notes_file = str(self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)) notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
note = ''
if os.path.exists(notes_file):
try: try:
with open(notes_file, encoding='utf-8') as fn: note = notes_path.read_text()
note = fn.read()
except: except:
log.exception('Failed to open/read notes file') log.exception('Failed to open/read notes file')
note = '' note = ''
@ -338,18 +337,19 @@ class PresentationDocument(object):
def save_titles_and_notes(self, titles, notes): def save_titles_and_notes(self, titles, notes):
""" """
Performs the actual persisting of titles to the titles.txt Performs the actual persisting of titles to the titles.txt and notes to the slideNote%.txt
and notes to the slideNote%.txt
:param list[str] titles: The titles to save
:param list[str] notes: The notes to save
:rtype: None
""" """
if titles: if titles:
titles_path = self.get_thumbnail_folder() / 'titles.txt' titles_path = self.get_thumbnail_folder() / 'titles.txt'
with titles_path.open(mode='wt', encoding='utf-8') as fo: titles_path.write_text('\n'.join(titles))
fo.writelines(titles)
if notes: if notes:
for slide_no, note in enumerate(notes, 1): for slide_no, note in enumerate(notes, 1):
notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no) notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
with notes_path.open(mode='wt', encoding='utf-8') as fn: notes_path.write_text(note)
fn.write(note)
class PresentationController(object): class PresentationController(object):
@ -426,12 +426,11 @@ class PresentationController(object):
self.document_class = document_class self.document_class = document_class
self.settings_section = self.plugin.settings_section self.settings_section = self.plugin.settings_section
self.available = None self.available = None
self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name) self.temp_folder = AppLocation.get_section_data_path(self.settings_section) / name
self.thumbnail_folder = os.path.join( self.thumbnail_folder = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
self.thumbnail_prefix = 'slide' self.thumbnail_prefix = 'slide'
check_directory_exists(Path(self.thumbnail_folder)) check_directory_exists(self.thumbnail_folder)
check_directory_exists(Path(self.temp_folder)) check_directory_exists(self.temp_folder)
def enabled(self): def enabled(self):
""" """
@ -466,11 +465,15 @@ class PresentationController(object):
log.debug('Kill') log.debug('Kill')
self.close_presentation() self.close_presentation()
def add_document(self, name): def add_document(self, document_path):
""" """
Called when a new presentation document is opened. Called when a new presentation document is opened.
:param openlp.core.common.path.Path document_path: Path to the document to load
:return: The document
:rtype: PresentationDocument
""" """
document = self.document_class(self, name) document = self.document_class(self, document_path)
self.docs.append(document) self.docs.append(document)
return document return document

View File

@ -38,7 +38,6 @@ class PresentationTab(SettingsTab):
""" """
Constructor Constructor
""" """
self.parent = parent
self.controllers = controllers self.controllers = controllers
super(PresentationTab, self).__init__(parent, title, visible_title, icon_path) super(PresentationTab, self).__init__(parent, title, visible_title, icon_path)
self.activated = False self.activated = False
@ -194,7 +193,7 @@ class PresentationTab(SettingsTab):
pdf_program_path = self.program_path_edit.path pdf_program_path = self.program_path_edit.path
enable_pdf_program = self.pdf_program_check_box.checkState() enable_pdf_program = self.pdf_program_check_box.checkState()
# If the given program is blank disable using the program # If the given program is blank disable using the program
if not pdf_program_path: if pdf_program_path is None:
enable_pdf_program = 0 enable_pdf_program = 0
if pdf_program_path != Settings().value(self.settings_section + '/pdf_program'): if pdf_program_path != Settings().value(self.settings_section + '/pdf_program'):
Settings().setValue(self.settings_section + '/pdf_program', pdf_program_path) Settings().setValue(self.settings_section + '/pdf_program', pdf_program_path)
@ -220,9 +219,11 @@ class PresentationTab(SettingsTab):
def on_program_path_edit_path_changed(self, new_path): def on_program_path_edit_path_changed(self, new_path):
""" """
Select the mudraw or ghostscript binary that should be used. Handle the `pathEditChanged` signal from program_path_edit
:param openlp.core.common.path.Path new_path: File path to the new program
:rtype: None
""" """
new_path = path_to_str(new_path)
if new_path: if new_path:
if not PdfController.process_check_binary(new_path): if not PdfController.process_check_binary(new_path):
critical_error_message_box(UiStrings().Error, critical_error_message_box(UiStrings().Error,

View File

@ -244,20 +244,3 @@ class TestPresentationDocument(TestCase):
# THEN: load_presentation should return false # THEN: load_presentation should return false
self.assertFalse(result, "PresentationDocument.load_presentation should return false.") self.assertFalse(result, "PresentationDocument.load_presentation should return false.")
def test_get_file_name(self):
"""
Test the PresentationDocument.get_file_name method.
"""
# GIVEN: A mocked os.path.split which returns a list, an instance of PresentationDocument and
# arbitary file_path.
self.mock_os.path.split.return_value = ['directory', 'file.ext']
instance = PresentationDocument(self.mock_controller, 'Name')
instance.file_path = 'filepath'
# WHEN: Calling get_file_name
result = instance.get_file_name()
# THEN: get_file_name should return 'file.ext'
self.assertEqual(result, 'file.ext')