More pathlib changes, focused mainly on the presentation plugin

Add this to your merge proposal:
--------------------------------
lp:~phill-ridout/openlp/pathlib5 (revision 2777)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2193/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2096/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1983/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1353/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/...

bzr-revno: 2768
This commit is contained in:
Phill 2017-09-22 20:53:59 -07:00 committed by Raoul Snyman
commit 35e9300be2
35 changed files with 808 additions and 602 deletions

View File

@ -29,8 +29,6 @@ logging and a plugin framework are contained within the openlp.core module.
import argparse import argparse
import logging import logging
import os
import shutil
import sys import sys
import time import time
from datetime import datetime from datetime import datetime
@ -40,7 +38,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \ from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
check_directory_exists, is_macosx, is_win, translate check_directory_exists, is_macosx, is_win, translate
from openlp.core.common.path import Path from openlp.core.common.path import Path, copytree
from openlp.core.common.versionchecker import VersionThread, get_application_version from openlp.core.common.versionchecker import VersionThread, get_application_version
from openlp.core.lib import ScreenList from openlp.core.lib import ScreenList
from openlp.core.resources import qInitResources from openlp.core.resources import qInitResources
@ -181,24 +179,19 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
""" """
Check if the data folder path exists. Check if the data folder path exists.
""" """
data_folder_path = str(AppLocation.get_data_path()) data_folder_path = AppLocation.get_data_path()
if not os.path.exists(data_folder_path): if not data_folder_path.exists():
log.critical('Database was not found in: ' + data_folder_path) log.critical('Database was not found in: %s', data_folder_path)
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'), status = QtWidgets.QMessageBox.critical(
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}' None, translate('OpenLP', 'Data Directory Error'),
'\n\nThe location of the data folder was ' translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}\n\nThe location of the data folder '
'previously changed from the OpenLP\'s ' 'was previously changed from the OpenLP\'s default location. If the data was '
'default location. If the data was stored on ' 'stored on removable device, that device needs to be made available.\n\nYou may '
'removable device, that device needs to be ' 'reset the data location back to the default location, or you can try to make the '
'made available.\n\nYou may reset the data ' 'current location available.\n\nDo you want to reset to the default data location? '
'location back to the default location, ' 'If not, OpenLP will be closed so you can try to fix the the problem.')
'or you can try to make the current location '
'available.\n\nDo you want to reset to the '
'default data location? If not, OpenLP will be '
'closed so you can try to fix the the problem.')
.format(path=data_folder_path), .format(path=data_folder_path),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.No)
if status == QtWidgets.QMessageBox.No: if status == QtWidgets.QMessageBox.No:
# If answer was "No", return "True", it will shutdown OpenLP in def main # If answer was "No", return "True", it will shutdown OpenLP in def main
@ -253,11 +246,11 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
'a backup of the old data folder?'), 'a backup of the old data folder?'),
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes: defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
# Create copy of data folder # Create copy of data folder
data_folder_path = str(AppLocation.get_data_path()) data_folder_path = AppLocation.get_data_path()
timestamp = time.strftime("%Y%m%d-%H%M%S") timestamp = time.strftime("%Y%m%d-%H%M%S")
data_folder_backup_path = data_folder_path + '-' + timestamp data_folder_backup_path = data_folder_path.with_name(data_folder_path.name + '-' + timestamp)
try: try:
shutil.copytree(data_folder_path, data_folder_backup_path) copytree(data_folder_path, data_folder_backup_path)
except OSError: except OSError:
QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'), QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),
translate('OpenLP', 'Backup of the data folder failed!')) translate('OpenLP', 'Backup of the data folder failed!'))

View File

@ -29,7 +29,6 @@ import sys
from openlp.core.common import Settings, is_win, is_macosx from openlp.core.common import Settings, is_win, is_macosx
from openlp.core.common.path import Path from openlp.core.common.path import Path
if not is_win() and not is_macosx(): if not is_win() and not is_macosx():
try: try:
from xdg import BaseDirectory from xdg import BaseDirectory

View File

@ -211,7 +211,7 @@ def url_get_file(callback, url, f_path, sha256=None):
:param callback: the class which needs to be updated :param callback: the class which needs to be updated
:param url: URL to download :param url: URL to download
:param f_path: Destination file :param openlp.core.common.path.Path f_path: Destination file
:param sha256: The check sum value to be checked against the download value :param sha256: The check sum value to be checked against the download value
""" """
block_count = 0 block_count = 0
@ -220,29 +220,23 @@ def url_get_file(callback, url, f_path, sha256=None):
log.debug("url_get_file: " + url) log.debug("url_get_file: " + url)
while True: while True:
try: try:
filename = open(f_path, "wb")
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
if sha256: if sha256:
hasher = hashlib.sha256() hasher = hashlib.sha256()
with f_path.open('wb') as file:
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
# Download until finished or canceled. # Download until finished or canceled.
while not callback.was_cancelled: while not callback.was_cancelled:
data = url_file.read(block_size) data = url_file.read(block_size)
if not data: if not data:
break break
filename.write(data) file.write(data)
if sha256: if sha256:
hasher.update(data) hasher.update(data)
block_count += 1 block_count += 1
callback._download_progress(block_count, block_size) callback._download_progress(block_count, block_size)
filename.close() except (urllib.error.URLError, socket.timeout):
if sha256 and hasher.hexdigest() != sha256:
log.error('sha256 sums did not match for file: {file}'.format(file=f_path))
os.remove(f_path)
return False
except (urllib.error.URLError, socket.timeout) as err:
trace_error_handler(log) trace_error_handler(log)
filename.close() f_path.unlink()
os.remove(f_path)
if retries > CONNECTION_RETRIES: if retries > CONNECTION_RETRIES:
return False return False
else: else:
@ -251,8 +245,12 @@ def url_get_file(callback, url, f_path, sha256=None):
continue continue
break break
# Delete file if cancelled, it may be a partial file. # Delete file if cancelled, it may be a partial file.
if sha256 and hasher.hexdigest() != sha256:
log.error('sha256 sums did not match for file: {file}'.format(file=f_path))
f_path.unlink()
return False
if callback.was_cancelled: if callback.was_cancelled:
os.remove(f_path) f_path.unlink()
return True return True

View File

