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 math
from PyQt5 import QtCore, QtGui, Qt, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import translate
from openlp.core.common.path import Path
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
QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name.
:param icon:
The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file
location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.
: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 path
location like ``Path(/path/to/file.png)``. However, the **recommended** way is to specify a resource string.
:return: The build icon.
:rtype: QtGui.QIcon
"""
if isinstance(icon, QtGui.QIcon):
return icon
@ -136,6 +138,8 @@ def build_icon(icon):
button_icon = QtGui.QIcon()
if isinstance(icon, str):
pix_map = QtGui.QPixmap(icon)
elif isinstance(icon, Path):
pix_map = QtGui.QPixmap(str(icon))
elif isinstance(icon, QtGui.QImage):
pix_map = QtGui.QPixmap.fromImage(icon)
if pix_map:
@ -221,10 +225,12 @@ def validate_thumb(file_path, thumb_path):
:param thumb_path: The path to the thumb.
: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
image_date = os.stat(file_path).st_mtime
thumb_date = os.stat(thumb_path).st_mtime
image_date = file_path.stat().st_mtime
thumb_date = thumb_path.stat().st_mtime
return image_date <= thumb_date

View File

@ -359,10 +359,8 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
:param files: The files to be loaded.
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
"""
names = []
full_list = []
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))
duplicates_found = 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),))
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
import logging
import os
import time
from openlp.core.common import is_win, Registry, delete_file
from openlp.core.common.path import Path
from PyQt5 import QtCore
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():
from win32com.client import Dispatch
@ -55,14 +58,6 @@ else:
except ImportError:
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__)
@ -203,12 +198,15 @@ class ImpressDocument(PresentationDocument):
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.
:param openlp.core.common.path.Path document_path: File path for the document to load
:rtype: None
"""
log.debug('Init Presentation OpenOffice')
super(ImpressDocument, self).__init__(controller, presentation)
super().__init__(controller, document_path)
self.document = None
self.presentation = None
self.control = None
@ -225,10 +223,10 @@ class ImpressDocument(PresentationDocument):
if desktop is None:
self.controller.start_process()
desktop = self.controller.get_com_desktop()
url = 'file:///' + self.file_path.replace('\\', '/').replace(':', '|').replace(' ', '%20')
url = self.file_path.as_uri()
else:
desktop = self.controller.get_uno_desktop()
url = uno.systemPathToFileUrl(self.file_path)
url = uno.systemPathToFileUrl(str(self.file_path))
if desktop is None:
return False
self.desktop = desktop
@ -254,11 +252,11 @@ class ImpressDocument(PresentationDocument):
log.debug('create thumbnails OpenOffice')
if self.check_thumbnails():
return
temp_folder_path = self.get_temp_folder()
if is_win():
thumb_dir_url = 'file:///' + str(self.get_temp_folder()).replace('\\', '/') \
.replace(':', '|').replace(' ', '%20')
thumb_dir_url = temp_folder_path.as_uri()
else:
thumb_dir_url = uno.systemPathToFileUrl(str(self.get_temp_folder()))
thumb_dir_url = uno.systemPathToFileUrl(str(temp_folder_path))
properties = []
properties.append(self.create_property('FilterName', 'impress_png_Export'))
properties = tuple(properties)
@ -266,17 +264,16 @@ class ImpressDocument(PresentationDocument):
pages = doc.getDrawPages()
if not pages:
return
temp_folder_path = self.get_temp_folder()
if not temp_folder_path.isdir():
temp_folder_path.mkdir()
if not temp_folder_path.is_dir():
temp_folder_path.mkdir(parents=True)
for index in range(pages.getCount()):
page = pages.getByIndex(index)
doc.getCurrentController().setCurrentPage(page)
url_path = '{path}/{name}.png'.format(path=thumb_dir_url, name=str(index + 1))
path = temp_folder_path / '{number).png'.format(number=index + 1)
url_path = '{path}/{name:d}.png'.format(path=thumb_dir_url, name=index + 1)
path = temp_folder_path / '{number:d}.png'.format(number=index + 1)
try:
doc.storeToURL(url_path, properties)
self.convert_thumbnail(str(path), index + 1)
self.convert_thumbnail(path, index + 1)
delete_file(path)
except ErrorCodeIOException as exception:
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 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, Settings, UiStrings, translate
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,\
build_icon, check_item_selected, create_thumb, validate_thumb
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))
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()
def populate_display_types(self):
@ -152,54 +150,57 @@ class PresentationMediaItem(MediaManagerItem):
else:
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
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()
titles = [file_path.name for file_path in current_list]
file_paths = [str_to_path(filename) for filename in file_paths]
current_paths = self.get_file_list()
titles = [file_path.name for file_path in current_paths]
self.application.set_busy_cursor()
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.
files.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
for file in files:
file_paths.sort(key=lambda file_path: get_locale_key(file_path.name))
for file_path in file_paths:
if not initial_load:
self.main_window.increment_progress_bar()
if current_list.count(file) > 0:
if current_paths.count(file_path) > 0:
continue
filename = os.path.split(file)[1]
if not os.path.exists(file):
item_name = QtWidgets.QListWidgetItem(filename)
file_name = file_path.name
if not file_path.exists():
item_name = QtWidgets.QListWidgetItem(file_name)
item_name.setIcon(build_icon(ERROR_IMAGE))
item_name.setData(QtCore.Qt.UserRole, file)
item_name.setToolTip(file)
item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
item_name.setToolTip(str(file_path))
self.list_view.addItem(item_name)
else:
if titles.count(filename) > 0:
if titles.count(file_name) > 0:
if not initial_load:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.'))
continue
controller_name = self.find_controller_by_type(filename)
controller_name = self.find_controller_by_type(file_path)
if controller_name:
controller = self.controllers[controller_name]
doc = controller.add_document(file)
thumb = str(doc.get_thumbnail_folder() / 'icon.png')
preview = doc.get_thumbnail_path(1, True)
if not preview and not initial_load:
doc = controller.add_document(file_path)
thumbnail_path = doc.get_thumbnail_folder() / 'icon.png'
preview_path = doc.get_thumbnail_path(1, True)
if not preview_path and not initial_load:
doc.load_presentation()
preview = doc.get_thumbnail_path(1, True)
preview_path = doc.get_thumbnail_path(1, True)
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')
else:
if validate_thumb(preview, thumb):
icon = build_icon(thumb)
if validate_thumb(preview_path, thumbnail_path):
icon = build_icon(thumbnail_path)
else:
icon = create_thumb(preview, thumb)
icon = create_thumb(str(preview_path), str(thumbnail_path))
else:
if initial_load:
icon = build_icon(':/general/general_delete.png')
@ -208,10 +209,10 @@ class PresentationMediaItem(MediaManagerItem):
translate('PresentationPlugin.MediaItem',
'This type of presentation is not supported.'))
continue
item_name = QtWidgets.QListWidgetItem(filename)
item_name.setData(QtCore.Qt.UserRole, file)
item_name = QtWidgets.QListWidgetItem(file_name)
item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
item_name.setIcon(icon)
item_name.setToolTip(file)
item_name.setToolTip(str(file_path))
self.list_view.addItem(item_name)
if not initial_load:
self.main_window.finished_progress_bar()
@ -228,8 +229,8 @@ class PresentationMediaItem(MediaManagerItem):
self.application.set_busy_cursor()
self.main_window.display_progress_bar(len(row_list))
for item in items:
filepath = str(item.data(QtCore.Qt.UserRole))
self.clean_up_thumbnails(filepath)
file_path = str_to_path(item.data(QtCore.Qt.UserRole))
self.clean_up_thumbnails(file_path)
self.main_window.increment_progress_bar()
self.main_window.finished_progress_bar()
for row in row_list:
@ -237,30 +238,29 @@ class PresentationMediaItem(MediaManagerItem):
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
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
:param filepath: File path of the presention to clean up after
:param clean_for_update: Only clean thumbnails if update is needed
:return: None
:param openlp.core.common.path.Path file_path: File path of the presention to clean up after
:param bool clean_for_update: Only clean thumbnails if update is needed
:rtype: None
"""
for cidx in self.controllers:
root, file_ext = os.path.splitext(filepath)
file_ext = file_ext[1:]
file_ext = file_path.suffix[1:]
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:
thumb_path = doc.get_thumbnail_path(1, True)
if not thumb_path or not os.path.exists(filepath) or os.path.getmtime(
thumb_path) < os.path.getmtime(filepath):
if not thumb_path or not file_path.exists() or \
thumb_path.stat().st_mtime < file_path.stat().st_mtime:
doc.presentation_deleted()
else:
doc.presentation_deleted()
doc.close_presentation()
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.
@ -276,10 +276,9 @@ class PresentationMediaItem(MediaManagerItem):
items = self.list_view.selectedItems()
if len(items) > 1:
return False
filename = presentation_file
if filename is None:
filename = items[0].data(QtCore.Qt.UserRole)
file_type = os.path.splitext(filename.lower())[1][1:]
if file_path is None:
file_path = str_to_path(items[0].data(QtCore.Qt.UserRole))
file_type = file_path.suffix.lower()[1:]
if not self.display_type_combo_box.currentText():
return False
service_item.add_capability(ItemCapabilities.CanEditTitle)
@ -292,29 +291,28 @@ class PresentationMediaItem(MediaManagerItem):
# force a nonexistent theme
service_item.theme = -1
for bitem in items:
filename = presentation_file
if filename is None:
filename = bitem.data(QtCore.Qt.UserRole)
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
processor = self.find_controller_by_type(filename)
if file_path is None:
file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
path, file_name = file_path.parent, file_path.name
service_item.title = file_name
if file_path.exists():
processor = self.find_controller_by_type(file_path)
if not processor:
return False
controller = self.controllers[processor]
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 \
not (doc.get_temp_folder() / 'mainslide001.png').is_file():
not (doc.get_temp_folder() / 'mainslide001.png').is_file():
doc.load_presentation()
i = 1
image = str(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))
while os.path.isfile(image):
service_item.add_from_image(image, name, thumbnail=thumbnail)
image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
while image_path.is_file():
service_item.add_from_image(str(image_path), file_name, thumbnail=str(thumbnail_path))
i += 1
image = str(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))
image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation()
return True
@ -324,34 +322,34 @@ class PresentationMediaItem(MediaManagerItem):
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation {name} no longer exists.'
).format(name=filename))
).format(name=file_path))
return False
else:
service_item.processor = self.display_type_combo_box.currentText()
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
for bitem in items:
filename = bitem.data(QtCore.Qt.UserRole)
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
path, file_name = file_path.parent, file_path.name
service_item.title = file_name
if file_path.exists:
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:
return False
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:
doc.load_presentation()
i = 1
img = doc.get_thumbnail_path(i, True)
if img:
thumbnail_path = doc.get_thumbnail_path(i, True)
if thumbnail_path:
# 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:
while thumbnail_path:
# Use title and note if available
title = ''
if titles and len(titles) >= i:
@ -359,9 +357,9 @@ class PresentationMediaItem(MediaManagerItem):
note = ''
if notes and len(notes) >= i:
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
img = doc.get_thumbnail_path(i, True)
thumbnail_path = doc.get_thumbnail_path(i, True)
doc.close_presentation()
return True
else:
@ -371,7 +369,7 @@ class PresentationMediaItem(MediaManagerItem):
'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation {name} is incomplete, '
'please reload.').format(name=filename))
'please reload.').format(name=file_path))
return False
else:
# File is no longer present
@ -379,18 +377,20 @@ class PresentationMediaItem(MediaManagerItem):
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation {name} no longer exists.'
).format(name=filename))
).format(name=file_path))
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
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.
: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:
return None
for controller in self.controllers:

