From 10b13872e50f8417caffad7375413d14199337f0 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Tue, 26 Sep 2017 17:39:13 +0100 Subject: [PATCH] Modify themes to work with pathlib --- openlp/core/lib/json/theme.json | 2 +- openlp/core/lib/renderer.py | 7 +- openlp/core/lib/theme.py | 34 +- openlp/core/ui/maindisplay.py | 8 +- openlp/core/ui/themeform.py | 50 ++- openlp/core/ui/thememanager.py | 396 ++++++++---------- openlp/core/ui/themestab.py | 4 +- openlp/plugins/images/lib/upgrade.py | 1 - .../functional/openlp_core_lib/test_theme.py | 11 +- .../openlp_core_ui/test_exceptionform.py | 50 ++- .../openlp_core_ui/test_maindisplay.py | 18 +- .../openlp_core_ui/test_themeform.py | 2 +- .../openlp_core_ui/test_thememanager.py | 51 +-- .../openlp_core_ui/test_thememanager.py | 25 +- 14 files changed, 326 insertions(+), 333 deletions(-) diff --git a/openlp/core/lib/json/theme.json b/openlp/core/lib/json/theme.json index e8862d0b4..b23593c6b 100644 --- a/openlp/core/lib/json/theme.json +++ b/openlp/core/lib/json/theme.json @@ -4,7 +4,7 @@ "color": "#000000", "direction": "vertical", "end_color": "#000000", - "filename": "", + "filename": null, "start_color": "#000000", "type": "solid" }, diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 67d33ce04..8b8d5669c 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -26,6 +26,7 @@ from string import Template from PyQt5 import QtGui, QtCore, QtWebKitWidgets from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings +from openlp.core.common.path import path_to_str from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, ScreenList, ServiceItem, expand_tags, \ build_lyrics_format_css, build_lyrics_outline_css, build_chords_css from openlp.core.common import ThemeLevel @@ -118,7 +119,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): theme_data, main_rect, footer_rect = self._theme_dimensions[theme_name] # if No file do not update cache if theme_data.background_filename: - self.image_manager.add_image(theme_data.background_filename, + self.image_manager.add_image(path_to_str(theme_data.background_filename), ImageSource.Theme, QtGui.QColor(theme_data.background_border_color)) def pre_render(self, override_theme_data=None): @@ -207,8 +208,8 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): service_item.raw_footer = FOOTER # if No file do not update cache if theme_data.background_filename: - self.image_manager.add_image( - theme_data.background_filename, ImageSource.Theme, QtGui.QColor(theme_data.background_border_color)) + self.image_manager.add_image(path_to_str(theme_data.background_filename), + ImageSource.Theme, QtGui.QColor(theme_data.background_border_color)) theme_data, main, footer = self.pre_render(theme_data) service_item.theme_data = theme_data service_item.main = main diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 4a55b1e7e..8522225d9 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -22,13 +22,13 @@ """ Provide the theme XML and handling functions for OpenLP v2 themes. """ -import os -import logging import json +import logging from lxml import etree, objectify from openlp.core.common import AppLocation, de_hump - +from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder +from openlp.core.common.path import Path, str_to_path from openlp.core.lib import str_to_bool, ScreenList, get_text_file_string log = logging.getLogger(__name__) @@ -160,9 +160,8 @@ class Theme(object): # basic theme object with defaults json_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json' jsn = get_text_file_string(json_path) - jsn = json.loads(jsn) - self.expand_json(jsn) - self.background_filename = '' + self.load_theme(jsn) + self.background_filename = None def expand_json(self, var, prev=None): """ @@ -174,8 +173,6 @@ class Theme(object): for key, value in var.items(): if prev: key = prev + "_" + key - else: - key = key if isinstance(value, dict): self.expand_json(value, key) else: @@ -185,13 +182,13 @@ class Theme(object): """ Add the path name to the image name so the background can be rendered. - :param path: The path name to be added. + :param openlp.core.common.path.Path path: The path name to be added. + :rtype: None """ if self.background_type == 'image' or self.background_type == 'video': if self.background_filename and path: self.theme_name = self.theme_name.strip() - self.background_filename = self.background_filename.strip() - self.background_filename = os.path.join(path, self.theme_name, self.background_filename) + self.background_filename = path / self.theme_name / self.background_filename def set_default_header_footer(self): """ @@ -206,16 +203,21 @@ class Theme(object): self.font_footer_y = current_screen['size'].height() * 9 / 10 self.font_footer_height = current_screen['size'].height() / 10 - def load_theme(self, theme): + def load_theme(self, theme, theme_path=None): """ Convert the JSON file and expand it. :param theme: the theme string + :param openlp.core.common.path.Path theme_path: The path to the theme + :rtype: None """ - jsn = json.loads(theme) + if theme_path: + jsn = json.loads(theme, cls=OpenLPJsonDecoder, base_path=theme_path) + else: + jsn = json.loads(theme, cls=OpenLPJsonDecoder) self.expand_json(jsn) - def export_theme(self): + def export_theme(self, theme_path=None): """ Loop through the fields and build a dictionary of them @@ -223,7 +225,9 @@ class Theme(object): theme_data = {} for attr, value in self.__dict__.items(): theme_data["{attr}".format(attr=attr)] = value - return json.dumps(theme_data) + if theme_path: + return json.dumps(theme_data, cls=OpenLPJsonEncoder, base_path=theme_path) + return json.dumps(theme_data, cls=OpenLPJsonEncoder) def parse(self, xml): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 862fcd0bc..8c8940369 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -346,7 +346,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): if not hasattr(self, 'service_item'): return False self.override['image'] = path - self.override['theme'] = self.service_item.theme_data.background_filename + self.override['theme'] = path_to_str(self.service_item.theme_data.background_filename) self.image(path) # Update the preview frame. if self.is_live: @@ -454,7 +454,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): Registry().execute('video_background_replaced') self.override = {} # We have a different theme. - elif self.override['theme'] != service_item.theme_data.background_filename: + elif self.override['theme'] != path_to_str(service_item.theme_data.background_filename): Registry().execute('live_theme_changed') self.override = {} else: @@ -466,7 +466,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): if self.service_item.theme_data.background_type == 'image': if self.service_item.theme_data.background_filename: self.service_item.bg_image_bytes = self.image_manager.get_image_bytes( - self.service_item.theme_data.background_filename, ImageSource.Theme) + path_to_str(self.service_item.theme_data.background_filename), ImageSource.Theme) if image_path: image_bytes = self.image_manager.get_image_bytes(image_path, ImageSource.ImagePlugin) created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes, @@ -488,7 +488,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): path = os.path.join(str(AppLocation.get_section_data_path('themes')), self.service_item.theme_data.theme_name) service_item.add_from_command(path, - self.service_item.theme_data.background_filename, + path_to_str(self.service_item.theme_data.background_filename), ':/media/slidecontroller_multimedia.png') self.media_controller.video(DisplayControllerType.Live, service_item, video_behind_text=True) self._hide_mouse() diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index cac45f28d..d2ebaa275 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -28,7 +28,6 @@ import os from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file -from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui import ThemeLayoutForm @@ -61,7 +60,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.setupUi(self) self.registerFields() self.update_theme_allowed = True - self.temp_background_filename = '' + self.temp_background_filename = None self.theme_layout_form = ThemeLayoutForm(self) self.background_combo_box.currentIndexChanged.connect(self.on_background_combo_box_current_index_changed) self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed) @@ -188,8 +187,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ background_image = BackgroundType.to_string(BackgroundType.Image) if self.page(self.currentId()) == self.background_page and \ - self.theme.background_type == background_image and \ - is_not_image_file(Path(self.theme.background_filename)): + self.theme.background_type == background_image and is_not_image_file(self.theme.background_filename): QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'), translate('OpenLP.ThemeWizard', 'You have not selected a ' 'background image. Please select one before continuing.')) @@ -273,7 +271,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): Run the wizard. """ log.debug('Editing theme {name}'.format(name=self.theme.theme_name)) - self.temp_background_filename = '' + self.temp_background_filename = None self.update_theme_allowed = False self.set_defaults() self.update_theme_allowed = True @@ -318,11 +316,11 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.setField('background_type', 1) elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image): self.image_color_button.color = self.theme.background_border_color - self.image_path_edit.path = str_to_path(self.theme.background_filename) + self.image_path_edit.path = self.theme.background_filename self.setField('background_type', 2) elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): self.video_color_button.color = self.theme.background_border_color - self.video_path_edit.path = str_to_path(self.theme.background_filename) + self.video_path_edit.path = self.theme.background_filename self.setField('background_type', 4) elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent): self.setField('background_type', 3) @@ -402,14 +400,14 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.theme.background_type = BackgroundType.to_string(index) if self.theme.background_type != BackgroundType.to_string(BackgroundType.Image) and \ self.theme.background_type != BackgroundType.to_string(BackgroundType.Video) and \ - self.temp_background_filename == '': + self.temp_background_filename is None: self.temp_background_filename = self.theme.background_filename - self.theme.background_filename = '' + self.theme.background_filename = None if (self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or self.theme.background_type != BackgroundType.to_string(BackgroundType.Video)) and \ - self.temp_background_filename != '': + self.temp_background_filename is not None: self.theme.background_filename = self.temp_background_filename - self.temp_background_filename = '' + self.temp_background_filename = None self.set_background_page_values() def on_gradient_combo_box_current_index_changed(self, index): @@ -450,18 +448,24 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ self.theme.background_end_color = color - def on_image_path_edit_path_changed(self, file_path): + def on_image_path_edit_path_changed(self, new_path): """ - Background Image button pushed. + Handle the `pathEditChanged` signal from image_path_edit + + :param openlp.core.common.path.Path new_path: Path to the new image + :rtype: None """ - self.theme.background_filename = path_to_str(file_path) + self.theme.background_filename = new_path self.set_background_page_values() - def on_video_path_edit_path_changed(self, file_path): + def on_video_path_edit_path_changed(self, new_path): """ - Background video button pushed. + Handle the `pathEditChanged` signal from video_path_edit + + :param openlp.core.common.path.Path new_path: Path to the new video + :rtype: None """ - self.theme.background_filename = path_to_str(file_path) + self.theme.background_filename = new_path self.set_background_page_values() def on_main_color_changed(self, color): @@ -537,14 +541,14 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): translate('OpenLP.ThemeWizard', 'Theme Name Invalid'), translate('OpenLP.ThemeWizard', 'Invalid theme name. Please enter one.')) return - save_from = None - save_to = None + source_path = None + destination_path = None if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \ self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): - filename = os.path.split(str(self.theme.background_filename))[1] - save_to = os.path.join(self.path, self.theme.theme_name, filename) - save_from = self.theme.background_filename + file_name = self.theme.background_filename.name + destination_path = self.path / self.theme.theme_name / file_name + source_path = self.theme.background_filename if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name): return - self.theme_manager.save_theme(self.theme, save_from, save_to) + self.theme_manager.save_theme(self.theme, source_path, destination_path) return QtWidgets.QDialog.accept(self) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 15e33cdb2..7e860ffca 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -24,14 +24,14 @@ The Theme Manager manages adding, deleteing and modifying of themes. """ import os import zipfile -import shutil - from xml.etree.ElementTree import ElementTree, XML + from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \ - UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file -from openlp.core.common.path import Path, path_to_str, str_to_path + UiStrings, check_directory_exists, translate, delete_file +from openlp.core.common.languagemanager import get_locale_key +from openlp.core.common.path import Path, copyfile, path_to_str, rmtree from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \ check_item_selected, create_thumb, validate_thumb from openlp.core.lib.theme import Theme, BackgroundType @@ -39,7 +39,6 @@ from openlp.core.lib.ui import critical_error_message_box, create_widget_action from openlp.core.ui import FileRenameForm, ThemeForm from openlp.core.ui.lib import OpenLPToolbar from openlp.core.ui.lib.filedialog import FileDialog -from openlp.core.common.languagemanager import get_locale_key class Ui_ThemeManager(object): @@ -135,7 +134,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage self.settings_section = 'themes' # Variables self.theme_list = [] - self.old_background_image = None + self.old_background_image_path = None def bootstrap_initialise(self): """ @@ -145,25 +144,41 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage self.global_theme = Settings().value(self.settings_section + '/global theme') self.build_theme_path() self.load_first_time_themes() + self.upgrade_themes() def bootstrap_post_set_up(self): """ process the bootstrap post setup request """ self.theme_form = ThemeForm(self) - self.theme_form.path = self.path + self.theme_form.path = self.theme_path self.file_rename_form = FileRenameForm() Registry().register_function('theme_update_global', self.change_global_from_tab) self.load_themes() + def upgrade_themes(self): + """ + Upgrade the xml files to json. + + :rtype: None + """ + xml_file_paths = AppLocation.get_section_data_path('themes').glob('*/*.xml') + for xml_file_path in xml_file_paths: + theme_data = get_text_file_string(xml_file_path) + theme = self._create_theme_from_xml(theme_data, self.theme_path) + self._write_theme(theme) + xml_file_path.unlink() + def build_theme_path(self): """ Set up the theme path variables + + :rtype: None """ - self.path = str(AppLocation.get_section_data_path(self.settings_section)) - check_directory_exists(Path(self.path)) - self.thumb_path = os.path.join(self.path, 'thumbnails') - check_directory_exists(Path(self.thumb_path)) + self.theme_path = AppLocation.get_section_data_path(self.settings_section) + check_directory_exists(self.theme_path) + self.thumb_path = self.theme_path / 'thumbnails' + check_directory_exists(self.thumb_path) def check_list_state(self, item, field=None): """ @@ -298,17 +313,18 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage """ Takes a theme and makes a new copy of it as well as saving it. - :param theme_data: The theme to be used - :param new_theme_name: The new theme name to save the data to + :param Theme theme_data: The theme to be used + :param str new_theme_name: The new theme name of the theme + :rtype: None """ - save_to = None - save_from = None + destination_path = None + source_path = None if theme_data.background_type == 'image' or theme_data.background_type == 'video': - save_to = os.path.join(self.path, new_theme_name, os.path.split(str(theme_data.background_filename))[1]) - save_from = theme_data.background_filename + destination_path = self.theme_path / new_theme_name / theme_data.background_filename.name + source_path = theme_data.background_filename theme_data.theme_name = new_theme_name - theme_data.extend_image_filename(self.path) - self.save_theme(theme_data, save_from, save_to) + theme_data.extend_image_filename(self.theme_path) + self.save_theme(theme_data, source_path, destination_path) self.load_themes() def on_edit_theme(self, field=None): @@ -322,10 +338,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage item = self.theme_list_widget.currentItem() theme = self.get_theme_data(item.data(QtCore.Qt.UserRole)) if theme.background_type == 'image' or theme.background_type == 'video': - self.old_background_image = theme.background_filename + self.old_background_image_path = theme.background_filename self.theme_form.theme = theme self.theme_form.exec(True) - self.old_background_image = None + self.old_background_image_path = None self.renderer.update_theme(theme.theme_name) self.load_themes() @@ -355,77 +371,76 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage """ self.theme_list.remove(theme) thumb = '{name}.png'.format(name=theme) - delete_file(Path(self.path, thumb)) - delete_file(Path(self.thumb_path, thumb)) + delete_file(self.theme_path / thumb) + delete_file(self.thumb_path / thumb) try: - # Windows is always unicode, so no need to encode filenames - if is_win(): - shutil.rmtree(os.path.join(self.path, theme)) - else: - encoding = get_filesystem_encoding() - shutil.rmtree(os.path.join(self.path, theme).encode(encoding)) - except OSError as os_error: - shutil.Error = os_error + rmtree(self.theme_path / theme) + except OSError: self.log_exception('Error deleting theme {name}'.format(name=theme)) - def on_export_theme(self, field=None): + def on_export_theme(self, checked=None): """ - Export the theme in a zip file - :param field: + Export the theme to a zip file + + :param bool checked: Sent by the QAction.triggered signal. It's not used in this method. + :rtype: None """ item = self.theme_list_widget.currentItem() if item is None: critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.')) return - theme = item.data(QtCore.Qt.UserRole) + theme_name = item.data(QtCore.Qt.UserRole) export_path, filter_used = \ FileDialog.getSaveFileName(self.main_window, - translate('OpenLP.ThemeManager', 'Save Theme - ({name})'). - format(name=theme), - Settings().value(self.settings_section + '/last directory export'), - translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)')) + translate('OpenLP.ThemeManager', + 'Save Theme - ({name})').format(name=theme_name), + Settings().value(self.settings_section + '/last directory export'), + translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'), + translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)')) self.application.set_busy_cursor() if export_path: Settings().setValue(self.settings_section + '/last directory export', export_path.parent) - if self._export_theme(str(export_path), theme): + if self._export_theme(export_path.with_suffix('.otz'), theme_name): QtWidgets.QMessageBox.information(self, translate('OpenLP.ThemeManager', 'Theme Exported'), translate('OpenLP.ThemeManager', 'Your theme has been successfully exported.')) self.application.set_normal_cursor() - def _export_theme(self, theme_path, theme): + def _export_theme(self, theme_path, theme_name): """ Create the zipfile with the theme contents. - :param theme_path: Location where the zip file will be placed - :param theme: The name of the theme to be exported + + :param openlp.core.common.path.Path theme_path: Location where the zip file will be placed + :param str theme_name: The name of the theme to be exported + :return: The success of creating the zip file + :rtype: bool """ - theme_zip = None try: - theme_zip = zipfile.ZipFile(theme_path, 'w') - source = os.path.join(self.path, theme) - for files in os.walk(source): - for name in files[2]: - theme_zip.write(os.path.join(source, name), os.path.join(theme, name)) - theme_zip.close() + with zipfile.ZipFile(str(theme_path), 'w') as theme_zip: + source_path = self.theme_path / theme_name + for file_path in source_path.iterdir(): + theme_zip.write(str(file_path), os.path.join(theme_name, file_path.name)) return True except OSError as ose: self.log_exception('Export Theme Failed') critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'), - translate('OpenLP.ThemeManager', 'The theme export failed because this error ' - 'occurred: {err}').format(err=ose.strerror)) - if theme_zip: - theme_zip.close() - shutil.rmtree(theme_path, True) + translate('OpenLP.ThemeManager', + 'The theme_name export failed because this error occurred: {err}') + .format(err=ose.strerror)) + if theme_path.exists(): + rmtree(theme_path, True) return False - def on_import_theme(self, field=None): + def on_import_theme(self, checked=None): """ Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from those files. This process will only load version 2 themes. - :param field: + + :param bool checked: Sent by the QAction.triggered signal. It's not used in this method. + :rtype: None """ - file_paths, selected_filter = FileDialog.getOpenFileNames( + file_paths, filter_used = FileDialog.getOpenFileNames( self, translate('OpenLP.ThemeManager', 'Select Theme Import File'), Settings().value(self.settings_section + '/last directory import'), @@ -435,8 +450,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage return self.application.set_busy_cursor() for file_path in file_paths: - self.unzip_theme(path_to_str(file_path), self.path) - Settings().setValue(self.settings_section + '/last directory import', file_path) + self.unzip_theme(file_path, self.theme_path) + Settings().setValue(self.settings_section + '/last directory import', file_path.parent) self.load_themes() self.application.set_normal_cursor() @@ -445,17 +460,17 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage Imports any themes on start up and makes sure there is at least one theme """ self.application.set_busy_cursor() - files = AppLocation.get_files(self.settings_section, '.otz') - for theme_file in files: - theme_file = os.path.join(self.path, str(theme_file)) - self.unzip_theme(theme_file, self.path) - delete_file(Path(theme_file)) - files = AppLocation.get_files(self.settings_section, '.png') + theme_paths = AppLocation.get_files(self.settings_section, '.otz') + for theme_path in theme_paths: + theme_path = self.theme_path / theme_path + self.unzip_theme(theme_path, self.theme_path) + delete_file(theme_path) + theme_paths = AppLocation.get_files(self.settings_section, '.png') # No themes have been found so create one - if not files: + if not theme_paths: theme = Theme() theme.theme_name = UiStrings().Default - self._write_theme(theme, None, None) + self._write_theme(theme) Settings().setValue(self.settings_section + '/global theme', theme.theme_name) self.application.set_normal_cursor() @@ -471,22 +486,21 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage # Sort the themes by its name considering language specific files.sort(key=lambda file_name: get_locale_key(str(file_name))) # now process the file list of png files - for name in files: - name = str(name) + for file in files: # check to see file is in theme root directory - theme = os.path.join(self.path, name) - if os.path.exists(theme): - text_name = os.path.splitext(name)[0] + theme_path = self.theme_path / file + if theme_path.exists(): + text_name = theme_path.stem if text_name == self.global_theme: name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name) else: name = text_name - thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=text_name)) + thumb = self.thumb_path / '{name}.png'.format(name=text_name) item_name = QtWidgets.QListWidgetItem(name) - if validate_thumb(Path(theme), Path(thumb)): + if validate_thumb(theme_path, thumb): icon = build_icon(thumb) else: - icon = create_thumb(theme, thumb) + icon = create_thumb(str(theme_path), str(thumb)) item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, text_name) self.theme_list_widget.addItem(item_name) @@ -507,27 +521,19 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage def get_theme_data(self, theme_name): """ - Returns a theme object from an XML or JSON file + Returns a theme object from a JSON file - :param theme_name: Name of the theme to load from file - :return: The theme object. + :param str theme_name: Name of the theme to load from file + :return: The theme object. + :rtype: Theme """ - self.log_debug('get theme data for theme {name}'.format(name=theme_name)) - theme_file_path = Path(self.path, str(theme_name), '{file_name}.json'.format(file_name=theme_name)) + theme_name = str(theme_name) + theme_file_path = self.theme_path / theme_name / '{file_name}.json'.format(file_name=theme_name) theme_data = get_text_file_string(theme_file_path) - jsn = True - if not theme_data: - theme_file_path = theme_file_path.with_suffix('.xml') - theme_data = get_text_file_string(theme_file_path) - jsn = False if not theme_data: self.log_debug('No theme data - using default theme') return Theme() - else: - if jsn: - return self._create_theme_from_json(theme_data, self.path) - else: - return self._create_theme_from_xml(theme_data, self.path) + return self._create_theme_from_json(theme_data, self.theme_path) def over_write_message_box(self, theme_name): """ @@ -543,172 +549,149 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage defaultButton=QtWidgets.QMessageBox.No) return ret == QtWidgets.QMessageBox.Yes - def unzip_theme(self, file_name, directory): + def unzip_theme(self, file_path, directory_path): """ Unzip the theme, remove the preview file if stored. Generate a new preview file. Check the XML theme version and upgrade if necessary. - :param file_name: - :param directory: + :param openlp.core.common.path.Path file_path: + :param openlp.core.common.path.Path directory_path: """ - self.log_debug('Unzipping theme {name}'.format(name=file_name)) - theme_zip = None - out_file = None + self.log_debug('Unzipping theme {name}'.format(name=file_path)) file_xml = None abort_import = True json_theme = False theme_name = "" try: - theme_zip = zipfile.ZipFile(file_name) - json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json'] - if len(json_file) != 1: - # TODO: remove XML handling at some point but would need a auto conversion to run first. - xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml'] - if len(xml_file) != 1: - self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file))) - raise ValidationError - xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot() - theme_version = xml_tree.get('version', default=None) - if not theme_version or float(theme_version) < 2.0: - self.log_error('Theme version is less than 2.0') - raise ValidationError - theme_name = xml_tree.find('name').text.strip() - else: - new_theme = Theme() - new_theme.load_theme(theme_zip.read(json_file[0]).decode("utf-8")) - theme_name = new_theme.theme_name - json_theme = True - theme_folder = os.path.join(directory, theme_name) - theme_exists = os.path.exists(theme_folder) - if theme_exists and not self.over_write_message_box(theme_name): - abort_import = True - return - else: - abort_import = False - for name in theme_zip.namelist(): - out_name = name.replace('/', os.path.sep) - split_name = out_name.split(os.path.sep) - if split_name[-1] == '' or len(split_name) == 1: - # is directory or preview file - continue - full_name = os.path.join(directory, out_name) - check_directory_exists(Path(os.path.dirname(full_name))) - if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json': - file_xml = str(theme_zip.read(name), 'utf-8') - out_file = open(full_name, 'w', encoding='utf-8') - out_file.write(file_xml) + with zipfile.ZipFile(str(file_path)) as theme_zip: + json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json'] + if len(json_file) != 1: + # TODO: remove XML handling after the 2.6 release. + xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml'] + if len(xml_file) != 1: + self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file))) + raise ValidationError + xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot() + theme_version = xml_tree.get('version', default=None) + if not theme_version or float(theme_version) < 2.0: + self.log_error('Theme version is less than 2.0') + raise ValidationError + theme_name = xml_tree.find('name').text.strip() else: - out_file = open(full_name, 'wb') - out_file.write(theme_zip.read(name)) - out_file.close() + new_theme = Theme() + new_theme.load_theme(theme_zip.read(json_file[0]).decode("utf-8")) + theme_name = new_theme.theme_name + json_theme = True + theme_folder = directory_path / theme_name + if theme_folder.exists() and not self.over_write_message_box(theme_name): + abort_import = True + return + else: + abort_import = False + for zipped_file in theme_zip.namelist(): + zipped_file_rel_path = Path(zipped_file) + split_name = zipped_file_rel_path.parts + if split_name[-1] == '' or len(split_name) == 1: + # is directory or preview file + continue + full_name = directory_path / zipped_file_rel_path + check_directory_exists(full_name.parent) + if zipped_file_rel_path.suffix.lower() == '.xml' or zipped_file_rel_path.suffix.lower() == '.json': + file_xml = str(theme_zip.read(zipped_file), 'utf-8') + with full_name.open('w', encoding='utf-8') as out_file: + out_file.write(file_xml) + else: + with full_name.open('wb') as out_file: + out_file.write(theme_zip.read(zipped_file)) except (IOError, zipfile.BadZipfile): - self.log_exception('Importing theme from zip failed {name}'.format(name=file_name)) + self.log_exception('Importing theme from zip failed {name}'.format(name=file_path)) raise ValidationError except ValidationError: critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'), translate('OpenLP.ThemeManager', 'File is not a valid theme.')) finally: - # Close the files, to be able to continue creating the theme. - if theme_zip: - theme_zip.close() - if out_file: - out_file.close() if not abort_import: # As all files are closed, we can create the Theme. if file_xml: if json_theme: - theme = self._create_theme_from_json(file_xml, self.path) + theme = self._create_theme_from_json(file_xml, self.theme_path) else: - theme = self._create_theme_from_xml(file_xml, self.path) + theme = self._create_theme_from_xml(file_xml, self.theme_path) self.generate_and_save_image(theme_name, theme) - # Only show the error message, when IOError was not raised (in - # this case the error message has already been shown). - elif theme_zip is not None: - critical_error_message_box( - translate('OpenLP.ThemeManager', 'Validation Error'), - translate('OpenLP.ThemeManager', 'File is not a valid theme.')) - self.log_error('Theme file does not contain XML data {name}'.format(name=file_name)) def check_if_theme_exists(self, theme_name): """ Check if theme already exists and displays error message - :param theme_name: Name of the Theme to test + :param str theme_name: Name of the Theme to test :return: True or False if theme exists + :rtype: bool """ - theme_dir = os.path.join(self.path, theme_name) - if os.path.exists(theme_dir): + if (self.theme_path / theme_name).exists(): critical_error_message_box( translate('OpenLP.ThemeManager', 'Validation Error'), translate('OpenLP.ThemeManager', 'A theme with this name already exists.')) return False return True - def save_theme(self, theme, image_from, image_to): + def save_theme(self, theme, image_source_path, image_destination_path): """ Called by theme maintenance Dialog to save the theme and to trigger the reload of the theme list - :param theme: The theme data object. - :param image_from: Where the theme image is currently located. - :param image_to: Where the Theme Image is to be saved to + + :param Theme theme: The theme data object. + :param openlp.core.common.path.Path image_source_path: Where the theme image is currently located. + :param openlp.core.common.path.Path image_destination_path: Where the Theme Image is to be saved to + :rtype: None """ - self._write_theme(theme, image_from, image_to) + self._write_theme(theme, image_source_path, image_destination_path) if theme.background_type == BackgroundType.to_string(BackgroundType.Image): - self.image_manager.update_image_border(theme.background_filename, + self.image_manager.update_image_border(path_to_str(theme.background_filename), ImageSource.Theme, QtGui.QColor(theme.background_border_color)) self.image_manager.process_updates() - def _write_theme(self, theme, image_from, image_to): + def _write_theme(self, theme, image_source_path=None, image_destination_path=None): """ Writes the theme to the disk and handles the background image if necessary - :param theme: The theme data object. - :param image_from: Where the theme image is currently located. - :param image_to: Where the Theme Image is to be saved to + :param Theme theme: The theme data object. + :param openlp.core.common.path.Path image_source_path: Where the theme image is currently located. + :param openlp.core.common.path.Path image_destination_path: Where the Theme Image is to be saved to + :rtype: None """ name = theme.theme_name - theme_pretty = theme.export_theme() - theme_dir = os.path.join(self.path, name) - check_directory_exists(Path(theme_dir)) - theme_file = os.path.join(theme_dir, name + '.json') - if self.old_background_image and image_to != self.old_background_image: - delete_file(Path(self.old_background_image)) - out_file = None + theme_pretty = theme.export_theme(self.theme_path) + theme_dir = self.theme_path / name + check_directory_exists(theme_dir) + theme_path = theme_dir / '{file_name}.json'.format(file_name=name) try: - out_file = open(theme_file, 'w', encoding='utf-8') - out_file.write(theme_pretty) + theme_path.write_text(theme_pretty) except IOError: self.log_exception('Saving theme to file failed') - finally: - if out_file: - out_file.close() - if image_from and os.path.abspath(image_from) != os.path.abspath(image_to): - try: - # Windows is always unicode, so no need to encode filenames - if is_win(): - shutil.copyfile(image_from, image_to) - else: - encoding = get_filesystem_encoding() - shutil.copyfile(image_from.encode(encoding), image_to.encode(encoding)) - except IOError as xxx_todo_changeme: - shutil.Error = xxx_todo_changeme - self.log_exception('Failed to save theme image') + if image_source_path and image_destination_path: + if self.old_background_image_path and image_destination_path != self.old_background_image_path: + delete_file(self.old_background_image_path) + if image_source_path != image_destination_path: + try: + copyfile(image_source_path, image_destination_path) + except IOError: + self.log_exception('Failed to save theme image') self.generate_and_save_image(name, theme) - def generate_and_save_image(self, name, theme): + def generate_and_save_image(self, theme_name, theme): """ Generate and save a preview image - :param name: The name of the theme. + :param str theme_name: The name of the theme. :param theme: The theme data object. """ frame = self.generate_image(theme) - sample_path_name = os.path.join(self.path, name + '.png') - if os.path.exists(sample_path_name): - os.unlink(sample_path_name) - frame.save(sample_path_name, 'png') - thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=name)) - create_thumb(sample_path_name, thumb, False) + sample_path_name = self.theme_path / '{file_name}.png'.format(file_name=theme_name) + if sample_path_name.exists(): + sample_path_name.unlink() + frame.save(str(sample_path_name), 'png') + thumb_path = self.thumb_path / '{name}.png'.format(name=theme_name) + create_thumb(str(sample_path_name), str(thumb_path), False) def update_preview_images(self): """ @@ -730,39 +713,32 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage """ return self.renderer.generate_preview(theme_data, force_page) - def get_preview_image(self, theme): - """ - Return an image representing the look of the theme - - :param theme: The theme to return the image for. - """ - return os.path.join(self.path, theme + '.png') - @staticmethod def _create_theme_from_xml(theme_xml, image_path): """ Return a theme object using information parsed from XML :param theme_xml: The Theme data object. - :param image_path: Where the theme image is stored + :param openlp.core.common.path.Path image_path: Where the theme image is stored :return: Theme data. + :rtype: Theme """ theme = Theme() theme.parse(theme_xml) theme.extend_image_filename(image_path) return theme - @staticmethod - def _create_theme_from_json(theme_json, image_path): + def _create_theme_from_json(self, theme_json, image_path): """ Return a theme object using information parsed from JSON :param theme_json: The Theme data object. - :param image_path: Where the theme image is stored + :param openlp.core.common.path.Path image_path: Where the theme image is stored :return: Theme data. + :rtype: Theme """ theme = Theme() - theme.load_theme(theme_json) + theme.load_theme(theme_json, self.theme_path) theme.extend_image_filename(image_path) return theme diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index f3b5bbb71..bf4be809c 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -211,8 +211,8 @@ class ThemesTab(SettingsTab): """ Utility method to update the global theme preview image. """ - image = self.theme_manager.get_preview_image(self.global_theme) - preview = QtGui.QPixmap(str(image)) + image_path = self.theme_manager.theme_path / '{file_name}.png'.format(file_name=self.global_theme) + preview = QtGui.QPixmap(str(image_path)) if not preview.isNull(): preview = preview.scaled(300, 255, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self.default_list_view.setPixmap(preview) diff --git a/openlp/plugins/images/lib/upgrade.py b/openlp/plugins/images/lib/upgrade.py index 63690d404..d467e9d3c 100644 --- a/openlp/plugins/images/lib/upgrade.py +++ b/openlp/plugins/images/lib/upgrade.py @@ -48,7 +48,6 @@ def upgrade_2(session, metadata): """ Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev """ - # TODO: Update tests log.debug('Starting upgrade_2 for file_path to JSON') old_table = Table('image_filenames', metadata, autoload=True) if 'file_path' not in [col.name for col in old_table.c.values()]: diff --git a/tests/functional/openlp_core_lib/test_theme.py b/tests/functional/openlp_core_lib/test_theme.py index bb90e574a..93bc06f24 100644 --- a/tests/functional/openlp_core_lib/test_theme.py +++ b/tests/functional/openlp_core_lib/test_theme.py @@ -22,8 +22,9 @@ """ Package to test the openlp.core.lib.theme package. """ -from unittest import TestCase import os +from pathlib import Path +from unittest import TestCase from openlp.core.lib.theme import Theme @@ -79,16 +80,16 @@ class TestTheme(TestCase): """ # GIVEN: A theme object theme = Theme() - theme.theme_name = 'MyBeautifulTheme ' - theme.background_filename = ' video.mp4' + theme.theme_name = 'MyBeautifulTheme' + theme.background_filename = Path('video.mp4') theme.background_type = 'video' - path = os.path.expanduser('~') + path = Path.home() # WHEN: Theme.extend_image_filename is run theme.extend_image_filename(path) # THEN: The filename of the background should be correct - expected_filename = os.path.join(path, 'MyBeautifulTheme', 'video.mp4') + expected_filename = path / 'MyBeautifulTheme' / 'video.mp4' self.assertEqual(expected_filename, theme.background_filename) self.assertEqual('MyBeautifulTheme', theme.theme_name) diff --git a/tests/functional/openlp_core_ui/test_exceptionform.py b/tests/functional/openlp_core_ui/test_exceptionform.py index 40eb19ac8..1b5c5fb59 100644 --- a/tests/functional/openlp_core_ui/test_exceptionform.py +++ b/tests/functional/openlp_core_ui/test_exceptionform.py @@ -22,11 +22,10 @@ """ Package to test the openlp.core.ui.exeptionform package. """ - import os import tempfile from unittest import TestCase -from unittest.mock import mock_open, patch +from unittest.mock import call, patch from openlp.core.common import Registry from openlp.core.common.path import Path @@ -142,15 +141,15 @@ class TestExceptionForm(TestMixin, TestCase): test_form = exceptionform.ExceptionForm() test_form.file_attachment = None - with patch.object(test_form, '_pyuno_import') as mock_pyuno: - with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback: - with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: - mock_pyuno.return_value = 'UNO Bridge Test' - mock_traceback.return_value = 'openlp: Traceback Test' - mock_description.return_value = 'Description Test' + with patch.object(test_form, '_pyuno_import') as mock_pyuno, \ + patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \ + patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: + mock_pyuno.return_value = 'UNO Bridge Test' + mock_traceback.return_value = 'openlp: Traceback Test' + mock_description.return_value = 'Description Test' - # WHEN: on_save_report_button_clicked called - test_form.on_send_report_button_clicked() + # WHEN: on_save_report_button_clicked called + test_form.on_send_report_button_clicked() # THEN: Verify strings were formatted properly mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT) @@ -182,25 +181,24 @@ class TestExceptionForm(TestMixin, TestCase): mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test' mocked_is_linux.return_value = False mocked_application_version.return_value = 'Trunk Test' - mocked_save_filename.return_value = (Path('testfile.txt'), 'filter') - test_form = exceptionform.ExceptionForm() - test_form.file_attachment = None + with patch.object(Path, 'open') as mocked_path_open: + x = Path('testfile.txt') + mocked_save_filename.return_value = x, 'ext' - with patch.object(test_form, '_pyuno_import') as mock_pyuno: - with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback: - with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: - with patch("openlp.core.ui.exceptionform.open", mock_open(), create=True) as mocked_open: - mock_pyuno.return_value = 'UNO Bridge Test' - mock_traceback.return_value = 'openlp: Traceback Test' - mock_description.return_value = 'Description Test' + test_form = exceptionform.ExceptionForm() + test_form.file_attachment = None - # WHEN: on_save_report_button_clicked called - test_form.on_save_report_button_clicked() + with patch.object(test_form, '_pyuno_import') as mock_pyuno, \ + patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \ + patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: + mock_pyuno.return_value = 'UNO Bridge Test' + mock_traceback.return_value = 'openlp: Traceback Test' + mock_description.return_value = 'Description Test' + + # WHEN: on_save_report_button_clicked called + test_form.on_save_report_button_clicked() # THEN: Verify proper calls to save file # self.maxDiff = None - check_text = "call().write({text})".format(text=MAIL_ITEM_TEXT.__repr__()) - write_text = "{text}".format(text=mocked_open.mock_calls[1]) - mocked_open.assert_called_with('testfile.txt', 'w') - self.assertEquals(check_text, write_text, "Saved information should match test text") + mocked_path_open.assert_has_calls([call().__enter__().write(MAIL_ITEM_TEXT)]) diff --git a/tests/functional/openlp_core_ui/test_maindisplay.py b/tests/functional/openlp_core_ui/test_maindisplay.py index 8da2dbd55..c3f798982 100644 --- a/tests/functional/openlp_core_ui/test_maindisplay.py +++ b/tests/functional/openlp_core_ui/test_maindisplay.py @@ -27,10 +27,10 @@ from unittest.mock import MagicMock, patch from PyQt5 import QtCore -from openlp.core.common import Registry, is_macosx, Settings +from openlp.core.common import Registry, is_macosx +from openlp.core.common.path import Path from openlp.core.lib import ScreenList, PluginManager from openlp.core.ui import MainDisplay, AudioPlayer -from openlp.core.ui.media import MediaController from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET from tests.helpers.testmixin import TestMixin @@ -184,7 +184,7 @@ class TestMainDisplay(TestCase, TestMixin): self.assertEqual(pyobjc_nsview.window().collectionBehavior(), NSWindowCollectionBehaviorManaged, 'Window collection behavior should be NSWindowCollectionBehaviorManaged') - @patch(u'openlp.core.ui.maindisplay.Settings') + @patch('openlp.core.ui.maindisplay.Settings') def test_show_display_startup_logo(self, MockedSettings): # GIVEN: Mocked show_display, setting for logo visibility display = MagicMock() @@ -204,7 +204,7 @@ class TestMainDisplay(TestCase, TestMixin): # THEN: setVisible should had been called with "True" main_display.setVisible.assert_called_once_with(True) - @patch(u'openlp.core.ui.maindisplay.Settings') + @patch('openlp.core.ui.maindisplay.Settings') def test_show_display_hide_startup_logo(self, MockedSettings): # GIVEN: Mocked show_display, setting for logo visibility display = MagicMock() @@ -224,8 +224,8 @@ class TestMainDisplay(TestCase, TestMixin): # THEN: setVisible should had not been called main_display.setVisible.assert_not_called() - @patch(u'openlp.core.ui.maindisplay.Settings') - @patch(u'openlp.core.ui.maindisplay.build_html') + @patch('openlp.core.ui.maindisplay.Settings') + @patch('openlp.core.ui.maindisplay.build_html') def test_build_html_no_video(self, MockedSettings, Mocked_build_html): # GIVEN: Mocked display display = MagicMock() @@ -252,8 +252,8 @@ class TestMainDisplay(TestCase, TestMixin): self.assertEquals(main_display.media_controller.video.call_count, 0, 'Media Controller video should not have been called') - @patch(u'openlp.core.ui.maindisplay.Settings') - @patch(u'openlp.core.ui.maindisplay.build_html') + @patch('openlp.core.ui.maindisplay.Settings') + @patch('openlp.core.ui.maindisplay.build_html') def test_build_html_video(self, MockedSettings, Mocked_build_html): # GIVEN: Mocked display display = MagicMock() @@ -270,7 +270,7 @@ class TestMainDisplay(TestCase, TestMixin): service_item.theme_data = MagicMock() service_item.theme_data.background_type = 'video' service_item.theme_data.theme_name = 'name' - service_item.theme_data.background_filename = 'background_filename' + service_item.theme_data.background_filename = Path('background_filename') mocked_plugin = MagicMock() display.plugin_manager = PluginManager() display.plugin_manager.plugins = [mocked_plugin] diff --git a/tests/functional/openlp_core_ui/test_themeform.py b/tests/functional/openlp_core_ui/test_themeform.py index cff097893..e487742cc 100644 --- a/tests/functional/openlp_core_ui/test_themeform.py +++ b/tests/functional/openlp_core_ui/test_themeform.py @@ -49,5 +49,5 @@ class TestThemeManager(TestCase): self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h')) # THEN: The theme background file should be set and `set_background_page_values` should have been called - self.assertEqual(self.instance.theme.background_filename, '/new/pat.h') + self.assertEqual(self.instance.theme.background_filename, Path('/', 'new', 'pat.h')) mocked_set_background_page_values.assert_called_once_with() diff --git a/tests/functional/openlp_core_ui/test_thememanager.py b/tests/functional/openlp_core_ui/test_thememanager.py index d778fb8ef..e4b044b29 100644 --- a/tests/functional/openlp_core_ui/test_thememanager.py +++ b/tests/functional/openlp_core_ui/test_thememanager.py @@ -30,8 +30,9 @@ from unittest.mock import ANY, MagicMock, patch from PyQt5 import QtWidgets -from openlp.core.ui import ThemeManager from openlp.core.common import Registry +from openlp.core.common.path import Path +from openlp.core.ui import ThemeManager from tests.utils.constants import TEST_RESOURCES_PATH @@ -57,13 +58,13 @@ class TestThemeManager(TestCase): """ # GIVEN: A new ThemeManager instance. theme_manager = ThemeManager() - theme_manager.path = os.path.join(TEST_RESOURCES_PATH, 'themes') + theme_manager.theme_path = Path(TEST_RESOURCES_PATH, 'themes') with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \ patch('zipfile.ZipFile.write') as mocked_zipfile_write: mocked_zipfile_init.return_value = None # WHEN: The theme is exported - theme_manager._export_theme(os.path.join('some', 'path', 'Default.otz'), 'Default') + theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default') # THEN: The zipfile should be created at the given path mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w') @@ -86,57 +87,49 @@ class TestThemeManager(TestCase): """ Test that we don't try to overwrite a theme background image with itself """ - # GIVEN: A new theme manager instance, with mocked builtins.open, shutil.copyfile, + # GIVEN: A new theme manager instance, with mocked builtins.open, copyfile, # theme, check_directory_exists and thememanager-attributes. - with patch('builtins.open') as mocked_open, \ - patch('openlp.core.ui.thememanager.shutil.copyfile') as mocked_copyfile, \ + with patch('openlp.core.ui.thememanager.copyfile') as mocked_copyfile, \ patch('openlp.core.ui.thememanager.check_directory_exists'): - mocked_open.return_value = MagicMock() theme_manager = ThemeManager(None) theme_manager.old_background_image = None theme_manager.generate_and_save_image = MagicMock() - theme_manager.path = '' + theme_manager.theme_path = MagicMock() mocked_theme = MagicMock() mocked_theme.theme_name = 'themename' mocked_theme.extract_formatted_xml = MagicMock() mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode() # WHEN: Calling _write_theme with path to the same image, but the path written slightly different - file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg') - # Do replacement from end of string to avoid problems with path start - file_name2 = file_name1[::-1].replace(os.sep, os.sep + os.sep, 2)[::-1] - theme_manager._write_theme(mocked_theme, file_name1, file_name2) + file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg') + theme_manager._write_theme(mocked_theme, file_name1, file_name1) # THEN: The mocked_copyfile should not have been called - self.assertFalse(mocked_copyfile.called, 'shutil.copyfile should not be called') + self.assertFalse(mocked_copyfile.called, 'copyfile should not be called') def test_write_theme_diff_images(self): """ Test that we do overwrite a theme background image when a new is submitted """ - # GIVEN: A new theme manager instance, with mocked builtins.open, shutil.copyfile, + # GIVEN: A new theme manager instance, with mocked builtins.open, copyfile, # theme, check_directory_exists and thememanager-attributes. - with patch('builtins.open') as mocked_open, \ - patch('openlp.core.ui.thememanager.shutil.copyfile') as mocked_copyfile, \ + with patch('openlp.core.ui.thememanager.copyfile') as mocked_copyfile, \ patch('openlp.core.ui.thememanager.check_directory_exists'): - mocked_open.return_value = MagicMock() theme_manager = ThemeManager(None) theme_manager.old_background_image = None theme_manager.generate_and_save_image = MagicMock() - theme_manager.path = '' + theme_manager.theme_path = MagicMock() mocked_theme = MagicMock() mocked_theme.theme_name = 'themename' mocked_theme.filename = "filename" - # mocked_theme.extract_formatted_xml = MagicMock() - # mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode() # WHEN: Calling _write_theme with path to different images - file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg') - file_name2 = os.path.join(TEST_RESOURCES_PATH, 'church2.jpg') + file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg') + file_name2 = Path(TEST_RESOURCES_PATH, 'church2.jpg') theme_manager._write_theme(mocked_theme, file_name1, file_name2) # THEN: The mocked_copyfile should not have been called - self.assertTrue(mocked_copyfile.called, 'shutil.copyfile should be called') + self.assertTrue(mocked_copyfile.called, 'copyfile should be called') def test_write_theme_special_char_name(self): """ @@ -146,7 +139,7 @@ class TestThemeManager(TestCase): theme_manager = ThemeManager(None) theme_manager.old_background_image = None theme_manager.generate_and_save_image = MagicMock() - theme_manager.path = self.temp_folder + theme_manager.theme_path = Path(self.temp_folder) mocked_theme = MagicMock() mocked_theme.theme_name = 'theme 愛 name' mocked_theme.export_theme.return_value = "{}" @@ -208,17 +201,17 @@ class TestThemeManager(TestCase): theme_manager = ThemeManager(None) theme_manager._create_theme_from_xml = MagicMock() theme_manager.generate_and_save_image = MagicMock() - theme_manager.path = '' - folder = mkdtemp() - theme_file = os.path.join(TEST_RESOURCES_PATH, 'themes', 'Moss_on_tree.otz') + theme_manager.theme_path = None + folder = Path(mkdtemp()) + theme_file = Path(TEST_RESOURCES_PATH, 'themes', 'Moss_on_tree.otz') # WHEN: We try to unzip it theme_manager.unzip_theme(theme_file, folder) # THEN: Files should be unpacked - self.assertTrue(os.path.exists(os.path.join(folder, 'Moss on tree', 'Moss on tree.xml'))) + self.assertTrue((folder / 'Moss on tree' / 'Moss on tree.xml').exists()) self.assertEqual(mocked_critical_error_message_box.call_count, 0, 'No errors should have happened') - shutil.rmtree(folder) + shutil.rmtree(str(folder)) def test_unzip_theme_invalid_version(self): """ diff --git a/tests/interfaces/openlp_core_ui/test_thememanager.py b/tests/interfaces/openlp_core_ui/test_thememanager.py index b560df154..0797aa9b8 100644 --- a/tests/interfaces/openlp_core_ui/test_thememanager.py +++ b/tests/interfaces/openlp_core_ui/test_thememanager.py @@ -26,7 +26,8 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from openlp.core.common import Registry, Settings -from openlp.core.ui import ThemeManager, ThemeForm, FileRenameForm +from openlp.core.common.path import Path +from openlp.core.ui import ThemeManager from tests.helpers.testmixin import TestMixin @@ -91,6 +92,23 @@ class TestThemeManager(TestCase, TestMixin): assert self.theme_manager.thumb_path.startswith(self.theme_manager.path) is True, \ 'The thumb path and the main path should start with the same value' + def test_build_theme_path(self): + """ + Test the thememanager build_theme_path - basic test + """ + # GIVEN: A new a call to initialise + with patch('openlp.core.common.AppLocation.get_section_data_path', return_value=Path('test/path')): + Settings().setValue('themes/global theme', 'my_theme') + + self.theme_manager.theme_form = MagicMock() + self.theme_manager.load_first_time_themes = MagicMock() + + # WHEN: the build_theme_path is run + self.theme_manager.build_theme_path() + + # THEN: The thumbnail path should be a sub path of the test path + self.assertEqual(self.theme_manager.thumb_path, Path('test/path/thumbnails')) + def test_click_on_new_theme(self): """ Test the on_add_theme event handler is called by the UI @@ -109,17 +127,16 @@ class TestThemeManager(TestCase, TestMixin): @patch('openlp.core.ui.themeform.ThemeForm._setup') @patch('openlp.core.ui.filerenameform.FileRenameForm._setup') - def test_bootstrap_post(self, mocked_theme_form, mocked_rename_form): + def test_bootstrap_post(self, mocked_rename_form, mocked_theme_form): """ Test the functions of bootstrap_post_setup are called. """ # GIVEN: self.theme_manager.load_themes = MagicMock() - self.theme_manager.path = MagicMock() + self.theme_manager.theme_path = MagicMock() # WHEN: self.theme_manager.bootstrap_post_set_up() # THEN: - self.assertEqual(self.theme_manager.path, self.theme_manager.theme_form.path) self.assertEqual(1, self.theme_manager.load_themes.call_count, "load_themes should have been called once")