@ -141,7 +141,7 @@ class LanguageManager(object):
if reg_ex.exactMatch(qmf): if reg_ex.exactMatch(qmf):
name = '{regex}'.format(regex=reg_ex.cap(1)) name = '{regex}'.format(regex=reg_ex.cap(1))
LanguageManager.__qm_list__[ LanguageManager.__qm_list__[
'{count:>2i} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name '{count:>2d} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
@staticmethod @staticmethod
def get_qm_list(): def get_qm_list():

View File

@ -19,6 +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 shutil
from contextlib import suppress from contextlib import suppress
from openlp.core.common import is_win from openlp.core.common import is_win
@ -29,6 +30,121 @@ else:
from pathlib import PosixPath as PathVariant from pathlib import PosixPath as PathVariant
def replace_params(args, kwargs, params):
"""
Apply a transformation function to the specified args or kwargs
:param tuple args: Positional arguments
:param dict kwargs: Key Word arguments
:param params: A tuple of tuples with the position and the key word to replace.
:return: The modified positional and keyword arguments
:rtype: tuple[tuple, dict]
Usage:
Take a method with the following signature, and assume we which to apply the str function to arg2:
def method(arg1=None, arg2=None, arg3=None)
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
would call this function as follows:
replace_params(args, kwargs, ((1, 'arg2', str),))
"""
args = list(args)
for position, key_word, transform in params:
if len(args) > position:
args[position] = transform(args[position])
elif key_word in kwargs:
kwargs[key_word] = transform(kwargs[key_word])
return tuple(args), kwargs
def copy(*args, **kwargs):
"""
Wraps :func:`shutil.copy` so that we can accept Path objects.
:param src openlp.core.common.path.Path: Takes a Path object which is then converted to a str object
:param dst openlp.core.common.path.Path: Takes a Path object which is then converted to a str object
:return: Converts the str object received from :func:`shutil.copy` to a Path or NoneType object
:rtype: openlp.core.common.path.Path | None
See the following link for more information on the other parameters:
https://docs.python.org/3/library/shutil.html#shutil.copy
"""
args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
return str_to_path(shutil.copy(*args, **kwargs))
def copyfile(*args, **kwargs):
"""
Wraps :func:`shutil.copyfile` so that we can accept Path objects.
:param openlp.core.common.path.Path src: Takes a Path object which is then converted to a str object
:param openlp.core.common.path.Path dst: Takes a Path object which is then converted to a str object
:return: Converts the str object received from :func:`shutil.copyfile` to a Path or NoneType object
:rtype: openlp.core.common.path.Path | None
See the following link for more information on the other parameters:
https://docs.python.org/3/library/shutil.html#shutil.copyfile
"""
args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
return str_to_path(shutil.copyfile(*args, **kwargs))
def copytree(*args, **kwargs):
"""
Wraps :func:shutil.copytree` so that we can accept Path objects.
:param openlp.core.common.path.Path src : Takes a Path object which is then converted to a str object
:param openlp.core.common.path.Path dst: Takes a Path object which is then converted to a str object
:return: Converts the str object received from :func:`shutil.copytree` to a Path or NoneType object
:rtype: openlp.core.common.path.Path | None
See the following link for more information on the other parameters:
https://docs.python.org/3/library/shutil.html#shutil.copytree
"""
args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
return str_to_path(shutil.copytree(*args, **kwargs))
def rmtree(*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
"""
args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),))
return shutil.rmtree(*args, **kwargs)
def which(*args, **kwargs):
"""
Wraps :func:shutil.which` so that it return a Path objects.
:rtype: openlp.core.common.Path
See the following link for more information on the other parameters:
https://docs.python.org/3/library/shutil.html#shutil.which
"""
file_name = shutil.which(*args, **kwargs)
if file_name:
return str_to_path(file_name)
return None
def path_to_str(path=None): def path_to_str(path=None):
""" """
A utility function to convert a Path object or NoneType to a string equivalent. A utility function to convert a Path object or NoneType to a string equivalent.

View File

@ -143,7 +143,7 @@ class Registry(object):
log.exception('Exception for function {function}'.format(function=function)) log.exception('Exception for function {function}'.format(function=function))
else: else:
trace_error_handler(log) trace_error_handler(log)
log.error("Event {event} called but not registered".format(event=event)) log.exception('Event {event} called but not registered'.format(event=event))
return results return results
def get_flag(self, key): def get_flag(self, key):

View File

@ -88,9 +88,6 @@ class UiStrings(object):
self.Error = translate('OpenLP.Ui', 'Error') self.Error = translate('OpenLP.Ui', 'Error')
self.Export = translate('OpenLP.Ui', 'Export') self.Export = translate('OpenLP.Ui', 'Export')
self.File = translate('OpenLP.Ui', 'File') self.File = translate('OpenLP.Ui', 'File')
self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
self.FileNotFoundMessage = translate('OpenLP.Ui',
'File {name} not found.\nPlease try selecting it individually.')
self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit') self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
self.Help = translate('OpenLP.Ui', 'Help') self.Help = translate('OpenLP.Ui', 'Help')
self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours') self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')

View File

@ -32,6 +32,7 @@ import math
from PyQt5 import QtCore, QtGui, Qt, QtWidgets from PyQt5 import QtCore, QtGui, Qt, 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:
@ -217,14 +221,15 @@ def validate_thumb(file_path, thumb_path):
Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function, Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function,
before checking the existence of the file. before checking the existence of the file.
:param file_path: The path to the file. The file **must** exist! :param openlp.core.common.path.Path file_path: The path to the file. The file **must** exist!
:param thumb_path: The path to the thumb. :param openlp.core.common.path.Path thumb_path: The path to the thumb.
:return: True, False if the image has changed since the thumb was created. :return: Has the image changed since the thumb was created?
:rtype: bool
""" """
if not os.path.exists(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
@ -606,35 +611,6 @@ def create_separated_list(string_list):
return list_to_string return list_to_string
def replace_params(args, kwargs, params):
"""
Apply a transformation function to the specified args or kwargs
:param tuple args: Positional arguments
:param dict kwargs: Key Word arguments
:param params: A tuple of tuples with the position and the key word to replace.
:return: The modified positional and keyword arguments
:rtype: tuple[tuple, dict]
Usage:
Take a method with the following signature, and assume we which to apply the str function to arg2:
def method(arg1=None, arg2=None, arg3=None)
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
would call this function as follows:
replace_params(args, kwargs, ((1, 'arg2', str),))
"""
args = list(args)
for position, key_word, transform in params:
if len(args) > position:
args[position] = transform(args[position])
elif key_word in kwargs:
kwargs[key_word] = transform(kwargs[key_word])
return tuple(args), kwargs
from .exceptions import ValidationError from .exceptions import ValidationError
from .screen import ScreenList from .screen import ScreenList
from .formattingtags import FormattingTags from .formattingtags import FormattingTags

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

@ -563,7 +563,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
filename, sha256 = item.data(QtCore.Qt.UserRole) filename, sha256 = item.data(QtCore.Qt.UserRole)
self._increment_progress_bar(self.downloading.format(name=filename), 0) self._increment_progress_bar(self.downloading.format(name=filename), 0)
self.previous_size = 0 self.previous_size = 0
destination = os.path.join(songs_destination, str(filename)) destination = Path(songs_destination, str(filename))
if not url_get_file(self, '{path}{name}'.format(path=self.songs_url, name=filename), if not url_get_file(self, '{path}{name}'.format(path=self.songs_url, name=filename),
destination, sha256): destination, sha256):
missed_files.append('Song: {name}'.format(name=filename)) missed_files.append('Song: {name}'.format(name=filename))
@ -576,7 +576,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
self._increment_progress_bar(self.downloading.format(name=bible), 0) self._increment_progress_bar(self.downloading.format(name=bible), 0)
self.previous_size = 0 self.previous_size = 0
if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible), if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
os.path.join(bibles_destination, bible), Path(bibles_destination, bible),
sha256): sha256):
missed_files.append('Bible: {name}'.format(name=bible)) missed_files.append('Bible: {name}'.format(name=bible))
bibles_iterator += 1 bibles_iterator += 1
@ -588,7 +588,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
self._increment_progress_bar(self.downloading.format(name=theme), 0) self._increment_progress_bar(self.downloading.format(name=theme), 0)
self.previous_size = 0 self.previous_size = 0
if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme), if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
os.path.join(themes_destination, theme), Path(themes_destination, theme),
sha256): sha256):
missed_files.append('Theme: {name}'.format(name=theme)) missed_files.append('Theme: {name}'.format(name=theme))
if missed_files: if missed_files:

View File

@ -22,8 +22,7 @@
""" Patch the QFileDialog so it accepts and returns Path objects""" """ Patch the QFileDialog so it accepts and returns Path objects"""
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.path import Path, path_to_str, replace_params, str_to_path
from openlp.core.lib import replace_params
class FileDialog(QtWidgets.QFileDialog): class FileDialog(QtWidgets.QFileDialog):

View File

@ -310,7 +310,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
""" """
folder_path = FileDialog.getExistingDirectory( folder_path = FileDialog.getExistingDirectory(
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name), self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
QtWidgets.QFileDialog.ShowDirsOnly) FileDialog.ShowDirsOnly)
if folder_path: if folder_path:
editbox.setText(str(folder_path)) editbox.setText(str(folder_path))
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path) Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)

View File

@ -39,7 +39,7 @@ from openlp.core.api.http import server
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \ from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
check_directory_exists, translate, is_win, is_macosx, add_actions check_directory_exists, translate, is_win, is_macosx, add_actions
from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.path import Path, copyfile, path_to_str, str_to_path
from openlp.core.common.versionchecker import get_application_version from openlp.core.common.versionchecker import get_application_version
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
from openlp.core.lib.ui import create_action from openlp.core.lib.ui import create_action
@ -848,12 +848,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.No: if answer == QtWidgets.QMessageBox.No:
return return
import_file_name, filter_used = QtWidgets.QFileDialog.getOpenFileName( import_file_path, filter_used = FileDialog.getOpenFileName(
self, self,
translate('OpenLP.MainWindow', 'Import settings'), translate('OpenLP.MainWindow', 'Import settings'),
'', None,
translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)')) translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)'))
if not import_file_name: if import_file_path is None:
return return
setting_sections = [] setting_sections = []
# Add main sections. # Add main sections.
@ -871,12 +871,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
# Add plugin sections. # Add plugin sections.
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins]) setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
# Copy the settings file to the tmp dir, because we do not want to change the original one. # Copy the settings file to the tmp dir, because we do not want to change the original one.
temp_directory = os.path.join(str(gettempdir()), 'openlp') temp_dir_path = Path(gettempdir(), 'openlp')
check_directory_exists(Path(temp_directory)) check_directory_exists(temp_dir_path)
temp_config = os.path.join(temp_directory, os.path.basename(import_file_name)) temp_config_path = temp_dir_path / import_file_path.name
shutil.copyfile(import_file_name, temp_config) copyfile(import_file_path, temp_config_path)
settings = Settings() settings = Settings()
import_settings = Settings(temp_config, Settings.IniFormat) import_settings = Settings(str(temp_config_path), Settings.IniFormat)
log.info('hook upgrade_plugin_settings') log.info('hook upgrade_plugin_settings')
self.plugin_manager.hook_upgrade_plugin_settings(import_settings) self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
@ -920,7 +920,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
settings.setValue('{key}'.format(key=section_key), value) settings.setValue('{key}'.format(key=section_key), value)
now = datetime.now() now = datetime.now()
settings.beginGroup(self.header_section) settings.beginGroup(self.header_section)
settings.setValue('file_imported', import_file_name) settings.setValue('file_imported', import_file_path)
settings.setValue('file_date_imported', now.strftime("%Y-%m-%d %H:%M")) settings.setValue('file_date_imported', now.strftime("%Y-%m-%d %H:%M"))
settings.endGroup() settings.endGroup()
settings.sync() settings.sync()

View File

@ -366,16 +366,20 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
""" """
return self._modified return self._modified
def set_file_name(self, file_name): def set_file_name(self, file_path):
""" """
Setter for service file. Setter for service file.
:param file_name: The service file name :param openlp.core.common.path.Path file_path: The service file name
:rtype: None
""" """
self._file_name = str(file_name) self._file_name = path_to_str(file_path)
self.main_window.set_service_modified(self.is_modified(), self.short_file_name()) self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
Settings().setValue('servicemanager/last file', Path(file_name)) Settings().setValue('servicemanager/last file', file_path)
self._save_lite = self._file_name.endswith('.oszl') if file_path and file_path.suffix() == '.oszl':
self._save_lite = True
else:
self._save_lite = False
def file_name(self): def file_name(self):
""" """
@ -474,7 +478,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
""" """
self.service_manager_list.clear() self.service_manager_list.clear()
self.service_items = [] self.service_items = []
self.set_file_name('') self.set_file_name(None)
self.service_id += 1 self.service_id += 1
self.set_modified(False) self.set_modified(False)
Settings().setValue('servicemanager/last file', None) Settings().setValue('servicemanager/last file', None)
@ -695,27 +699,23 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
default_file_name = format_time(default_pattern, local_time) default_file_name = format_time(default_pattern, local_time)
else: else:
default_file_name = '' default_file_name = ''
directory = path_to_str(Settings().value(self.main_window.service_manager_settings_section + '/last directory')) directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory')
path = os.path.join(directory, default_file_name) file_path = directory_path / default_file_name
# SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
# the long term. # the long term.
if self._file_name.endswith('oszl') or self.service_has_all_original_files: if self._file_name.endswith('oszl') or self.service_has_all_original_files:
file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName( file_path, filter_used = FileDialog.getSaveFileName(
self.main_window, UiStrings().SaveService, path, self.main_window, UiStrings().SaveService, file_path,
translate('OpenLP.ServiceManager', translate('OpenLP.ServiceManager',
'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)')) 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
else: else:
file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName( file_path, filter_used = FileDialog.getSaveFileName(
self.main_window, UiStrings().SaveService, path, self.main_window, UiStrings().SaveService, file_path,
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;')) translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;'))
if not file_name: if not file_path:
return False return False
if os.path.splitext(file_name)[1] == '': file_path.with_suffix('.osz')
file_name += '.osz' self.set_file_name(file_path)
else:
ext = os.path.splitext(file_name)[1]
file_name.replace(ext, '.osz')
self.set_file_name(file_name)
self.decide_save_method() self.decide_save_method()
def decide_save_method(self, field=None): def decide_save_method(self, field=None):
@ -772,7 +772,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
return return
file_to.close() file_to.close()
self.new_file() self.new_file()
self.set_file_name(file_name) self.set_file_name(str_to_path(file_name))
self.main_window.display_progress_bar(len(items)) self.main_window.display_progress_bar(len(items))
self.process_service_items(items) self.process_service_items(items)
delete_file(Path(p_file)) delete_file(Path(p_file))

View File

@ -483,7 +483,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
name = text_name name = text_name
thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=text_name)) thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=text_name))
item_name = QtWidgets.QListWidgetItem(name) item_name = QtWidgets.QListWidgetItem(name)
if validate_thumb(theme, thumb): if validate_thumb(Path(theme), Path(thumb)):
icon = build_icon(thumb) icon = build_icon(thumb)
else: else:
icon = create_thumb(theme, thumb) icon = create_thumb(theme, thumb)

View File

@ -360,7 +360,7 @@ class ImageMediaItem(MediaManagerItem):
if not os.path.exists(image_file.filename): if not os.path.exists(image_file.filename):
icon = build_icon(':/general/general_delete.png') icon = build_icon(':/general/general_delete.png')
else: else:
if validate_thumb(image_file.filename, thumb): if validate_thumb(Path(image_file.filename), Path(thumb)):
icon = build_icon(thumb) icon = build_icon(thumb)
else: else:
icon = create_thumb(image_file.filename, thumb) icon = create_thumb(image_file.filename, thumb)

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,9 @@ 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')
else: else:
desktop = self.controller.get_uno_desktop() desktop = self.controller.get_uno_desktop()
url = uno.systemPathToFileUrl(self.file_path) url = self.file_path.as_uri()
if desktop is None: if desktop is None:
return False return False
self.desktop = desktop self.desktop = desktop
@ -254,11 +251,8 @@ class ImpressDocument(PresentationDocument):
log.debug('create thumbnails OpenOffice') log.debug('create thumbnails OpenOffice')
if self.check_thumbnails(): if self.check_thumbnails():
return return
if is_win(): temp_folder_path = self.get_temp_folder()
thumb_dir_url = 'file:///' + self.get_temp_folder().replace('\\', '/') \ thumb_dir_url = temp_folder_path.as_uri()
.replace(':', '|').replace(' ', '%20')
else:
thumb_dir_url = uno.systemPathToFileUrl(self.get_temp_folder())
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 +260,17 @@ class ImpressDocument(PresentationDocument):
pages = doc.getDrawPages() pages = doc.getDrawPages()
if not pages: if not pages:
return return
if not os.path.isdir(self.get_temp_folder()): if not temp_folder_path.is_dir():
os.makedirs(self.get_temp_folder()) temp_folder_path.mkdir(parents=True)
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 = os.path.join(self.get_temp_folder(), str(index + 1) + '.png') 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(path, index + 1) self.convert_thumbnail(path, index + 1)
delete_file(Path(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))
except: except:

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 = os.path.join(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(Path(preview_path), 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 not os.path.isfile( if doc.get_thumbnail_path(1, True) is None or \
os.path.join(doc.get_temp_folder(), 'mainslide001.png')): not (doc.get_temp_folder() / 'mainslide001.png').is_file():
doc.load_presentation() doc.load_presentation()
i = 1 i = 1
image = os.path.join(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 = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % 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 = os.path.join(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 = os.path.join(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,12 +23,11 @@
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 which
from openlp.core.lib import ScreenList from openlp.core.lib import ScreenList
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
@ -66,11 +65,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 +107,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 +142,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 +171,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 +202,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))
@ -240,46 +242,47 @@ class PdfDocument(PresentationDocument):
:return: True is loading succeeded, otherwise False. :return: True is loading succeeded, otherwise False.
""" """
log.debug('load_presentation pdf') log.debug('load_presentation pdf')
temp_dir_path = self.get_temp_folder()
# Check if the images has already been created, and if yes load them # Check if the images has already been created, and if yes load them
if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): if (temp_dir_path / 'mainslide001.png').is_file():
created_files = sorted(os.listdir(self.get_temp_folder())) created_files = sorted(temp_dir_path.glob('*'))
for fn in created_files: for image_path in created_files:
if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): if image_path.is_file():
self.image_files.append(os.path.join(self.get_temp_folder(), fn)) 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']
# Generate images from PDF that will fit the frame. # Generate images from PDF that will fit the frame.
runlog = '' runlog = ''
try: try:
if not os.path.isdir(self.get_temp_folder()): if not temp_dir_path.is_dir():
os.makedirs(self.get_temp_folder()) temp_dir_path.mkdir(parents=True)
# 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()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path], '-h', str(size.height()),
'-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', os.path.join(self.get_temp_folder(), '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=' + os.path.join(self.get_temp_folder(), '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(os.listdir(self.get_temp_folder())) created_files = sorted(temp_dir_path.glob('*'))
for fn in created_files: for image_path in created_files:
if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): if image_path.is_file():
self.image_files.append(os.path.join(self.get_temp_folder(), fn)) self.image_files.append(image_path)
except Exception as e: except Exception as e:
log.debug(e) log.exception(runlog)
log.debug(runlog)
return False return False
self.num_pages = len(self.image_files) self.num_pages = len(self.image_files)
# Create thumbnails # Create thumbnails

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()
@ -177,7 +178,7 @@ class PowerpointDocument(PresentationDocument):
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden: if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
self.index_map[key] = num + 1 self.index_map[key] = num + 1
self.presentation.Slides(num + 1).Export( self.presentation.Slides(num + 1).Export(
os.path.join(self.get_thumbnail_folder(), 'slide{key:d}.png'.format(key=key)), 'png', 320, 240) str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
key += 1 key += 1
self.slide_count = key - 1 self.slide_count = key - 1
@ -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_folder = 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 = os.path.join(temp_folder, '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_path = preview_path.encode('utf-16-le') + b'\0' preview_path_utf16 = str(preview_path).encode('utf-16-le') + b'\0'
if not os.path.isdir(temp_folder): if not temp_path.is_dir():
os.makedirs(temp_folder) temp_path.mkdir(parents=True)
self.ppt_id = self.controller.process.OpenPPT(byte_file_path, None, rect, preview_path) 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,15 +19,12 @@
# 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
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, md5_hash from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, md5_hash
from openlp.core.common.path import Path from openlp.core.common.path import Path, rmtree
from openlp.core.lib import create_thumb, validate_thumb from openlp.core.lib import create_thumb, validate_thumb
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -86,20 +83,27 @@ 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(Path(self.get_thumbnail_folder())) check_directory_exists(self.get_thumbnail_folder())
def load_presentation(self): def load_presentation(self):
""" """
@ -116,49 +120,54 @@ class PresentationDocument(object):
a file, e.g. thumbnails a file, e.g. thumbnails
""" """
try: try:
if os.path.exists(self.get_thumbnail_folder()): thumbnail_folder_path = self.get_thumbnail_folder()
shutil.rmtree(self.get_thumbnail_folder()) temp_folder_path = self.get_temp_folder()
if os.path.exists(self.get_temp_folder()): if thumbnail_folder_path.exists():
shutil.rmtree(self.get_temp_folder()) rmtree(thumbnail_folder_path)
if temp_folder_path.exists():
rmtree(temp_folder_path)
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
:return: The path to the thumbnail
:rtype: openlp.core.common.path.Path
""" """
# 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 os.path.join(self.controller.thumbnail_folder, folder) return Path(self.controller.thumbnail_folder, folder)
def get_temp_folder(self): def get_temp_folder(self):
""" """
The location where thumbnail images will be stored The location where thumbnail images will be stored
:return: The path to the temporary file folder
:rtype: openlp.core.common.path.Path
""" """
# 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 = folder = self.get_file_name() folder = self.file_path.name
return os.path.join(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(Path(self.file_path), Path(last_image_path))
def close_presentation(self): def close_presentation(self):
""" """
@ -241,25 +250,31 @@ 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): 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 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 check_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
:rtype: openlp.core.common.path.Path | None
""" """
path = os.path.join(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 os.path.isfile(path) or not check_exists: if path.is_file() or not check_exists:
return path return path
else: else:
return None return None
@ -302,23 +317,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 = os.path.join(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 = os.path.join(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 = ''
@ -327,19 +336,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_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt') titles_path = self.get_thumbnail_folder() / 'titles.txt'
with open(titles_file, 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_file = os.path.join(self.get_thumbnail_folder(), notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
'slideNotes{number:d}.txt'.format(number=slide_no)) notes_path.write_text(note)
with open(notes_file, mode='wt', encoding='utf-8') as fn:
fn.write(note)
class PresentationController(object): class PresentationController(object):
@ -416,12 +425,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):
""" """
@ -456,11 +464,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

@ -64,6 +64,6 @@ def download_and_check(callback=None):
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip') file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
callback.setRange(0, file_size) callback.setRange(0, file_size)
if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'), if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
os.path.join(str(AppLocation.get_section_data_path('remotes')), 'site.zip'), AppLocation.get_section_data_path('remotes') / 'site.zip',
sha256=sha256): sha256=sha256):
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip') deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')

View File

@ -25,10 +25,10 @@ The :mod:`db` module provides the ability to provide a csv file of all songs
import csv import csv
import logging import logging
from PyQt5 import QtWidgets
from openlp.core.common import Registry, translate from openlp.core.common import Registry, translate
from openlp.core.common.path import Path
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.db import Song
@ -42,26 +42,23 @@ def report_song_list():
""" """
main_window = Registry().get('main_window') main_window = Registry().get('main_window')
plugin = Registry().get('songs').plugin plugin = Registry().get('songs').plugin
report_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName( report_file_path, filter_used = FileDialog.getSaveFileName(
main_window, main_window,
translate('SongPlugin.ReportSongList', 'Save File'), translate('SongPlugin.ReportSongList', 'Save File'),
translate('SongPlugin.ReportSongList', 'song_extract.csv'), Path(translate('SongPlugin.ReportSongList', 'song_extract.csv')),
translate('SongPlugin.ReportSongList', 'CSV format (*.csv)')) translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))
if not report_file_name: if report_file_path is None:
main_window.error_message( main_window.error_message(
translate('SongPlugin.ReportSongList', 'Output Path Not Selected'), translate('SongPlugin.ReportSongList', 'Output Path Not Selected'),
translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your ' translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your report. \n'
'report. \nPlease select an existing path ' 'Please select an existing path on your computer.')
'on your computer.')
) )
return return
if not report_file_name.endswith('csv'): report_file_path.with_suffix('.csv')
report_file_name += '.csv'
file_handle = None
Registry().get('application').set_busy_cursor() Registry().get('application').set_busy_cursor()
try: try:
file_handle = open(report_file_name, 'wt') with report_file_path.open('wt') as file_handle:
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic') fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL) writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
headers = dict((n, n) for n in fieldnames) headers = dict((n, n) for n in fieldnames)
@ -92,7 +89,7 @@ def report_song_list():
main_window.information_message( main_window.information_message(
translate('SongPlugin.ReportSongList', 'Report Creation'), translate('SongPlugin.ReportSongList', 'Report Creation'),
translate('SongPlugin.ReportSongList', translate('SongPlugin.ReportSongList',
'Report \n{name} \nhas been successfully created. ').format(name=report_file_name) 'Report \n{name} \nhas been successfully created. ').format(name=report_file_path)
) )
except OSError as ose: except OSError as ose:
Registry().get('application').set_normal_cursor() Registry().get('application').set_normal_cursor()
@ -101,6 +98,3 @@ def report_song_list():
translate('SongPlugin.ReportSongList', translate('SongPlugin.ReportSongList',
'An error occurred while extracting: {error}' 'An error occurred while extracting: {error}'
).format(error=ose.strerror)) ).format(error=ose.strerror))
finally:
if file_handle:
file_handle.close()

View File

@ -29,6 +29,7 @@ from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, url_get_file, ping from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, url_get_file, ping
from openlp.core.common.path import Path
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -267,7 +268,7 @@ class TestHttpUtils(TestCase, TestMixin):
mocked_urlopen.side_effect = socket.timeout() mocked_urlopen.side_effect = socket.timeout()
# WHEN: Attempt to retrieve a file # WHEN: Attempt to retrieve a file
url_get_file(MagicMock(), url='http://localhost/test', f_path=self.tempfile) url_get_file(MagicMock(), url='http://localhost/test', f_path=Path(self.tempfile))
# THEN: socket.timeout should have been caught # THEN: socket.timeout should have been caught
# NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files

View File

@ -24,8 +24,209 @@ Package to test the openlp.core.common.path package.
""" """
import os import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import ANY, MagicMock, patch
from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.path import Path, copy, copyfile, copytree, path_to_str, replace_params, rmtree, str_to_path, \
which
class TestShutil(TestCase):
"""
Tests for the :mod:`openlp.core.common.path` module
"""
def test_replace_params_no_params(self):
"""
Test replace_params when called with and empty tuple instead of parameters to replace
"""
# GIVEN: Some test data
test_args = (1, 2)
test_kwargs = {'arg3': 3, 'arg4': 4}
test_params = tuple()
# WHEN: Calling replace_params
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
# THEN: The positional and keyword args should not have changed
self.assertEqual(test_args, result_args)
self.assertEqual(test_kwargs, result_kwargs)
def test_replace_params_params(self):
"""
Test replace_params when given a positional and a keyword argument to change
"""
# GIVEN: Some test data
test_args = (1, 2)
test_kwargs = {'arg3': 3, 'arg4': 4}
test_params = ((1, 'arg2', str), (2, 'arg3', str))
# WHEN: Calling replace_params
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
# THEN: The positional and keyword args should have have changed
self.assertEqual(result_args, (1, '2'))
self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
def test_copy(self):
"""
Test :func:`openlp.core.common.path.copy`
"""
# GIVEN: A mocked `shutil.copy` which returns a test path as a string
with patch('openlp.core.common.path.shutil.copy', return_value=os.path.join('destination', 'test', 'path')) \
as mocked_shutil_copy:
# WHEN: Calling :func:`openlp.core.common.path.copy` with the src and dst parameters as Path object types
result = copy(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
# THEN: :func:`shutil.copy` should have been called with the str equivalents of the Path objects.
# :func:`openlp.core.common.path.copy` should return the str type result of calling
# :func:`shutil.copy` as a Path object.
mocked_shutil_copy.assert_called_once_with(os.path.join('source', 'test', 'path'),
os.path.join('destination', 'test', 'path'))
self.assertEqual(result, Path('destination', 'test', 'path'))
def test_copy_follow_optional_params(self):
"""
Test :func:`openlp.core.common.path.copy` when follow_symlinks is set to false
"""
# GIVEN: A mocked `shutil.copy`
with patch('openlp.core.common.path.shutil.copy', return_value='') as mocked_shutil_copy:
# WHEN: Calling :func:`openlp.core.common.path.copy` with :param:`follow_symlinks` set to False
copy(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), follow_symlinks=False)
# THEN: :func:`shutil.copy` should have been called with :param:`follow_symlinks` set to false
mocked_shutil_copy.assert_called_once_with(ANY, ANY, follow_symlinks=False)
def test_copyfile(self):
"""
Test :func:`openlp.core.common.path.copyfile`
"""
# GIVEN: A mocked :func:`shutil.copyfile` which returns a test path as a string
with patch('openlp.core.common.path.shutil.copyfile',
return_value=os.path.join('destination', 'test', 'path')) as mocked_shutil_copyfile:
# WHEN: Calling :func:`openlp.core.common.path.copyfile` with the src and dst parameters as Path object
# types
result = copyfile(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
# THEN: :func:`shutil.copyfile` should have been called with the str equivalents of the Path objects.
# :func:`openlp.core.common.path.copyfile` should return the str type result of calling
# :func:`shutil.copyfile` as a Path object.
mocked_shutil_copyfile.assert_called_once_with(os.path.join('source', 'test', 'path'),
os.path.join('destination', 'test', 'path'))
self.assertEqual(result, Path('destination', 'test', 'path'))
def test_copyfile_optional_params(self):
"""
Test :func:`openlp.core.common.path.copyfile` when follow_symlinks is set to false
"""
# GIVEN: A mocked :func:`shutil.copyfile`
with patch('openlp.core.common.path.shutil.copyfile', return_value='') as mocked_shutil_copyfile:
# WHEN: Calling :func:`openlp.core.common.path.copyfile` with :param:`follow_symlinks` set to False
copyfile(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), follow_symlinks=False)
# THEN: :func:`shutil.copyfile` should have been called with the optional parameters, with out any of the
# values being modified
mocked_shutil_copyfile.assert_called_once_with(ANY, ANY, follow_symlinks=False)
def test_copytree(self):
"""
Test :func:`openlp.core.common.path.copytree`
"""
# GIVEN: A mocked :func:`shutil.copytree` which returns a test path as a string
with patch('openlp.core.common.path.shutil.copytree',
return_value=os.path.join('destination', 'test', 'path')) as mocked_shutil_copytree:
# WHEN: Calling :func:`openlp.core.common.path.copytree` with the src and dst parameters as Path object
# types
result = copytree(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
# THEN: :func:`shutil.copytree` should have been called with the str equivalents of the Path objects.
# :func:`openlp.core.common.path.copytree` should return the str type result of calling
# :func:`shutil.copytree` as a Path object.
mocked_shutil_copytree.assert_called_once_with(os.path.join('source', 'test', 'path'),
os.path.join('destination', 'test', 'path'))
self.assertEqual(result, Path('destination', 'test', 'path'))
def test_copytree_optional_params(self):
"""
Test :func:`openlp.core.common.path.copytree` when optional parameters are passed
"""
# GIVEN: A mocked :func:`shutil.copytree`
with patch('openlp.core.common.path.shutil.copytree', return_value='') as mocked_shutil_copytree:
mocked_ignore = MagicMock()
mocked_copy_function = MagicMock()
# WHEN: Calling :func:`openlp.core.common.path.copytree` with the optional parameters set
copytree(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), symlinks=True,
ignore=mocked_ignore, copy_function=mocked_copy_function, ignore_dangling_symlinks=True)
# THEN: :func:`shutil.copytree` should have been called with the optional parameters, with out any of the
# values being modified
mocked_shutil_copytree.assert_called_once_with(ANY, ANY, symlinks=True, ignore=mocked_ignore,
copy_function=mocked_copy_function,
ignore_dangling_symlinks=True)
def test_rmtree(self):
"""
Test :func:`rmtree`
"""
# GIVEN: A mocked :func:`shutil.rmtree`
with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree:
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type
result = rmtree(Path('test', 'path'))
# THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object.
mocked_shutil_rmtree.assert_called_once_with(os.path.join('test', 'path'))
self.assertIsNone(result)
def test_rmtree_optional_params(self):
"""
Test :func:`openlp.core.common.path.rmtree` when optional parameters are passed
"""
# GIVEN: A mocked :func:`shutil.rmtree`
with patch('openlp.core.common.path.shutil.rmtree', return_value='') as mocked_shutil_rmtree:
mocked_on_error = MagicMock()
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with :param:`ignore_errors` set to True and
# :param:`onerror` set to a mocked object
rmtree(Path('test', 'path'), ignore_errors=True, onerror=mocked_on_error)
# THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the
# values being modified
mocked_shutil_rmtree.assert_called_once_with(ANY, ignore_errors=True, onerror=mocked_on_error)
def test_which_no_command(self):
"""
Test :func:`openlp.core.common.path.which` when the command is not found.
"""
# GIVEN: A mocked :func:`shutil.which` when the command is not found.
with patch('openlp.core.common.path.shutil.which', return_value=None) as mocked_shutil_which:
# WHEN: Calling :func:`openlp.core.common.path.which` with a command that does not exist.
result = which('no_command')
# THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return None.
mocked_shutil_which.assert_called_once_with('no_command')
self.assertIsNone(result)
def test_which_command(self):
"""
Test :func:`openlp.core.common.path.which` when a command has been found.
"""
# GIVEN: A mocked :func:`shutil.which` when the command is found.
with patch('openlp.core.common.path.shutil.which',
return_value=os.path.join('path', 'to', 'command')) as mocked_shutil_which:
# WHEN: Calling :func:`openlp.core.common.path.which` with a command that exists.
result = which('command')
# THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return a
# Path object equivalent of the command path.
mocked_shutil_which.assert_called_once_with('command')
self.assertEqual(result, Path('path', 'to', 'command'))
class TestPath(TestCase): class TestPath(TestCase):

View File

@ -32,7 +32,7 @@ from PyQt5 import QtCore, QtGui
from openlp.core.common.path import Path from openlp.core.common.path import Path
from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \ from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \ create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
get_text_file_string, image_to_byte, replace_params, resize_image, str_to_bool, validate_thumb get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
@ -595,93 +595,46 @@ class TestLib(TestCase):
Test the validate_thumb() function when the thumbnail does not exist Test the validate_thumb() function when the thumbnail does not exist
""" """
# GIVEN: A mocked out os module, with path.exists returning False, and fake paths to a file and a thumb # GIVEN: A mocked out os module, with path.exists returning False, and fake paths to a file and a thumb
with patch('openlp.core.lib.os') as mocked_os: with patch.object(Path, 'exists', return_value=False) as mocked_path_exists:
file_path = 'path/to/file' file_path = Path('path', 'to', 'file')
thumb_path = 'path/to/thumb' thumb_path = Path('path', 'to', 'thumb')
mocked_os.path.exists.return_value = False
# WHEN: we run the validate_thumb() function # WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path) result = validate_thumb(file_path, thumb_path)
# THEN: we should have called a few functions, and the result should be False # THEN: we should have called a few functions, and the result should be False
mocked_os.path.exists.assert_called_with(thumb_path) thumb_path.exists.assert_called_once_with()
assert result is False, 'The result should be False' self.assertFalse(result, 'The result should be False')
def test_validate_thumb_file_exists_and_newer(self): def test_validate_thumb_file_exists_and_newer(self):
""" """
Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file
""" """
# GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb with patch.object(Path, 'exists'), patch.object(Path, 'stat'):
with patch('openlp.core.lib.os') as mocked_os: # GIVEN: Mocked file_path and thumb_path which return different values fo the modified times
file_path = 'path/to/file' file_path = MagicMock(**{'stat.return_value': MagicMock(st_mtime=10)})
thumb_path = 'path/to/thumb' thumb_path = MagicMock(**{'exists.return_value': True, 'stat.return_value': MagicMock(st_mtime=11)})
file_mocked_stat = MagicMock()
file_mocked_stat.st_mtime = datetime.now()
thumb_mocked_stat = MagicMock()
thumb_mocked_stat.st_mtime = datetime.now() + timedelta(seconds=10)
mocked_os.path.exists.return_value = True
mocked_os.stat.side_effect = [file_mocked_stat, thumb_mocked_stat]
# WHEN: we run the validate_thumb() function # WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path)
# THEN: we should have called a few functions, and the result should be True # THEN: `validate_thumb` should return True
# mocked_os.path.exists.assert_called_with(thumb_path) self.assertTrue(result)
def test_validate_thumb_file_exists_and_older(self): def test_validate_thumb_file_exists_and_older(self):
""" """
Test the validate_thumb() function when the thumbnail exists but is older than the file Test the validate_thumb() function when the thumbnail exists but is older than the file
""" """
# GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb # GIVEN: Mocked file_path and thumb_path which return different values fo the modified times
with patch('openlp.core.lib.os') as mocked_os: file_path = MagicMock(**{'stat.return_value': MagicMock(st_mtime=10)})
file_path = 'path/to/file' thumb_path = MagicMock(**{'exists.return_value': True, 'stat.return_value': MagicMock(st_mtime=9)})
thumb_path = 'path/to/thumb'
file_mocked_stat = MagicMock()
file_mocked_stat.st_mtime = datetime.now()
thumb_mocked_stat = MagicMock()
thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10)
mocked_os.path.exists.return_value = True
mocked_os.stat.side_effect = lambda fname: file_mocked_stat if fname == file_path else thumb_mocked_stat
# WHEN: we run the validate_thumb() function # WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path) result = validate_thumb(file_path, thumb_path)
# THEN: we should have called a few functions, and the result should be False # THEN: `validate_thumb` should return False
mocked_os.path.exists.assert_called_with(thumb_path) thumb_path.stat.assert_called_once_with()
mocked_os.stat.assert_any_call(file_path) self.assertFalse(result, 'The result should be False')
mocked_os.stat.assert_any_call(thumb_path)
assert result is False, 'The result should be False'
def test_replace_params_no_params(self):
"""
Test replace_params when called with and empty tuple instead of parameters to replace
"""
# GIVEN: Some test data
test_args = (1, 2)
test_kwargs = {'arg3': 3, 'arg4': 4}
test_params = tuple()
# WHEN: Calling replace_params
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
# THEN: The positional and keyword args should not have changed
self.assertEqual(test_args, result_args)
self.assertEqual(test_kwargs, result_kwargs)
def test_replace_params_params(self):
"""
Test replace_params when given a positional and a keyword argument to change
"""
# GIVEN: Some test data
test_args = (1, 2)
test_kwargs = {'arg3': 3, 'arg4': 4}
test_params = ((1, 'arg2', str), (2, 'arg3', str))
# WHEN: Calling replace_params
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
# THEN: The positional and keyword args should have have changed
self.assertEqual(result_args, (1, '2'))
self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
def test_resize_thumb(self): def test_resize_thumb(self):
""" """

View File

@ -103,7 +103,7 @@ class TestExceptionForm(TestMixin, TestCase):
os.remove(self.tempfile) os.remove(self.tempfile)
@patch("openlp.core.ui.exceptionform.Ui_ExceptionDialog") @patch("openlp.core.ui.exceptionform.Ui_ExceptionDialog")
@patch("openlp.core.ui.exceptionform.QtWidgets.QFileDialog") @patch("openlp.core.ui.exceptionform.FileDialog")
@patch("openlp.core.ui.exceptionform.QtCore.QUrl") @patch("openlp.core.ui.exceptionform.QtCore.QUrl")
@patch("openlp.core.ui.exceptionform.QtCore.QUrlQuery.addQueryItem") @patch("openlp.core.ui.exceptionform.QtCore.QUrlQuery.addQueryItem")
@patch("openlp.core.ui.exceptionform.Qt") @patch("openlp.core.ui.exceptionform.Qt")

View File

@ -24,13 +24,12 @@ Functional tests to test the Impress class and related methods.
""" """
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock from unittest.mock import MagicMock
import os
import shutil import shutil
from tempfile import mkdtemp from tempfile import mkdtemp
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.plugins.presentations.lib.impresscontroller import \ from openlp.core.common.path import Path
ImpressController, ImpressDocument, TextType from openlp.plugins.presentations.lib.impresscontroller import ImpressController, ImpressDocument, TextType
from openlp.plugins.presentations.presentationplugin import __default_settings__ from openlp.plugins.presentations.presentationplugin import __default_settings__
from tests.utils.constants import TEST_RESOURCES_PATH from tests.utils.constants import TEST_RESOURCES_PATH
@ -82,7 +81,7 @@ class TestImpressDocument(TestCase):
mocked_plugin = MagicMock() mocked_plugin = MagicMock()
mocked_plugin.settings_section = 'presentations' mocked_plugin.settings_section = 'presentations'
Settings().extend_default_settings(__default_settings__) Settings().extend_default_settings(__default_settings__)
self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx') self.file_name = Path(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
self.ppc = ImpressController(mocked_plugin) self.ppc = ImpressController(mocked_plugin)
self.doc = ImpressDocument(self.ppc, self.file_name) self.doc = ImpressDocument(self.ppc, self.file_name)

View File

@ -26,6 +26,7 @@ from unittest import TestCase
from unittest.mock import patch, MagicMock, call from unittest.mock import patch, MagicMock, call
from openlp.core.common import Registry from openlp.core.common import Registry
from openlp.core.common.path import Path
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -92,17 +93,18 @@ class TestMediaItem(TestCase, TestMixin):
""" """
# GIVEN: A mocked controller, and mocked os.path.getmtime # GIVEN: A mocked controller, and mocked os.path.getmtime
mocked_controller = MagicMock() mocked_controller = MagicMock()
mocked_doc = MagicMock() mocked_doc = MagicMock(**{'get_thumbnail_path.return_value': Path()})
mocked_controller.add_document.return_value = mocked_doc mocked_controller.add_document.return_value = mocked_doc
mocked_controller.supports = ['tmp'] mocked_controller.supports = ['tmp']
self.media_item.controllers = { self.media_item.controllers = {
'Mocked': mocked_controller 'Mocked': mocked_controller
} }
presentation_file = 'file.tmp'
with patch('openlp.plugins.presentations.lib.mediaitem.os.path.getmtime') as mocked_getmtime, \ thmub_path = MagicMock(st_mtime=100)
patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists: file_path = MagicMock(st_mtime=400)
mocked_getmtime.side_effect = [100, 200] with patch.object(Path, 'stat', side_effect=[thmub_path, file_path]), \
mocked_exists.return_value = True patch.object(Path, 'exists', return_value=True):
presentation_file = Path('file.tmp')
# WHEN: calling clean_up_thumbnails # WHEN: calling clean_up_thumbnails
self.media_item.clean_up_thumbnails(presentation_file, True) self.media_item.clean_up_thumbnails(presentation_file, True)
@ -123,9 +125,8 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.controllers = { self.media_item.controllers = {
'Mocked': mocked_controller 'Mocked': mocked_controller
} }
presentation_file = 'file.tmp' presentation_file = Path('file.tmp')
with patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists: with patch.object(Path, 'exists', return_value=False):
mocked_exists.return_value = False
# WHEN: calling clean_up_thumbnails # WHEN: calling clean_up_thumbnails
self.media_item.clean_up_thumbnails(presentation_file, True) self.media_item.clean_up_thumbnails(presentation_file, True)

View File

@ -32,6 +32,7 @@ from PyQt5 import QtCore, QtGui
from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.core.common.path import Path
from openlp.core.lib import ScreenList from openlp.core.lib import ScreenList
from tests.utils.constants import TEST_RESOURCES_PATH from tests.utils.constants import TEST_RESOURCES_PATH
@ -66,8 +67,8 @@ class TestPdfController(TestCase, TestMixin):
self.desktop.screenGeometry.return_value = SCREEN['size'] self.desktop.screenGeometry.return_value = SCREEN['size']
self.screens = ScreenList.create(self.desktop) self.screens = ScreenList.create(self.desktop)
Settings().extend_default_settings(__default_settings__) Settings().extend_default_settings(__default_settings__)
self.temp_folder = mkdtemp() self.temp_folder = Path(mkdtemp())
self.thumbnail_folder = mkdtemp() self.thumbnail_folder = Path(mkdtemp())
self.mock_plugin = MagicMock() self.mock_plugin = MagicMock()
self.mock_plugin.settings_section = self.temp_folder self.mock_plugin.settings_section = self.temp_folder
@ -77,8 +78,8 @@ class TestPdfController(TestCase, TestMixin):
""" """
del self.screens del self.screens
self.destroy_settings() self.destroy_settings()
shutil.rmtree(self.thumbnail_folder) shutil.rmtree(str(self.thumbnail_folder))
shutil.rmtree(self.temp_folder) shutil.rmtree(str(self.temp_folder))
def test_constructor(self): def test_constructor(self):
""" """
@ -98,7 +99,7 @@ class TestPdfController(TestCase, TestMixin):
Test loading of a Pdf using the PdfController Test loading of a Pdf using the PdfController
""" """
# GIVEN: A Pdf-file # GIVEN: A Pdf-file
test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') test_file = Path(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
# WHEN: The Pdf is loaded # WHEN: The Pdf is loaded
controller = PdfController(plugin=self.mock_plugin) controller = PdfController(plugin=self.mock_plugin)
@ -118,7 +119,7 @@ class TestPdfController(TestCase, TestMixin):
Test loading of a Pdf and check size of generate pictures Test loading of a Pdf and check size of generate pictures
""" """
# GIVEN: A Pdf-file # GIVEN: A Pdf-file
test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') test_file = Path(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
# WHEN: The Pdf is loaded # WHEN: The Pdf is loaded
controller = PdfController(plugin=self.mock_plugin) controller = PdfController(plugin=self.mock_plugin)
@ -131,7 +132,7 @@ class TestPdfController(TestCase, TestMixin):
# THEN: The load should succeed and pictures should be created and have been scales to fit the screen # THEN: The load should succeed and pictures should be created and have been scales to fit the screen
self.assertTrue(loaded, 'The loading of the PDF should succeed.') self.assertTrue(loaded, 'The loading of the PDF should succeed.')
image = QtGui.QImage(os.path.join(self.temp_folder, 'pdf_test1.pdf', 'mainslide001.png')) image = QtGui.QImage(os.path.join(str(self.temp_folder), 'pdf_test1.pdf', 'mainslide001.png'))
# Based on the converter used the resolution will differ a bit # Based on the converter used the resolution will differ a bit
if controller.gsbin: if controller.gsbin:
self.assertEqual(760, image.height(), 'The height should be 760') self.assertEqual(760, image.height(), 'The height should be 760')

View File

@ -22,7 +22,6 @@
""" """
This module contains tests for the pptviewcontroller module of the Presentations plugin. This module contains tests for the pptviewcontroller module of the Presentations plugin.
""" """
import os
import shutil import shutil
from tempfile import mkdtemp from tempfile import mkdtemp
from unittest import TestCase from unittest import TestCase
@ -30,6 +29,7 @@ from unittest.mock import MagicMock, patch
from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
from openlp.core.common import is_win from openlp.core.common import is_win
from openlp.core.common.path import Path
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
from tests.utils.constants import TEST_RESOURCES_PATH from tests.utils.constants import TEST_RESOURCES_PATH
@ -184,7 +184,7 @@ class TestPptviewDocument(TestCase):
""" """
# GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file # GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
doc = PptviewDocument(self.mock_controller, self.mock_presentation) doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx') doc.file_path = Path(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
doc.save_titles_and_notes = MagicMock() doc.save_titles_and_notes = MagicMock()
# WHEN reading the titles and notes # WHEN reading the titles and notes
@ -201,13 +201,13 @@ class TestPptviewDocument(TestCase):
""" """
# GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file # GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
with patch('builtins.open') as mocked_open, \ with patch('builtins.open') as mocked_open, \
patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.exists') as mocked_exists, \ patch.object(Path, 'exists') as mocked_path_exists, \
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \ patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
mocked_dir_exists: mocked_dir_exists:
mocked_exists.return_value = False mocked_path_exists.return_value = False
mocked_dir_exists.return_value = False mocked_dir_exists.return_value = False
doc = PptviewDocument(self.mock_controller, self.mock_presentation) doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = 'Idontexist.pptx' doc.file_path = Path('Idontexist.pptx')
doc.save_titles_and_notes = MagicMock() doc.save_titles_and_notes = MagicMock()
# WHEN: Reading the titles and notes # WHEN: Reading the titles and notes
@ -215,7 +215,7 @@ class TestPptviewDocument(TestCase):
# THEN: File existens should have been checked, and not have been opened. # THEN: File existens should have been checked, and not have been opened.
doc.save_titles_and_notes.assert_called_once_with(None, None) doc.save_titles_and_notes.assert_called_once_with(None, None)
mocked_exists.assert_any_call('Idontexist.pptx') mocked_path_exists.assert_called_with()
self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.') self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
def test_create_titles_and_notes_invalid_file(self): def test_create_titles_and_notes_invalid_file(self):
@ -228,7 +228,7 @@ class TestPptviewDocument(TestCase):
mocked_is_zf.return_value = False mocked_is_zf.return_value = False
mocked_open.filesize = 10 mocked_open.filesize = 10
doc = PptviewDocument(self.mock_controller, self.mock_presentation) doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.ppt') doc.file_path = Path(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
doc.save_titles_and_notes = MagicMock() doc.save_titles_and_notes = MagicMock()
# WHEN: reading the titles and notes # WHEN: reading the titles and notes

View File

@ -23,9 +23,8 @@
Functional tests to test the PresentationController and PresentationDocument Functional tests to test the PresentationController and PresentationDocument
classes and related methods. classes and related methods.
""" """
import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, mock_open, patch from unittest.mock import MagicMock, call, patch
from openlp.core.common.path import Path from openlp.core.common.path import Path
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
@ -67,23 +66,18 @@ class TestPresentationController(TestCase):
Test PresentationDocument.save_titles_and_notes method with two valid lists Test PresentationDocument.save_titles_and_notes method with two valid lists
""" """
# GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder # GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
mocked_open = mock_open() with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.write_text') as mocked_write_text, \
with patch('builtins.open', mocked_open), patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder: patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
titles = ['uno', 'dos'] titles = ['uno', 'dos']
notes = ['one', 'two'] notes = ['one', 'two']
# WHEN: calling save_titles_and_notes # WHEN: calling save_titles_and_notes
mocked_get_thumbnail_folder.return_value = 'test' mocked_get_thumbnail_folder.return_value = Path('test')
self.document.save_titles_and_notes(titles, notes) self.document.save_titles_and_notes(titles, notes)
# THEN: the last call to open should have been for slideNotes2.txt # THEN: the last call to open should have been for slideNotes2.txt
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), mode='wt', encoding='utf-8') self.assertEqual(mocked_write_text.call_count, 3, 'There should be exactly three files written')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), mode='wt', encoding='utf-8') mocked_write_text.assert_has_calls([call('uno\ndos'), call('one'), call('two')])
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), mode='wt', encoding='utf-8')
self.assertEqual(mocked_open.call_count, 3, 'There should be exactly three files opened')
mocked_open().writelines.assert_called_once_with(['uno', 'dos'])
mocked_open().write.assert_any_call('one')
mocked_open().write.assert_any_call('two')
def test_save_titles_and_notes_with_None(self): def test_save_titles_and_notes_with_None(self):
""" """
@ -107,10 +101,11 @@ class TestPresentationController(TestCase):
""" """
# GIVEN: A mocked open, get_thumbnail_folder and exists # GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open', mock_open(read_data='uno\ndos\n')) as mocked_open, \ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text',
return_value='uno\ndos\n') as mocked_read_text, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists: patch('openlp.plugins.presentations.lib.presentationcontroller.Path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test' mocked_get_thumbnail_folder.return_value = Path('test')
mocked_exists.return_value = True mocked_exists.return_value = True
# WHEN: calling get_titles_and_notes # WHEN: calling get_titles_and_notes
@ -121,45 +116,36 @@ class TestPresentationController(TestCase):
self.assertEqual(len(result_titles), 2, 'There should be two items in the titles') self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
self.assertIs(type(result_notes), list, 'result_notes should be of type list') self.assertIs(type(result_notes), list, 'result_notes should be of type list')
self.assertEqual(len(result_notes), 2, 'There should be two items in the notes') self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
self.assertEqual(mocked_open.call_count, 3, 'Three files should be opened') self.assertEqual(mocked_read_text.call_count, 3, 'Three files should be read')
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), encoding='utf-8')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), encoding='utf-8')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), encoding='utf-8')
self.assertEqual(mocked_exists.call_count, 3, 'Three files should have been checked')
def test_get_titles_and_notes_with_file_not_found(self): def test_get_titles_and_notes_with_file_not_found(self):
""" """
Test PresentationDocument.get_titles_and_notes method with file not found Test PresentationDocument.get_titles_and_notes method with file not found
""" """
# GIVEN: A mocked open, get_thumbnail_folder and exists # GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open') as mocked_open, \ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists: mocked_read_text.side_effect = FileNotFoundError()
mocked_get_thumbnail_folder.return_value = 'test' mocked_get_thumbnail_folder.return_value = Path('test')
mocked_exists.return_value = False
# WHEN: calling get_titles_and_notes # WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes() result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two empty lists # THEN: it should return two empty lists
self.assertIs(type(result_titles), list, 'result_titles should be of type list') self.assertIsInstance(result_titles, list, 'result_titles should be of type list')
self.assertEqual(len(result_titles), 0, 'there be no titles') self.assertEqual(len(result_titles), 0, 'there be no titles')
self.assertIs(type(result_notes), list, 'result_notes should be a list') self.assertIsInstance(result_notes, list, 'result_notes should be a list')
self.assertEqual(len(result_notes), 0, 'but the list should be empty') self.assertEqual(len(result_notes), 0, 'but the list should be empty')
self.assertEqual(mocked_open.call_count, 0, 'No calls to open files')
self.assertEqual(mocked_exists.call_count, 1, 'There should be one call to file exists')
def test_get_titles_and_notes_with_file_error(self): def test_get_titles_and_notes_with_file_error(self):
""" """
Test PresentationDocument.get_titles_and_notes method with file errors Test PresentationDocument.get_titles_and_notes method with file errors
""" """
# GIVEN: A mocked open, get_thumbnail_folder and exists # GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open') as mocked_open, \ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists: mocked_read_text.side_effect = IOError()
mocked_get_thumbnail_folder.return_value = 'test' mocked_get_thumbnail_folder.return_value = Path('test')
mocked_exists.return_value = True
mocked_open.side_effect = IOError()
# WHEN: calling get_titles_and_notes # WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes() result_titles, result_notes = self.document.get_titles_and_notes()
@ -180,18 +166,16 @@ class TestPresentationDocument(TestCase):
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
self.get_thumbnail_folder_patcher = \ self.get_thumbnail_folder_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder') patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
self.os_patcher = patch('openlp.plugins.presentations.lib.presentationcontroller.os')
self._setup_patcher = \ self._setup_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup') patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
self.mock_check_directory_exists = self.check_directory_exists_patcher.start() self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start() self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
self.mock_os = self.os_patcher.start()
self.mock_setup = self._setup_patcher.start() self.mock_setup = self._setup_patcher.start()
self.mock_controller = MagicMock() self.mock_controller = MagicMock()
self.mock_get_thumbnail_folder.return_value = 'returned/path/' self.mock_get_thumbnail_folder.return_value = Path('returned/path/')
def tearDown(self): def tearDown(self):
""" """
@ -199,7 +183,6 @@ class TestPresentationDocument(TestCase):
""" """
self.check_directory_exists_patcher.stop() self.check_directory_exists_patcher.stop()
self.get_thumbnail_folder_patcher.stop() self.get_thumbnail_folder_patcher.stop()
self.os_patcher.stop()
self._setup_patcher.stop() self._setup_patcher.stop()
def test_initialise_presentation_document(self): def test_initialise_presentation_document(self):
@ -227,7 +210,7 @@ class TestPresentationDocument(TestCase):
PresentationDocument(self.mock_controller, 'Name') PresentationDocument(self.mock_controller, 'Name')
# THEN: check_directory_exists should have been called with 'returned/path/' # THEN: check_directory_exists should have been called with 'returned/path/'
self.mock_check_directory_exists.assert_called_once_with(Path('returned', 'path')) self.mock_check_directory_exists.assert_called_once_with(Path('returned', 'path/'))
self._setup_patcher.start() self._setup_patcher.start()
@ -244,20 +227,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')