View File

@ -19,16 +19,15 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
import copy
import os
import logging
from PyQt5 import QtCore
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.ui import HideMode
from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
log = logging.getLogger(__name__)
@ -325,21 +324,25 @@ class MessageListener(object):
is_live = message[1]
item = message[0]
hide_mode = message[2]
file = item.get_frame_path()
file_path = Path(item.get_frame_path())
self.handler = item.processor
# When starting presentation from the servicemanager we convert
# PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager
# 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:
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
item_cpy = copy.copy(item)
item.__init__(None)
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:
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
item.footer = item_cpy.footer
item.from_service = item_cpy.from_service
@ -352,13 +355,13 @@ class MessageListener(object):
self.handler = None
else:
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:
return
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:
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:
return
if is_live:
@ -370,7 +373,7 @@ class MessageListener(object):
if self.handler is None:
self.controller = controller
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()
def slide(self, message):

View File

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

View File

@ -85,9 +85,9 @@ class PptviewController(PresentationController):
if self.process:
return
log.debug('start PPTView')
dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
self.process = cdll.LoadLibrary(dll_path)
dll_path = AppLocation.get_directory(AppLocation.AppDir) \
/ 'plugins' / 'presentations' / 'lib' / 'pptviewlib' / 'pptviewlib.dll'
self.process = cdll.LoadLibrary(str(dll_path))
if log.isEnabledFor(logging.DEBUG):
self.process.SetDebug(1)
@ -104,12 +104,15 @@ class PptviewDocument(PresentationDocument):
"""
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.
:param openlp.core.common.path.Path document_path: File path to the document to load
:rtype: None
"""
log.debug('Init Presentation PowerPoint')
super(PptviewDocument, self).__init__(controller, presentation)
super().__init__(controller, document_path)
self.presentation = None
self.ppt_id = None
self.blanked = False
@ -121,17 +124,16 @@ class PptviewDocument(PresentationDocument):
the background PptView task started earlier.
"""
log.debug('LoadPresentation')
temp_dir_path = self.get_temp_folder()
temp_path = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
self.file_path = os.path.normpath(self.file_path)
preview_path = temp_dir_path / 'slide'
preview_path = temp_path / 'slide'
# Ensure that the paths are null terminated
byte_file_path = self.file_path.encode('utf-16-le') + b'\0'
preview_file_name = str(preview_path).encode('utf-16-le') + b'\0'
if not temp_dir_path:
temp_dir_path.mkdir(parents=True)
self.ppt_id = self.controller.process.OpenPPT(byte_file_path, None, rect, preview_file_name)
file_path_utf16 = str(self.file_path).encode('utf-16-le') + b'\0'
preview_path_utf16 = str(preview_path).encode('utf-16-le') + b'\0'
if not temp_path.is_dir():
temp_path.mkdir(parents=True)
self.ppt_id = self.controller.process.OpenPPT(file_path_utf16, None, rect, preview_path_utf16)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()
@ -148,7 +150,7 @@ class PptviewDocument(PresentationDocument):
return
log.debug('create_thumbnails proceeding')
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)
def create_titles_and_notes(self):
@ -161,13 +163,12 @@ class PptviewDocument(PresentationDocument):
"""
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):
if self.file_path.exists() and zipfile.is_zipfile(str(self.file_path)):
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:
with zipfile.ZipFile(str(self.file_path)) 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)

View File

@ -19,10 +19,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
import os
import shutil
from PyQt5 import QtCore
@ -87,19 +84,26 @@ class PresentationDocument(object):
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
:param controller:
:param openlp.core.common.path.Path document_path: Path to the document to load.
:rtype: None
"""
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.
:param openlp.core.common.path.Path document_path: Path to the document to load.
:rtype: None
"""
self.slide_number = 0
self.file_path = name
self.file_path = document_path
check_directory_exists(self.get_thumbnail_folder())
def load_presentation(self):
@ -126,12 +130,6 @@ class PresentationDocument(object):
except OSError:
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):
"""
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
if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash(self.file_path.encode('utf-8'))
folder = md5_hash(bytes(self.file_path))
else:
folder = self.get_file_name()
folder = self.file_path.name
return Path(self.controller.thumbnail_folder, folder)
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
if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash(self.file_path.encode('utf-8'))
folder = md5_hash(bytes(self.file_path))
else:
folder = self.get_file_name()
folder = self.file_path.name
return Path(self.controller.temp_folder, folder)
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)
if not (last_image and os.path.isfile(last_image)):
last_image_path = self.get_thumbnail_path(self.get_slide_count(), True)
if not (last_image_path and last_image_path.is_file()):
return False
return validate_thumb(self.file_path, last_image)
return validate_thumb(self.file_path, last_image_path)
def close_presentation(self):
"""
@ -250,24 +251,28 @@ class PresentationDocument(object):
"""
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.
: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():
return
if os.path.isfile(file):
thumb_path = self.get_thumbnail_path(idx, False)
create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360))
if image_path.is_file():
thumb_path = self.get_thumbnail_path(index, False)
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
:param int slide_no: The slide an image is required for, starting at 1
: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
: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')
if path.is_file() or not check_exists:
@ -313,43 +318,38 @@ class PresentationDocument(object):
Reads the titles from the titles file and
the notes files and returns the content in two lists
"""
titles = []
notes = []
titles_file = str(self.get_thumbnail_folder() / 'titles.txt')
if os.path.exists(titles_file):
try:
with open(titles_file, encoding='utf-8') as fi:
titles = fi.read().splitlines()
except:
log.exception('Failed to open/read existing titles file')
titles = []
titles_path = self.get_thumbnail_folder() / 'titles.txt'
try:
titles = titles_path.read_text().splitlines()
except:
log.exception('Failed to open/read existing titles file')
titles = []
for slide_no, title in enumerate(titles, 1):
notes_file = str(self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no))
note = ''
if os.path.exists(notes_file):
try:
with open(notes_file, encoding='utf-8') as fn:
note = fn.read()
except:
log.exception('Failed to open/read notes file')
note = ''
notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
try:
note = notes_path.read_text()
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
Performs the actual persisting of titles to the titles.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:
titles_path = self.get_thumbnail_folder() / 'titles.txt'
with titles_path.open(mode='wt', encoding='utf-8') as fo:
fo.writelines(titles)
titles_path.write_text('\n'.join(titles))
if notes:
for slide_no, note in enumerate(notes, 1):
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:
fn.write(note)
notes_path.write_text(note)
class PresentationController(object):
@ -426,12 +426,11 @@ class PresentationController(object):
self.document_class = document_class
self.settings_section = self.plugin.settings_section
self.available = None
self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name)
self.thumbnail_folder = os.path.join(
str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
self.temp_folder = AppLocation.get_section_data_path(self.settings_section) / name
self.thumbnail_folder = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
self.thumbnail_prefix = 'slide'
check_directory_exists(Path(self.thumbnail_folder))
check_directory_exists(Path(self.temp_folder))
check_directory_exists(self.thumbnail_folder)
check_directory_exists(self.temp_folder)
def enabled(self):
"""
@ -466,11 +465,15 @@ class PresentationController(object):
log.debug('Kill')
self.close_presentation()
def add_document(self, name):
def add_document(self, document_path):
"""
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)
return document

View File

@ -38,7 +38,6 @@ class PresentationTab(SettingsTab):
"""
Constructor
"""
self.parent = parent
self.controllers = controllers
super(PresentationTab, self).__init__(parent, title, visible_title, icon_path)
self.activated = False
@ -194,7 +193,7 @@ class PresentationTab(SettingsTab):
pdf_program_path = self.program_path_edit.path
enable_pdf_program = self.pdf_program_check_box.checkState()
# 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
if pdf_program_path != Settings().value(self.settings_section + '/pdf_program'):
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):
"""
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 not PdfController.process_check_binary(new_path):
critical_error_message_box(UiStrings().Error,

View File

@ -244,20 +244,3 @@ class TestPresentationDocument(TestCase):
# THEN: 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')