From 779e95b52342df84330c367814b87cf7999a7640 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 18:33:03 +0100 Subject: [PATCH 01/56] Fix for samuel_m issue and bug #1672229 --- openlp/core/common/applocation.py | 2 +- openlp/core/lib/mediamanageritem.py | 4 +++- openlp/core/ui/lib/listwidgetwithdnd.py | 2 +- openlp/plugins/images/lib/mediaitem.py | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 02a872303..a0c927666 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -85,7 +85,7 @@ class AppLocation(object): """ # Check if we have a different data location. if Settings().contains('advanced/data path'): - path = Settings().value('advanced/data path') + path = Path(Settings().value('advanced/data path')) else: path = AppLocation.get_directory(AppLocation.DataDir) check_directory_exists(path) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 1c7a5b4ef..ce106854a 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -341,7 +341,9 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): else: new_files.append(file_name) if new_files: - self.validate_and_load(new_files, data['target']) + if 'target' in data: + self.validate_and_load(new_files, data['target']) + self.validate_and_load(new_files) def dnd_move_internal(self, target): """ diff --git a/openlp/core/ui/lib/listwidgetwithdnd.py b/openlp/core/ui/lib/listwidgetwithdnd.py index 1758d46fb..e8f0b729f 100755 --- a/openlp/core/ui/lib/listwidgetwithdnd.py +++ b/openlp/core/ui/lib/listwidgetwithdnd.py @@ -121,7 +121,7 @@ class ListWidgetWithDnD(QtWidgets.QListWidget): for file in listing: files.append(os.path.join(local_file, file)) Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text), - {'files': files, 'target': self.itemAt(event.pos())}) + {'files': files}) else: event.ignore() diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index b436d2708..5eac4b3b5 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -364,7 +364,7 @@ class ImageMediaItem(MediaManagerItem): if validate_thumb(image.file_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(image.file_path, thumbnail_path) + icon = create_thumb(str(image.file_path), str(thumbnail_path)) item_name = QtWidgets.QTreeWidgetItem([file_name]) item_name.setText(0, file_name) item_name.setIcon(0, icon) @@ -388,6 +388,7 @@ class ImageMediaItem(MediaManagerItem): :param files: A List of strings containing the filenames of the files to be loaded :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files """ + file_paths = [Path(file) for file in file_paths] self.application.set_normal_cursor() self.load_list(file_paths, target_group) last_dir = file_paths[0].parent From 98d5f0ec670940fc7b010f76220a8560a7c25642 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 19:38:56 +0100 Subject: [PATCH 02/56] fix 1660473, shamlessly ported from 2.4.5 --- openlp/core/ui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 25fe818ee..4a1200084 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -175,7 +175,7 @@ class Ui_MainWindow(object): triggers=self.service_manager_contents.on_load_service_clicked) self.file_save_item = create_action(main_window, 'fileSaveItem', icon=':/general/general_save.png', can_shortcuts=True, category=UiStrings().File, - triggers=self.service_manager_contents.save_file) + triggers=self.service_manager_contents.decide_save_method) self.file_save_as_item = create_action(main_window, 'fileSaveAsItem', can_shortcuts=True, category=UiStrings().File, triggers=self.service_manager_contents.save_file_as) From 71ad60d038f27747d691edafca9e956f7836b281 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 20:01:09 +0100 Subject: [PATCH 03/56] fixes #1660478 - Opening recent file does not prompt to save changes --- openlp/core/ui/servicemanager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 153034e33..2bee2cae0 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -469,6 +469,12 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa Load a recent file as the service triggered by mainwindow recent service list. :param field: """ + if self.is_modified(): + result = self.save_modified_service() + if result == QtWidgets.QMessageBox.Cancel: + return False + elif result == QtWidgets.QMessageBox.Save: + self.decide_save_method() sender = self.sender() self.load_file(sender.data()) From 37da12affd0953d9824caf5e9bd43b4695af6256 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 20:29:54 +0100 Subject: [PATCH 04/56] fixes #1715125 - Missing .osz file extension on save service --- openlp/core/ui/servicemanager.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 2bee2cae0..42c8dea6d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -713,18 +713,23 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa default_file_path = directory_path / default_file_path # 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. + lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)') + packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)') + if self._file_name.endswith('oszl') or self.service_has_all_original_files: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, - translate('OpenLP.ServiceManager', - 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)')) + '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter)) else: file_path, filter_used = FileDialog.getSaveFileName( - self.main_window, UiStrings().SaveService, file_path, - translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;')) + self.main_window, UiStrings().SaveService, default_file_path, + '{packaged};;'.format(packaged=packaged_filter)) if not file_path: return False - file_path.with_suffix('.osz') + if filter_used == lite_filter: + file_path = file_path.with_suffix('.oszl') + else: + file_path = file_path.with_suffix('.osz') self.set_file_name(file_path) self.decide_save_method() From 2be17b5c6fda2490fa5e44cd235a65ded29a3b45 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 20:41:07 +0100 Subject: [PATCH 05/56] fixes #1532193 - Typos in songusageplugin.py annother fix shamelessly ported from 2.4 --- openlp/core/common/settings.py | 6 ++++++ openlp/plugins/songusage/songusageplugin.py | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index ca59d4e6f..48d8710a0 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -270,6 +270,12 @@ class Settings(QtCore.QSettings): ('media/last directory', 'media/last directory', [(str_to_path, None)]) ] + __setting_upgrade_3__ = [ + ('songuasge/db password', 'songusage/db password', []) + ('songuasge/db hostname', 'songusage/db hostname', []) + ('songuasge/db database', 'songusage/db database', []) + ] + @staticmethod def extend_default_settings(default_values): """ diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index d0c2f7fe7..8d5e08322 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -44,9 +44,9 @@ if QtCore.QDate().currentDate().month() < 9: __default_settings__ = { 'songusage/db type': 'sqlite', 'songusage/db username': '', - 'songuasge/db password': '', - 'songuasge/db hostname': '', - 'songuasge/db database': '', + 'songusage/db password': '', + 'songusage/db hostname': '', + 'songusage/db database': '', 'songusage/active': False, 'songusage/to date': QtCore.QDate(YEAR, 8, 31), 'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1), From cecd2298a74ec36613eb7d719ea92d10f5f5dfd8 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 21:36:11 +0100 Subject: [PATCH 06/56] fixes #1660486 - Dragging item in service manager without changes triggeres 'unsaved' --- openlp/core/common/settings.py | 4 ++-- openlp/core/ui/servicemanager.py | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 48d8710a0..6f681b7d5 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -271,8 +271,8 @@ class Settings(QtCore.QSettings): ] __setting_upgrade_3__ = [ - ('songuasge/db password', 'songusage/db password', []) - ('songuasge/db hostname', 'songusage/db hostname', []) + ('songuasge/db password', 'songusage/db password', []), + ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []) ] diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 42c8dea6d..42a45cafd 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -1663,14 +1663,15 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa if start_pos == -1: return if item is None: - end_pos = len(self.service_items) + end_pos = len(self.service_items) - 1 else: end_pos = get_parent_item_data(item) - 1 service_item = self.service_items[start_pos] - self.service_items.remove(service_item) - self.service_items.insert(end_pos, service_item) - self.repaint_service_list(end_pos, child) - self.set_modified() + if start_pos != end_pos: + self.service_items.remove(service_item) + self.service_items.insert(end_pos, service_item) + self.repaint_service_list(end_pos, child) + self.set_modified() else: # we are not over anything so drop replace = False From 66049f0c96985391f73822182109b5c75e127dbf Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Tue, 10 Oct 2017 20:09:20 +0100 Subject: [PATCH 07/56] bible pathlib changes --- openlp/core/common/applocation.py | 2 +- openlp/core/ui/lib/pathedit.py | 2 +- .../plugins/bibles/forms/bibleimportform.py | 286 ++++++------------ openlp/plugins/bibles/lib/bibleimport.py | 28 +- openlp/plugins/bibles/lib/biblestab.py | 4 +- openlp/plugins/bibles/lib/db.py | 34 ++- .../plugins/bibles/lib/importers/csvbible.py | 23 +- .../plugins/bibles/lib/importers/opensong.py | 11 +- openlp/plugins/bibles/lib/importers/osis.py | 8 +- openlp/plugins/bibles/lib/importers/sword.py | 2 +- .../bibles/lib/importers/wordproject.py | 27 +- .../plugins/bibles/lib/importers/zefania.py | 8 +- openlp/plugins/bibles/lib/manager.py | 19 +- .../openlp_plugins/bibles/test_bibleimport.py | 43 +-- .../openlp_plugins/bibles/test_csvimport.py | 49 +-- .../bibles/test_opensongimport.py | 35 +-- .../openlp_plugins/bibles/test_osisimport.py | 51 ++-- .../openlp_plugins/bibles/test_swordimport.py | 4 +- .../bibles/test_wordprojectimport.py | 55 ++-- .../bibles/test_zefaniaimport.py | 11 +- 20 files changed, 307 insertions(+), 395 deletions(-) diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 02a872303..a0c927666 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -85,7 +85,7 @@ class AppLocation(object): """ # Check if we have a different data location. if Settings().contains('advanced/data path'): - path = Settings().value('advanced/data path') + path = Path(Settings().value('advanced/data path')) else: path = AppLocation.get_directory(AppLocation.DataDir) check_directory_exists(path) diff --git a/openlp/core/ui/lib/pathedit.py b/openlp/core/ui/lib/pathedit.py index d5b9272fa..2089772dd 100644 --- a/openlp/core/ui/lib/pathedit.py +++ b/openlp/core/ui/lib/pathedit.py @@ -84,7 +84,7 @@ class PathEdit(QtWidgets.QWidget): self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished) self.update_button_tool_tips() - @property + @QtCore.pyqtProperty('QVariant') def path(self): """ A property getter method to return the selected path. diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index fd270fced..346492c40 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -23,7 +23,6 @@ The bible import functions for OpenLP """ import logging -import os import urllib.error from lxml import etree @@ -39,6 +38,7 @@ from openlp.core.common.languagemanager import get_locale_key from openlp.core.lib.db import delete_database from openlp.core.lib.exceptions import ValidationError from openlp.core.lib.ui import critical_error_message_box +from openlp.core.ui.lib.pathedit import PathEdit from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings from openlp.plugins.bibles.lib.db import clean_filename from openlp.plugins.bibles.lib.importers.http import CWExtract, BGExtract, BSExtract @@ -120,15 +120,9 @@ class BibleImportForm(OpenLPWizard): Set up the signals used in the bible importer. """ self.web_source_combo_box.currentIndexChanged.connect(self.on_web_source_combo_box_index_changed) - self.osis_browse_button.clicked.connect(self.on_osis_browse_button_clicked) - self.csv_books_button.clicked.connect(self.on_csv_books_browse_button_clicked) - self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked) - self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked) - self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked) - self.wordproject_browse_button.clicked.connect(self.on_wordproject_browse_button_clicked) self.web_update_button.clicked.connect(self.on_web_update_button_clicked) - self.sword_browse_button.clicked.connect(self.on_sword_browse_button_clicked) - self.sword_zipbrowse_button.clicked.connect(self.on_sword_zipbrowse_button_clicked) + self.sword_folder_path_edit.pathChanged.connect(self.on_sword_folder_path_edit_path_changed) + self.sword_zipfile_path_edit.pathChanged.connect(self.on_sword_zipfile_path_edit_path_changed) def add_custom_pages(self): """ @@ -159,17 +153,12 @@ class BibleImportForm(OpenLPWizard): self.osis_layout.setObjectName('OsisLayout') self.osis_file_label = QtWidgets.QLabel(self.osis_widget) self.osis_file_label.setObjectName('OsisFileLabel') - self.osis_file_layout = QtWidgets.QHBoxLayout() - self.osis_file_layout.setObjectName('OsisFileLayout') - self.osis_file_edit = QtWidgets.QLineEdit(self.osis_widget) - self.osis_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.osis_file_edit.setObjectName('OsisFileEdit') - self.osis_file_layout.addWidget(self.osis_file_edit) - self.osis_browse_button = QtWidgets.QToolButton(self.osis_widget) - self.osis_browse_button.setIcon(self.open_icon) - self.osis_browse_button.setObjectName('OsisBrowseButton') - self.osis_file_layout.addWidget(self.osis_browse_button) - self.osis_layout.addRow(self.osis_file_label, self.osis_file_layout) + self.osis_path_edit = PathEdit( + self.osis_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.OSIS), + show_revert=False) + self.osis_layout.addRow(self.osis_file_label, self.osis_path_edit) self.osis_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.osis_widget) self.csv_widget = QtWidgets.QWidget(self.select_page) @@ -179,30 +168,27 @@ class BibleImportForm(OpenLPWizard): self.csv_layout.setObjectName('CsvLayout') self.csv_books_label = QtWidgets.QLabel(self.csv_widget) self.csv_books_label.setObjectName('CsvBooksLabel') - self.csv_books_layout = QtWidgets.QHBoxLayout() - self.csv_books_layout.setObjectName('CsvBooksLayout') - self.csv_books_edit = QtWidgets.QLineEdit(self.csv_widget) - self.csv_books_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.csv_books_edit.setObjectName('CsvBooksEdit') - self.csv_books_layout.addWidget(self.csv_books_edit) - self.csv_books_button = QtWidgets.QToolButton(self.csv_widget) - self.csv_books_button.setIcon(self.open_icon) - self.csv_books_button.setObjectName('CsvBooksButton') - self.csv_books_layout.addWidget(self.csv_books_button) - self.csv_layout.addRow(self.csv_books_label, self.csv_books_layout) + self.csv_books_path_edit = PathEdit( + self.csv_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.CSV), + show_revert=False, + ) + self.csv_books_path_edit.filters = \ + '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File')) + self.csv_layout.addRow(self.csv_books_label, self.csv_books_path_edit) self.csv_verses_label = QtWidgets.QLabel(self.csv_widget) self.csv_verses_label.setObjectName('CsvVersesLabel') - self.csv_verses_layout = QtWidgets.QHBoxLayout() - self.csv_verses_layout.setObjectName('CsvVersesLayout') - self.csv_verses_edit = QtWidgets.QLineEdit(self.csv_widget) - self.csv_verses_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.csv_verses_edit.setObjectName('CsvVersesEdit') - self.csv_verses_layout.addWidget(self.csv_verses_edit) - self.csv_verses_button = QtWidgets.QToolButton(self.csv_widget) - self.csv_verses_button.setIcon(self.open_icon) - self.csv_verses_button.setObjectName('CsvVersesButton') - self.csv_verses_layout.addWidget(self.csv_verses_button) - self.csv_layout.addRow(self.csv_verses_label, self.csv_verses_layout) + self.csv_verses_path_edit = PathEdit( + self.csv_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.CSV), + show_revert=False, + ) + self.csv_verses_path_edit.filters = \ + '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File')) + self.csv_layout.addRow(self.csv_books_label, self.csv_books_path_edit) + self.csv_layout.addRow(self.csv_verses_label, self.csv_verses_path_edit) self.csv_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.csv_widget) self.open_song_widget = QtWidgets.QWidget(self.select_page) @@ -212,17 +198,13 @@ class BibleImportForm(OpenLPWizard): self.open_song_layout.setObjectName('OpenSongLayout') self.open_song_file_label = QtWidgets.QLabel(self.open_song_widget) self.open_song_file_label.setObjectName('OpenSongFileLabel') - self.open_song_file_layout = QtWidgets.QHBoxLayout() - self.open_song_file_layout.setObjectName('OpenSongFileLayout') - self.open_song_file_edit = QtWidgets.QLineEdit(self.open_song_widget) - self.open_song_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.open_song_file_edit.setObjectName('OpenSongFileEdit') - self.open_song_file_layout.addWidget(self.open_song_file_edit) - self.open_song_browse_button = QtWidgets.QToolButton(self.open_song_widget) - self.open_song_browse_button.setIcon(self.open_icon) - self.open_song_browse_button.setObjectName('OpenSongBrowseButton') - self.open_song_file_layout.addWidget(self.open_song_browse_button) - self.open_song_layout.addRow(self.open_song_file_label, self.open_song_file_layout) + self.open_song_path_edit = PathEdit( + self.open_song_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.OS), + show_revert=False, + ) + self.open_song_layout.addRow(self.open_song_file_label, self.open_song_path_edit) self.open_song_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.open_song_widget) self.web_tab_widget = QtWidgets.QTabWidget(self.select_page) @@ -290,17 +272,14 @@ class BibleImportForm(OpenLPWizard): self.zefania_layout.setObjectName('ZefaniaLayout') self.zefania_file_label = QtWidgets.QLabel(self.zefania_widget) self.zefania_file_label.setObjectName('ZefaniaFileLabel') - self.zefania_file_layout = QtWidgets.QHBoxLayout() - self.zefania_file_layout.setObjectName('ZefaniaFileLayout') - self.zefania_file_edit = QtWidgets.QLineEdit(self.zefania_widget) - self.zefania_file_edit.setObjectName('ZefaniaFileEdit') - self.zefania_file_layout.addWidget(self.zefania_file_edit) - self.zefania_browse_button = QtWidgets.QToolButton(self.zefania_widget) - self.zefania_browse_button.setIcon(self.open_icon) - self.zefania_browse_button.setObjectName('ZefaniaBrowseButton') - self.zefania_file_layout.addWidget(self.zefania_browse_button) - self.zefania_layout.addRow(self.zefania_file_label, self.zefania_file_layout) - self.zefania_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.zefania_path_edit = PathEdit( + self.zefania_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.ZEF), + show_revert=False, + ) + self.zefania_layout.addRow(self.zefania_file_label, self.zefania_path_edit) + self.zefania_layout.setItem(2, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.zefania_widget) self.sword_widget = QtWidgets.QWidget(self.select_page) self.sword_widget.setObjectName('SwordWidget') @@ -316,16 +295,13 @@ class BibleImportForm(OpenLPWizard): self.sword_folder_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_folder_label.setObjectName('SwordSourceLabel') self.sword_folder_label.setObjectName('SwordFolderLabel') - self.sword_folder_edit = QtWidgets.QLineEdit(self.sword_folder_tab) - self.sword_folder_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.sword_folder_edit.setObjectName('SwordFolderEdit') - self.sword_browse_button = QtWidgets.QToolButton(self.sword_folder_tab) - self.sword_browse_button.setIcon(self.open_icon) - self.sword_browse_button.setObjectName('SwordBrowseButton') - self.sword_folder_layout = QtWidgets.QHBoxLayout() - self.sword_folder_layout.addWidget(self.sword_folder_edit) - self.sword_folder_layout.addWidget(self.sword_browse_button) - self.sword_folder_tab_layout.addRow(self.sword_folder_label, self.sword_folder_layout) + self.sword_folder_path_edit = PathEdit( + self.sword_folder_tab, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.SWORD), + show_revert=False, + ) + self.sword_folder_tab_layout.addRow(self.sword_folder_label, self.sword_folder_path_edit) self.sword_bible_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_bible_label.setObjectName('SwordBibleLabel') self.sword_bible_combo_box = QtWidgets.QComboBox(self.sword_folder_tab) @@ -340,16 +316,13 @@ class BibleImportForm(OpenLPWizard): self.sword_zip_layout.setObjectName('SwordZipLayout') self.sword_zipfile_label = QtWidgets.QLabel(self.sword_zip_tab) self.sword_zipfile_label.setObjectName('SwordZipFileLabel') - self.sword_zipfile_edit = QtWidgets.QLineEdit(self.sword_zip_tab) - self.sword_zipfile_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.sword_zipfile_edit.setObjectName('SwordZipFileEdit') - self.sword_zipbrowse_button = QtWidgets.QToolButton(self.sword_zip_tab) - self.sword_zipbrowse_button.setIcon(self.open_icon) - self.sword_zipbrowse_button.setObjectName('SwordZipBrowseButton') - self.sword_zipfile_layout = QtWidgets.QHBoxLayout() - self.sword_zipfile_layout.addWidget(self.sword_zipfile_edit) - self.sword_zipfile_layout.addWidget(self.sword_zipbrowse_button) - self.sword_zip_layout.addRow(self.sword_zipfile_label, self.sword_zipfile_layout) + self.sword_zipfile_path_edit = PathEdit( + self.sword_zip_tab, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.SWORD), + show_revert=False, + ) + self.sword_zip_layout.addRow(self.sword_zipfile_label, self.sword_zipfile_path_edit) self.sword_zipbible_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_zipbible_label.setObjectName('SwordZipBibleLabel') self.sword_zipbible_combo_box = QtWidgets.QComboBox(self.sword_zip_tab) @@ -370,18 +343,13 @@ class BibleImportForm(OpenLPWizard): self.wordproject_layout.setObjectName('WordProjectLayout') self.wordproject_file_label = QtWidgets.QLabel(self.wordproject_widget) self.wordproject_file_label.setObjectName('WordProjectFileLabel') - self.wordproject_file_layout = QtWidgets.QHBoxLayout() - self.wordproject_file_layout.setObjectName('WordProjectFileLayout') - self.wordproject_file_edit = QtWidgets.QLineEdit(self.wordproject_widget) - self.wordproject_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.wordproject_file_edit.setObjectName('WordProjectFileEdit') - self.wordproject_file_layout.addWidget(self.wordproject_file_edit) - self.wordproject_browse_button = QtWidgets.QToolButton(self.wordproject_widget) - self.wordproject_browse_button.setIcon(self.open_icon) - self.wordproject_browse_button.setObjectName('WordProjectBrowseButton') - self.wordproject_file_layout.addWidget(self.wordproject_browse_button) - self.wordproject_layout.addRow(self.wordproject_file_label, self.wordproject_file_layout) - self.wordproject_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.wordproject_path_edit = PathEdit( + self.wordproject_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.WordProject), + show_revert=False) + self.wordproject_layout.addRow(self.wordproject_file_label, self.wordproject_path_edit) + self.wordproject_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.wordproject_widget) self.select_page_layout.addLayout(self.select_stack) self.addPage(self.select_page) @@ -466,8 +434,6 @@ class BibleImportForm(OpenLPWizard): self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:')) self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:')) self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:')) - self.sword_folder_edit.setPlaceholderText(translate('BiblesPlugin.ImportWizardForm', - 'Defaults to the standard SWORD data folder')) self.sword_zipbible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:')) self.sword_tab_widget.setTabText(self.sword_tab_widget.indexOf(self.sword_folder_tab), translate('BiblesPlugin.ImportWizardForm', 'Import from folder')) @@ -516,7 +482,7 @@ class BibleImportForm(OpenLPWizard): if self.field('source_format') == BibleFormat.OSIS: if not self.field('osis_location'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OSIS) - self.osis_file_edit.setFocus() + self.osis_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.CSV: if not self.field('csv_booksfile'): @@ -536,18 +502,18 @@ class BibleImportForm(OpenLPWizard): elif self.field('source_format') == BibleFormat.OpenSong: if not self.field('opensong_file'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OS) - self.open_song_file_edit.setFocus() + self.open_song_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.Zefania: if not self.field('zefania_file'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.ZEF) - self.zefania_file_edit.setFocus() + self.zefania_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.WordProject: if not self.field('wordproject_file'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.WordProject) - self.wordproject_file_edit.setFocus() + self.wordproject_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.WebDownload: # If count is 0 the bible list has not yet been downloaded @@ -561,7 +527,7 @@ class BibleImportForm(OpenLPWizard): if not self.field('sword_folder_path') and self.sword_bible_combo_box.count() == 0: critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFolder % WizardStrings.SWORD) - self.sword_folder_edit.setFocus() + self.sword_folder_path_edit.setFocus() return False key = self.sword_bible_combo_box.itemData(self.sword_bible_combo_box.currentIndex()) if 'description' in self.pysword_folder_modules_json[key]: @@ -573,7 +539,7 @@ class BibleImportForm(OpenLPWizard): elif self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_zip_tab): if not self.field('sword_zip_path'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.SWORD) - self.sword_zipfile_edit.setFocus() + self.sword_zipfile_path_edit.setFocus() return False key = self.sword_zipbible_combo_box.itemData(self.sword_zipbible_combo_box.currentIndex()) if 'description' in self.pysword_zip_modules_json[key]: @@ -584,7 +550,6 @@ class BibleImportForm(OpenLPWizard): elif self.currentPage() == self.license_details_page: license_version = self.field('license_version') license_copyright = self.field('license_copyright') - path = str(AppLocation.get_section_data_path('bibles')) if not license_version: critical_error_message_box( UiStrings().EmptyField, @@ -606,7 +571,7 @@ class BibleImportForm(OpenLPWizard): 'existing one.')) self.version_name_edit.setFocus() return False - elif os.path.exists(os.path.join(path, clean_filename(license_version))): + elif (AppLocation.get_section_data_path('bibles') / clean_filename(license_version)).exists(): critical_error_message_box( translate('BiblesPlugin.ImportWizardForm', 'Bible Exists'), translate('BiblesPlugin.ImportWizardForm', 'This Bible already exists. Please import ' @@ -629,57 +594,6 @@ class BibleImportForm(OpenLPWizard): bibles.sort(key=get_locale_key) self.web_translation_combo_box.addItems(bibles) - def on_osis_browse_button_clicked(self): - """ - Show the file open dialog for the OSIS file. - """ - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.OSIS, self.osis_file_edit, - 'last directory import') - - def on_csv_books_browse_button_clicked(self): - """ - Show the file open dialog for the books CSV file. - """ - # TODO: Verify format() with varible template - self.get_file_name( - WizardStrings.OpenTypeFile % WizardStrings.CSV, - self.csv_books_edit, - 'last directory import', - '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File'))) - - def on_csv_verses_browse_button_clicked(self): - """ - Show the file open dialog for the verses CSV file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.CSV, self.csv_verses_edit, - 'last directory import', - '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File'))) - - def on_open_song_browse_button_clicked(self): - """ - Show the file open dialog for the OpenSong file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.OS, self.open_song_file_edit, - 'last directory import') - - def on_zefania_browse_button_clicked(self): - """ - Show the file open dialog for the Zefania file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit, - 'last directory import') - - def on_wordproject_browse_button_clicked(self): - """ - Show the file open dialog for the WordProject file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.WordProject, self.wordproject_file_edit, - 'last directory import') - def on_web_update_button_clicked(self): """ Download list of bibles from Crosswalk, BibleServer and BibleGateway. @@ -716,15 +630,13 @@ class BibleImportForm(OpenLPWizard): self.web_update_button.setEnabled(True) self.web_progress_bar.setVisible(False) - def on_sword_browse_button_clicked(self): + def on_sword_folder_path_edit_path_changed(self, new_path): """ Show the file open dialog for the SWORD folder. """ - self.get_folder(WizardStrings.OpenTypeFolder % WizardStrings.SWORD, self.sword_folder_edit, - 'last directory import') - if self.sword_folder_edit.text(): + if new_path: try: - self.pysword_folder_modules = modules.SwordModules(self.sword_folder_edit.text()) + self.pysword_folder_modules = modules.SwordModules(str(new_path)) self.pysword_folder_modules_json = self.pysword_folder_modules.parse_modules() bible_keys = self.pysword_folder_modules_json.keys() self.sword_bible_combo_box.clear() @@ -733,15 +645,13 @@ class BibleImportForm(OpenLPWizard): except: self.sword_bible_combo_box.clear() - def on_sword_zipbrowse_button_clicked(self): + def on_sword_zipfile_path_edit_path_changed(self, new_path): """ Show the file open dialog for a SWORD zip-file. """ - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.SWORD, self.sword_zipfile_edit, - 'last directory import') - if self.sword_zipfile_edit.text(): + if new_path: try: - self.pysword_zip_modules = modules.SwordModules(self.sword_zipfile_edit.text()) + self.pysword_zip_modules = modules.SwordModules(str(new_path)) self.pysword_zip_modules_json = self.pysword_zip_modules.parse_modules() bible_keys = self.pysword_zip_modules_json.keys() self.sword_zipbible_combo_box.clear() @@ -755,16 +665,16 @@ class BibleImportForm(OpenLPWizard): Register the bible import wizard fields. """ self.select_page.registerField('source_format', self.format_combo_box) - self.select_page.registerField('osis_location', self.osis_file_edit) - self.select_page.registerField('csv_booksfile', self.csv_books_edit) - self.select_page.registerField('csv_versefile', self.csv_verses_edit) - self.select_page.registerField('opensong_file', self.open_song_file_edit) - self.select_page.registerField('zefania_file', self.zefania_file_edit) - self.select_page.registerField('wordproject_file', self.wordproject_file_edit) + self.select_page.registerField('osis_location', self.osis_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('csv_booksfile', self.csv_books_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('csv_versefile', self.csv_verses_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('opensong_file', self.open_song_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('zefania_file', self.zefania_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('wordproject_file', self.wordproject_path_edit, 'path', PathEdit.pathChanged) self.select_page.registerField('web_location', self.web_source_combo_box) self.select_page.registerField('web_biblename', self.web_translation_combo_box) - self.select_page.registerField('sword_folder_path', self.sword_folder_edit) - self.select_page.registerField('sword_zip_path', self.sword_zipfile_edit) + self.select_page.registerField('sword_folder_path', self.sword_folder_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('sword_zip_path', self.sword_zipfile_path_edit, 'path', PathEdit.pathChanged) self.select_page.registerField('proxy_server', self.web_server_edit) self.select_page.registerField('proxy_username', self.web_user_edit) self.select_page.registerField('proxy_password', self.web_password_edit) @@ -783,13 +693,13 @@ class BibleImportForm(OpenLPWizard): self.finish_button.setVisible(False) self.cancel_button.setVisible(True) self.setField('source_format', 0) - self.setField('osis_location', '') - self.setField('csv_booksfile', '') - self.setField('csv_versefile', '') - self.setField('opensong_file', '') - self.setField('zefania_file', '') - self.setField('sword_folder_path', '') - self.setField('sword_zip_path', '') + self.setField('osis_location', None) + self.setField('csv_booksfile', None) + self.setField('csv_versefile', None) + self.setField('opensong_file', None) + self.setField('zefania_file', None) + self.setField('sword_folder_path', None) + self.setField('sword_zip_path', None) self.setField('web_location', WebDownload.Crosswalk) self.setField('web_biblename', self.web_translation_combo_box.currentIndex()) self.setField('proxy_server', settings.value('proxy address')) @@ -831,16 +741,16 @@ class BibleImportForm(OpenLPWizard): if bible_type == BibleFormat.OSIS: # Import an OSIS bible. importer = self.manager.import_bible(BibleFormat.OSIS, name=license_version, - filename=self.field('osis_location')) + file_path=self.field('osis_location')) elif bible_type == BibleFormat.CSV: # Import a CSV bible. importer = self.manager.import_bible(BibleFormat.CSV, name=license_version, - booksfile=self.field('csv_booksfile'), - versefile=self.field('csv_versefile')) + books_path=self.field('csv_booksfile'), + verse_path=self.field('csv_versefile')) elif bible_type == BibleFormat.OpenSong: # Import an OpenSong bible. importer = self.manager.import_bible(BibleFormat.OpenSong, name=license_version, - filename=self.field('opensong_file')) + file_path=self.field('opensong_file')) elif bible_type == BibleFormat.WebDownload: # Import a bible from the web. self.progress_bar.setMaximum(1) @@ -859,11 +769,11 @@ class BibleImportForm(OpenLPWizard): elif bible_type == BibleFormat.Zefania: # Import a Zefania bible. importer = self.manager.import_bible(BibleFormat.Zefania, name=license_version, - filename=self.field('zefania_file')) + file_path=self.field('zefania_file')) elif bible_type == BibleFormat.WordProject: # Import a WordProject bible. importer = self.manager.import_bible(BibleFormat.WordProject, name=license_version, - filename=self.field('wordproject_file')) + file_path=self.field('wordproject_file')) elif bible_type == BibleFormat.SWORD: # Import a SWORD bible. if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab): diff --git a/openlp/plugins/bibles/lib/bibleimport.py b/openlp/plugins/bibles/lib/bibleimport.py index 00c7262a0..9fd6d937e 100644 --- a/openlp/plugins/bibles/lib/bibleimport.py +++ b/openlp/plugins/bibles/lib/bibleimport.py @@ -35,23 +35,23 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.filename = kwargs['filename'] if 'filename' in kwargs else None + self.file_path = kwargs.get('file_path') self.wizard = None self.stop_import_flag = False Registry().register_function('openlp_stop_wizard', self.stop_import) @staticmethod - def is_compressed(file): + def is_compressed(file_path): """ Check if the supplied file is compressed - :param file: A path to the file to check + :param file_path: A path to the file to check """ - if is_zipfile(file): + if is_zipfile(str(file_path)): critical_error_message_box( message=translate('BiblesPlugin.BibleImport', 'The file "{file}" you supplied is compressed. You must decompress it before import.' - ).format(file=file)) + ).format(file=file_path)) return True return False @@ -139,24 +139,24 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): self.log_debug('No book name supplied. Falling back to guess_id') book_ref_id = guess_id if not book_ref_id: - raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.filename)) + raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.file_path)) book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) if book_details is None: raise ValidationError(msg='book_ref_id: {book_ref} Could not be found in the BibleResourcesDB while ' - 'importing {file}'.format(book_ref=book_ref_id, file=self.filename)) + 'importing {file}'.format(book_ref=book_ref_id, file=self.file_path)) return self.create_book(name, book_ref_id, book_details['testament_id']) - def parse_xml(self, filename, use_objectify=False, elements=None, tags=None): + def parse_xml(self, file_path, use_objectify=False, elements=None, tags=None): """ Parse and clean the supplied file by removing any elements or tags we don't use. - :param filename: The filename of the xml file to parse. Str + :param file_path: The filename of the xml file to parse. Str :param use_objectify: Use the objectify parser rather than the etree parser. (Bool) :param elements: A tuple of element names (Str) to remove along with their content. :param tags: A tuple of element names (Str) to remove, preserving their content. :return: The root element of the xml document """ try: - with open(filename, 'rb') as import_file: + with file_path.open('rb') as import_file: # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own # encoding detection, and the two mechanisms together interfere with each other. if not use_objectify: @@ -203,17 +203,17 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): self.log_debug('Stopping import') self.stop_import_flag = True - def validate_xml_file(self, filename, tag): + def validate_xml_file(self, file_path, tag): """ Validate the supplied file - :param filename: The supplied file + :param file_path: The supplied file :param tag: The expected root tag type :return: True if valid. ValidationError is raised otherwise. """ - if BibleImport.is_compressed(filename): + if BibleImport.is_compressed(file_path): raise ValidationError(msg='Compressed file') - bible = self.parse_xml(filename, use_objectify=True) + bible = self.parse_xml(file_path, use_objectify=True) if bible is None: raise ValidationError(msg='Error when opening file') root_tag = bible.tag.lower() diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 1420dcc33..59f8655eb 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -39,11 +39,11 @@ class BiblesTab(SettingsTab): """ log.info('Bible Tab loaded') - def _init_(self, parent, title, visible_title, icon_path): + def _init_(self, *args, **kwargs): self.paragraph_style = True self.show_new_chapters = False self.display_style = 0 - super(BiblesTab, self).__init__(parent, title, visible_title, icon_path) + super().__init__(*args, **kwargs) def setupUi(self): self.setObjectName('BiblesTab') diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 64744113b..7487ab2a9 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -34,6 +34,7 @@ from sqlalchemy.orm import class_mapper, mapper, relation from sqlalchemy.orm.exc import UnmappedClassError from openlp.core.common import AppLocation, translate, clean_filename +from openlp.core.common.path import Path from openlp.core.lib.db import BaseModel, init_db, Manager from openlp.core.lib.ui import critical_error_message_box from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade @@ -128,10 +129,15 @@ class BibleDB(Manager): :param parent: :param kwargs: ``path`` - The path to the bible database file. + The path to the bible database file. Type: openlp.core.common.path.Path ``name`` The name of the database. This is also used as the file name for SQLite databases. + + ``file`` + Type: openlp.core.common.path.Path + + :rtype: None """ log.info('BibleDB loaded') self._setup(parent, **kwargs) @@ -144,20 +150,20 @@ class BibleDB(Manager): self.session = None if 'path' not in kwargs: raise KeyError('Missing keyword argument "path".') + self.path = kwargs['path'] if 'name' not in kwargs and 'file' not in kwargs: raise KeyError('Missing keyword argument "name" or "file".') if 'name' in kwargs: self.name = kwargs['name'] if not isinstance(self.name, str): self.name = str(self.name, 'utf-8') - self.file = clean_filename(self.name) + '.sqlite' + # TODO: To path object + file_path = Path(clean_filename(self.name) + '.sqlite') if 'file' in kwargs: - self.file = kwargs['file'] - Manager.__init__(self, 'bibles', init_schema, self.file, upgrade) + file_path = kwargs['file'] + Manager.__init__(self, 'bibles', init_schema, file_path, upgrade) if self.session and 'file' in kwargs: self.get_name() - if 'path' in kwargs: - self.path = kwargs['path'] self._is_web_bible = None def get_name(self): @@ -470,9 +476,9 @@ class BiblesResourcesDB(QtCore.QObject, Manager): Return the cursor object. Instantiate one if it doesn't exist yet. """ if BiblesResourcesDB.cursor is None: - file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)), - 'bibles', 'resources', 'bibles_resources.sqlite') - conn = sqlite3.connect(file_path) + file_path = \ + AppLocation.get_directory(AppLocation.PluginsDir) / 'bibles' / 'resources' / 'bibles_resources.sqlite' + conn = sqlite3.connect(str(file_path)) BiblesResourcesDB.cursor = conn.cursor() return BiblesResourcesDB.cursor @@ -758,17 +764,13 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): If necessary loads up the database and creates the tables if the database doesn't exist. """ if AlternativeBookNamesDB.cursor is None: - file_path = os.path.join( - str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite') - if not os.path.exists(file_path): + file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite' + AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path)) + if not file_path.exists(): # create new DB, create table alternative_book_names - AlternativeBookNamesDB.conn = sqlite3.connect(file_path) AlternativeBookNamesDB.conn.execute( 'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, ' 'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))') - else: - # use existing DB - AlternativeBookNamesDB.conn = sqlite3.connect(file_path) AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor() return AlternativeBookNamesDB.cursor diff --git a/openlp/plugins/bibles/lib/importers/csvbible.py b/openlp/plugins/bibles/lib/importers/csvbible.py index e319bdf36..42b1dd51d 100644 --- a/openlp/plugins/bibles/lib/importers/csvbible.py +++ b/openlp/plugins/bibles/lib/importers/csvbible.py @@ -73,8 +73,8 @@ class CSVBible(BibleImport): """ super().__init__(*args, **kwargs) self.log_info(self.__class__.__name__) - self.books_file = kwargs['booksfile'] - self.verses_file = kwargs['versefile'] + self.books_path = kwargs['books_path'] + self.verses_path = kwargs['verse_path'] @staticmethod def get_book_name(name, books): @@ -92,21 +92,22 @@ class CSVBible(BibleImport): return book_name @staticmethod - def parse_csv_file(filename, results_tuple): + def parse_csv_file(file_path, results_tuple): """ Parse the supplied CSV file. - :param filename: The name of the file to parse. Str - :param results_tuple: The namedtuple to use to store the results. namedtuple - :return: An iterable yielding namedtuples of type results_tuple + :param openlp.core.common.path.Path file_path: The name of the file to parse. + :param namedtuple results_tuple: The namedtuple to use to store the results. + :return: An list of namedtuples of type results_tuple + :rtype: list[namedtuple] """ try: - encoding = get_file_encoding(Path(filename))['encoding'] - with open(filename, 'r', encoding=encoding, newline='') as csv_file: + encoding = get_file_encoding(file_path)['encoding'] + with file_path.open('r', encoding=encoding, newline='') as csv_file: csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"') return [results_tuple(*line) for line in csv_reader] except (OSError, csv.Error): - raise ValidationError(msg='Parsing "{file}" failed'.format(file=filename)) + raise ValidationError(msg='Parsing "{file}" failed'.format(file=file_path)) def process_books(self, books): """ @@ -159,12 +160,12 @@ class CSVBible(BibleImport): self.language_id = self.get_language(bible_name) if not self.language_id: return False - books = self.parse_csv_file(self.books_file, Book) + books = self.parse_csv_file(self.books_path, Book) self.wizard.progress_bar.setValue(0) self.wizard.progress_bar.setMinimum(0) self.wizard.progress_bar.setMaximum(len(books)) book_list = self.process_books(books) - verses = self.parse_csv_file(self.verses_file, Verse) + verses = self.parse_csv_file(self.verses_path, Verse) self.wizard.progress_bar.setValue(0) self.wizard.progress_bar.setMaximum(len(books) + 1) self.process_verses(verses, book_list) diff --git a/openlp/plugins/bibles/lib/importers/opensong.py b/openlp/plugins/bibles/lib/importers/opensong.py index 2ac72503d..8400c9bf7 100644 --- a/openlp/plugins/bibles/lib/importers/opensong.py +++ b/openlp/plugins/bibles/lib/importers/opensong.py @@ -46,7 +46,8 @@ def parse_chapter_number(number, previous_number): :param number: The raw data from the xml :param previous_number: The previous chapter number - :return: Number of current chapter. (Int) + :return: Number of current chapter. + :rtype: int """ if number: return int(number.split()[-1]) @@ -132,13 +133,13 @@ class OpenSongBible(BibleImport): :param bible_name: The name of the bible being imported :return: True if import completed, False if import was unsuccessful """ - self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.filename)) - self.validate_xml_file(self.filename, 'bible') - bible = self.parse_xml(self.filename, use_objectify=True) + self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.file_path)) + self.validate_xml_file(self.file_path, 'bible') + bible = self.parse_xml(self.file_path, use_objectify=True) if bible is None: return False # No language info in the opensong format, so ask the user - self.language_id = self.get_language_id(bible_name=self.filename) + self.language_id = self.get_language_id(bible_name=str(self.file_path)) if not self.language_id: return False self.process_books(bible.b) diff --git a/openlp/plugins/bibles/lib/importers/osis.py b/openlp/plugins/bibles/lib/importers/osis.py index f10e084bc..cde57954a 100644 --- a/openlp/plugins/bibles/lib/importers/osis.py +++ b/openlp/plugins/bibles/lib/importers/osis.py @@ -159,14 +159,14 @@ class OSISBible(BibleImport): """ Loads a Bible from file. """ - self.log_debug('Starting OSIS import from "{name}"'.format(name=self.filename)) - self.validate_xml_file(self.filename, '{http://www.bibletechnologies.net/2003/osis/namespace}osis') - bible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) + self.log_debug('Starting OSIS import from "{name}"'.format(name=self.file_path)) + self.validate_xml_file(self.file_path, '{http://www.bibletechnologies.net/2003/osis/namespace}osis') + bible = self.parse_xml(self.file_path, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) if bible is None: return False # Find bible language language = bible.xpath("//ns:osisText/@xml:lang", namespaces=NS) - self.language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename) + self.language_id = self.get_language_id(language[0] if language else None, bible_name=str(self.file_path)) if not self.language_id: return False self.process_books(bible) diff --git a/openlp/plugins/bibles/lib/importers/sword.py b/openlp/plugins/bibles/lib/importers/sword.py index e01783087..c43208ae5 100644 --- a/openlp/plugins/bibles/lib/importers/sword.py +++ b/openlp/plugins/bibles/lib/importers/sword.py @@ -60,7 +60,7 @@ class SwordBible(BibleImport): bible = pysword_modules.get_bible_from_module(self.sword_key) language = pysword_module_json['lang'] language = language[language.find('.') + 1:] - language_id = self.get_language_id(language, bible_name=self.filename) + language_id = self.get_language_id(language, bible_name=str(self.file_path)) if not language_id: return False books = bible.get_structure().get_books() diff --git a/openlp/plugins/bibles/lib/importers/wordproject.py b/openlp/plugins/bibles/lib/importers/wordproject.py index bf5c5fc75..90d23a2b5 100644 --- a/openlp/plugins/bibles/lib/importers/wordproject.py +++ b/openlp/plugins/bibles/lib/importers/wordproject.py @@ -19,15 +19,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os -import re import logging -from codecs import open as copen +import re from tempfile import TemporaryDirectory from zipfile import ZipFile from bs4 import BeautifulSoup, Tag, NavigableString +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.bibleimport import BibleImport BOOK_NUMBER_PATTERN = re.compile(r'\[(\d+)\]') @@ -51,9 +50,9 @@ class WordProjectBible(BibleImport): Unzip the file to a temporary directory """ self.tmp = TemporaryDirectory() - zip_file = ZipFile(os.path.abspath(self.filename)) - zip_file.extractall(self.tmp.name) - self.base_dir = os.path.join(self.tmp.name, os.path.splitext(os.path.basename(self.filename))[0]) + with ZipFile(str(self.file_path)) as zip_file: + zip_file.extractall(self.tmp.name) + self.base_path = Path(self.tmp.name, self.file_path.stem) def process_books(self): """ @@ -62,8 +61,7 @@ class WordProjectBible(BibleImport): :param bible_data: parsed xml :return: None """ - with copen(os.path.join(self.base_dir, 'index.htm'), encoding='utf-8', errors='ignore') as index_file: - page = index_file.read() + page = (self.base_path / 'index.htm').read_text(encoding='utf-8', errors='ignore') soup = BeautifulSoup(page, 'lxml') bible_books = soup.find('div', 'textOptions').find_all('li') book_count = len(bible_books) @@ -93,9 +91,7 @@ class WordProjectBible(BibleImport): :return: None """ log.debug(book_link) - book_file = os.path.join(self.base_dir, os.path.normpath(book_link)) - with copen(book_file, encoding='utf-8', errors='ignore') as f: - page = f.read() + page = (self.base_path / book_link).read_text(encoding='utf-8', errors='ignore') soup = BeautifulSoup(page, 'lxml') header_div = soup.find('div', 'textHeader') chapters_p = header_div.find('p') @@ -114,9 +110,8 @@ class WordProjectBible(BibleImport): """ Get the verses for a particular book """ - chapter_file_name = os.path.join(self.base_dir, '{:02d}'.format(book_number), '{}.htm'.format(chapter_number)) - with copen(chapter_file_name, encoding='utf-8', errors='ignore') as chapter_file: - page = chapter_file.read() + chapter_file_path = self.base_path / '{:02d}'.format(book_number) / '{}.htm'.format(chapter_number) + page = chapter_file_path.read_text(encoding='utf-8', errors='ignore') soup = BeautifulSoup(page, 'lxml') text_body = soup.find('div', 'textBody') if text_body: @@ -158,9 +153,9 @@ class WordProjectBible(BibleImport): """ Loads a Bible from file. """ - self.log_debug('Starting WordProject import from "{name}"'.format(name=self.filename)) + self.log_debug('Starting WordProject import from "{name}"'.format(name=self.file_path)) self._unzip_file() - self.language_id = self.get_language_id(None, bible_name=self.filename) + self.language_id = self.get_language_id(None, bible_name=str(self.file_path)) result = False if self.language_id: self.process_books() diff --git a/openlp/plugins/bibles/lib/importers/zefania.py b/openlp/plugins/bibles/lib/importers/zefania.py index 221d57cb3..be32d08ed 100644 --- a/openlp/plugins/bibles/lib/importers/zefania.py +++ b/openlp/plugins/bibles/lib/importers/zefania.py @@ -45,13 +45,13 @@ class ZefaniaBible(BibleImport): """ Loads a Bible from file. """ - log.debug('Starting Zefania import from "{name}"'.format(name=self.filename)) + log.debug('Starting Zefania import from "{name}"'.format(name=self.file_path)) success = True try: - xmlbible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) + xmlbible = self.parse_xml(self.file_path, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) # Find bible language language = xmlbible.xpath("/XMLBIBLE/INFORMATION/language/text()") - language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename) + language_id = self.get_language_id(language[0] if language else None, bible_name=str(self.file_path)) if not language_id: return False no_of_books = int(xmlbible.xpath('count(//BIBLEBOOK)')) @@ -69,7 +69,7 @@ class ZefaniaBible(BibleImport): log.debug('Could not find a name, will use number, basically a guess.') book_ref_id = int(bnumber) if not book_ref_id: - log.error('Importing books from "{name}" failed'.format(name=self.filename)) + log.error('Importing books from "{name}" failed'.format(name=self.file_path)) return False book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id']) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 5c8728e58..06f26c016 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -19,7 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - import logging from openlp.core.common import AppLocation, OpenLPMixin, RegistryProperties, Settings, translate, delete_file, UiStrings @@ -111,7 +110,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): self.settings_section = 'bibles' self.web = 'Web' self.db_cache = None - self.path = str(AppLocation.get_section_data_path(self.settings_section)) + self.path = AppLocation.get_section_data_path(self.settings_section) self.proxy_name = Settings().value(self.settings_section + '/proxy name') self.suffix = '.sqlite' self.import_wizard = None @@ -124,20 +123,20 @@ class BibleManager(OpenLPMixin, RegistryProperties): of HTTPBible is loaded instead of the BibleDB class. """ log.debug('Reload bibles') - files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)] - if 'alternative_book_names.sqlite' in files: - files.remove('alternative_book_names.sqlite') - log.debug('Bible Files {text}'.format(text=files)) + file_paths = AppLocation.get_files(self.settings_section, self.suffix) + if Path('alternative_book_names.sqlite') in file_paths: + file_paths.remove(Path('alternative_book_names.sqlite')) + log.debug('Bible Files {text}'.format(text=file_paths)) self.db_cache = {} - for filename in files: - bible = BibleDB(self.parent, path=self.path, file=filename) + for file_path in file_paths: + bible = BibleDB(self.parent, path=self.path, file=file_path) if not bible.session: continue name = bible.get_name() # Remove corrupted files. if name is None: bible.session.close_all() - delete_file(Path(self.path, filename)) + delete_file(self.path / file_path) continue log.debug('Bible Name: "{name}"'.format(name=name)) self.db_cache[name] = bible @@ -146,7 +145,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): source = self.db_cache[name].get_object(BibleMeta, 'download_source') download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value meta_proxy = self.db_cache[name].get_object(BibleMeta, 'proxy_server') - web_bible = HTTPBible(self.parent, path=self.path, file=filename, download_source=source.value, + web_bible = HTTPBible(self.parent, path=self.path, file=file_path, download_source=source.value, download_name=download_name) if meta_proxy: web_bible.proxy_server = meta_proxy.value diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py index b28ab0dfa..56f44e533 100644 --- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py +++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py @@ -30,6 +30,7 @@ from lxml import etree, objectify from PyQt5.QtWidgets import QDialog from openlp.core.common.languages import Language +from openlp.core.common.path import Path from openlp.core.lib.exceptions import ValidationError from openlp.plugins.bibles.lib.bibleimport import BibleImport from openlp.plugins.bibles.lib.db import BibleDB @@ -48,7 +49,7 @@ class TestBibleImport(TestCase): b' Testdatatodiscard\n' b'' ) - self.open_patcher = patch('builtins.open') + self.open_patcher = patch.object(Path, 'open') self.addCleanup(self.open_patcher.stop) self.mocked_open = self.open_patcher.start() self.critical_error_message_box_patcher = \ @@ -75,7 +76,7 @@ class TestBibleImport(TestCase): instance = BibleImport(MagicMock()) # THEN: The filename attribute should be None - self.assertIsNone(instance.filename) + self.assertIsNone(instance.file_path) self.assertIsInstance(instance, BibleDB) def init_kwargs_set_test(self): @@ -84,11 +85,11 @@ class TestBibleImport(TestCase): """ # GIVEN: A patched BibleDB._setup, BibleImport class and mocked parent # WHEN: Creating an instance of BibleImport with selected key word arguments - kwargs = {'filename': 'bible.xml'} + kwargs = {'file_path': 'bible.xml'} instance = BibleImport(MagicMock(), **kwargs) # THEN: The filename keyword should be set to bible.xml - self.assertEqual(instance.filename, 'bible.xml') + self.assertEqual(instance.file_path, 'bible.xml') self.assertIsInstance(instance, BibleDB) def get_language_canceled_test(self): @@ -359,7 +360,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml - result = instance.parse_xml('file.tst') + result = instance.parse_xml(Path('file.tst')) # THEN: The result returned should contain the correct data, and should be an instance of eetree_Element self.assertEqual(etree.tostring(result), @@ -377,7 +378,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml - result = instance.parse_xml('file.tst', use_objectify=True) + result = instance.parse_xml(Path('file.tst'), use_objectify=True) # THEN: The result returned should contain the correct data, and should be an instance of ObjectifiedElement self.assertEqual(etree.tostring(result), @@ -396,7 +397,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml, with a test file - result = instance.parse_xml('file.tst', elements=elements) + result = instance.parse_xml(Path('file.tst'), elements=elements) # THEN: The result returned should contain the correct data self.assertEqual(etree.tostring(result), @@ -413,7 +414,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml, with a test file - result = instance.parse_xml('file.tst', tags=tags) + result = instance.parse_xml(Path('file.tst'), tags=tags) # THEN: The result returned should contain the correct data self.assertEqual(etree.tostring(result), b'\n Testdatatokeep\n Test' @@ -431,7 +432,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml, with a test file - result = instance.parse_xml('file.tst', elements=elements, tags=tags) + result = instance.parse_xml(Path('file.tst'), elements=elements, tags=tags) # THEN: The result returned should contain the correct data self.assertEqual(etree.tostring(result), b'\n Testdatatokeep\n \n') @@ -446,10 +447,10 @@ class TestBibleImport(TestCase): exception.filename = 'file.tst' exception.strerror = 'No such file or directory' self.mocked_open.side_effect = exception - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_xml - result = importer.parse_xml('file.tst') + result = importer.parse_xml(Path('file.tst')) # THEN: parse_xml should have caught the error, informed the user and returned None mocked_log_exception.assert_called_once_with('Opening file.tst failed.') @@ -468,10 +469,10 @@ class TestBibleImport(TestCase): exception.filename = 'file.tst' exception.strerror = 'Permission denied' self.mocked_open.side_effect = exception - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_xml - result = importer.parse_xml('file.tst') + result = importer.parse_xml(Path('file.tst')) # THEN: parse_xml should have caught the error, informed the user and returned None mocked_log_exception.assert_called_once_with('Opening file.tst failed.') @@ -485,7 +486,7 @@ class TestBibleImport(TestCase): Test set_current_chapter """ # GIVEN: An instance of BibleImport and a mocked wizard - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) importer.wizard = MagicMock() # WHEN: Calling set_current_chapter @@ -500,7 +501,7 @@ class TestBibleImport(TestCase): """ # GIVEN: A mocked parse_xml which returns None with patch.object(BibleImport, 'is_compressed', return_value=True): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling is_compressed # THEN: ValidationError should be raised, with the message 'Compressed file' @@ -515,7 +516,7 @@ class TestBibleImport(TestCase): # GIVEN: A mocked parse_xml which returns None with patch.object(BibleImport, 'parse_xml', return_value=None), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, with the message 'Error when opening file' @@ -531,7 +532,7 @@ class TestBibleImport(TestCase): # GIVEN: Some test data with an OpenSong Bible "bible" root tag with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file result = importer.validate_xml_file('file.name', 'bible') @@ -546,7 +547,7 @@ class TestBibleImport(TestCase): # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing @@ -565,7 +566,7 @@ class TestBibleImport(TestCase): with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring( '')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing @@ -583,7 +584,7 @@ class TestBibleImport(TestCase): # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing @@ -602,7 +603,7 @@ class TestBibleImport(TestCase): with patch.object( BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing diff --git a/tests/functional/openlp_plugins/bibles/test_csvimport.py b/tests/functional/openlp_plugins/bibles/test_csvimport.py index 63d3d5282..0ff9a7dcf 100644 --- a/tests/functional/openlp_plugins/bibles/test_csvimport.py +++ b/tests/functional/openlp_plugins/bibles/test_csvimport.py @@ -29,6 +29,7 @@ from collections import namedtuple from unittest import TestCase from unittest.mock import ANY, MagicMock, PropertyMock, call, patch +from openlp.core.common.path import Path from openlp.core.lib.exceptions import ValidationError from openlp.plugins.bibles.lib.bibleimport import BibleImport from openlp.plugins.bibles.lib.importers.csvbible import Book, CSVBible, Verse @@ -59,12 +60,13 @@ class TestCSVImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = \ + CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), verse_path=Path('verse.csv')) # THEN: The importer should be an instance of BibleImport self.assertIsInstance(importer, BibleImport) - self.assertEqual(importer.books_file, 'books.csv') - self.assertEqual(importer.verses_file, 'verse.csv') + self.assertEqual(importer.books_path, Path('books.csv')) + self.assertEqual(importer.verses_path, Path('verse.csv')) def book_namedtuple_test(self): """ @@ -134,17 +136,17 @@ class TestCSVImport(TestCase): with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding', return_value={'encoding': 'utf-8', 'confidence': 0.99}),\ - patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True) as mocked_open,\ + patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', create=True) as mocked_open,\ patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader', return_value=iter(test_data)) as mocked_reader: # WHEN: Calling the CSVBible parse_csv_file method with a file name and TestTuple - result = CSVBible.parse_csv_file('file.csv', TestTuple) + result = CSVBible.parse_csv_file(Path('file.csv'), TestTuple) # THEN: A list of TestTuple instances with the parsed data should be returned self.assertEqual(result, [TestTuple('1', 'Line 1', 'Data 1'), TestTuple('2', 'Line 2', 'Data 2'), TestTuple('3', 'Line 3', 'Data 3')]) - mocked_open.assert_called_once_with('file.csv', 'r', encoding='utf-8', newline='') + mocked_open.assert_called_once_with('r', encoding='utf-8', newline='') mocked_reader.assert_called_once_with(ANY, delimiter=',', quotechar='"') def parse_csv_file_oserror_test(self): @@ -154,12 +156,12 @@ class TestCSVImport(TestCase): # GIVEN: Mocked a mocked open object which raises an OSError with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding', return_value={'encoding': 'utf-8', 'confidence': 0.99}),\ - patch('openlp.plugins.bibles.lib.importers.csvbible.open', side_effect=OSError, create=True): + patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', side_effect=OSError, create=True): # WHEN: Calling CSVBible.parse_csv_file # THEN: A ValidationError should be raised with self.assertRaises(ValidationError) as context: - CSVBible.parse_csv_file('file.csv', None) + CSVBible.parse_csv_file(Path('file.csv'), None) self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed') def parse_csv_file_csverror_test(self): @@ -169,13 +171,13 @@ class TestCSVImport(TestCase): # GIVEN: Mocked a csv.reader which raises an csv.Error with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding', return_value={'encoding': 'utf-8', 'confidence': 0.99}),\ - patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True),\ + patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', create=True),\ patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader', side_effect=csv.Error): # WHEN: Calling CSVBible.parse_csv_file # THEN: A ValidationError should be raised with self.assertRaises(ValidationError) as context: - CSVBible.parse_csv_file('file.csv', None) + CSVBible.parse_csv_file(Path('file.csv'), None) self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed') def process_books_stopped_import_test(self): @@ -185,7 +187,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible with the stop_import_flag set to True mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) type(importer).application = PropertyMock() importer.stop_import_flag = True importer.wizard = MagicMock() @@ -205,7 +208,8 @@ class TestCSVImport(TestCase): mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\ patch('openlp.plugins.bibles.lib.importers.csvbible.translate'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.find_and_create_book = MagicMock() importer.language_id = 10 importer.stop_import_flag = False @@ -229,7 +233,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible with the stop_import_flag set to True mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.get_book_name = MagicMock() importer.session = MagicMock() importer.stop_import_flag = True @@ -250,7 +255,8 @@ class TestCSVImport(TestCase): mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\ patch('openlp.plugins.bibles.lib.importers.csvbible.translate'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.create_verse = MagicMock() importer.get_book = MagicMock(return_value=Book('1', '1', '1. Mosebog', '1Mos')) importer.get_book_name = MagicMock(return_value='1. Mosebog') @@ -281,7 +287,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible and a mocked get_language which simulates the user cancelling the language box mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.get_language = MagicMock(return_value=None) # WHEN: Calling do_import @@ -298,7 +305,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verses.csv')) importer.get_language = MagicMock(return_value=10) importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']]) importer.process_books = MagicMock(return_value=['Book 1']) @@ -312,7 +320,8 @@ class TestCSVImport(TestCase): # THEN: parse_csv_file should be called twice, # and True should be returned. - self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)]) + self.assertEqual(importer.parse_csv_file.mock_calls, + [call(Path('books.csv'), Book), call(Path('verses.csv'), Verse)]) importer.process_books.assert_called_once_with(['Book 1']) importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1']) self.assertTrue(result) @@ -325,12 +334,12 @@ class TestCSVImport(TestCase): # get_book_ref_id_by_name, create_verse, create_book, session and get_language. result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb') test_data = json.loads(result_file.read().decode()) - books_file = os.path.join(TEST_PATH, 'dk1933-books.csv') - verses_file = os.path.join(TEST_PATH, 'dk1933-verses.csv') + books_file = Path(TEST_PATH, 'dk1933-books.csv') + verses_file = Path(TEST_PATH, 'dk1933-verses.csv') with patch('openlp.plugins.bibles.lib.importers.csvbible.CSVBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = CSVBible(mocked_manager, path='.', name='.', booksfile=books_file, versefile=verses_file) + importer = CSVBible(mocked_manager, path='.', name='.', books_path=books_file, verse_path=verses_file) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() diff --git a/tests/functional/openlp_plugins/bibles/test_opensongimport.py b/tests/functional/openlp_plugins/bibles/test_opensongimport.py index 4842e9c5f..885d1e27a 100644 --- a/tests/functional/openlp_plugins/bibles/test_opensongimport.py +++ b/tests/functional/openlp_plugins/bibles/test_opensongimport.py @@ -30,6 +30,7 @@ from unittest.mock import MagicMock, patch, call from lxml import objectify from openlp.core.common import Registry +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible, get_text, parse_chapter_number from openlp.plugins.bibles.lib.bibleimport import BibleImport @@ -64,7 +65,7 @@ class TestOpenSongImport(TestCase, TestMixin): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = OpenSongBible(mocked_manager, path='.', name='.', filename='') + importer = OpenSongBible(mocked_manager, path='.', name='.', file_path=None) # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleImport) @@ -126,7 +127,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when supplied with a valid verse number """ # GIVEN: An instance of OpenSongBible, the number 15 represented as a string and an instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('15', 0) @@ -139,7 +140,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when supplied with a verse range """ # GIVEN: An instance of OpenSongBible, and the range 24-26 represented as a string - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('24-26', 0) @@ -152,7 +153,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when supplied with a invalid verse number """ # GIVEN: An instance of OpenSongBible, a non numeric string represented as a string - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('invalid', 41) @@ -165,7 +166,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when the verse number is an empty string. (Bug #1074727) """ # GIVEN: An instance of OpenSongBible, an empty string, and the previous verse number set as 14 - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('', 14) @@ -178,7 +179,7 @@ class TestOpenSongImport(TestCase, TestMixin): """ with patch.object(OpenSongBible, 'log_warning')as mocked_log_warning: # GIVEN: An instanceofOpenSongBible, a Tuple, and the previous verse number set as 12 - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number((1, 2, 3), 12) @@ -193,7 +194,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_books when stop_import is set to True """ # GIVEN: An instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: stop_import_flag is set to True importer.stop_import_flag = True @@ -209,7 +210,7 @@ class TestOpenSongImport(TestCase, TestMixin): # GIVEN: An instance of OpenSongBible Importer and two mocked books self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2'] with patch.object(OpenSongBible, 'process_chapters') as mocked_process_chapters: - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) book1 = MagicMock() book1.attrib = {'n': 'Name1'} @@ -236,7 +237,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_chapters when stop_import is set to True """ # GIVEN: An isntance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.parse_chapter_number = MagicMock() # WHEN: stop_import_flag is set to True @@ -252,7 +253,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_chapters when it completes """ # GIVEN: An instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.wizard = MagicMock() # WHEN: called with some valid data @@ -284,7 +285,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_verses when stop_import is set to True """ # GIVEN: An isntance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.parse_verse_number = MagicMock() # WHEN: stop_import_flag is set to True @@ -303,7 +304,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'parse_verse_number', **{'side_effect': [1, 2]}) as mocked_parse_verse_number: # GIVEN: An instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.wizard = MagicMock() # WHEN: called with some valid data @@ -338,7 +339,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'validate_xml_file'), \ patch.object(OpenSongBible, 'parse_xml', return_value=None), \ patch.object(OpenSongBible, 'get_language_id') as mocked_language_id: - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -357,7 +358,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'parse_xml'), \ patch.object(OpenSongBible, 'get_language_id', return_value=False), \ patch.object(OpenSongBible, 'process_books') as mocked_process_books: - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -376,7 +377,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'parse_xml'), \ patch.object(OpenSongBible, 'get_language_id', return_value=10), \ patch.object(OpenSongBible, 'process_books'): - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -406,7 +407,7 @@ class TestOpenSongImportFileImports(TestCase, TestMixin): with patch('openlp.plugins.bibles.lib.importers.opensong.OpenSongBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OpenSongBible(mocked_manager, path='.', name='.', filename='') + importer = OpenSongBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -416,7 +417,7 @@ class TestOpenSongImportFileImports(TestCase, TestMixin): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. diff --git a/tests/functional/openlp_plugins/bibles/test_osisimport.py b/tests/functional/openlp_plugins/bibles/test_osisimport.py index 5be6c459c..de8a3c384 100644 --- a/tests/functional/openlp_plugins/bibles/test_osisimport.py +++ b/tests/functional/openlp_plugins/bibles/test_osisimport.py @@ -27,6 +27,7 @@ import json from unittest import TestCase from unittest.mock import MagicMock, call, patch +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.bibleimport import BibleImport from openlp.plugins.bibles.lib.db import BibleDB from openlp.plugins.bibles.lib.importers.osis import OSISBible @@ -63,7 +64,7 @@ class TestOsisImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) @@ -73,7 +74,7 @@ class TestOsisImport(TestCase): Test process_books when stop_import is set to True """ # GIVEN: An instance of OSISBible adn some mocked data - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) mocked_data = MagicMock(**{'xpath.return_value': ['Book']}) # WHEN: stop_import_flag is set to True and process_books is called @@ -90,7 +91,7 @@ class TestOsisImport(TestCase): # GIVEN: An instance of OSISBible Importer and two mocked books self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2'] with patch.object(OSISBible, 'process_chapters') as mocked_process_chapters: - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) book1 = MagicMock() book1.get.return_value = 'Name1' @@ -128,7 +129,7 @@ class TestOsisImport(TestCase): test_chapter = MagicMock() test_chapter.__iter__.return_value = [test_verse] test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_chapters importer.process_chapters(test_book, [test_chapter]) @@ -155,7 +156,7 @@ class TestOsisImport(TestCase): test_chapter = MagicMock() test_chapter.__iter__.return_value = [test_verse] test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_chapters importer.process_chapters(test_book, [test_chapter]) @@ -180,7 +181,7 @@ class TestOsisImport(TestCase): test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4'}.get(x) # WHEN: Calling process_chapters - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) importer.process_chapters(test_book, [test_chapter]) # THEN: neither set_current_chapter or process_verse should have been called @@ -201,7 +202,7 @@ class TestOsisImport(TestCase): test_chapter = MagicMock() test_chapter.tag = '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter' test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_chapters importer.process_chapters(test_book, [test_chapter]) @@ -228,7 +229,7 @@ class TestOsisImport(TestCase): test_verse.text = 'Verse Text' # WHEN: Calling process_chapters - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) importer.process_chapters(test_book, [test_verse]) # THEN: process_verse should have been called with the test data @@ -245,7 +246,7 @@ class TestOsisImport(TestCase): test_verse.get.side_effect = lambda x: {}.get(x) test_verse.tail = 'Verse Text' test_verse.text = None - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -264,7 +265,7 @@ class TestOsisImport(TestCase): test_verse.get.side_effect = lambda x: {}.get(x) test_verse.tail = 'Verse Text' test_verse.text = None - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -282,7 +283,7 @@ class TestOsisImport(TestCase): test_verse.tail = None test_verse.text = None test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse, use_milestones=True) @@ -301,7 +302,7 @@ class TestOsisImport(TestCase): test_verse.tail = 'Verse Text' test_verse.text = None test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse, use_milestones=True) @@ -320,7 +321,7 @@ class TestOsisImport(TestCase): test_verse.tail = '\n ' # Whitespace test_verse.text = None test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -339,7 +340,7 @@ class TestOsisImport(TestCase): test_verse.tail = '\n ' # Whitespace test_verse.text = 'Verse Text' test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -356,7 +357,7 @@ class TestOsisImport(TestCase): patch.object(OSISBible, 'validate_xml_file'), \ patch.object(OSISBible, 'parse_xml', return_value=None), \ patch.object(OSISBible, 'get_language_id') as mocked_language_id: - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -375,7 +376,7 @@ class TestOsisImport(TestCase): patch.object(OSISBible, 'parse_xml'), \ patch.object(OSISBible, 'get_language_id', **{'return_value': False}), \ patch.object(OSISBible, 'process_books') as mocked_process_books: - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -394,7 +395,7 @@ class TestOsisImport(TestCase): patch.object(OSISBible, 'parse_xml'), \ patch.object(OSISBible, 'get_language_id', **{'return_value': 10}), \ patch.object(OSISBible, 'process_books'): - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -427,7 +428,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -437,7 +438,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -457,7 +458,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -467,7 +468,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'English' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -487,7 +488,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -497,7 +498,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'English' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -517,7 +518,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -527,7 +528,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. diff --git a/tests/functional/openlp_plugins/bibles/test_swordimport.py b/tests/functional/openlp_plugins/bibles/test_swordimport.py index 235beea58..34e011498 100644 --- a/tests/functional/openlp_plugins/bibles/test_swordimport.py +++ b/tests/functional/openlp_plugins/bibles/test_swordimport.py @@ -64,7 +64,7 @@ class TestSwordImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='') + importer = SwordBible(mocked_manager, path='.', name='.', file_path=None, sword_key='', sword_path='') # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) @@ -80,7 +80,7 @@ class TestSwordImport(TestCase): # Also mocked pysword structures mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='') + importer = SwordBible(mocked_manager, path='.', name='.', file_path=None, sword_key='', sword_path='') result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb') test_data = json.loads(result_file.read().decode()) importer.wizard = mocked_import_wizard diff --git a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py index 6e62dae9e..fbf5b0412 100644 --- a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py +++ b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py @@ -26,6 +26,7 @@ import os from unittest import TestCase from unittest.mock import MagicMock, patch, call +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.importers.wordproject import WordProjectBible @@ -48,19 +49,17 @@ class TestWordProjectImport(TestCase): self.addCleanup(self.manager_patcher.stop) self.manager_patcher.start() - @patch('openlp.plugins.bibles.lib.importers.wordproject.os') - @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') - def test_process_books(self, mocked_open, mocked_os): + @patch.object(Path, 'read_text') + def test_process_books(self, mocked_read_text): """ Test the process_books() method """ # GIVEN: A WordProject importer and a bunch of mocked things - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') - importer.base_dir = '' + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) + importer.base_path = Path() importer.stop_import_flag = False importer.language_id = 'en' - mocked_open.return_value.__enter__.return_value.read.return_value = INDEX_PAGE - mocked_os.path.join.side_effect = lambda *x: ''.join(x) + mocked_read_text.return_value = INDEX_PAGE # WHEN: process_books() is called with patch.object(importer, 'find_and_create_book') as mocked_find_and_create_book, \ @@ -69,26 +68,22 @@ class TestWordProjectImport(TestCase): importer.process_books() # THEN: The right methods should have been called - mocked_os.path.join.assert_called_once_with('', 'index.htm') - mocked_open.assert_called_once_with('index.htm', encoding='utf-8', errors='ignore') + mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore') assert mocked_find_and_create_book.call_count == 66, 'There should be 66 books' assert mocked_process_chapters.call_count == 66, 'There should be 66 books' assert mocked_session.commit.call_count == 66, 'There should be 66 books' - @patch('openlp.plugins.bibles.lib.importers.wordproject.os') - @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') - def test_process_chapters(self, mocked_open, mocked_os): + @patch.object(Path, 'read_text') + def test_process_chapters(self, mocked_read_text): """ Test the process_chapters() method """ # GIVEN: A WordProject importer and a bunch of mocked things - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') - importer.base_dir = '' + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) + importer.base_path = Path() importer.stop_import_flag = False importer.language_id = 'en' - mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE - mocked_os.path.join.side_effect = lambda *x: ''.join(x) - mocked_os.path.normpath.side_effect = lambda x: x + mocked_read_text.return_value = CHAPTER_PAGE mocked_db_book = MagicMock() mocked_db_book.name = 'Genesis' book_id = 1 @@ -102,24 +97,21 @@ class TestWordProjectImport(TestCase): # THEN: The right methods should have been called expected_set_current_chapter_calls = [call('Genesis', ch) for ch in range(1, 51)] expected_process_verses_calls = [call(mocked_db_book, 1, ch) for ch in range(1, 51)] - mocked_os.path.join.assert_called_once_with('', '01/1.htm') - mocked_open.assert_called_once_with('01/1.htm', encoding='utf-8', errors='ignore') + mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore') assert mocked_set_current_chapter.call_args_list == expected_set_current_chapter_calls assert mocked_process_verses.call_args_list == expected_process_verses_calls - @patch('openlp.plugins.bibles.lib.importers.wordproject.os') - @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') - def test_process_verses(self, mocked_open, mocked_os): + @patch.object(Path, 'read_text') + def test_process_verses(self, mocked_read_text): """ Test the process_verses() method """ # GIVEN: A WordProject importer and a bunch of mocked things - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') - importer.base_dir = '' + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) + importer.base_path = Path() importer.stop_import_flag = False importer.language_id = 'en' - mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE - mocked_os.path.join.side_effect = lambda *x: '/'.join(x) + mocked_read_text.return_value = CHAPTER_PAGE mocked_db_book = MagicMock() mocked_db_book.name = 'Genesis' book_number = 1 @@ -130,8 +122,7 @@ class TestWordProjectImport(TestCase): importer.process_verses(mocked_db_book, book_number, chapter_number) # THEN: All the right methods should have been called - mocked_os.path.join.assert_called_once_with('', '01', '1.htm') - mocked_open.assert_called_once_with('/01/1.htm', encoding='utf-8', errors='ignore') + mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore') assert mocked_process_verse.call_count == 31 def test_process_verse(self): @@ -139,7 +130,7 @@ class TestWordProjectImport(TestCase): Test the process_verse() method """ # GIVEN: An importer and a mocked method - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) mocked_db_book = MagicMock() mocked_db_book.id = 1 chapter_number = 1 @@ -158,7 +149,7 @@ class TestWordProjectImport(TestCase): Test the process_verse() method when there's no text """ # GIVEN: An importer and a mocked method - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) mocked_db_book = MagicMock() mocked_db_book.id = 1 chapter_number = 1 @@ -177,7 +168,7 @@ class TestWordProjectImport(TestCase): Test the do_import() method """ # GIVEN: An importer and mocked methods - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path='kj.zip') # WHEN: do_import() is called with patch.object(importer, '_unzip_file') as mocked_unzip_file, \ @@ -199,7 +190,7 @@ class TestWordProjectImport(TestCase): Test the do_import() method when the language is not available """ # GIVEN: An importer and mocked methods - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path='kj.zip') # WHEN: do_import() is called with patch.object(importer, '_unzip_file') as mocked_unzip_file, \ diff --git a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py index 8e43d55b5..d423a2153 100644 --- a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py +++ b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py @@ -27,6 +27,7 @@ import json from unittest import TestCase from unittest.mock import MagicMock, patch +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.importers.zefania import ZefaniaBible from openlp.plugins.bibles.lib.db import BibleDB @@ -55,7 +56,7 @@ class TestZefaniaImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') + importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None) # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) @@ -72,7 +73,7 @@ class TestZefaniaImport(TestCase): with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') + importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.create_verse = MagicMock() importer.create_book = MagicMock() @@ -81,7 +82,7 @@ class TestZefaniaImport(TestCase): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -102,7 +103,7 @@ class TestZefaniaImport(TestCase): with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') + importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.create_verse = MagicMock() importer.create_book = MagicMock() @@ -111,7 +112,7 @@ class TestZefaniaImport(TestCase): importer.get_language.return_value = 'Russian' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. From eb107b72373dcfefd99c6cfb4a960d4d7a221eb4 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Tue, 10 Oct 2017 22:16:04 +0100 Subject: [PATCH 08/56] test fixes --- openlp/core/ui/lib/pathedit.py | 2 +- .../plugins/bibles/lib/importers/__init__.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 openlp/plugins/bibles/lib/importers/__init__.py diff --git a/openlp/core/ui/lib/pathedit.py b/openlp/core/ui/lib/pathedit.py index 1953e47ab..47a70505d 100644 --- a/openlp/core/ui/lib/pathedit.py +++ b/openlp/core/ui/lib/pathedit.py @@ -192,5 +192,5 @@ class PathEdit(QtWidgets.QWidget): :rtype: None """ if self._path != path: - self.path = path + self._path = path self.pathChanged.emit(path) diff --git a/openlp/plugins/bibles/lib/importers/__init__.py b/openlp/plugins/bibles/lib/importers/__init__.py new file mode 100644 index 000000000..f83bbd595 --- /dev/null +++ b/openlp/plugins/bibles/lib/importers/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`~openlp.plugins.bibles.lib.importers` module contains importers for the Bibles plugin. +""" From 93e0a35489c5443317d37ace58a0135c7ddcb675 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 26 Oct 2017 13:33:22 -0700 Subject: [PATCH 09/56] Update Jenkins script to use python-jenkins --- scripts/jenkins_script.py | 118 ++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 9a0e9a4ec..8ce6af90d 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -31,25 +31,16 @@ You probably want to create an alias. Add this to your ~/.bashrc file and then l You can look up the token in the Branch-01-Pull job configuration or ask in IRC. """ - +import os import re -import sys import time -from optparse import OptionParser +from argparse import ArgumentParser from subprocess import Popen, PIPE -import warnings -from requests.exceptions import HTTPError from jenkins import Jenkins - JENKINS_URL = 'https://ci.openlp.io/' REPO_REGEX = r'(.*/+)(~.*)' -# Allows us to black list token. So when we change the token, we can display a proper message to the user. -OLD_TOKENS = [] - -# Disable the InsecureRequestWarning we get from urllib3, because we're not verifying our own self-signed certificate -warnings.simplefilter('ignore') class OpenLPJobs(object): @@ -85,13 +76,22 @@ class JenkinsTrigger(object): :param token: The token we need to trigger the build. If you do not have this token, ask in IRC. """ - def __init__(self, token): + def __init__(self, username, password, can_use_colour): """ Create the JenkinsTrigger instance. """ - self.token = token + self.build_numbers = {} + self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() - self.jenkins_instance = Jenkins(JENKINS_URL) + self.server = Jenkins(JENKINS_URL, username=username, password=password) + + def fetch_build_numbers(self): + """ + Get the next build number from all the jobs + """ + for job_name in OpenLPJobs.Jobs: + job_info = self.server.get_job_info(job_name) + self.build_number[job_name] = job_info['nextBuildNumber'] def trigger_build(self): """ @@ -102,8 +102,7 @@ class JenkinsTrigger(object): # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) - self.jenkins_instance.job(OpenLPJobs.Branch_Pull).build({'BRANCH_NAME': self.repo_name, 'cause': cause}, - token=self.token) + self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): """ @@ -129,6 +128,20 @@ class JenkinsTrigger(object): # Open the url Popen(('xdg-open', url), stderr=PIPE) + def _get_build_info(self, job_name, build_number): + """ + Get the build info from the server. This method will check the queue and wait for the build. + """ + queue_info = self.server.get_queue_info() + tries = 0 + while queue_info and tries < 50: + tries += 1 + time.sleep(0.5) + queue_info = self.server.get_queue_info() + if tries >= 50: + raise Exception('Build has not started yet, it may be stuck in the queue.') + return self.server.get_build_info(job_name, build_number) + def __print_build_info(self, job_name): """ This helper method prints the job information of the given ``job_name`` @@ -136,21 +149,21 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ + build_info = self._get_build_info(job_name, self.build_number[job_name]) + print('{} ... '.format(build_info['url']), end='', flush=True) is_success = False - job = self.jenkins_instance.job(job_name) - while job.info['inQueue']: - time.sleep(1) - build = job.last_build - build.wait() - if build.info['result'] == 'SUCCESS': + while build_info['building'] is True: + time.sleep(0.5) + build_info = self.server.get_build_info(job_name, self.build_number[job_name]) + result_string = build_info['result'] + if self.can_use_colour and result_string == 'SUCCESS': # Make 'SUCCESS' green. - result_string = '%s%s%s' % (Colour.GREEN_START, build.info['result'], Colour.GREEN_END) + result_string = '%s%s%s' % (Colour.GREEN_START, result_string, Colour.GREEN_END) is_success = True - else: + elif self.can_use_colour: # Make 'FAILURE' red. - result_string = '%s%s%s' % (Colour.RED_START, build.info['result'], Colour.RED_END) - url = build.info['url'] - print('[%s] %s' % (result_string, url)) + result_string = '%s%s%s' % (Colour.RED_START, result_string, Colour.RED_END) + print('[{}]'.format(result_string)) return is_success @@ -186,36 +199,29 @@ def get_repo_name(): def main(): - usage = 'Usage: python %prog TOKEN [options]' + """ + Run the script + """ + parser = ArgumentParser() + parser.add_argument('-d', '--disable-output', action='store_false', default=True, help='Disable output') + parser.add_argument('-b', '--open-browser', action='store_true', default=False, + help='Opens the jenkins page in your browser') + parser.add_argument('-c', '--enable-colour', action='store_false', default=True, + help='Enable coloured output. Disabled on Windows') + parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') + parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') + args = parser.parse_args() - parser = OptionParser(usage=usage) - parser.add_option('-d', '--disable-output', dest='enable_output', action='store_false', default=True, - help='Disable output.') - parser.add_option('-b', '--open-browser', dest='open_browser', action='store_true', default=False, - help='Opens the jenkins page in your browser.') - options, args = parser.parse_args(sys.argv) - - if len(args) == 2: - if not get_repo_name(): - print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') - return - token = args[-1] - if token in OLD_TOKENS: - print('Your token is not valid anymore. Get the most recent one.') - return - jenkins_trigger = JenkinsTrigger(token) - try: - jenkins_trigger.trigger_build() - except HTTPError: - print('Wrong token.') - return - # Open the browser before printing the output. - if options.open_browser: - jenkins_trigger.open_browser() - if options.enable_output: - jenkins_trigger.print_output() - else: - parser.print_help() + if not get_repo_name(): + print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') + return + jenkins_trigger = JenkinsTrigger(username=args.username, password=args.password) + jenkins_trigger.trigger_build() + # Open the browser before printing the output. + if args.open_browser: + jenkins_trigger.open_browser() + if not args.disable_output: + jenkins_trigger.print_output() if __name__ == '__main__': From f6243f6e8828edc534d70be969420f673e41fa3f Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 26 Oct 2017 15:05:10 -0700 Subject: [PATCH 10/56] Fix some bugs, line up the statuses, provide some failure feedback, flip the colour option --- scripts/jenkins_script.py | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 8ce6af90d..2d93d53d3 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -80,7 +80,7 @@ class JenkinsTrigger(object): """ Create the JenkinsTrigger instance. """ - self.build_numbers = {} + self.build_number = {} self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() self.server = Jenkins(JENKINS_URL, username=username, password=password) @@ -102,6 +102,7 @@ class JenkinsTrigger(object): # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) + self.fetch_build_numbers() self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): @@ -117,7 +118,10 @@ class JenkinsTrigger(object): for job in OpenLPJobs.Jobs: if not self.__print_build_info(job): - print('Stopping after failure') + if self.current_build: + print('Stopping after failure, see {}console for more details'.format(self.current_build['url'])) + else: + print('Stopping after failure') break def open_browser(self): @@ -149,21 +153,22 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ - build_info = self._get_build_info(job_name, self.build_number[job_name]) - print('{} ... '.format(build_info['url']), end='', flush=True) + self.current_build = self._get_build_info(job_name, self.build_number[job_name]) + print('{:<60} [RUNNING]'.format(self.current_build['url']), end='', flush=True) is_success = False - while build_info['building'] is True: + while self.current_build['building'] is True: time.sleep(0.5) - build_info = self.server.get_build_info(job_name, self.build_number[job_name]) - result_string = build_info['result'] - if self.can_use_colour and result_string == 'SUCCESS': - # Make 'SUCCESS' green. - result_string = '%s%s%s' % (Colour.GREEN_START, result_string, Colour.GREEN_END) - is_success = True - elif self.can_use_colour: - # Make 'FAILURE' red. - result_string = '%s%s%s' % (Colour.RED_START, result_string, Colour.RED_END) - print('[{}]'.format(result_string)) + self.current_build = self.server.get_build_info(job_name, self.build_number[job_name]) + result_string = self.current_build['result'] + is_success = result_string == 'SUCCESS' + if self.can_use_colour: + if is_success: + # Make 'SUCCESS' green. + result_string = '{}{}{}'.format(Colour.GREEN_START, result_string, Colour.GREEN_END) + else: + # Make 'FAILURE' red. + result_string = '{}{}{}'.format(Colour.RED_START, result_string, Colour.RED_END) + print('\b\b\b\b\b\b\b\b\b[{:>7}]'.format(result_string)) return is_success @@ -203,11 +208,11 @@ def main(): Run the script """ parser = ArgumentParser() - parser.add_argument('-d', '--disable-output', action='store_false', default=True, help='Disable output') + parser.add_argument('-d', '--disable-output', action='store_true', default=False, help='Disable output') parser.add_argument('-b', '--open-browser', action='store_true', default=False, help='Opens the jenkins page in your browser') - parser.add_argument('-c', '--enable-colour', action='store_false', default=True, - help='Enable coloured output. Disabled on Windows') + parser.add_argument('-n', '--no-colour', action='store_true', default=False, + help='Disable coloured output (always disabled on Windows)') parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') args = parser.parse_args() @@ -215,7 +220,7 @@ def main(): if not get_repo_name(): print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') return - jenkins_trigger = JenkinsTrigger(username=args.username, password=args.password) + jenkins_trigger = JenkinsTrigger(args.username, args.password, not args.no_colour) jenkins_trigger.trigger_build() # Open the browser before printing the output. if args.open_browser: From 0d486d051448546c5758ebe2bc678435b5447b7c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 26 Oct 2017 15:32:20 -0700 Subject: [PATCH 11/56] Updated the documentation in the file --- scripts/jenkins_script.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 2d93d53d3..05c589ddd 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -21,15 +21,24 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -This script helps to trigger builds of branches. To use it you have to install the jenkins-webapi package: +This script helps to trigger builds of branches. To use it you have to install the python-jenkins module. On Fedora +and Ubuntu/Debian, it is available as the ``python3-jenkins`` package:: - pip3 install jenkins-webapi + $ sudo dnf/apt install python3-jenkins -You probably want to create an alias. Add this to your ~/.bashrc file and then logout and login (to apply the alias): +To make it easier to run you may want to create a shell script or an alias. To create an alias, add this to your +``~/.bashrc`` (or ``~/.zshrc``) file and then log out and log back in again (to apply the alias):: - alias ci="python3 ./scripts/jenkins_script.py TOKEN" + alias ci="python3 /path/to/openlp_root/scripts/jenkins_script.py -u USERNAME -p PASSWORD" -You can look up the token in the Branch-01-Pull job configuration or ask in IRC. +To create a shell script, create the following file in a location in your ``$PATH`` (I called mine ``ci``):: + + #!/bin/bash + python3 /path/to/openlp_root/scripts/jenkins_script.py -u USERNAME -p PASSWORD + +``USERNAME`` is your Jenkins username, and ``PASSWORD`` is your Jenkins password or personal token. + +An older version of this script used to use a shared TOKEN, but this has been replaced with the username and password. """ import os import re @@ -154,7 +163,7 @@ class JenkinsTrigger(object): variables from the :class:`OpenLPJobs` class. """ self.current_build = self._get_build_info(job_name, self.build_number[job_name]) - print('{:<60} [RUNNING]'.format(self.current_build['url']), end='', flush=True) + print('{:<70} [RUNNING]'.format(self.current_build['url']), end='', flush=True) is_success = False while self.current_build['building'] is True: time.sleep(0.5) From 960ddedc6f08744f3585c28e4e66c55345c75b19 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sat, 28 Oct 2017 11:04:09 +0100 Subject: [PATCH 12/56] make tidy text part of core.\nRemove redundant (since py3) re.UNICODE flag Fixes: https://launchpad.net/bugs/1727517 --- openlp/core/common/__init__.py | 18 +++++++++++++-- openlp/core/common/i18n.py | 2 +- openlp/core/lib/__init__.py | 1 - openlp/core/lib/mediamanageritem.py | 2 +- openlp/core/ui/formattingtagcontroller.py | 2 +- openlp/plugins/bibles/forms/booknameform.py | 3 +-- openlp/plugins/bibles/lib/__init__.py | 6 ++--- openlp/plugins/bibles/lib/db.py | 3 +-- openlp/plugins/songs/forms/editsongform.py | 6 ++--- openlp/plugins/songs/lib/__init__.py | 5 ++-- .../plugins/songs/lib/importers/easyslides.py | 3 ++- .../plugins/songs/lib/importers/mediashout.py | 2 +- .../plugins/songs/lib/importers/openoffice.py | 4 ++-- .../plugins/songs/lib/importers/opensong.py | 3 ++- .../plugins/songs/lib/importers/songimport.py | 23 ++----------------- .../songs/lib/importers/songsoffellowship.py | 1 - openlp/plugins/songs/lib/openlyricsxml.py | 2 +- 17 files changed, 39 insertions(+), 47 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index f8017fdbd..f4c22f490 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -43,9 +43,13 @@ log = logging.getLogger(__name__ + '.__init__') FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)') SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])') -CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE) -INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE) +CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]') +INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]') IMAGES_FILTER = None +REPLACMENT_CHARS_MAP = str.maketrans({'\u2018': '\'', '\u2019': '\'', '\u201c': '"', '\u201d': '"', '\u2026': '...', + '\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'}) +NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?') +WHITESPACE_REGEX = re.compile(r'[ \t]+') def trace_error_handler(logger): @@ -436,3 +440,13 @@ def get_file_encoding(file_path): return detector.result except OSError: log.exception('Error detecting file encoding') + +def normalize_str(irreg_str): + """ + + :param str irreg_str: + :return: + """ + irreg_str = irreg_str.translate(REPLACMENT_CHARS_MAP) + irreg_str = NEW_LINE_REGEX.sub('\n', irreg_str) + return WHITESPACE_REGEX.sub(' ', irreg_str) diff --git a/openlp/core/common/i18n.py b/openlp/core/common/i18n.py index 1f4357808..9149f3fe6 100644 --- a/openlp/core/common/i18n.py +++ b/openlp/core/common/i18n.py @@ -53,7 +53,7 @@ def translate(context, text, comment=None, qt_translate=QtCore.QCoreApplication. Language = namedtuple('Language', ['id', 'name', 'code']) ICU_COLLATOR = None -DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE) +DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+') LANGUAGES = sorted([ Language(1, translate('common.languages', '(Afan) Oromo', 'Language code: om'), 'om'), Language(2, translate('common.languages', 'Abkhazian', 'Language code: ab'), 'ab'), diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 0f4078420..f78065774 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -38,7 +38,6 @@ log = logging.getLogger(__name__ + '.__init__') SLIMCHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' - class ServiceItemContext(object): """ The context in which a Service Item is being generated diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index c650ad80e..cc884279c 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -92,7 +92,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): Run some initial setup. This method is separate from __init__ in order to mock it out in tests. """ self.hide() - self.whitespace = re.compile(r'[\W_]+', re.UNICODE) + self.whitespace = re.compile(r'[\W_]+') visible_title = self.plugin.get_string(StringContent.VisibleName) self.title = str(visible_title['title']) Registry().register(self.plugin.name, self) diff --git a/openlp/core/ui/formattingtagcontroller.py b/openlp/core/ui/formattingtagcontroller.py index e92173fed..4b9d75fee 100644 --- a/openlp/core/ui/formattingtagcontroller.py +++ b/openlp/core/ui/formattingtagcontroller.py @@ -43,7 +43,7 @@ class FormattingTagController(object): r'(?P[^\s/!\?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P/)?' r'|(?P!\[CDATA\[(?:(?!\]\]>).)*\]\])' r'|(?P\?(?:(?!\?>).)*\?)' - r'|(?P!--(?:(?!-->).)*--))>', re.UNICODE) + r'|(?P!--(?:(?!-->).)*--))>') self.html_regex = re.compile(r'^(?:[^<>]*%s)*[^<>]*$' % self.html_tag_regex.pattern) def pre_save(self): diff --git a/openlp/plugins/bibles/forms/booknameform.py b/openlp/plugins/bibles/forms/booknameform.py index f78559ce5..7c8a2c3cd 100644 --- a/openlp/plugins/bibles/forms/booknameform.py +++ b/openlp/plugins/bibles/forms/booknameform.py @@ -113,8 +113,7 @@ class BookNameForm(QDialog, Ui_BookNameDialog): cor_book = self.corresponding_combo_box.currentText() for character in '\\.^$*+?{}[]()': cor_book = cor_book.replace(character, '\\' + character) - books = [key for key in list(self.book_names.keys()) if re.match(cor_book, str(self.book_names[key]), - re.UNICODE)] + books = [key for key in list(self.book_names.keys()) if re.match(cor_book, str(self.book_names[key]))] books = [_f for _f in map(BiblesResourcesDB.get_book, books) if _f] if books: self.book_id = books[0]['id'] diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 9247485c1..f9d93a43e 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -224,13 +224,13 @@ def update_reference_separators(): range_regex = '(?:(?P[0-9]+){sep_v})?' \ '(?P[0-9]+)(?P{sep_r}(?:(?:(?P' \ '[0-9]+){sep_v})?(?P[0-9]+)|{sep_e})?)?'.format_map(REFERENCE_SEPARATORS) - REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex), re.UNICODE) - REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE) + REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex)) + REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l']) # full reference match: ((,(?!$)|(?=$)))+ REFERENCE_MATCHES['full'] = \ re.compile(r'^\s*(?!\s)(?P[\d]*[.]?[^\d\.]+)\.*(?(?:{range_regex}(?:{sep_l}(?!\s*$)|(?=\s*$)))+)\s*$'.format( - range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l']), re.UNICODE) + range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l'])) def get_reference_separator(separator_type): diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index bc8ce4150..995a9691a 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -307,8 +307,7 @@ class BibleDB(Manager): book_escaped = book for character in RESERVED_CHARACTERS: book_escaped = book_escaped.replace(character, '\\' + character) - regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), - re.UNICODE | re.IGNORECASE) + regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), re.IGNORECASE) if language_selection == LanguageSelection.Bible: db_book = self.get_book(book) if db_book: diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index fa475a63f..6e0772418 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -105,9 +105,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.topics_list_view.setSortingEnabled(False) self.topics_list_view.setAlternatingRowColors(True) self.audio_list_widget.setAlternatingRowColors(True) - self.find_verse_split = re.compile('---\[\]---\n', re.UNICODE) - self.whitespace = re.compile(r'\W+', re.UNICODE) - self.find_tags = re.compile(u'\{/?\w+\}', re.UNICODE) + self.find_verse_split = re.compile('---\[\]---\n') + self.whitespace = re.compile(r'\W+') + self.find_tags = re.compile(r'\{/?\w+\}') def _load_objects(self, cls, combo, cache): """ diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index f88aa8678..74334ef0d 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -24,7 +24,6 @@ The :mod:`~openlp.plugins.songs.lib` module contains a number of library functio """ import logging -import os import re from PyQt5 import QtWidgets @@ -39,8 +38,8 @@ from openlp.plugins.songs.lib.ui import SongStrings log = logging.getLogger(__name__) -WHITESPACE = re.compile(r'[\W_]+', re.UNICODE) -APOSTROPHE = re.compile('[\'`’ʻ′]', re.UNICODE) +WHITESPACE = re.compile(r'[\W_]+') +APOSTROPHE = re.compile(r'[\'`’ʻ′]') # PATTERN will look for the next occurence of one of these symbols: # \controlword - optionally preceded by \*, optionally followed by a number # \'## - where ## is a pair of hex digits, representing a single character diff --git a/openlp/plugins/songs/lib/importers/easyslides.py b/openlp/plugins/songs/lib/importers/easyslides.py index a1ffb7b7c..6d717bdb4 100644 --- a/openlp/plugins/songs/lib/importers/easyslides.py +++ b/openlp/plugins/songs/lib/importers/easyslides.py @@ -25,6 +25,7 @@ import re from lxml import etree, objectify +from openlp.core.common import normalize_str from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.importers.songimport import SongImport @@ -225,7 +226,7 @@ class EasySlidesImport(SongImport): verses[reg].setdefault(vt, {}) verses[reg][vt].setdefault(vn, {}) verses[reg][vt][vn].setdefault(inst, []) - verses[reg][vt][vn][inst].append(self.tidy_text(line)) + verses[reg][vt][vn][inst].append(normalize_str(line)) # done parsing versetags = [] # we use our_verse_order to ensure, we insert lyrics in the same order diff --git a/openlp/plugins/songs/lib/importers/mediashout.py b/openlp/plugins/songs/lib/importers/mediashout.py index 67cf0d0fb..9df9baa0f 100644 --- a/openlp/plugins/songs/lib/importers/mediashout.py +++ b/openlp/plugins/songs/lib/importers/mediashout.py @@ -101,7 +101,7 @@ class MediaShoutImport(SongImport): self.song_book_name = song.SongID for verse in verses: tag = VERSE_TAGS[verse.Type] + str(verse.Number) if verse.Type < len(VERSE_TAGS) else 'O' - self.add_verse(self.tidy_text(verse.Text), tag) + self.add_verse(verse.Text, tag) for order in verse_order: if order.Type < len(VERSE_TAGS): self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number)) diff --git a/openlp/plugins/songs/lib/importers/openoffice.py b/openlp/plugins/songs/lib/importers/openoffice.py index a097d8b85..f2a8b2147 100644 --- a/openlp/plugins/songs/lib/importers/openoffice.py +++ b/openlp/plugins/songs/lib/importers/openoffice.py @@ -24,7 +24,7 @@ import time from PyQt5 import QtCore -from openlp.core.common import is_win, get_uno_command, get_uno_instance +from openlp.core.common import get_uno_command, get_uno_instance, is_win, normalize_str from openlp.core.common.i18n import translate from .songimport import SongImport @@ -241,7 +241,7 @@ class OpenOfficeImport(SongImport): :param text: The text. """ - song_texts = self.tidy_text(text).split('\f') + song_texts = normalize_str(text).split('\f') self.set_defaults() for song_text in song_texts: if song_text.strip(): diff --git a/openlp/plugins/songs/lib/importers/opensong.py b/openlp/plugins/songs/lib/importers/opensong.py index e6924e7b2..6cd690562 100644 --- a/openlp/plugins/songs/lib/importers/opensong.py +++ b/openlp/plugins/songs/lib/importers/opensong.py @@ -25,6 +25,7 @@ import re from lxml import objectify from lxml.etree import Error, LxmlError +from openlp.core.common import normalize_str from openlp.core.common.i18n import translate from openlp.core.common.settings import Settings from openlp.plugins.songs.lib import VerseType @@ -262,7 +263,7 @@ class OpenSongImport(SongImport): post=this_line[offset + column:]) offset += len(chord) + 2 # Tidy text and remove the ____s from extended words - this_line = self.tidy_text(this_line) + this_line = normalize_str(this_line) this_line = this_line.replace('_', '') this_line = this_line.replace('||', '\n[---]\n') this_line = this_line.strip() diff --git a/openlp/plugins/songs/lib/importers/songimport.py b/openlp/plugins/songs/lib/importers/songimport.py index a67c17fe7..2bd8c0e56 100644 --- a/openlp/plugins/songs/lib/importers/songimport.py +++ b/openlp/plugins/songs/lib/importers/songimport.py @@ -25,6 +25,7 @@ import re from PyQt5 import QtCore +from openlp.core.common import normalize_str from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate from openlp.core.common.path import copyfile, create_paths @@ -130,26 +131,6 @@ class SongImport(QtCore.QObject): def register(self, import_wizard): self.import_wizard = import_wizard - def tidy_text(self, text): - """ - Get rid of some dodgy unicode and formatting characters we're not interested in. Some can be converted to ascii. - """ - text = text.replace('\u2018', '\'') - text = text.replace('\u2019', '\'') - text = text.replace('\u201c', '"') - text = text.replace('\u201d', '"') - text = text.replace('\u2026', '...') - text = text.replace('\u2013', '-') - text = text.replace('\u2014', '-') - # Replace vertical tab with 2 linebreaks - text = text.replace('\v', '\n\n') - # Replace form feed (page break) with 2 linebreaks - text = text.replace('\f', '\n\n') - # Remove surplus blank lines, spaces, trailing/leading spaces - text = re.sub(r'[ \t]+', ' ', text) - text = re.sub(r' ?(\r\n?|\n) ?', '\n', text) - return text - def process_song_text(self, text): """ Process the song text from import @@ -368,7 +349,7 @@ class SongImport(QtCore.QObject): verse_tag = VerseType.tags[VerseType.Other] log.info('Versetype {old} changing to {new}'.format(old=verse_def, new=new_verse_def)) verse_def = new_verse_def - sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang) + sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], normalize_str(verse_text), lang) song.lyrics = str(sxml.extract_xml(), 'utf-8') if not self.verse_order_list and self.verse_order_list_generated_useful: self.verse_order_list = self.verse_order_list_generated diff --git a/openlp/plugins/songs/lib/importers/songsoffellowship.py b/openlp/plugins/songs/lib/importers/songsoffellowship.py index 13e073cc1..bbba654c9 100644 --- a/openlp/plugins/songs/lib/importers/songsoffellowship.py +++ b/openlp/plugins/songs/lib/importers/songsoffellowship.py @@ -194,7 +194,6 @@ class SongsOfFellowshipImport(OpenOfficeImport): :param text_portion: A Piece of text """ text = text_portion.getString() - text = self.tidy_text(text) if text.strip() == '': return text if text_portion.CharWeight == BOLD: diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py index 74d91068c..ef47fa77b 100644 --- a/openlp/plugins/songs/lib/openlyricsxml.py +++ b/openlp/plugins/songs/lib/openlyricsxml.py @@ -281,7 +281,7 @@ class OpenLyrics(object): # Process the formatting tags. # Have we any tags in song lyrics? tags_element = None - match = re.search('\{/?\w+\}', song.lyrics, re.UNICODE) + match = re.search(r'\{/?\w+\}', song.lyrics) if match: # Named 'format_' - 'format' is built-in function in Python. format_ = etree.SubElement(song_xml, 'format') From a268c4476b11fd9f6cb50bf1c1541419b637b236 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sat, 28 Oct 2017 19:58:34 +0100 Subject: [PATCH 13/56] Couple fixes for bugs caused by controll chars --- openlp/core/common/__init__.py | 8 ++++++-- openlp/core/widgets/edits.py | 12 ++++++++++- .../plugins/songs/lib/importers/zionworx.py | 20 +++++-------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index f4c22f490..b7e579803 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -441,12 +441,16 @@ def get_file_encoding(file_path): except OSError: log.exception('Error detecting file encoding') + def normalize_str(irreg_str): """ + Normalize the supplied string. Remove unicode control chars and tidy up white space. - :param str irreg_str: - :return: + :param str irreg_str: The string to normalize. + :return: The normalized string + :rtype: str """ irreg_str = irreg_str.translate(REPLACMENT_CHARS_MAP) + irreg_str = CONTROL_CHARS.sub('', irreg_str) irreg_str = NEW_LINE_REGEX.sub('\n', irreg_str) return WHITESPACE_REGEX.sub(' ', irreg_str) diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py index c2396810f..e64125833 100644 --- a/openlp/core/widgets/edits.py +++ b/openlp/core/widgets/edits.py @@ -27,6 +27,7 @@ import re from PyQt5 import QtCore, QtGui, QtWidgets +from openlp.core.common import CONTROL_CHARS from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.settings import Settings @@ -470,12 +471,21 @@ class SpellTextEdit(QtWidgets.QPlainTextEdit): cursor.insertText(html['start tag']) cursor.insertText(html['end tag']) + def insertFromMimeData(self, source): + """ + Reimplement `insertFromMimeData` so that we can remove any control characters + + :param QtCore.QMimeData source: The mime data to insert + :rtype: None + """ + self.insertPlainText(CONTROL_CHARS.sub('', source.text())) + class Highlighter(QtGui.QSyntaxHighlighter): """ Provides a text highlighter for pointing out spelling errors in text. """ - WORDS = r'(?iu)[\w\']+' + WORDS = r'(?i)[\w\']+' def __init__(self, *args): """ diff --git a/openlp/plugins/songs/lib/importers/zionworx.py b/openlp/plugins/songs/lib/importers/zionworx.py index 5cfc0576d..23817c31a 100644 --- a/openlp/plugins/songs/lib/importers/zionworx.py +++ b/openlp/plugins/songs/lib/importers/zionworx.py @@ -30,9 +30,6 @@ from openlp.plugins.songs.lib.importers.songimport import SongImport log = logging.getLogger(__name__) -# Used to strip control chars (except 10=LF, 13=CR) -CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14, 32)) + [127]) - class ZionWorxImport(SongImport): """ @@ -95,12 +92,12 @@ class ZionWorxImport(SongImport): return self.set_defaults() try: - self.title = self._decode(record['Title1']) + self.title = record['Title1'] if record['Title2']: - self.alternate_title = self._decode(record['Title2']) - self.parse_author(self._decode(record['Writer'])) - self.add_copyright(self._decode(record['Copyright'])) - lyrics = self._decode(record['Lyrics']) + self.alternate_title = record['Title2'] + self.parse_author(record['Writer']) + self.add_copyright(record['Copyright']) + lyrics = record['Lyrics'] except UnicodeDecodeError as e: self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record {index}').format(index=index), translate('SongsPlugin.ZionWorxImport', 'Decoding error: {error}').format(error=e)) @@ -122,10 +119,3 @@ class ZionWorxImport(SongImport): if not self.finish(): self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index + (': "' + title + '"' if title else '')) - - def _decode(self, str): - """ - Strips all control characters (except new lines). - """ - # ZionWorx has no option for setting the encoding for its songs, so we assume encoding is always the same. - return str.translate(CONTROL_CHARS_MAP) From 7697febb2a1f42977bf48fd7baf302c3b4b491b9 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 2 Nov 2017 21:46:02 +0000 Subject: [PATCH 14/56] Fixes Bug #1661416 Fixes: https://launchpad.net/bugs/1661416 --- openlp/plugins/songusage/forms/songusagedetailform.py | 10 ++++++++-- openlp/plugins/songusage/songusageplugin.py | 9 +++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index 147e26d10..dc3393c8d 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -54,8 +54,14 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP """ We need to set up the screen """ - self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date')) - self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date')) + to_date = Settings().value(self.plugin.settings_section + '/to date') + if not (isinstance(to_date, QtCore.QDate) and to_date.isValid()): + to_date = QtCore.QDate.currentDate() + from_date = Settings().value(self.plugin.settings_section + '/from date') + if not (isinstance(from_date, QtCore.QDate) and from_date.isValid()): + from_date = to_date.addYears(-1) + self.from_date_calendar.setSelectedDate(from_date) + self.to_date_calendar.setSelectedDate(to_date) self.report_path_edit.path = Settings().value(self.plugin.settings_section + '/last directory export') def on_report_path_edit_path_changed(self, file_path): diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index a49661143..79f21a8cf 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -38,10 +38,7 @@ from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem log = logging.getLogger(__name__) -YEAR = QtCore.QDate().currentDate().year() -if QtCore.QDate().currentDate().month() < 9: - YEAR -= 1 - +TODAY = QtCore.QDate.currentDate() __default_settings__ = { 'songusage/db type': 'sqlite', @@ -50,8 +47,8 @@ __default_settings__ = { 'songusage/db hostname': '', 'songusage/db database': '', 'songusage/active': False, - 'songusage/to date': QtCore.QDate(YEAR, 8, 31), - 'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1), + 'songusage/to date': TODAY, + 'songusage/from date': TODAY.addYears(-1), 'songusage/last directory export': None } From 94dd107abe38915412ac15fb26467cb9af03e6cd Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 3 Nov 2017 20:55:41 +0000 Subject: [PATCH 15/56] Changed merged errors Fixes: https://launchpad.net/bugs/1400415 --- openlp/core/common/__init__.py | 2 +- openlp/core/common/httputils.py | 8 ++++---- openlp/core/common/path.py | 2 +- openlp/core/lib/__init__.py | 2 +- openlp/core/ui/exceptionform.py | 2 +- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/servicemanager.py | 8 ++++---- openlp/core/ui/thememanager.py | 6 +++--- openlp/core/version.py | 4 ++-- openlp/plugins/presentations/lib/pptviewcontroller.py | 2 +- tests/functional/openlp_core/common/test_httputils.py | 2 +- tests/functional/openlp_core/common/test_path.py | 8 ++++---- tests/functional/openlp_core/lib/test_lib.py | 2 +- tests/functional/openlp_core/ui/test_first_time.py | 2 +- .../presentations/test_presentationcontroller.py | 2 +- tests/utils/__init__.py | 2 +- 16 files changed, 28 insertions(+), 28 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index b7e579803..d280cbde2 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -343,7 +343,7 @@ def delete_file(file_path): if file_path.exists(): file_path.unlink() return True - except (IOError, OSError): + except OSError: log.exception('Unable to delete file {file_path}'.format(file_path=file_path)) return False diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index 11ae7b563..21b778b80 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -97,8 +97,8 @@ def get_web_page(url, headers=None, update_openlp=False, proxies=None): response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT)) log.debug('Downloaded page {url}'.format(url=response.url)) break - except IOError: - # For now, catch IOError. All requests errors inherit from IOError + except OSError: + # For now, catch OSError. All requests errors inherit from OSError log.exception('Unable to connect to {url}'.format(url=url)) response = None if retries >= CONNECTION_RETRIES: @@ -127,7 +127,7 @@ def get_url_file_size(url): try: response = requests.head(url, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True) return int(response.headers['Content-Length']) - except IOError: + except OSError: if retries > CONNECTION_RETRIES: raise ConnectionError('Unable to download {url}'.format(url=url)) else: @@ -173,7 +173,7 @@ def url_get_file(callback, url, file_path, sha256=None): file_path.unlink() return False break - except IOError: + except OSError: trace_error_handler(log) if retries > CONNECTION_RETRIES: if file_path.exists(): diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 19e17470b..6b89acfb5 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -233,7 +233,7 @@ def create_paths(*paths, **kwargs): try: if not path.exists(): path.mkdir(parents=True) - except IOError: + except OSError: if not kwargs.get('do_not_log', False): log.exception('failed to check if directory exists or create directory') diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index f78065774..04d3b9e5e 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -103,7 +103,7 @@ def get_text_file_string(text_file_path): # no BOM was found file_handle.seek(0) content = file_handle.read() - except (IOError, UnicodeError): + except (OSError, UnicodeError): log.exception('Failed to open text file {text}'.format(text=text_file_path)) return content diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 70fe2c416..45124dac8 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -155,7 +155,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties): try: with file_path.open('w') as report_file: report_file.write(report_text) - except IOError: + except OSError: log.exception('Failed to write crash report') def on_send_report_button_clicked(self): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 33cdc3301..c0e704afb 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -1367,7 +1367,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): '- Please wait for copy to finish').format(path=self.new_data_path)) dir_util.copy_tree(str(old_data_path), str(self.new_data_path)) log.info('Copy successful') - except (IOError, os.error, DistutilsFileError) as why: + except (OSError, DistutilsFileError) as why: self.application.set_normal_cursor() log.exception('Data copy failed {err}'.format(err=str(why))) err_text = translate('OpenLP.MainWindow', diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 5dda18044..71c6a1147 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -609,7 +609,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if not os.path.exists(save_file): shutil.copy(audio_from, save_file) zip_file.write(audio_from, audio_to) - except IOError: + except OSError: self.log_exception('Failed to save service to disk: {name}'.format(name=temp_file_name)) self.main_window.error_message(translate('OpenLP.ServiceManager', 'Error Saving File'), translate('OpenLP.ServiceManager', 'There was an error saving your file.')) @@ -670,7 +670,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi zip_file = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, True) # First we add service contents. zip_file.writestr(service_file_name, service_content) - except IOError: + except OSError: self.log_exception('Failed to save service to disk: {name}'.format(name=temp_file_name)) self.main_window.error_message(translate('OpenLP.ServiceManager', 'Error Saving File'), translate('OpenLP.ServiceManager', 'There was an error saving your file.')) @@ -802,11 +802,11 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi else: critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.')) self.log_error('File contains no service data') - except (IOError, NameError): + except (OSError, NameError): self.log_exception('Problem loading service file {name}'.format(name=file_name)) critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File could not be opened because it is corrupt.')) - except zipfile.BadZipfile: + except zipfile.BadZipFile: if os.path.getsize(file_name) == 0: self.log_exception('Service file is zero sized: {name}'.format(name=file_name)) QtWidgets.QMessageBox.information(self, translate('OpenLP.ServiceManager', 'Empty File'), diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 5b4c5cbb9..1b39e5fec 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -604,7 +604,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R else: with full_name.open('wb') as out_file: out_file.write(theme_zip.read(zipped_file)) - except (IOError, zipfile.BadZipfile): + except (OSError, zipfile.BadZipFile): self.log_exception('Importing theme from zip failed {name}'.format(name=file_path)) raise ValidationError except ValidationError: @@ -667,7 +667,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R theme_path = theme_dir / '{file_name}.json'.format(file_name=name) try: theme_path.write_text(theme_pretty) - except IOError: + except OSError: self.log_exception('Saving theme to file failed') if image_source_path and image_destination_path: if self.old_background_image_path and image_destination_path != self.old_background_image_path: @@ -675,7 +675,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R if image_source_path != image_destination_path: try: copyfile(image_source_path, image_destination_path) - except IOError: + except OSError: self.log_exception('Failed to save theme image') self.generate_and_save_image(name, theme) diff --git a/openlp/core/version.py b/openlp/core/version.py index 6d038a3d9..314c4865f 100644 --- a/openlp/core/version.py +++ b/openlp/core/version.py @@ -96,7 +96,7 @@ class VersionWorker(QtCore.QObject): remote_version = response.text log.debug('New version found: %s', remote_version) break - except IOError: + except OSError: log.exception('Unable to connect to OpenLP server to download version file') retries += 1 else: @@ -182,7 +182,7 @@ def get_version(): try: version_file = open(file_path, 'r') full_version = str(version_file.read()).rstrip() - except IOError: + except OSError: log.exception('Error in version file.') full_version = '0.0.0-bzr000' finally: diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 0a403df37..ddabe07e1 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -70,7 +70,7 @@ class PptviewController(PresentationController): try: self.start_process() return self.process.CheckInstalled() - except WindowsError: + except OSError: return False def start_process(self): diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index e620fa04e..5e7a396b2 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -233,7 +233,7 @@ class TestHttpUtils(TestCase, TestMixin): Test socket timeout gets caught """ # GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download - mocked_requests.get.side_effect = IOError + mocked_requests.get.side_effect = OSError # WHEN: Attempt to retrieve a file url_get_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile)) diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 4b30bd2cb..2ec89771b 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -371,13 +371,13 @@ class TestPath(TestCase): @patch('openlp.core.common.path.log') def test_create_paths_dir_io_error(self, mocked_logger): """ - Test the create_paths() when an IOError is raised + Test the create_paths() when an OSError is raised """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() - mocked_path.exists.side_effect = IOError('Cannot make directory') + mocked_path.exists.side_effect = OSError('Cannot make directory') - # WHEN: An IOError is raised when checking the if the path exists. + # WHEN: An OSError is raised when checking the if the path exists. create_paths(mocked_path) # THEN: The Error should have been logged @@ -385,7 +385,7 @@ class TestPath(TestCase): def test_create_paths_dir_value_error(self): """ - Test the create_paths() when an error other than IOError is raised + Test the create_paths() when an error other than OSError is raised """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index f2bfaf79c..1352b5da5 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -168,7 +168,7 @@ class TestLib(TestCase): patch.object(Path, 'open'): file_path = Path('testfile.txt') file_path.is_file.return_value = True - file_path.open.side_effect = IOError() + file_path.open.side_effect = OSError() # WHEN: get_text_file_string is called result = get_text_file_string(file_path) diff --git a/tests/functional/openlp_core/ui/test_first_time.py b/tests/functional/openlp_core/ui/test_first_time.py index eb9464375..2be5e1ad6 100644 --- a/tests/functional/openlp_core/ui/test_first_time.py +++ b/tests/functional/openlp_core/ui/test_first_time.py @@ -40,7 +40,7 @@ class TestFirstTimeWizard(TestMixin, TestCase): Test get_web_page will attempt CONNECTION_RETRIES+1 connections - bug 1409031 """ # GIVEN: Initial settings and mocks - mocked_requests.get.side_effect = IOError('Unable to connect') + mocked_requests.get.side_effect = OSError('Unable to connect') # WHEN: A webpage is requested try: diff --git a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py index 30ab11561..a921ef81e 100644 --- a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py @@ -144,7 +144,7 @@ class TestPresentationController(TestCase): # GIVEN: A mocked open, get_thumbnail_folder and exists with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder: - mocked_read_text.side_effect = IOError() + mocked_read_text.side_effect = OSError() mocked_get_thumbnail_folder.return_value = Path('test') # WHEN: calling get_titles_and_notes diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index fd5aeccfd..dd4d78354 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -36,7 +36,7 @@ def convert_file_service_item(test_path, name, row=0): try: items = json.load(open_file) first_line = items[row] - except IOError: + except OSError: first_line = '' finally: open_file.close() From f307568cbbff54e390ed923dd277319634aa6738 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 3 Nov 2017 22:52:24 +0000 Subject: [PATCH 16/56] Misc test fixes --- openlp/core/common/actions.py | 1 + openlp/core/ui/servicemanager.py | 2 +- tests/functional/openlp_core/common/test_actions.py | 1 + tests/functional/openlp_core/common/test_i18n.py | 2 +- tests/interfaces/openlp_core/ui/test_projectormanager.py | 2 +- tests/interfaces/openlp_core/ui/test_projectorsourceform.py | 2 +- tests/interfaces/openlp_core/ui/test_thememanager.py | 2 +- 7 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py index a5b417017..739aba4e3 100644 --- a/openlp/core/common/actions.py +++ b/openlp/core/common/actions.py @@ -366,6 +366,7 @@ class ActionList(object): continue if existing_action in affected_actions: return False + print(existing_action.shortcutContext()) if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: return False elif action in self.get_all_child_objects(existing_action.parent()): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index d13d7879d..f5a71934d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -719,7 +719,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)')) else: file_path, filter_used = FileDialog.getSaveFileName( - self.main_window, UiStrings().SaveService, file_path, + self.main_window, UiStrings().SaveService, default_file_path, translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;')) if not file_path: return False diff --git a/tests/functional/openlp_core/common/test_actions.py b/tests/functional/openlp_core/common/test_actions.py index bd59d6577..57905654d 100644 --- a/tests/functional/openlp_core/common/test_actions.py +++ b/tests/functional/openlp_core/common/test_actions.py @@ -153,6 +153,7 @@ class TestActionList(TestCase, TestMixin): """ Prepare the tests """ + self.setup_application() self.action_list = ActionList.get_instance() self.build_settings() self.settings = Settings() diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index d6828fb6f..bffb819dc 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -155,7 +155,7 @@ def test_check_same_instance(): assert first_instance is second_instance, 'Two UiStrings objects should be the same instance' -def test_translate(self): +def test_translate(): """ Test the translate() function """ diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py index ff95c4276..9184035b8 100644 --- a/tests/interfaces/openlp_core/ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py @@ -42,8 +42,8 @@ class TestProjectorManager(TestCase, TestMixin): """ Create the UI and setup necessary options """ - self.build_settings() self.setup_application() + self.build_settings() Registry.create() with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: if os.path.exists(TEST_DB): diff --git a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py index 4b9e2f402..58094a17c 100644 --- a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py +++ b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py @@ -64,8 +64,8 @@ class ProjectorSourceFormTest(TestCase, TestMixin): Set up anything necessary for all tests """ mocked_init_url.return_value = 'sqlite:///{}'.format(TEST_DB) - self.build_settings() self.setup_application() + self.build_settings() Registry.create() # Do not try to recreate if we've already been created from a previous test if not hasattr(self, 'projectordb'): diff --git a/tests/interfaces/openlp_core/ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py index 7f3927cf5..0808b12d0 100644 --- a/tests/interfaces/openlp_core/ui/test_thememanager.py +++ b/tests/interfaces/openlp_core/ui/test_thememanager.py @@ -41,8 +41,8 @@ class TestThemeManager(TestCase, TestMixin): """ Create the UI """ - self.build_settings() self.setup_application() + self.build_settings() Registry.create() self.theme_manager = ThemeManager() From 3140ea434d43a7ca8b4be11b26bafafc4ebdfdb8 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 8 Nov 2017 22:06:48 -0700 Subject: [PATCH 17/56] Add support for a multi to single setting migration --- openlp/core/common/settings.py | 60 ++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index a6bc549f1..6726624ef 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -62,6 +62,30 @@ def media_players_conv(string): return string +def upgrade_monitor(number, x_position, y_position, height, width, can_override, can_display_on_monitor): + """ + Upgrade them monitor setting from a few single entries to a composite JSON entry + + :param int number: The old monitor number + :param int x_position: The X position + :param int y_position: The Y position + :param bool can_override: Are the screen positions overridden + :param bool can_display_on_monitor: Can OpenLP display on the monitor + :returns dict: Dictionary with the new value + """ + return { + number: { + 'displayGeometry': { + 'x': x_position, + 'y': y_position, + 'height': height, + 'width': width + } + }, + 'canDisplayOnMonitor': can_display_on_monitor + } + + class Settings(QtCore.QSettings): """ Class to wrap QSettings. @@ -255,7 +279,9 @@ class Settings(QtCore.QSettings): ('core/logo file', 'core/logo file', [(str_to_path, None)]), ('presentations/last directory', 'presentations/last directory', [(str_to_path, None)]), ('images/last directory', 'images/last directory', [(str_to_path, None)]), - ('media/last directory', 'media/last directory', [(str_to_path, None)]) + ('media/last directory', 'media/last directory', [(str_to_path, None)]), + (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override', + 'core/display on monitor'], 'core/monitors', [(upgrade_monitor, [1, 0, 0, None, None, False, False])]) ] @staticmethod @@ -464,31 +490,37 @@ class Settings(QtCore.QSettings): for version in range(current_version, __version__): version += 1 upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version)) - for old_key, new_key, rules in upgrade_list: + for old_keys, new_key, rules in upgrade_list: # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning # system. - if not self.contains(old_key): + if not isinstance(old_keys, (tuple, list)): + old_keys = [old_keys] + if not any([self.contains(old_key) for old_key in old_keys]): + log.warning('One of {} does not exist, skipping upgrade'.format(old_keys)) continue if new_key: # Get the value of the old_key. - old_value = super(Settings, self).value(old_key) + old_values = [super(Settings, self).value(old_key) for old_key in old_keys] # When we want to convert the value, we have to figure out the default value (because we cannot get # the default value from the central settings dict. if rules: - default_value = rules[0][1] - old_value = self._convert_value(old_value, default_value) + default_values = rules[0][1] + if not isinstance(default_values, (list, tuple)): + default_values = [default_values] + old_values = [self._convert_value(old_value, default_value) + for old_value, default_value in zip(old_values, default_values)] # Iterate over our rules and check what the old_value should be "converted" to. - for new, old in rules: + new_value = None + for new_rule, old_rule in rules: # If the value matches with the condition (rule), then use the provided value. This is used to # convert values. E. g. an old value 1 results in True, and 0 in False. - if callable(new): - old_value = new(old_value) - elif old == old_value: - old_value = new + if callable(new_rule): + new_value = new_rule(*old_values) + elif old_rule in old_values: + new_value = new_rule break - self.setValue(new_key, old_value) - if new_key != old_key: - self.remove(old_key) + self.setValue(new_key, new_value) + [self.remove(old_key) for old_key in old_keys if old_key != new_key] self.setValue('settings/version', version) def value(self, key): From 83eaad48ade4e3779dd199deeb856802bcad9768 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 9 Nov 2017 19:11:37 +0000 Subject: [PATCH 18/56] Fix action test failure by removing un used actions --- openlp/core/common/actions.py | 1 - openlp/core/ui/servicemanager.py | 16 ++-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py index 739aba4e3..a5b417017 100644 --- a/openlp/core/common/actions.py +++ b/openlp/core/common/actions.py @@ -366,7 +366,6 @@ class ActionList(object): continue if existing_action in affected_actions: return False - print(existing_action.shortcutContext()) if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: return False elif action in self.get_all_child_objects(existing_action.parent()): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index f5a71934d..f3b107a50 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -193,18 +193,6 @@ class Ui_ServiceManager(object): text=translate('OpenLP.ServiceManager', 'Move to &bottom'), icon=':/services/service_bottom.png', tooltip=translate('OpenLP.ServiceManager', 'Move item to the end of the service.'), can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_end) - self.down_action = self.order_toolbar.add_toolbar_action( - 'down', - text=translate('OpenLP.ServiceManager', 'Move &down'), can_shortcuts=True, - tooltip=translate('OpenLP.ServiceManager', 'Moves the selection down the window.'), visible=False, - triggers=self.on_move_selection_down) - action_list.add_action(self.down_action) - self.up_action = self.order_toolbar.add_toolbar_action( - 'up', - text=translate('OpenLP.ServiceManager', 'Move up'), can_shortcuts=True, - tooltip=translate('OpenLP.ServiceManager', 'Moves the selection up the window.'), visible=False, - triggers=self.on_move_selection_up) - action_list.add_action(self.up_action) self.order_toolbar.addSeparator() self.delete_action = self.order_toolbar.add_toolbar_action( 'delete', can_shortcuts=True, @@ -300,8 +288,8 @@ class Ui_ServiceManager(object): self.theme_menu = QtWidgets.QMenu(translate('OpenLP.ServiceManager', '&Change Item Theme')) self.menu.addMenu(self.theme_menu) self.service_manager_list.addActions([self.move_down_action, self.move_up_action, self.make_live_action, - self.move_top_action, self.move_bottom_action, self.up_action, - self.down_action, self.expand_action, self.collapse_action]) + self.move_top_action, self.move_bottom_action, self.expand_action, + self.collapse_action]) Registry().register_function('theme_update_list', self.update_theme_list) Registry().register_function('config_screen_changed', self.regenerate_service_items) Registry().register_function('theme_update_global', self.theme_change) From 6d66cadb0ae683ceafa3329d4b1682d3818e161d Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 9 Nov 2017 19:15:31 +0000 Subject: [PATCH 19/56] head --- openlp/core/widgets/edits.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py index e64125833..89310a713 100644 --- a/openlp/core/widgets/edits.py +++ b/openlp/core/widgets/edits.py @@ -242,7 +242,7 @@ class PathEdit(QtWidgets.QWidget): self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished) self.update_button_tool_tips() - @property + @QtCore.pyqtProperty('QVariant') def path(self): """ A property getter method to return the selected path. @@ -350,7 +350,7 @@ class PathEdit(QtWidgets.QWidget): :rtype: None """ if self._path != path: - self.path = path + self._path = path self.pathChanged.emit(path) From 1a0a828ad3bd7d68a61fc952f819a1cac3175a27 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 9 Nov 2017 19:25:26 +0000 Subject: [PATCH 20/56] fix errors --- openlp/core/api/endpoint/remote.py | 2 +- openlp/core/common/registry.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openlp/core/api/endpoint/remote.py b/openlp/core/api/endpoint/remote.py index 4741ada15..3a408f74e 100644 --- a/openlp/core/api/endpoint/remote.py +++ b/openlp/core/api/endpoint/remote.py @@ -27,7 +27,7 @@ from openlp.core.api.endpoint.core import TRANSLATED_STRINGS log = logging.getLogger(__name__) -remote_endpoint = Endpoint('remote', template_dir='remotes', static_dir='remotes') +remote_endpoint = Endpoint('remote', template_dir='remotes') @remote_endpoint.route('{view}') diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 252274d4d..c3f9011ad 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -151,8 +151,9 @@ class Registry(object): trace_error_handler(log) log.exception('Exception for function {function}'.format(function=function)) else: - trace_error_handler(log) - log.exception('Event {event} called but not registered'.format(event=event)) + if self._logger.getEffectiveLevel() == logging.DEBUG: + trace_error_handler(log) + log.exception('Event {event} called but not registered'.format(event=event)) return results def get_flag(self, key): From 0d1bbe3417029c097ec71d057869cbec701b56c6 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 9 Nov 2017 19:42:30 +0000 Subject: [PATCH 21/56] add preview to live --- openlp/core/ui/slidecontroller.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 80eb155e0..c6db1f1de 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -236,6 +236,9 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): self.hide_menu.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.hide_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Hide'), self.toolbar)) self.toolbar.add_toolbar_widget(self.hide_menu) + self.toolbar.add_toolbar_action('goPreview', icon=':/general/general_live.png', + tooltip=translate('OpenLP.SlideController', 'Move to preview.'), + triggers=self.on_go_preview) # The order of the blank to modes in Shortcuts list comes from here. self.desktop_screen_enable = create_action(self, 'desktopScreenEnable', text=translate('OpenLP.SlideController', 'Show Desktop'), @@ -1420,6 +1423,18 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): self.live_controller.add_service_manager_item(self.service_item, row) self.live_controller.preview_widget.setFocus() + def on_go_preview(self, field=None): + """ + If live copy slide item to preview controller from live Controller + """ + row = self.preview_widget.current_slide_number() + if -1 < row < self.preview_widget.slide_count(): + if self.service_item.from_service: + self.service_manager.preview_live(self.service_item.unique_identifier, row) + else: + self.preview_controller.add_service_manager_item(self.service_item, row) + self.preview_controller.preview_widget.setFocus() + def on_media_start(self, item): """ Respond to the arrival of a media service item From 7a345d577013e7661105acb24326bb5042df3d67 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 9 Nov 2017 19:55:01 +0000 Subject: [PATCH 22/56] fix code --- openlp/core/ui/slidecontroller.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index c6db1f1de..5b9872082 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1429,10 +1429,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): """ row = self.preview_widget.current_slide_number() if -1 < row < self.preview_widget.slide_count(): - if self.service_item.from_service: - self.service_manager.preview_live(self.service_item.unique_identifier, row) - else: - self.preview_controller.add_service_manager_item(self.service_item, row) + self.preview_controller.add_service_manager_item(self.service_item, row) self.preview_controller.preview_widget.setFocus() def on_media_start(self, item): From 536a187fb9864dd9785eef8947d27959a1cc97c5 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 9 Nov 2017 20:39:17 +0000 Subject: [PATCH 23/56] PEP8 --- openlp/core/lib/__init__.py | 1 + tests/functional/openlp_core/widgets/test_views.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 04d3b9e5e..9bab13b71 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -38,6 +38,7 @@ log = logging.getLogger(__name__ + '.__init__') SLIMCHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' + class ServiceItemContext(object): """ The context in which a Service Item is being generated diff --git a/tests/functional/openlp_core/widgets/test_views.py b/tests/functional/openlp_core/widgets/test_views.py index d931a5ef5..0fa028f11 100644 --- a/tests/functional/openlp_core/widgets/test_views.py +++ b/tests/functional/openlp_core/widgets/test_views.py @@ -627,4 +627,3 @@ class TestTreeWidgetWithDnD(TestCase): assert widget.allow_internal_dnd is False assert widget.indentation() == 0 assert widget.isAnimated() is True - From e0341ab3eb9590cf2ff294169ecfac94d1c2fff0 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 9 Nov 2017 21:24:38 +0000 Subject: [PATCH 24/56] fix tests --- openlp/core/common/registry.py | 2 +- .../openlp_core/ui/test_slidecontroller.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index c3f9011ad..4c9e92108 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -151,7 +151,7 @@ class Registry(object): trace_error_handler(log) log.exception('Exception for function {function}'.format(function=function)) else: - if self._logger.getEffectiveLevel() == logging.DEBUG: + if log.getEffectiveLevel() == logging.DEBUG: trace_error_handler(log) log.exception('Event {event} called but not registered'.format(event=event)) return results diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index b5f0d99ee..bc13f6db8 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -208,6 +208,33 @@ class TestSlideController(TestCase): mocked_on_theme_display.assert_called_once_with(False) mocked_on_hide_display.assert_called_once_with(False) + def test_on_go_live_preview_controller(self): + """ + Test that when the on_go_preview() method is called the message is sent to the preview controller and focus is + set correctly. + """ + # GIVEN: A new SlideController instance and plugin preview then pressing go live should respond + mocked_display = MagicMock() + mocked_preview_controller = MagicMock() + mocked_preview_widget = MagicMock() + mocked_service_item = MagicMock() + mocked_service_item.from_service = False + mocked_preview_widget.current_slide_number.return_value = 1 + mocked_preview_widget.slide_count = MagicMock(return_value=2) + mocked_preview_controller.preview_widget = MagicMock() + Registry.create() + Registry().register('preview_controller', mocked_preview_controller) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller.preview_widget = mocked_preview_widget + slide_controller.display = mocked_display + + # WHEN: on_go_live() is called + slide_controller.on_go_preview() + + # THEN: the preview controller should have the service item and the focus set to live + mocked_preview_controller.preview_widget.setFocus.assert_called_once_with() + def test_on_go_live_live_controller(self): """ Test that when the on_go_live() method is called the message is sent to the live controller and focus is From a408e3d74e3a069db96d9595332ff0a005370af0 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 9 Nov 2017 21:11:03 -0700 Subject: [PATCH 25/56] Fix some linting issues, and fix the test for newer versions of PyLint --- openlp/core/common/mixins.py | 14 ++++++++++++++ tests/utils/test_pylint.py | 14 +++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/openlp/core/common/mixins.py b/openlp/core/common/mixins.py index 1bc6907a0..a07940e10 100644 --- a/openlp/core/common/mixins.py +++ b/openlp/core/common/mixins.py @@ -101,6 +101,20 @@ class RegistryProperties(object): """ This adds registry components to classes to use at run time. """ + _application = None + _plugin_manager = None + _image_manager = None + _media_controller = None + _service_manager = None + _preview_controller = None + _live_controller = None + _main_window = None + _renderer = None + _theme_manager = None + _settings_form = None + _alerts_manager = None + _projector_manager = None + @property def application(self): """ diff --git a/tests/utils/test_pylint.py b/tests/utils/test_pylint.py index 128c0741b..50ca64db6 100644 --- a/tests/utils/test_pylint.py +++ b/tests/utils/test_pylint.py @@ -58,17 +58,21 @@ class TestPylint(TestCase): # GIVEN: Some checks to disable and enable, and the pylint script disabled_checks = 'import-error,no-member' enabled_checks = 'missing-format-argument-key,unused-format-string-argument,bad-format-string' - if is_win() or 'arch' in platform.dist()[0].lower(): - pylint_script = 'pylint' - else: - pylint_script = 'pylint3' + pylint_kwargs = { + 'return_std': True + } + if version < '1.7.0': + if is_win() or 'arch' in platform.dist()[0].lower(): + pylint_kwargs.update({'script': 'pylint'}) + else: + pylint_kwargs.update({'script': 'pylint3'}) # WHEN: Running pylint (pylint_stdout, pylint_stderr) = \ lint.py_run('openlp --errors-only --disable={disabled} --enable={enabled} ' '--reports=no --output-format=parseable'.format(disabled=disabled_checks, enabled=enabled_checks), - return_std=True, script=pylint_script) + **pylint_kwargs) stdout = pylint_stdout.read() stderr = pylint_stderr.read() filtered_stdout = self._filter_tolerated_errors(stdout) From 3d4ed035e9cef0730c39787dedc9fc3cbc72ce18 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 10 Nov 2017 03:59:38 -0800 Subject: [PATCH 26/56] PJLink2-L updates --- openlp/core/lib/__init__.py | 3 - .../{lib/projector => projectors}/__init__.py | 12 ++++ .../projector => projectors}/constants.py | 0 .../core/{lib/projector => projectors}/db.py | 4 +- .../{ui/projector => projectors}/editform.py | 4 +- .../{ui/projector => projectors}/manager.py | 15 +++-- .../{lib/projector => projectors}/pjlink.py | 6 +- .../sourceselectform.py | 4 +- .../core/{ui/projector => projectors}/tab.py | 2 +- .../{lib/projector => projectors}/upgrade.py | 0 openlp/core/ui/__init__.py | 7 ++- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/settingsform.py | 2 +- .../openlp_core/projectors}/__init__.py | 2 +- .../test_projector_constants.py | 4 +- .../{lib => projectors}/test_projector_db.py | 10 ++-- .../test_projector_pjlink_base.py | 7 +-- .../test_projector_pjlink_cmd_routing.py | 29 +++++----- .../test_projector_pjlink_commands.py | 57 +++++++++---------- .../openlp_core/ui/test_projectoreditform.py | 11 ++-- .../openlp_core/ui/test_projectormanager.py | 5 +- .../ui/test_projectorsourceform.py | 8 +-- 22 files changed, 97 insertions(+), 97 deletions(-) rename openlp/core/{lib/projector => projectors}/__init__.py (78%) rename openlp/core/{lib/projector => projectors}/constants.py (100%) rename openlp/core/{lib/projector => projectors}/db.py (99%) rename openlp/core/{ui/projector => projectors}/editform.py (99%) rename openlp/core/{ui/projector => projectors}/manager.py (98%) rename openlp/core/{lib/projector => projectors}/pjlink.py (99%) rename openlp/core/{ui/projector => projectors}/sourceselectform.py (99%) rename openlp/core/{ui/projector => projectors}/tab.py (99%) rename openlp/core/{lib/projector => projectors}/upgrade.py (100%) rename {openlp/core/ui/projector => tests/functional/openlp_core/projectors}/__init__.py (96%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_constants.py (93%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_db.py (98%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_pjlink_base.py (97%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_pjlink_cmd_routing.py (90%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_pjlink_commands.py (96%) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 0f4078420..0b26fcaf4 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -620,6 +620,3 @@ from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css, build_chords_css from .imagemanager import ImageManager from .mediamanageritem import MediaManagerItem -from .projector.db import ProjectorDB, Projector -from .projector.pjlink import PJLink -from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING diff --git a/openlp/core/lib/projector/__init__.py b/openlp/core/projectors/__init__.py similarity index 78% rename from openlp/core/lib/projector/__init__.py rename to openlp/core/projectors/__init__.py index dc7d2c89d..a3f052d9b 100644 --- a/openlp/core/lib/projector/__init__.py +++ b/openlp/core/projectors/__init__.py @@ -25,10 +25,22 @@ Initialization for the openlp.core.ui.projector modules. """ +__all__ = ['PJLINK_PORT', 'ERROR_MSG', 'ERROR_STRING', 'DialogSourceStyle', 'PJLink', 'Projector', + 'ProjectorDB', 'ProjectorEditForm', 'ProjectorManager', 'ProjectorTab'] + +# Due to circular dependencies, put the imports after defines class DialogSourceStyle(object): """ An enumeration for projector dialog box type. """ Tabbed = 0 Single = 1 + + +from .constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING +from .db import Projector, ProjectorDB +from .editform import ProjectorEditForm +from .manager import ProjectorManager +from .pjlink import PJLink +from .tab import ProjectorTab diff --git a/openlp/core/lib/projector/constants.py b/openlp/core/projectors/constants.py similarity index 100% rename from openlp/core/lib/projector/constants.py rename to openlp/core/projectors/constants.py diff --git a/openlp/core/lib/projector/db.py b/openlp/core/projectors/db.py similarity index 99% rename from openlp/core/lib/projector/db.py rename to openlp/core/projectors/db.py index fa8934ae2..99fe9515b 100644 --- a/openlp/core/lib/projector/db.py +++ b/openlp/core/projectors/db.py @@ -43,8 +43,8 @@ from sqlalchemy.ext.declarative import declarative_base, declared_attr from sqlalchemy.orm import relationship from openlp.core.lib.db import Manager, init_db, init_url -from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES -from openlp.core.lib.projector import upgrade +from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES +from openlp.core.projectors import upgrade Base = declarative_base(MetaData()) diff --git a/openlp/core/ui/projector/editform.py b/openlp/core/projectors/editform.py similarity index 99% rename from openlp/core/ui/projector/editform.py rename to openlp/core/projectors/editform.py index bd3267665..4ae1f96d9 100644 --- a/openlp/core/ui/projector/editform.py +++ b/openlp/core/projectors/editform.py @@ -30,8 +30,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common import verify_ip_address from openlp.core.common.i18n import translate from openlp.core.lib import build_icon -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.constants import PJLINK_PORT +from openlp.core.projectors.db import Projector +from openlp.core.projectors.constants import PJLINK_PORT log = logging.getLogger(__name__) log.debug('editform loaded') diff --git a/openlp/core/ui/projector/manager.py b/openlp/core/projectors/manager.py similarity index 98% rename from openlp/core/ui/projector/manager.py rename to openlp/core/projectors/manager.py index 0770886e4..0e00d602d 100644 --- a/openlp/core/ui/projector/manager.py +++ b/openlp/core/projectors/manager.py @@ -34,14 +34,14 @@ from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.registry import RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib.ui import create_widget_action -from openlp.core.lib.projector import DialogSourceStyle -from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \ +from openlp.core.projectors import DialogSourceStyle +from openlp.core.projectors.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \ E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \ S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP -from openlp.core.lib.projector.db import ProjectorDB -from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP -from openlp.core.ui.projector.editform import ProjectorEditForm -from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle +from openlp.core.projectors.db import ProjectorDB +from openlp.core.projectors.pjlink import PJLink, PJLinkUDP +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.sourceselectform import SourceSelectTabs, SourceSelectSingle from openlp.core.widgets.toolbar import OpenLPToolbar log = logging.getLogger(__name__) @@ -518,7 +518,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM projector.thread.quit() new_list = [] for item in self.projector_list: - if item.link.dbid == projector.link.dbid: + if item.link.db_item.id == projector.link.db_item.id: continue new_list.append(item) self.projector_list = new_list @@ -730,7 +730,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM thread.started.connect(item.link.thread_started) thread.finished.connect(item.link.thread_stopped) thread.finished.connect(thread.deleteLater) - item.link.projectorNetwork.connect(self.update_status) item.link.changeStatus.connect(self.update_status) item.link.projectorAuthentication.connect(self.authentication_error) item.link.projectorNoAuthentication.connect(self.no_authentication_error) diff --git a/openlp/core/lib/projector/pjlink.py b/openlp/core/projectors/pjlink.py similarity index 99% rename from openlp/core/lib/projector/pjlink.py rename to openlp/core/projectors/pjlink.py index 2272b971f..38013097f 100644 --- a/openlp/core/lib/projector/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -54,7 +54,7 @@ from PyQt5 import QtCore, QtNetwork from openlp.core.common import qmd5_hash from openlp.core.common.i18n import translate -from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \ +from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \ E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \ PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \ @@ -520,7 +520,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ # Signals sent by this module changeStatus = QtCore.pyqtSignal(str, int, str) - projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity projectorStatus = QtCore.pyqtSignal(int) # Status update projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed @@ -846,7 +845,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) return self.receive_data_signal() self.socket_timer.stop() - self.projectorNetwork.emit(S_NETWORK_RECEIVED) return self.get_data(buff=read, ip=self.ip) def get_data(self, buff, ip): @@ -925,7 +923,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): if cmd not in PJLINK_VALID_CMD: log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip)) return - self.projectorNetwork.emit(S_NETWORK_SENDING) log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip, command=cmd, data=opts, @@ -996,7 +993,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue)) self.socket_timer.start() - self.projectorNetwork.emit(S_NETWORK_SENDING) sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii'))) self.waitForBytesWritten(2000) # 2 seconds should be enough if sent == -1: diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/projectors/sourceselectform.py similarity index 99% rename from openlp/core/ui/projector/sourceselectform.py rename to openlp/core/projectors/sourceselectform.py index 0c150d25b..aaf8170ac 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/projectors/sourceselectform.py @@ -31,8 +31,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common import is_macosx from openlp.core.common.i18n import translate from openlp.core.lib import build_icon -from openlp.core.lib.projector.db import ProjectorSource -from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES +from openlp.core.projectors.db import ProjectorSource +from openlp.core.projectors.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES log = logging.getLogger(__name__) diff --git a/openlp/core/ui/projector/tab.py b/openlp/core/projectors/tab.py similarity index 99% rename from openlp/core/ui/projector/tab.py rename to openlp/core/projectors/tab.py index b7c2e5dda..29e6a9511 100644 --- a/openlp/core/ui/projector/tab.py +++ b/openlp/core/projectors/tab.py @@ -29,7 +29,7 @@ from PyQt5 import QtWidgets from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab -from openlp.core.lib.projector import DialogSourceStyle +from openlp.core.projectors import DialogSourceStyle log = logging.getLogger(__name__) log.debug('projectortab module loaded') diff --git a/openlp/core/lib/projector/upgrade.py b/openlp/core/projectors/upgrade.py similarity index 100% rename from openlp/core/lib/projector/upgrade.py rename to openlp/core/projectors/upgrade.py diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 2b8383755..2bf3e4e47 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -115,9 +115,10 @@ from .formattingtagcontroller import FormattingTagController from .shortcutlistform import ShortcutListForm from .servicemanager import ServiceManager from .thememanager import ThemeManager -from .projector.manager import ProjectorManager -from .projector.tab import ProjectorTab -from .projector.editform import ProjectorEditForm + +from openlp.core.projectors import ProjectorManager +from openlp.core.projectors import ProjectorTab +from openlp.core.projectors import ProjectorEditForm __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm', 'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer', diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 5fa9b2a2f..dd07357f9 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -55,7 +55,7 @@ from openlp.core.widgets.dialogs import FileDialog from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm -from openlp.core.ui.projector.manager import ProjectorManager +from openlp.core.projectors import ProjectorManager from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet from openlp.core.version import get_version diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index e7a86bac2..54bd01935 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -32,7 +32,7 @@ from openlp.core.common.registry import Registry from openlp.core.lib import build_icon from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab from openlp.core.ui.media import PlayerTab -from openlp.core.ui.projector.tab import ProjectorTab +from openlp.core.projectors import ProjectorTab from openlp.core.ui.settingsdialog import Ui_SettingsDialog log = logging.getLogger(__name__) diff --git a/openlp/core/ui/projector/__init__.py b/tests/functional/openlp_core/projectors/__init__.py similarity index 96% rename from openlp/core/ui/projector/__init__.py rename to tests/functional/openlp_core/projectors/__init__.py index eb20e9232..7efaa18af 100644 --- a/openlp/core/ui/projector/__init__.py +++ b/tests/functional/openlp_core/projectors/__init__.py @@ -20,5 +20,5 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The Projector driver module. +Module-level functions for the functional test suite """ diff --git a/tests/functional/openlp_core/lib/test_projector_constants.py b/tests/functional/openlp_core/projectors/test_projector_constants.py similarity index 93% rename from tests/functional/openlp_core/lib/test_projector_constants.py rename to tests/functional/openlp_core/projectors/test_projector_constants.py index 90fee1e13..ed1afa677 100644 --- a/tests/functional/openlp_core/lib/test_projector_constants.py +++ b/tests/functional/openlp_core/projectors/test_projector_constants.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.constants package. +Package to test the openlp.core.projectors.constants module. """ from unittest import TestCase @@ -37,7 +37,7 @@ class TestProjectorConstants(TestCase): from tests.resources.projector.data import TEST_VIDEO_CODES # WHEN: Import projector PJLINK_DEFAULT_CODES - from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES + from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES # THEN: Verify dictionary was build correctly self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match') diff --git a/tests/functional/openlp_core/lib/test_projector_db.py b/tests/functional/openlp_core/projectors/test_projector_db.py similarity index 98% rename from tests/functional/openlp_core/lib/test_projector_db.py rename to tests/functional/openlp_core/projectors/test_projector_db.py index bcbc9c547..1dfe47a54 100644 --- a/tests/functional/openlp_core/lib/test_projector_db.py +++ b/tests/functional/openlp_core/projectors/test_projector_db.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.ui.projectordb find, edit, delete +Package to test the openlp.core.projectors.db module. record functions. PREREQUISITE: add_record() and get_all() functions validated. @@ -32,10 +32,10 @@ from tempfile import mkdtemp from unittest import TestCase from unittest.mock import patch -from openlp.core.lib.projector import upgrade from openlp.core.lib.db import upgrade_db -from openlp.core.lib.projector.constants import PJLINK_PORT -from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source +from openlp.core.projectors import upgrade +from openlp.core.projectors.constants import PJLINK_PORT +from openlp.core.projectors.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA from tests.utils.constants import TEST_RESOURCES_PATH @@ -129,7 +129,7 @@ class TestProjectorDB(TestCase): """ Test case for ProjectorDB """ - @patch('openlp.core.lib.projector.db.init_url') + @patch('openlp.core.projectors.db.init_url') def setUp(self, mocked_init_url): """ Set up anything necessary for all tests diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py similarity index 97% rename from tests/functional/openlp_core/lib/test_projector_pjlink_base.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 578f37ede..8c0c17955 100644 --- a/tests/functional/openlp_core/lib/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -20,14 +20,13 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.pjlink base package. +Package to test the openlp.core.projectors.pjlink base package. """ from unittest import TestCase from unittest.mock import call, patch, MagicMock -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.pjlink import PJLink -from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED +from openlp.core.projectors import PJLink, Projector +from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py similarity index 90% rename from tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index 006abfff6..92dd24f82 100644 --- a/tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -20,20 +20,19 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.pjlink class command routing. +Package to test the openlp.core.projectors.pjlink command routing. """ from unittest import TestCase from unittest.mock import patch, MagicMock -import openlp.core.lib.projector.pjlink -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.pjlink import PJLink -from openlp.core.lib.projector.constants import PJLINK_ERRORS, \ +import openlp.core.projectors.pjlink +from openlp.core.projectors import PJLink, Projector +from openlp.core.projectors.constants import PJLINK_ERRORS, \ E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED ''' -from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ +from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON ''' from tests.resources.projector.data import TEST_PIN, TEST1_DATA @@ -46,7 +45,7 @@ class TestPJLinkRouting(TestCase): """ Tests for the PJLink module command routing """ - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_call_clss(self, mock_log): """ Test process_command calls proper function @@ -66,7 +65,7 @@ class TestPJLinkRouting(TestCase): mock_process_clss.assert_called_with('1') @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err1(self, mock_log, mock_change_status): """ Test ERR1 - Undefined projector function @@ -85,7 +84,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_with(log_text) @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err2(self, mock_log, mock_change_status): """ Test ERR2 - Parameter Error @@ -104,7 +103,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_with(log_text) @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err3(self, mock_log, mock_change_status): """ Test ERR3 - Unavailable error @@ -123,7 +122,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_with(log_text) @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err4(self, mock_log, mock_change_status): """ Test ERR3 - Unavailable error @@ -144,7 +143,7 @@ class TestPJLinkRouting(TestCase): @patch.object(pjlink_test, 'projectorAuthentication') @patch.object(pjlink_test, 'change_status') @patch.object(pjlink_test, 'disconnect_from_host') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate): """ Test ERRA - Authentication Error @@ -163,7 +162,7 @@ class TestPJLinkRouting(TestCase): mock_change_status.assert_called_once_with(E_AUTHENTICATION) mock_log.error.assert_called_with(log_text) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_future(self, mock_log): """ Test command valid but no method to process yet @@ -184,7 +183,7 @@ class TestPJLinkRouting(TestCase): mock_log.warning.assert_called_once_with(log_text) @patch.object(pjlink_test, 'pjlink_functions') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_invalid(self, mock_log, mock_functions): """ Test not a valid command @@ -203,7 +202,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_once_with(log_text) @patch.object(pjlink_test, 'pjlink_functions') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_ok(self, mock_log, mock_functions): """ Test command returned success diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py similarity index 96% rename from tests/functional/openlp_core/lib/test_projector_pjlink_commands.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py index 143206d0a..4907005ee 100644 --- a/tests/functional/openlp_core/lib/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py @@ -20,15 +20,14 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.pjlink commands package. +Package to test the openlp.core.projectors.pjlink commands package. """ from unittest import TestCase from unittest.mock import patch -import openlp.core.lib.projector.pjlink -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.pjlink import PJLink -from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ +import openlp.core.projectors.pjlink +from openlp.core.projectors import PJLink, Projector +from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ PJLINK_POWR_STATUS, \ E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \ S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY @@ -50,7 +49,7 @@ class TestPJLinkCommands(TestCase): Tests for the PJLink module """ @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_error(self, mock_log, mock_change_status): """ Test change_status with connection error @@ -74,7 +73,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_status_connecting(self, mock_log, mock_change_status): """ Test change_status with connection status @@ -97,7 +96,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_status_connected(self, mock_log, mock_change_status): """ Test change_status with connection status @@ -120,7 +119,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_status_with_message(self, mock_log, mock_change_status): """ Test change_status with connection status @@ -144,7 +143,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_av_mute_status(self, mock_log, mock_send_command): """ Test sending command to retrieve shutter/audio state @@ -164,7 +163,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_available_inputs(self, mock_log, mock_send_command): """ Test sending command to retrieve avaliable inputs @@ -184,7 +183,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_error_status(self, mock_log, mock_send_command): """ Test sending command to retrieve projector error status @@ -204,7 +203,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_input_source(self, mock_log, mock_send_command): """ Test sending command to retrieve current input @@ -224,7 +223,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_lamp_status(self, mock_log, mock_send_command): """ Test sending command to retrieve lamp(s) status @@ -244,7 +243,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_manufacturer(self, mock_log, mock_send_command): """ Test sending command to retrieve manufacturer name @@ -264,7 +263,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_model(self, mock_log, mock_send_command): """ Test sending command to get model information @@ -284,7 +283,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_name(self, mock_log, mock_send_command): """ Test sending command to get user-assigned name @@ -304,7 +303,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_other_info(self, mock_log, mock_send_command): """ Test sending command to retrieve other information @@ -324,7 +323,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_power_status(self, mock_log, mock_send_command): """ Test sending command to retrieve current power state @@ -599,7 +598,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.pjlink_class, '2', 'Non-standard class reply should have set class=2') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_clss_invalid_nan(self, mock_log): """ Test CLSS reply has no class number @@ -616,7 +615,7 @@ class TestPJLinkCommands(TestCase): 'Non-standard class reply should have set class=1') mock_log.error.assert_called_once_with(log_text) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_clss_invalid_no_version(self, mock_log): """ Test CLSS reply has no class number @@ -648,7 +647,7 @@ class TestPJLinkCommands(TestCase): # THEN: PJLink instance errors should be None self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_erst_data_invalid_length(self, mock_log): """ Test test_projector_process_erst_data_invalid_length @@ -666,7 +665,7 @@ class TestPJLinkCommands(TestCase): self.assertTrue(mock_log.warning.called, 'Warning should have been logged') mock_log.warning.assert_called_once_with(log_text) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_erst_data_invalid_nan(self, mock_log): """ Test test_projector_process_erst_data_invalid_nan @@ -764,7 +763,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"') @patch.object(pjlink_test, 'projectorUpdateIcons') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_inst(self, mock_log, mock_UpdateIcons): """ Test saving video source available information @@ -787,7 +786,7 @@ class TestPJLinkCommands(TestCase): mock_log.debug.assert_called_once_with(log_data) self.assertTrue(mock_UpdateIcons.emit.called, 'Update Icons should have been called') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_lamp_invalid(self, mock_log): """ Test status multiple lamp on/off and hours @@ -858,7 +857,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.lamp[0]['Hours'], 22222, 'Lamp hours should have been set to 22222') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_name(self, mock_log): """ Test saving NAME data from projector @@ -1040,7 +1039,7 @@ class TestPJLinkCommands(TestCase): self.assertNotEquals(pjlink.serial_no, test_number, 'Projector serial number should NOT have been set') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_sver(self, mock_log): """ Test invalid software version information - too long @@ -1061,7 +1060,7 @@ class TestPJLinkCommands(TestCase): self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed') mock_log.debug.assert_called_once_with(test_log) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_sver_changed(self, mock_log): """ Test invalid software version information - Received different than saved @@ -1086,7 +1085,7 @@ class TestPJLinkCommands(TestCase): # There was 4 calls, but only the last one is checked with this method mock_log.warning.assert_called_with(test_log) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_sver_invalid(self, mock_log): """ Test invalid software version information - too long diff --git a/tests/interfaces/openlp_core/ui/test_projectoreditform.py b/tests/interfaces/openlp_core/ui/test_projectoreditform.py index fc261d9f5..3f328cb04 100644 --- a/tests/interfaces/openlp_core/ui/test_projectoreditform.py +++ b/tests/interfaces/openlp_core/ui/test_projectoreditform.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Interface tests to test the openlp.core.ui.projector.editform.ProjectorEditForm() +Interface tests to test the openlp.core.projectors.editform.ProjectorEditForm() class and methods. """ import os @@ -28,8 +28,7 @@ from unittest import TestCase from unittest.mock import patch from openlp.core.common.registry import Registry -from openlp.core.lib.projector.db import Projector, ProjectorDB -from openlp.core.ui import ProjectorEditForm +from openlp.core.projectors import Projector, ProjectorDB, ProjectorEditForm, ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA @@ -48,7 +47,7 @@ class TestProjectorEditForm(TestCase, TestMixin): self.setup_application() self.build_settings() Registry.create() - with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + with patch('openlp.core.projectors.db.init_url') as mocked_init_url: if os.path.exists(TEST_DB): os.unlink(TEST_DB) mocked_init_url.return_value = 'sqlite:///' + TEST_DB @@ -66,7 +65,7 @@ class TestProjectorEditForm(TestCase, TestMixin): del self.projector_form self.destroy_settings() - @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec') + @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec') def test_edit_form_add_projector(self, mocked_exec): """ Test projector edit form with no parameters creates a new entry. @@ -84,7 +83,7 @@ class TestProjectorEditForm(TestCase, TestMixin): self.assertTrue((item.ip is None and item.name is None), 'Projector edit form should have a new Projector() instance to edit') - @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec') + @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec') def test_edit_form_edit_projector(self, mocked_exec): """ Test projector edit form with existing projector entry diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py index ff95c4276..81c7ea863 100644 --- a/tests/interfaces/openlp_core/ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py @@ -27,8 +27,7 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from openlp.core.common.registry import Registry -from openlp.core.ui import ProjectorManager, ProjectorEditForm -from openlp.core.lib.projector.db import ProjectorDB +from openlp.core.projectors import ProjectorDB, ProjectorEditForm, ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB @@ -45,7 +44,7 @@ class TestProjectorManager(TestCase, TestMixin): self.build_settings() self.setup_application() Registry.create() - with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + with patch('openlp.core.projectors.db.init_url') as mocked_init_url: if os.path.exists(TEST_DB): os.unlink(TEST_DB) mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB diff --git a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py index 4b9e2f402..e8570ac7e 100644 --- a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py +++ b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py @@ -32,9 +32,9 @@ from unittest.mock import patch from PyQt5.QtWidgets import QDialog from openlp.core.common.registry import Registry -from openlp.core.lib.projector.db import ProjectorDB, Projector -from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES -from openlp.core.ui.projector.sourceselectform import source_group, SourceSelectSingle +from openlp.core.projectors.db import ProjectorDB, Projector +from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES +from openlp.core.projectors.sourceselectform import source_group, SourceSelectSingle from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB, TEST1_DATA @@ -58,7 +58,7 @@ class ProjectorSourceFormTest(TestCase, TestMixin): """ Test class for the Projector Source Select form module """ - @patch('openlp.core.lib.projector.db.init_url') + @patch('openlp.core.projectors.db.init_url') def setUp(self, mocked_init_url): """ Set up anything necessary for all tests From ac61fb5a4fff38a30c3bdd4fadff084112ca6d5b Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 10 Nov 2017 08:57:35 -0700 Subject: [PATCH 27/56] Make the heading line as long as the others --- scripts/jenkins_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 05c589ddd..706d808af 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -119,7 +119,7 @@ class JenkinsTrigger(object): Print the status information of the build triggered. """ print('Add this to your merge proposal:') - print('--------------------------------') + print('-' * 80) bzr = Popen(('bzr', 'revno'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() revno = raw_output.decode().strip() From a9692f83910b10ba75fbc4fdd0c329f0767580e7 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 10 Nov 2017 09:36:06 -0700 Subject: [PATCH 28/56] Print out the build info before the build starts with a WAITING status --- scripts/jenkins_script.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 706d808af..d0ba665b7 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -89,18 +89,18 @@ class JenkinsTrigger(object): """ Create the JenkinsTrigger instance. """ - self.build_number = {} + self.jobs = {} self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() self.server = Jenkins(JENKINS_URL, username=username, password=password) - def fetch_build_numbers(self): + def fetch_jobs(self): """ - Get the next build number from all the jobs + Get the job info for all the jobs """ for job_name in OpenLPJobs.Jobs: job_info = self.server.get_job_info(job_name) - self.build_number[job_name] = job_info['nextBuildNumber'] + self.jobs[job_name] = job_info def trigger_build(self): """ @@ -111,7 +111,7 @@ class JenkinsTrigger(object): # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) - self.fetch_build_numbers() + self.fetch_jobs() self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): @@ -162,8 +162,10 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ - self.current_build = self._get_build_info(job_name, self.build_number[job_name]) - print('{:<70} [RUNNING]'.format(self.current_build['url']), end='', flush=True) + job = self.jobs[job_name] + print('{:<70} [WAITING]'.format(job['url'] + '/' + job['nextBuildNumber']), end='', flush=True) + self.current_build = self._get_build_info(job_name, job[job_name]['nextBuildNumber']) + print('\b\b\b\b\b\b\b\b\b[RUNNING]', end='', flush=True) is_success = False while self.current_build['building'] is True: time.sleep(0.5) From dd2d1002ce188a3e1cf094a79ddfa9bc3d9e1c4f Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 10 Nov 2017 10:51:42 -0700 Subject: [PATCH 29/56] Fix up a couple of issues, and move URL creation to where the job info is pulled --- scripts/jenkins_script.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index d0ba665b7..a669de71e 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -101,6 +101,7 @@ class JenkinsTrigger(object): for job_name in OpenLPJobs.Jobs: job_info = self.server.get_job_info(job_name) self.jobs[job_name] = job_info + self.jobs[job_name]['nextBuildUrl'] = '{url}{nextBuildNumber}/'.format(**job_info) def trigger_build(self): """ @@ -163,13 +164,13 @@ class JenkinsTrigger(object): variables from the :class:`OpenLPJobs` class. """ job = self.jobs[job_name] - print('{:<70} [WAITING]'.format(job['url'] + '/' + job['nextBuildNumber']), end='', flush=True) - self.current_build = self._get_build_info(job_name, job[job_name]['nextBuildNumber']) + print('{:<70} [WAITING]'.format(job['nextBuildUrl']), end='', flush=True) + self.current_build = self._get_build_info(job_name, job['nextBuildNumber']) print('\b\b\b\b\b\b\b\b\b[RUNNING]', end='', flush=True) is_success = False while self.current_build['building'] is True: time.sleep(0.5) - self.current_build = self.server.get_build_info(job_name, self.build_number[job_name]) + self.current_build = self.server.get_build_info(job_name, job['nextBuildNumber']) result_string = self.current_build['result'] is_success = result_string == 'SUCCESS' if self.can_use_colour: From 1c760e44e5fe5be57e9bb921c61cbe2dfe3f253e Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 10 Nov 2017 22:50:04 +0000 Subject: [PATCH 30/56] Small modification to upgrade settings as per superflys request --- openlp/core/common/settings.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index aa0e4a6f6..b98929138 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -255,10 +255,7 @@ class Settings(QtCore.QSettings): ('core/logo file', 'core/logo file', [(str_to_path, None)]), ('presentations/last directory', 'presentations/last directory', [(str_to_path, None)]), ('images/last directory', 'images/last directory', [(str_to_path, None)]), - ('media/last directory', 'media/last directory', [(str_to_path, None)]) - ] - - __setting_upgrade_3__ = [ + ('media/last directory', 'media/last directory', [(str_to_path, None)]), ('songuasge/db password', 'songusage/db password', []), ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []) From b1d88ac4f7e6c98b8177ea68832e3389debfce62 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sat, 11 Nov 2017 16:36:50 +0000 Subject: [PATCH 31/56] test fix --- tests/functional/openlp_plugins/bibles/test_bibleimport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py index 1c13d8874..9b9a8fae8 100644 --- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py +++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py @@ -362,7 +362,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml - result = instance.parse_xml('file.tst') + result = instance.parse_xml(Path('file.tst')) # THEN: The result returned should contain the correct data, and should be an instance of eetree_Element assert etree.tostring(result) == b'\n
Test

data

tokeep
\n' \ From 9606b999760237e02f702e09be3ff783c89b1996 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Mon, 13 Nov 2017 20:07:20 +0000 Subject: [PATCH 32/56] Fix md5 thumbnail regression Fixes: https://launchpad.net/bugs/1692187 --- openlp/plugins/presentations/lib/mediaitem.py | 2 +- .../lib/presentationcontroller.py | 6 +++-- .../presentations/presentationplugin.py | 15 ++++++++++++ .../presentations/test_mediaitem.py | 24 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index b801597b1..04bec02a9 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -243,7 +243,7 @@ class PresentationMediaItem(MediaManagerItem): """ Clean up the files created such as thumbnails - :param openlp.core.common.path.Path file_path: File path of the presention to clean up after + :param openlp.core.common.path.Path file_path: File path of the presentation to clean up after :param bool clean_for_update: Only clean thumbnails if update is needed :rtype: None """ diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index dd099c130..bb424c9fa 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -139,7 +139,8 @@ class PresentationDocument(object): :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: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in + # get_temp_folder and PresentationPluginapp_startup is removed if Settings().value('presentations/thumbnail_scheme') == 'md5': folder = md5_hash(bytes(self.file_path)) else: @@ -153,7 +154,8 @@ class PresentationDocument(object): :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: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in + # get_thumbnail_folder and PresentationPluginapp_startup is removed if Settings().value('presentations/thumbnail_scheme') == 'md5': folder = md5_hash(bytes(self.file_path)) else: diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 7f3333049..5e32af7b6 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -31,6 +31,7 @@ from PyQt5 import QtCore from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import translate +from openlp.core.common.settings import Settings from openlp.core.lib import Plugin, StringContent, build_icon from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab @@ -136,6 +137,20 @@ class PresentationPlugin(Plugin): self.register_controllers(controller) return bool(self.controllers) + def app_startup(self): + """ + Perform tasks on application startup. + """ + # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in + # PresentationDocument.get_thumbnail_folder and PresentationDocument.get_temp_folder is removed + super().app_startup() + files_from_config = Settings().value('presentations/presentations files') + for file in files_from_config: + self.media_item.clean_up_thumbnails(file, clean_for_update=True) + self.media_item.list_view.clear() + Settings().setValue('presentations/thumbnail_scheme', 'md5') + self.media_item.validate_and_load(files_from_config) + @staticmethod def about(): """ diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 1116ce4cb..fd28b9b03 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -133,3 +133,27 @@ class TestMediaItem(TestCase, TestMixin): # THEN: doc.presentation_deleted should have been called since the presentation file did not exists. mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True) + + @patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup') + @patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item') + @patch('openlp.plugins.presentations.lib.mediaitem.Settings') + def test_search(self, mocked_settings, *unreferenced_mocks): + """ + Test that the search method finds the correct results + """ + # GIVEN: A mocked Settings class which returns a list of Path objects, + # and an instance of the PresentationMediaItem + path_1 = Path('some_dir', 'Impress_file_1') + path_2 = Path('some_other_dir', 'impress_file_2') + path_3 = Path('another_dir', 'ppt_file') + mocked_returned_settings = MagicMock() + mocked_returned_settings.value.return_value = [path_1, path_2, path_3] + mocked_settings.return_value = mocked_returned_settings + media_item = PresentationMediaItem(None, MagicMock(), None) + media_item.settings_section = '' + + # WHEN: Calling search + results = media_item.search('IMPRE', False) + + # THEN: The first two results should have been returned + assert results == [[str(path_1), 'Impress_file_1'], [str(path_2), 'impress_file_2']] From 4f769d56229abb4b1e1d3a95693a4000f76fe7a9 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Mon, 13 Nov 2017 21:31:54 +0000 Subject: [PATCH 33/56] Fix the handler passing False Fixes: https://launchpad.net/bugs/1672777 --- openlp/core/ui/servicemanager.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6d5c16fbd..3e1d0f544 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -216,7 +216,7 @@ class Ui_ServiceManager(object): text=translate('OpenLP.ServiceManager', 'Go Live'), icon=':/general/general_live.png', tooltip=translate('OpenLP.ServiceManager', 'Send the selected item to Live.'), category=UiStrings().Service, - triggers=self.make_live) + triggers=self.on_make_live_action_triggered) self.layout.addWidget(self.order_toolbar) # Connect up our signals and slots self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected) @@ -1730,6 +1730,15 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.service_items[item]['service_item'].update_theme(theme) self.regenerate_service_items(True) + def on_make_live_action_triggered(self, checked): + """ + Handle `make_live_action` when the action is triggered. + + :param bool checked: Not Used. + :rtype: None + """ + self.make_live() + def get_drop_position(self): """ Getter for drop_position. Used in: MediaManagerItem From 4e9fcf2e505dbcef742d50c7fe5f19ab23b44620 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Mon, 13 Nov 2017 21:40:49 +0000 Subject: [PATCH 34/56] Sort media items by natural order Fixes: https://launchpad.net/bugs/1625087 --- openlp/plugins/custom/lib/db.py | 6 +++--- openlp/plugins/images/lib/mediaitem.py | 12 ++++++------ openlp/plugins/media/lib/mediaitem.py | 6 +++--- openlp/plugins/presentations/lib/mediaitem.py | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openlp/plugins/custom/lib/db.py b/openlp/plugins/custom/lib/db.py index dc1f74567..6581ee5ae 100644 --- a/openlp/plugins/custom/lib/db.py +++ b/openlp/plugins/custom/lib/db.py @@ -26,7 +26,7 @@ the Custom plugin from sqlalchemy import Column, Table, types from sqlalchemy.orm import mapper -from openlp.core.common.i18n import get_locale_key +from openlp.core.common.i18n import get_natural_key from openlp.core.lib.db import BaseModel, init_db @@ -36,10 +36,10 @@ class CustomSlide(BaseModel): """ # By default sort the customs by its title considering language specific characters. def __lt__(self, other): - return get_locale_key(self.title) < get_locale_key(other.title) + return get_natural_key(self.title) < get_natural_key(other.title) def __eq__(self, other): - return get_locale_key(self.title) == get_locale_key(other.title) + return get_natural_key(self.title) == get_natural_key(other.title) def __hash__(self): """ diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index a4e76d51b..b678122a5 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -26,7 +26,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import delete_file, get_images_filter from openlp.core.common.applocation import AppLocation -from openlp.core.common.i18n import UiStrings, translate, get_locale_key +from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import Path, create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -271,7 +271,7 @@ class ImageMediaItem(MediaManagerItem): :param parent_group_id: The ID of the group that will be added recursively. """ image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id) - image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name)) + image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name)) folder_icon = build_icon(':/images/image_group.png') for image_group in image_groups: group = QtWidgets.QTreeWidgetItem() @@ -298,7 +298,7 @@ class ImageMediaItem(MediaManagerItem): combobox.clear() combobox.top_level_group_added = False image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id) - image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name)) + image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name)) for image_group in image_groups: combobox.addItem(prefix + image_group.group_name, image_group.id) self.fill_groups_combobox(combobox, image_group.id, prefix + ' ') @@ -355,7 +355,7 @@ class ImageMediaItem(MediaManagerItem): self.expand_group(open_group.id) # Sort the images by its filename considering language specific. # characters. - images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name)) + images.sort(key=lambda image_object: get_natural_key(image_object.file_path.name)) for image in images: log.debug('Loading image: {name}'.format(name=image.file_path)) file_name = image.file_path.name @@ -533,9 +533,9 @@ class ImageMediaItem(MediaManagerItem): group_items.append(item) if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames): image_items.append(item) - group_items.sort(key=lambda item: get_locale_key(item.text(0))) + group_items.sort(key=lambda item: get_natural_key(item.text(0))) target_group.addChildren(group_items) - image_items.sort(key=lambda item: get_locale_key(item.text(0))) + image_items.sort(key=lambda item: get_natural_key(item.text(0))) target_group.addChildren(image_items) def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index ed787166b..f0ec1dfe2 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -26,7 +26,7 @@ import os from PyQt5 import QtCore, QtWidgets from openlp.core.common.applocation import AppLocation -from openlp.core.common.i18n import UiStrings, translate, get_locale_key +from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import Path, path_to_str, create_paths from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry @@ -362,7 +362,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): :param media: The media :param target_group: """ - media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1])) + media.sort(key=lambda file_name: get_natural_key(os.path.split(str(file_name))[1])) for track in media: track_info = QtCore.QFileInfo(track) item_name = None @@ -404,7 +404,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): :return: The media list """ media_file_paths = Settings().value(self.settings_section + '/media files') - media_file_paths.sort(key=lambda file_path: get_locale_key(file_path.name)) + media_file_paths.sort(key=lambda file_path: get_natural_key(file_path.name)) if media_type == MediaType.Audio: extension = self.media_controller.audio_extensions_list else: diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 04bec02a9..f58ba4861 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -23,7 +23,7 @@ import logging from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common.i18n import UiStrings, translate, get_locale_key +from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -165,7 +165,7 @@ class PresentationMediaItem(MediaManagerItem): if not initial_load: self.main_window.display_progress_bar(len(file_paths)) # Sort the presentations by its filename considering language specific characters. - file_paths.sort(key=lambda file_path: get_locale_key(file_path.name)) + file_paths.sort(key=lambda file_path: get_natural_key(file_path.name)) for file_path in file_paths: if not initial_load: self.main_window.increment_progress_bar() From 1cb11832bdf4475690e45fa4f23648bff0541f93 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 14 Nov 2017 00:19:10 -0700 Subject: [PATCH 35/56] Added some tests for the settings upgrade and fixed a bug I had introduced. --- nose2.cfg | 5 + openlp/core/common/settings.py | 6 +- .../openlp_core/common/test_settings.py | 105 +++++++++++++++--- .../ui/media/vendor/test_mediainfoWrapper.py | 2 - 4 files changed, 96 insertions(+), 22 deletions(-) diff --git a/nose2.cfg b/nose2.cfg index 1f2e01126..ba02ed419 100644 --- a/nose2.cfg +++ b/nose2.cfg @@ -20,3 +20,8 @@ coverage-report = html [multiprocess] always-on = false processes = 4 + +[output-buffer] +always-on = True +stderr = True +stdout = False diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 6726624ef..3efac81d3 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -40,7 +40,7 @@ __version__ = 2 # Fix for bug #1014422. X11_BYPASS_DEFAULT = True -if is_linux(): +if is_linux(): # pragma: no cover # Default to False on Gnome. X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID')) # Default to False on Xfce. @@ -495,7 +495,7 @@ class Settings(QtCore.QSettings): # system. if not isinstance(old_keys, (tuple, list)): old_keys = [old_keys] - if not any([self.contains(old_key) for old_key in old_keys]): + if any([not self.contains(old_key) for old_key in old_keys]): log.warning('One of {} does not exist, skipping upgrade'.format(old_keys)) continue if new_key: @@ -521,7 +521,7 @@ class Settings(QtCore.QSettings): break self.setValue(new_key, new_value) [self.remove(old_key) for old_key in old_keys if old_key != new_key] - self.setValue('settings/version', version) + self.setValue('settings/version', version) def value(self, key): """ diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index d54a6a1e1..b1f9b0a96 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -23,9 +23,10 @@ Package to test the openlp.core.lib.settings package. """ from unittest import TestCase -from unittest.mock import patch +from unittest.mock import call, patch -from openlp.core.common.settings import Settings +from openlp.core.common import settings +from openlp.core.common.settings import Settings, media_players_conv from tests.helpers.testmixin import TestMixin @@ -47,10 +48,19 @@ class TestSettings(TestCase, TestMixin): """ self.destroy_settings() + def test_media_players_conv(self): + """Test the media players conversion function""" + # GIVEN: A list of media players + media_players = 'phonon,webkit,vlc' + + # WHEN: The media converter function is called + result = media_players_conv(media_players) + + # THEN: The list should have been converted correctly + assert result == 'system,webkit,vlc' + def test_settings_basic(self): - """ - Test the Settings creation and its default usage - """ + """Test the Settings creation and its default usage""" # GIVEN: A new Settings setup # WHEN reading a setting for the first time @@ -65,10 +75,28 @@ class TestSettings(TestCase, TestMixin): # THEN the new value is returned when re-read self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned') + def test_set_up_default_values(self): + """Test that the default values are updated""" + # GIVEN: A Settings object with defaults + # WHEN: set_up_default_values() is called + Settings.set_up_default_values() + + # THEN: The default values should have been added to the dictionary + assert 'advanced/default service name' in Settings.__default_settings__ + + def test_get_default_value(self): + """Test that the default value for a setting is returned""" + # GIVEN: A Settings class with a default value + Settings.__default_settings__['test/moo'] = 'baa' + + # WHEN: get_default_value() is called + result = Settings().get_default_value('test/moo') + + # THEN: The correct default value should be returned + assert result == 'baa' + def test_settings_override(self): - """ - Test the Settings creation and its override usage - """ + """Test the Settings creation and its override usage""" # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', @@ -88,9 +116,7 @@ class TestSettings(TestCase, TestMixin): self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') def test_settings_override_with_group(self): - """ - Test the Settings creation and its override usage - with groups - """ + """Test the Settings creation and its override usage - with groups""" # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', @@ -112,9 +138,7 @@ class TestSettings(TestCase, TestMixin): self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') def test_settings_nonexisting(self): - """ - Test the Settings on query for non-existing value - """ + """Test the Settings on query for non-existing value""" # GIVEN: A new Settings setup with self.assertRaises(KeyError) as cm: # WHEN reading a setting that doesn't exists @@ -124,9 +148,7 @@ class TestSettings(TestCase, TestMixin): self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception') def test_extend_default_settings(self): - """ - Test that the extend_default_settings method extends the default settings - """ + """Test that the extend_default_settings method extends the default settings""" # GIVEN: A patched __default_settings__ dictionary with patch.dict(Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True): @@ -138,3 +160,52 @@ class TestSettings(TestCase, TestMixin): self.assertEqual( Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2}) + + @patch('openlp.core.common.settings.QtCore.QSettings.contains') + @patch('openlp.core.common.settings.QtCore.QSettings.value') + @patch('openlp.core.common.settings.QtCore.QSettings.setValue') + @patch('openlp.core.common.settings.QtCore.QSettings.remove') + def test_upgrade_single_setting(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains): + """Test that the upgrade mechanism for settings works correctly for single value upgrades""" + # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones) + local_settings = Settings() + local_settings.__setting_upgrade_99__ = [ + ('single/value', 'single/new value', [(str, '')]) + ] + settings.__version__ = 99 + mocked_value.side_effect = [98, 10] + mocked_contains.return_value = True + + # WHEN: upgrade_settings() is called + local_settings.upgrade_settings() + + # THEN: The correct calls should have been made with the correct values + assert mocked_value.call_count == 2, 'Settings().value() should have been called twice' + assert mocked_value.call_args_list == [call('settings/version', 0), call('single/value')] + assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice' + assert mocked_setValue.call_args_list == [call('single/new value', '10'), call('settings/version', 99)] + mocked_contains.assert_called_once_with('single/value') + mocked_remove.assert_called_once_with('single/value') + + @patch('openlp.core.common.settings.QtCore.QSettings.contains') + @patch('openlp.core.common.settings.QtCore.QSettings.value') + @patch('openlp.core.common.settings.QtCore.QSettings.setValue') + @patch('openlp.core.common.settings.QtCore.QSettings.remove') + def test_upgrade_multiple_one_invalid(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains): + """Test that the upgrade mechanism for settings works correctly for multiple values where one is invalid""" + # GIVEN: A settings object with an upgrade step to take + local_settings = Settings() + local_settings.__setting_upgrade_99__ = [ + (['multiple/value 1', 'multiple/value 2'], 'single/new value', []) + ] + settings.__version__ = 99 + mocked_value.side_effect = [98, 10] + mocked_contains.side_effect = [True, False] + + # WHEN: upgrade_settings() is called + local_settings.upgrade_settings() + + # THEN: The correct calls should have been made with the correct values + mocked_value.assert_called_once_with('settings/version', 0) + mocked_setValue.assert_called_once_with('settings/version', 99) + assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')] diff --git a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py index f8fee253d..f2fb84c34 100644 --- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -29,10 +29,8 @@ from unittest import TestCase from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media')) - TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]] - class TestMediainfoWrapper(TestCase): def test_media_length(self): From 66bae6716cccd2f4f712c528c5f2f90702299ee4 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Tue, 14 Nov 2017 17:35:37 +0000 Subject: [PATCH 36/56] chage media replace background context icon, add replace and reset actions to image context menu Fixes: https://launchpad.net/bugs/1650358 --- openlp/plugins/images/lib/mediaitem.py | 14 ++++++++++++++ openlp/plugins/media/lib/mediaitem.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index b678122a5..de347f8ce 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -81,8 +81,12 @@ class ImageMediaItem(MediaManagerItem): self.add_group_action.setToolTip(UiStrings().AddGroupDot) self.replace_action.setText(UiStrings().ReplaceBG) self.replace_action.setToolTip(UiStrings().ReplaceLiveBG) + self.replace_action_context.setText(UiStrings().ReplaceBG) + self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBG) self.reset_action.setText(UiStrings().ResetBG) self.reset_action.setToolTip(UiStrings().ResetLiveBG) + self.reset_action_context.setText(UiStrings().ResetBG) + self.reset_action_context.setToolTip(UiStrings().ResetLiveBG) def required_icons(self): """ @@ -184,6 +188,13 @@ class ImageMediaItem(MediaManagerItem): self.list_view, text=translate('ImagePlugin', 'Add new image(s)'), icon=':/general/general_open.png', triggers=self.on_file_click) + create_widget_action(self.list_view, separator=True) + self.replace_action_context = create_widget_action( + self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_theme.png', + triggers=self.on_replace_click) + self.reset_action_context = create_widget_action( + self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png', + visible=False, triggers=self.on_reset_click) def add_start_header_bar(self): """ @@ -659,6 +670,7 @@ class ImageMediaItem(MediaManagerItem): Called to reset the Live background with the image selected. """ self.reset_action.setVisible(False) + self.reset_action_context.setVisible(False) self.live_controller.display.reset_image() def live_theme_changed(self): @@ -666,6 +678,7 @@ class ImageMediaItem(MediaManagerItem): Triggered by the change of theme in the slide controller. """ self.reset_action.setVisible(False) + self.reset_action_context.setVisible(False) def on_replace_click(self): """ @@ -683,6 +696,7 @@ class ImageMediaItem(MediaManagerItem): if file_path.exists(): if self.live_controller.display.direct_image(str(file_path), background): self.reset_action.setVisible(True) + self.reset_action_context.setVisible(True) else: critical_error_message_box( UiStrings().LiveBGError, diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index f0ec1dfe2..e47ed70e0 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -176,7 +176,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): def add_custom_context_actions(self): create_widget_action(self.list_view, separator=True) self.replace_action_context = create_widget_action( - self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_blank.png', + self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_theme.png', triggers=self.on_replace_click) self.reset_action_context = create_widget_action( self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png', From 2ccd6a01f00da6562e80c15b511a5aa6dfc6df4d Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Tue, 14 Nov 2017 22:13:38 +0000 Subject: [PATCH 37/56] Set the default filter in accordance to the current service file type Fixes: https://launchpad.net/bugs/1673251 --- openlp/core/ui/servicemanager.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 3e1d0f544..81edd4930 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -704,15 +704,19 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory') if directory_path: default_file_path = directory_path / default_file_path - # 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. lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)') packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)') - + if self._file_name.endswith('oszl'): + default_filter = lite_filter + else: + default_filter = packaged_filter + # 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. if self._file_name.endswith('oszl') or self.service_has_all_original_files: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, - '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter)) + '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter), + default_filter) else: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, From 4fbed73e958f8b62fc60d07013b01cf01572f647 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Tue, 14 Nov 2017 22:30:46 +0000 Subject: [PATCH 38/56] Typo fix --- openlp/plugins/presentations/lib/messagelistener.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 2bfd1b3ed..33f5e0693 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -191,7 +191,7 @@ class Controller(object): """ Based on the handler passed at startup triggers the previous slide event. """ - log.debug('Live = {live}, previous'.formta(live=self.is_live)) + log.debug('Live = {live}, previous'.format(live=self.is_live)) if not self.doc: return if not self.is_live: From d05e39fc70dfd37fc801e79ea4a07b6602355c1f Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 14 Nov 2017 23:55:57 -0700 Subject: [PATCH 39/56] Fix up test after merges --- tests/functional/openlp_core/common/test_settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index 825057326..5d7e88fbe 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -115,13 +115,14 @@ class TestSettings(TestCase, TestMixin): def test_save_existing_setting(self): """Test that saving an existing setting returns the new value""" # GIVEN: An existing setting + Settings().extend_default_settings({'test/existing value': None}) Settings().setValue('test/existing value', 'old value') # WHEN a new value is saved into config - Settings().setValue('test/extend', 'new value') + Settings().setValue('test/existing value', 'new value') # THEN the new value is returned when re-read - assert Settings().value('test/extend') == 'new value', 'The saved value should be returned' + assert Settings().value('test/existing value') == 'new value', 'The saved value should be returned' def test_settings_override_with_group(self): """Test the Settings creation and its override usage - with groups""" From 8c3fd99ed52ecff8a80d4fe01d414dffca2b95d5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 15 Nov 2017 00:03:35 -0700 Subject: [PATCH 40/56] Fix missing line --- .../openlp_core/ui/media/vendor/test_mediainfoWrapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py index f2fb84c34..6ec2431b9 100644 --- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -31,6 +31,7 @@ from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media')) TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]] + class TestMediainfoWrapper(TestCase): def test_media_length(self): From 35e13022a640d57fc3fa318ee763646f89271e79 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Wed, 15 Nov 2017 18:33:21 +0000 Subject: [PATCH 41/56] image media test fixes --- tests/functional/openlp_plugins/images/test_lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional/openlp_plugins/images/test_lib.py b/tests/functional/openlp_plugins/images/test_lib.py index 4dfef57da..877ad722d 100644 --- a/tests/functional/openlp_plugins/images/test_lib.py +++ b/tests/functional/openlp_plugins/images/test_lib.py @@ -173,12 +173,14 @@ class TestImageMediaItem(TestCase): """ # GIVEN: A mocked version of reset_action self.media_item.reset_action = MagicMock() + self.media_item.reset_action_context = MagicMock() # WHEN: on_reset_click is called self.media_item.on_reset_click() # THEN: the reset_action should be set visible, and the image should be reset self.media_item.reset_action.setVisible.assert_called_with(False) + self.media_item.reset_action_context.setVisible.assert_called_with(False) self.media_item.live_controller.display.reset_image.assert_called_with() @patch('openlp.plugins.images.lib.mediaitem.delete_file') From 3560b27bfb8c6629cbf7668c2e202a6f0929db6e Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Wed, 15 Nov 2017 21:58:19 +0000 Subject: [PATCH 42/56] Use the OLDXML output type for the new version of mediainfo Fixes: https://launchpad.net/bugs/1732348 --- .../core/ui/media/vendor/mediainfoWrapper.py | 20 +++++++++++++++---- .../presentations/lib/pdfcontroller.py | 2 -- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 6f270d46e..1b55fb4bb 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -25,10 +25,10 @@ information related to the rwquested media. """ import json import os -from subprocess import Popen +import re +from subprocess import Popen, check_output from tempfile import mkstemp -import six from bs4 import BeautifulSoup, NavigableString ENV_DICT = os.environ @@ -80,7 +80,7 @@ class Track(object): def to_data(self): data = {} - for k, v in six.iteritems(self.__dict__): + for k, v in self.__dict__.items(): if k != 'xml_dom_fragment': data[k] = v return data @@ -100,7 +100,11 @@ class MediaInfoWrapper(object): @staticmethod def parse(filename, environment=ENV_DICT): - command = ["mediainfo", "-f", "--Output=XML", filename] + if MediaInfoWrapper._version(): + format = 'OLDXML' + else: + format = 'XML' + command = ["mediainfo", "-f", "--Output={format}".format(format=format), filename] fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-") fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-") fp_out = os.fdopen(fileno_out, 'r+b') @@ -116,6 +120,14 @@ class MediaInfoWrapper(object): os.unlink(fname_err) return MediaInfoWrapper(xml_dom) + @staticmethod + def _version(): + # For now we're only intrested in knowing the new style version (yy.mm) + version_str = check_output(['mediainfo', '--version']) + return re.search(b'\d\d\.\d\d', version_str) + + + def _populate_tracks(self): if self.xml_dom is None: return diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 26eb87d85..715e4e3e7 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -19,8 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - -import os import logging import re from subprocess import check_output, CalledProcessError From 923dc1436e8955be767ba758d4c41f69df284434 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 15 Nov 2017 17:19:26 -0700 Subject: [PATCH 43/56] Remove monitor migration --- openlp/core/common/settings.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index afc75dd25..225feb4e1 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -62,30 +62,6 @@ def media_players_conv(string): return string -def upgrade_monitor(number, x_position, y_position, height, width, can_override, can_display_on_monitor): - """ - Upgrade them monitor setting from a few single entries to a composite JSON entry - - :param int number: The old monitor number - :param int x_position: The X position - :param int y_position: The Y position - :param bool can_override: Are the screen positions overridden - :param bool can_display_on_monitor: Can OpenLP display on the monitor - :returns dict: Dictionary with the new value - """ - return { - number: { - 'displayGeometry': { - 'x': x_position, - 'y': y_position, - 'height': height, - 'width': width - } - }, - 'canDisplayOnMonitor': can_display_on_monitor - } - - class Settings(QtCore.QSettings): """ Class to wrap QSettings. @@ -283,9 +259,7 @@ class Settings(QtCore.QSettings): ('media/last directory', 'media/last directory', [(str_to_path, None)]), ('songuasge/db password', 'songusage/db password', []), ('songuasge/db hostname', 'songusage/db hostname', []), - ('songuasge/db database', 'songusage/db database', []), - (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override', - 'core/display on monitor'], 'core/monitors', [(upgrade_monitor, [1, 0, 0, None, None, False, False])]) + ('songuasge/db database', 'songusage/db database', []) ] @staticmethod From fad67f733932f10c1ca0de91fb439f05a5554135 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 15 Nov 2017 22:03:19 -0700 Subject: [PATCH 44/56] Add some more tests --- nose2.cfg | 10 +-- .../openlp_core/common/test_settings.py | 74 +++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/nose2.cfg b/nose2.cfg index ba02ed419..ae73407d7 100644 --- a/nose2.cfg +++ b/nose2.cfg @@ -1,5 +1,5 @@ [unittest] -verbose = true +verbose = True plugins = nose2.plugins.mp [log-capture] @@ -9,16 +9,16 @@ filter = -nose log-level = ERROR [test-result] -always-on = true -descriptions = true +always-on = True +descriptions = True [coverage] -always-on = true +always-on = True coverage = openlp coverage-report = html [multiprocess] -always-on = false +always-on = False processes = 4 [output-buffer] diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index 5d7e88fbe..ff6ee8765 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -22,6 +22,7 @@ """ Package to test the openlp.core.lib.settings package. """ +from pathlib import Path from unittest import TestCase from unittest.mock import call, patch @@ -196,6 +197,32 @@ class TestSettings(TestCase, TestMixin): mocked_contains.assert_called_once_with('single/value') mocked_remove.assert_called_once_with('single/value') + @patch('openlp.core.common.settings.QtCore.QSettings.contains') + @patch('openlp.core.common.settings.QtCore.QSettings.value') + @patch('openlp.core.common.settings.QtCore.QSettings.setValue') + @patch('openlp.core.common.settings.QtCore.QSettings.remove') + def test_upgrade_setting_value(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains): + """Test that the upgrade mechanism for settings correctly uses the new value when it's not a function""" + # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones) + local_settings = Settings() + local_settings.__setting_upgrade_99__ = [ + ('values/old value', 'values/new value', [(True, 1)]) + ] + settings.__version__ = 99 + mocked_value.side_effect = [98, 1] + mocked_contains.return_value = True + + # WHEN: upgrade_settings() is called + local_settings.upgrade_settings() + + # THEN: The correct calls should have been made with the correct values + assert mocked_value.call_count == 2, 'Settings().value() should have been called twice' + assert mocked_value.call_args_list == [call('settings/version', 0), call('values/old value')] + assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice' + assert mocked_setValue.call_args_list == [call('values/new value', True), call('settings/version', 99)] + mocked_contains.assert_called_once_with('values/old value') + mocked_remove.assert_called_once_with('values/old value') + @patch('openlp.core.common.settings.QtCore.QSettings.contains') @patch('openlp.core.common.settings.QtCore.QSettings.value') @patch('openlp.core.common.settings.QtCore.QSettings.setValue') @@ -218,3 +245,50 @@ class TestSettings(TestCase, TestMixin): mocked_value.assert_called_once_with('settings/version', 0) mocked_setValue.assert_called_once_with('settings/version', 99) assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')] + + def test_can_upgrade(self): + """Test the Settings.can_upgrade() method""" + # GIVEN: A Settings object + local_settings = Settings() + + # WHEN: can_upgrade() is run + result = local_settings.can_upgrade() + + # THEN: The result should be True + assert result is True, 'The settings should be upgradeable' + + def test_convert_value_setting_none_str(self): + """Test the Settings._convert_value() method when a setting is None and the default value is a string""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value(None, 'string') + + # THEN: The result should be an empty string + assert result == '', 'The result should be an empty string' + + def test_convert_value_setting_none_list(self): + """Test the Settings._convert_value() method when a setting is None and the default value is a list""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value(None, [None]) + + # THEN: The result should be an empty list + assert result == [], 'The result should be an empty list' + + def test_convert_value_setting_json_Path(self): + """Test the Settings._convert_value() method when a setting is JSON and represents a Path object""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value('{"__Path__": ["openlp", "core"]}', None) + + # THEN: The result should be a Path object + assert isinstance(result, Path), 'The result should be a Path object' + + def test_convert_value_setting_bool_str(self): + """Test the Settings._convert_value() method when a setting is supposed to be a boolean""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value('false', True) + + # THEN: The result should be False + assert result is False, 'The result should be False' From ab810734d86489eb8721d5b95ad78db5a32cd378 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 16 Nov 2017 17:17:47 +0000 Subject: [PATCH 45/56] Rework mediainfo fix Fixes: https://launchpad.net/bugs/1732348 --- .../core/ui/media/vendor/mediainfoWrapper.py | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 1b55fb4bb..3045bf070 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -100,34 +100,12 @@ class MediaInfoWrapper(object): @staticmethod def parse(filename, environment=ENV_DICT): - if MediaInfoWrapper._version(): - format = 'OLDXML' - else: - format = 'XML' - command = ["mediainfo", "-f", "--Output={format}".format(format=format), filename] - fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-") - fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-") - fp_out = os.fdopen(fileno_out, 'r+b') - fp_err = os.fdopen(fileno_err, 'r+b') - p = Popen(command, stdout=fp_out, stderr=fp_err, env=environment) - p.wait() - fp_out.seek(0) - - xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(fp_out.read()) - fp_out.close() - fp_err.close() - os.unlink(fname_out) - os.unlink(fname_err) + xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) + if not xml.startswith(b' Date: Thu, 16 Nov 2017 17:43:17 +0000 Subject: [PATCH 46/56] Remove some unused imports --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 3045bf070..97d936d6b 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -25,9 +25,7 @@ information related to the rwquested media. """ import json import os -import re -from subprocess import Popen, check_output -from tempfile import mkstemp +from subprocess import check_output from bs4 import BeautifulSoup, NavigableString From c46cd951ef083696ea502bb7f5db9a254441e924 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Thu, 16 Nov 2017 15:53:53 -0800 Subject: [PATCH 47/56] Projector imports to fully-qualified, renamed projector dock --- openlp/core/projectors/__init__.py | 16 +++------------- openlp/core/ui/__init__.py | 6 +++--- openlp/core/ui/firsttimewizard.py | 4 ++-- openlp/core/ui/mainwindow.py | 6 +++--- openlp/core/ui/settingsform.py | 2 +- .../projectors/test_projector_pjlink_base.py | 3 ++- .../test_projector_pjlink_cmd_routing.py | 3 ++- .../projectors/test_projector_pjlink_commands.py | 3 ++- .../openlp_core/ui/test_projectoreditform.py | 4 +++- .../openlp_core/ui/test_projectormanager.py | 4 +++- 10 files changed, 24 insertions(+), 27 deletions(-) diff --git a/openlp/core/projectors/__init__.py b/openlp/core/projectors/__init__.py index a3f052d9b..396422902 100644 --- a/openlp/core/projectors/__init__.py +++ b/openlp/core/projectors/__init__.py @@ -20,27 +20,17 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ - :mod:`openlp.core.ui.projector` + :mod:`openlp.core.projectors` - Initialization for the openlp.core.ui.projector modules. + Initialization for the openlp.core.projectors modules. """ -__all__ = ['PJLINK_PORT', 'ERROR_MSG', 'ERROR_STRING', 'DialogSourceStyle', 'PJLink', 'Projector', - 'ProjectorDB', 'ProjectorEditForm', 'ProjectorManager', 'ProjectorTab'] +from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING -# Due to circular dependencies, put the imports after defines class DialogSourceStyle(object): """ An enumeration for projector dialog box type. """ Tabbed = 0 Single = 1 - - -from .constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING -from .db import Projector, ProjectorDB -from .editform import ProjectorEditForm -from .manager import ProjectorManager -from .pjlink import PJLink -from .tab import ProjectorTab diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 2bf3e4e47..5c2463e5d 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -116,9 +116,9 @@ from .shortcutlistform import ShortcutListForm from .servicemanager import ServiceManager from .thememanager import ThemeManager -from openlp.core.projectors import ProjectorManager -from openlp.core.projectors import ProjectorTab -from openlp.core.projectors import ProjectorEditForm +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.manager import ProjectorManager +from openlp.core.projectors.tab import ProjectorTab __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm', 'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer', diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index be893cd5e..5212479ef 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -261,8 +261,8 @@ class UiFirstTimeWizard(object): self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Alerts – Display informative messages while showing other slides')) self.projectors_check_box.setText(translate('OpenLP.FirstTimeWizard', - 'Projectors – Control PJLink compatible projects on your network' - ' from OpenLP')) + 'Projector Controller – Control PJLink compatible projects on your' + ' network from OpenLP')) self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection')) self.no_internet_page.setSubTitle( translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.')) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 1e1a56daa..c1d452193 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -48,6 +48,7 @@ from openlp.core.display.screens import ScreenList from openlp.core.display.renderer import Renderer from openlp.core.lib import PluginManager, ImageManager, PluginStatus, build_icon from openlp.core.lib.ui import create_action +from openlp.core.projectors.manager import ProjectorManager from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \ ShortcutListForm, FormattingTagForm, PreviewController from openlp.core.ui.firsttimeform import FirstTimeForm @@ -55,7 +56,6 @@ from openlp.core.widgets.dialogs import FileDialog from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm -from openlp.core.projectors import ProjectorManager from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet from openlp.core.version import get_version @@ -375,7 +375,7 @@ class Ui_MainWindow(object): self.media_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Library')) self.service_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Service')) self.theme_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Themes')) - self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projectors')) + self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projector Controller')) self.file_new_item.setText(translate('OpenLP.MainWindow', '&New Service')) self.file_new_item.setToolTip(UiStrings().NewService) self.file_new_item.setStatusTip(UiStrings().CreateService) @@ -407,7 +407,7 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', 'Import settings from a *.config file previously exported from ' 'this or another machine.')) self.settings_import_item.setText(translate('OpenLP.MainWindow', 'Settings')) - self.view_projector_manager_item.setText(translate('OpenLP.MainWindow', '&Projectors')) + self.view_projector_manager_item.setText(translate('OpenLP.MainWindow', '&Projector Controller')) self.view_projector_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Hide or show Projectors.')) self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow', 'Toggle visibility of the Projectors.')) diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 54bd01935..4d7c4ff6c 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -30,9 +30,9 @@ from openlp.core.api import ApiTab from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry from openlp.core.lib import build_icon +from openlp.core.projectors.tab import ProjectorTab from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab from openlp.core.ui.media import PlayerTab -from openlp.core.projectors import ProjectorTab from openlp.core.ui.settingsdialog import Ui_SettingsDialog log = logging.getLogger(__name__) diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 8c0c17955..75b32d8c1 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -25,8 +25,9 @@ Package to test the openlp.core.projectors.pjlink base package. from unittest import TestCase from unittest.mock import call, patch, MagicMock -from openlp.core.projectors import PJLink, Projector from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index 92dd24f82..431da0606 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -27,9 +27,10 @@ from unittest import TestCase from unittest.mock import patch, MagicMock import openlp.core.projectors.pjlink -from openlp.core.projectors import PJLink, Projector from openlp.core.projectors.constants import PJLINK_ERRORS, \ E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink ''' from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py index 4907005ee..584b63cf9 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py @@ -26,11 +26,12 @@ from unittest import TestCase from unittest.mock import patch import openlp.core.projectors.pjlink -from openlp.core.projectors import PJLink, Projector from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ PJLINK_POWR_STATUS, \ E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \ S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_PIN, TEST1_DATA diff --git a/tests/interfaces/openlp_core/ui/test_projectoreditform.py b/tests/interfaces/openlp_core/ui/test_projectoreditform.py index 3f328cb04..ec2539a29 100644 --- a/tests/interfaces/openlp_core/ui/test_projectoreditform.py +++ b/tests/interfaces/openlp_core/ui/test_projectoreditform.py @@ -28,7 +28,9 @@ from unittest import TestCase from unittest.mock import patch from openlp.core.common.registry import Registry -from openlp.core.projectors import Projector, ProjectorDB, ProjectorEditForm, ProjectorManager +from openlp.core.projectors.db import Projector, ProjectorDB +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.manager import ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py index e26a5cfe2..484d4d68a 100644 --- a/tests/interfaces/openlp_core/ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py @@ -27,7 +27,9 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from openlp.core.common.registry import Registry -from openlp.core.projectors import ProjectorDB, ProjectorEditForm, ProjectorManager +from openlp.core.projectors.db import ProjectorDB +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.manager import ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB From 9196db5af0d4c83370b8fcd4f27e39bdf843fa82 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sat, 18 Nov 2017 11:23:15 +0000 Subject: [PATCH 48/56] Pathlib refactors and test fixes --- openlp/core/api/deploy.py | 15 ++-- openlp/core/api/endpoint/controller.py | 11 +-- openlp/core/api/endpoint/pluginhelpers.py | 15 ++-- openlp/core/api/http/endpoint.py | 12 +-- openlp/core/api/http/wsgiapp.py | 8 +- openlp/core/common/path.py | 3 + openlp/core/display/renderer.py | 4 +- openlp/core/lib/__init__.py | 9 +- openlp/core/lib/plugin.py | 17 +++- openlp/core/ui/maindisplay.py | 2 + openlp/core/ui/mainwindow.py | 7 +- openlp/core/ui/thememanager.py | 13 +-- openlp/core/version.py | 11 +-- openlp/plugins/images/lib/mediaitem.py | 2 +- openlp/plugins/presentations/lib/mediaitem.py | 4 +- .../lib/presentationcontroller.py | 2 +- .../functional/openlp_core/api/test_deploy.py | 26 +++--- tests/functional/openlp_core/lib/test_lib.py | 86 ++++++++++--------- .../openlp_core/ui/test_thememanager.py | 8 +- .../presentations/test_pdfcontroller.py | 20 ++--- .../openlp_core/lib/test_pluginmanager.py | 7 +- 21 files changed, 143 insertions(+), 139 deletions(-) diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py index 0419b45db..b64cc40d5 100644 --- a/openlp/core/api/deploy.py +++ b/openlp/core/api/deploy.py @@ -22,7 +22,6 @@ """ Download and "install" the remote web client """ -import os from zipfile import ZipFile from openlp.core.common.applocation import AppLocation @@ -30,18 +29,18 @@ from openlp.core.common.registry import Registry from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size -def deploy_zipfile(app_root, zip_name): +def deploy_zipfile(app_root_path, zip_name): """ Process the downloaded zip file and add to the correct directory - :param zip_name: the zip file to be processed - :param app_root: the directory where the zip get expanded to + :param str zip_name: the zip file name to be processed + :param openlp.core.common.path.Path app_root_path: The directory to expand the zip to :return: None """ - zip_file = os.path.join(app_root, zip_name) - web_zip = ZipFile(zip_file) - web_zip.extractall(app_root) + zip_path = app_root_path / zip_name + web_zip = ZipFile(str(zip_path)) + web_zip.extractall(str(app_root_path)) def download_sha256(): @@ -67,4 +66,4 @@ def download_and_check(callback=None): if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip', AppLocation.get_section_data_path('remotes') / 'site.zip', sha256=sha256): - deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip') + deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'site.zip') diff --git a/openlp/core/api/endpoint/controller.py b/openlp/core/api/endpoint/controller.py index 8ecfdb732..13e8d1681 100644 --- a/openlp/core/api/endpoint/controller.py +++ b/openlp/core/api/endpoint/controller.py @@ -28,6 +28,7 @@ import json from openlp.core.api.http.endpoint import Endpoint from openlp.core.api.http import requires_auth from openlp.core.common.applocation import AppLocation +from openlp.core.common.path import Path from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import ItemCapabilities, create_thumb @@ -66,12 +67,12 @@ def controller_text(request): elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'): item['tag'] = str(index + 1) thumbnail_path = os.path.join('images', 'thumbnails', frame['title']) - full_thumbnail_path = str(AppLocation.get_data_path() / thumbnail_path) + full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path # Create thumbnail if it doesn't exists - if not os.path.exists(full_thumbnail_path): - create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False) - Registry().get('image_manager').add_image(full_thumbnail_path, frame['title'], None, 88, 88) - item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path) + if not full_thumbnail_path.exists(): + create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False) + Registry().get('image_manager').add_image(str(full_thumbnail_path), frame['title'], None, 88, 88) + item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path)) item['text'] = str(frame['title']) item['html'] = str(frame['title']) else: diff --git a/openlp/core/api/endpoint/pluginhelpers.py b/openlp/core/api/endpoint/pluginhelpers.py index 9377bde6a..b8f606fbc 100644 --- a/openlp/core/api/endpoint/pluginhelpers.py +++ b/openlp/core/api/endpoint/pluginhelpers.py @@ -19,7 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os import json import re import urllib @@ -103,7 +102,7 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli :param controller_name: which controller is requesting the image :param log: the logger object :param dimensions: the image size eg 88x88 - :param file_name: the file name of the image + :param str file_name: the file name of the image :param slide: the individual image name :return: """ @@ -124,12 +123,10 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli if controller_name and file_name: file_name = urllib.parse.unquote(file_name) if '..' not in file_name: # no hacking please + full_path = AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name if slide: - full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name / slide) - else: - full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name) - if os.path.exists(full_path): - path, just_file_name = os.path.split(full_path) - Registry().get('image_manager').add_image(full_path, just_file_name, None, width, height) - image = Registry().get('image_manager').get_image(full_path, just_file_name, width, height) + full_path = full_path / slide + if full_path.exists(): + Registry().get('image_manager').add_image(full_path, full_path.name, None, width, height) + image = Registry().get('image_manager').get_image(full_path, full_path.name, width, height) return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8') diff --git a/openlp/core/api/http/endpoint.py b/openlp/core/api/http/endpoint.py index fe2b11d9a..011b7e73a 100644 --- a/openlp/core/api/http/endpoint.py +++ b/openlp/core/api/http/endpoint.py @@ -22,8 +22,6 @@ """ The Endpoint class, which provides plugins with a way to serve their own portion of the API """ -import os - from mako.template import Template from openlp.core.common.applocation import AppLocation @@ -67,13 +65,17 @@ class Endpoint(object): def render_template(self, filename, **kwargs): """ Render a mako template + + :param str filename: The file name of the template to render. + :return: The rendered template object. + :rtype: mako.template.Template """ - root = str(AppLocation.get_section_data_path('remotes')) + root_path = AppLocation.get_section_data_path('remotes') if not self.template_dir: raise Exception('No template directory specified') - path = os.path.join(root, self.template_dir, filename) + path = root_path / self.template_dir / filename if self.static_dir: kwargs['static_url'] = '/{prefix}/static'.format(prefix=self.url_prefix) kwargs['static_url'] = kwargs['static_url'].replace('//', '/') kwargs['assets_url'] = '/assets' - return Template(filename=path, input_encoding='utf-8').render(**kwargs) + return Template(filename=str(path), input_encoding='utf-8').render(**kwargs) diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py index f948d4096..4c863b7cb 100644 --- a/openlp/core/api/http/wsgiapp.py +++ b/openlp/core/api/http/wsgiapp.py @@ -25,7 +25,6 @@ App stuff """ import json import logging -import os import re from webob import Request, Response @@ -138,12 +137,11 @@ class WSGIApplication(object): Add a static directory as a route """ if route not in self.static_routes: - root = str(AppLocation.get_section_data_path('remotes')) - static_path = os.path.abspath(os.path.join(root, static_dir)) - if not os.path.exists(static_path): + static_path = AppLocation.get_section_data_path('remotes') / static_dir + if not static_path.exists(): log.error('Static path "%s" does not exist. Skipping creating static route/', static_path) return - self.static_routes[route] = DirectoryApp(static_path) + self.static_routes[route] = DirectoryApp(str(static_path.resolve())) def dispatch(self, request): """ diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 6b89acfb5..05d2ea4eb 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -69,6 +69,9 @@ class Path(PathVariant): path = path.relative_to(base_path) return {'__Path__': path.parts} + def rmtree(self, *args, **kwargs): + shutil.rmtree(str(self), *args, **kwargs) + def replace_params(args, kwargs, params): """ diff --git a/openlp/core/display/renderer.py b/openlp/core/display/renderer.py index 9dd7f3d1f..836e5edea 100644 --- a/openlp/core/display/renderer.py +++ b/openlp/core/display/renderer.py @@ -198,6 +198,7 @@ class Renderer(RegistryBase, LogMixin, RegistryProperties): :param theme_data: The theme to generated a preview for. :param force_page: Flag to tell message lines per page need to be generated. + :rtype: QtGui.QPixmap """ # save value for use in format_slide self.force_page = force_page @@ -222,8 +223,7 @@ class Renderer(RegistryBase, LogMixin, RegistryProperties): self.display.build_html(service_item) raw_html = service_item.get_rendered_frame(0) self.display.text(raw_html, False) - preview = self.display.preview() - return preview + return self.display.preview() self.force_page = False def format_slide(self, text, item): diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 9bab13b71..49bae26eb 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -179,8 +179,9 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None): height of 88 is used. :return: The final icon. """ - ext = os.path.splitext(thumb_path)[1].lower() - reader = QtGui.QImageReader(image_path) + # TODO: To path object + thumb_path = Path(thumb_path) + reader = QtGui.QImageReader(str(image_path)) if size is None: # No size given; use default height of 88 if reader.size().isEmpty(): @@ -207,10 +208,10 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None): # Invalid; use default height of 88 reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) thumb = reader.read() - thumb.save(thumb_path, ext[1:]) + thumb.save(str(thumb_path), thumb_path.suffix[1:].lower()) if not return_icon: return - if os.path.exists(thumb_path): + if thumb_path.exists(): return build_icon(thumb_path) # Fallback for files with animation support. return build_icon(image_path) diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index f155b3ce7..7e3b2e416 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -139,10 +139,6 @@ class Plugin(QtCore.QObject, RegistryProperties): self.text_strings = {} self.set_plugin_text_strings() self.name_strings = self.text_strings[StringContent.Name] - if version: - self.version = version - else: - self.version = get_version()['version'] self.settings_section = self.name self.icon = None self.media_item_class = media_item_class @@ -162,6 +158,19 @@ class Plugin(QtCore.QObject, RegistryProperties): Settings.extend_default_settings(default_settings) Registry().register_function('{name}_add_service_item'.format(name=self.name), self.process_add_service_event) Registry().register_function('{name}_config_updated'.format(name=self.name), self.config_update) + self._setup(version) + + def _setup(self, version): + """ + Run some initial setup. This method is separate from __init__ in order to mock it out in tests. + + :param version: Defaults to *None*, which means that the same version number is used as OpenLP's version number. + :rtype: None + """ + if version: + self.version = version + else: + self.version = get_version()['version'] def check_pre_conditions(self): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index ca634910e..daf6a165c 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -398,6 +398,8 @@ class MainDisplay(Display, LogMixin, RegistryProperties): def preview(self): """ Generates a preview of the image displayed. + + :rtype: QtGui.QPixmap """ was_visible = self.isVisible() self.application.process_events() diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index c0e704afb..60fabdf58 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -296,10 +296,9 @@ class Ui_MainWindow(object): # Give QT Extra Hint that this is an About Menu Item self.about_item.setMenuRole(QtWidgets.QAction.AboutRole) if is_win(): - self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm') + self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / 'OpenLP.chm' elif is_macosx(): - self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), - '..', 'Resources', 'OpenLP.help') + self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / '..' / 'Resources' / 'OpenLP.help' self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png', can_shortcuts=True, category=UiStrings().Help, triggers=self.on_help_clicked) @@ -760,7 +759,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): Use the Online manual in other cases. (Linux) """ if is_macosx() or is_win(): - QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(self.local_help_file)) + QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(str(self.local_help_file))) else: import webbrowser webbrowser.open_new('http://manual.openlp.org/') diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 1b39e5fec..fa8a9e4d9 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -497,12 +497,12 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name) else: name = text_name - thumb = self.thumb_path / '{name}.png'.format(name=text_name) + thumb_path = self.thumb_path / '{name}.png'.format(name=text_name) item_name = QtWidgets.QListWidgetItem(name) - if validate_thumb(theme_path, thumb): - icon = build_icon(thumb) + if validate_thumb(theme_path, thumb_path): + icon = build_icon(thumb_path) else: - icon = create_thumb(str(theme_path), str(thumb)) + icon = create_thumb(theme_path, thumb_path) item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, text_name) self.theme_list_widget.addItem(item_name) @@ -692,7 +692,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R 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) + create_thumb(sample_path_name, thumb_path, False) def update_preview_images(self): """ @@ -710,7 +710,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R Call the renderer to build a Sample Image :param theme_data: The theme to generated a preview for. - :param force_page: Flag to tell message lines per page need to be generated. + :param force_page: Flag to tell message lines per page need to be generated.7 + :rtype: QtGui.QPixmap """ return self.renderer.generate_preview(theme_data, force_page) diff --git a/openlp/core/version.py b/openlp/core/version.py index 314c4865f..6ba1b9ecc 100644 --- a/openlp/core/version.py +++ b/openlp/core/version.py @@ -23,7 +23,6 @@ The :mod:`openlp.core.version` module downloads the version details for OpenLP. """ import logging -import os import platform import sys import time @@ -176,18 +175,12 @@ def get_version(): full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip()) else: # We're not running the development version, let's use the file. - file_path = str(AppLocation.get_directory(AppLocation.VersionDir)) - file_path = os.path.join(file_path, '.version') - version_file = None + file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version' try: - version_file = open(file_path, 'r') - full_version = str(version_file.read()).rstrip() + full_version = file_path.read_text().rstrip() except OSError: log.exception('Error in version file.') full_version = '0.0.0-bzr000' - finally: - if version_file: - version_file.close() bits = full_version.split('-') APPLICATION_VERSION = { 'full': full_version, diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index de347f8ce..552a63d44 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -377,7 +377,7 @@ class ImageMediaItem(MediaManagerItem): if validate_thumb(image.file_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(str(image.file_path), str(thumbnail_path)) + icon = create_thumb(image.file_path,thumbnail_path) item_name = QtWidgets.QTreeWidgetItem([file_name]) item_name.setText(0, file_name) item_name.setIcon(0, icon) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index f58ba4861..923953040 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -198,10 +198,10 @@ class PresentationMediaItem(MediaManagerItem): if not (preview_path and preview_path.exists()): icon = build_icon(':/general/general_delete.png') else: - if validate_thumb(Path(preview_path), Path(thumbnail_path)): + if validate_thumb(preview_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(str(preview_path), str(thumbnail_path)) + icon = create_thumb(preview_path, thumbnail_path) else: if initial_load: icon = build_icon(':/general/general_delete.png') diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index bb424c9fa..db5786626 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -267,7 +267,7 @@ class PresentationDocument(object): return if image_path.is_file(): thumb_path = self.get_thumbnail_path(index, False) - create_thumb(str(image_path), str(thumb_path), False, QtCore.QSize(-1, 360)) + create_thumb(image_path, thumb_path, False, QtCore.QSize(-1, 360)) def get_thumbnail_path(self, slide_no, check_exists=False): """ diff --git a/tests/functional/openlp_core/api/test_deploy.py b/tests/functional/openlp_core/api/test_deploy.py index be36fb9c7..702e2a35d 100644 --- a/tests/functional/openlp_core/api/test_deploy.py +++ b/tests/functional/openlp_core/api/test_deploy.py @@ -19,15 +19,13 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - -import os -import shutil from tempfile import mkdtemp from unittest import TestCase from openlp.core.api.deploy import deploy_zipfile +from openlp.core.common.path import Path, copyfile -TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources')) +TEST_PATH = (Path(__file__).parent / '..' / '..' / '..' / 'resources').resolve() class TestRemoteDeploy(TestCase): @@ -39,25 +37,25 @@ class TestRemoteDeploy(TestCase): """ Setup for tests """ - self.app_root = mkdtemp() + self.app_root_path = Path(mkdtemp()) def tearDown(self): """ Clean up after tests """ - shutil.rmtree(self.app_root) + self.app_root_path.rmtree() def test_deploy_zipfile(self): """ Remote Deploy tests - test the dummy zip file is processed correctly """ # GIVEN: A new downloaded zip file - aa = TEST_PATH - zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip') - app_root = os.path.join(self.app_root, 'site.zip') - shutil.copyfile(zip_file, app_root) - # WHEN: I process the zipfile - deploy_zipfile(self.app_root, 'site.zip') + zip_path = TEST_PATH / 'remotes' / 'site.zip' + app_root_path = self.app_root_path / 'site.zip' + copyfile(zip_path, app_root_path) - # THEN test if www directory has been created - self.assertTrue(os.path.isdir(os.path.join(self.app_root, 'www')), 'We should have a www directory') + # WHEN: I process the zipfile + deploy_zipfile(self.app_root_path, 'site.zip') + + # THEN: test if www directory has been created + self.assertTrue((self.app_root_path / 'www').is_dir(), 'We should have a www directory') diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 1352b5da5..67649fa49 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -274,32 +274,32 @@ class TestLib(TestCase): Test the create_thumb() function with a given size. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(10, 20) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created and scaled to the given size. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(thumb_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(thumb_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -308,14 +308,14 @@ class TestLib(TestCase): Test the create_thumb() function with no size specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') expected_size = QtCore.QSize(63, 88) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -326,14 +326,15 @@ class TestLib(TestCase): icon = create_thumb(image_path, thumb_path) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), + 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -342,33 +343,34 @@ class TestLib(TestCase): Test the create_thumb() function with invalid size specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(-1, -1) expected_size = QtCore.QSize(63, 88) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), + 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -377,33 +379,33 @@ class TestLib(TestCase): Test the create_thumb() function with a size of only width specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(100, -1) expected_size = QtCore.QSize(100, 137) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -412,33 +414,33 @@ class TestLib(TestCase): Test the create_thumb() function with a size of only height specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(-1, 100) expected_size = QtCore.QSize(72, 100) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -447,8 +449,8 @@ class TestLib(TestCase): Test the create_thumb() function with a size of only height specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(-1, 100) expected_size_1 = QtCore.QSize(88, 88) expected_size_2 = QtCore.QSize(100, 100) @@ -456,12 +458,12 @@ class TestLib(TestCase): # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -469,10 +471,10 @@ class TestLib(TestCase): icon = create_thumb(image_path, thumb_path, size=None) # THEN: Check if the thumb was created with aspect ratio of 1. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_1, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -482,11 +484,11 @@ class TestLib(TestCase): # THEN: Check if the thumb was created with aspect ratio of 1. self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_2, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass diff --git a/tests/functional/openlp_core/ui/test_thememanager.py b/tests/functional/openlp_core/ui/test_thememanager.py index 62bf980b4..f44f558b0 100644 --- a/tests/functional/openlp_core/ui/test_thememanager.py +++ b/tests/functional/openlp_core/ui/test_thememanager.py @@ -199,16 +199,16 @@ class TestThemeManager(TestCase): theme_manager._create_theme_from_xml = MagicMock() theme_manager.generate_and_save_image = MagicMock() theme_manager.theme_path = None - folder = Path(mkdtemp()) + folder_path = 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) + theme_manager.unzip_theme(theme_file, folder_path) # THEN: Files should be unpacked - self.assertTrue((folder / 'Moss on tree' / 'Moss on tree.xml').exists()) + self.assertTrue((folder_path / '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(str(folder)) + folder_path.rmtree() def test_unzip_theme_invalid_version(self): """ diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py index a7281e062..9bd492983 100644 --- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -67,10 +67,10 @@ class TestPdfController(TestCase, TestMixin): self.desktop.screenGeometry.return_value = SCREEN['size'] self.screens = ScreenList.create(self.desktop) Settings().extend_default_settings(__default_settings__) - self.temp_folder = Path(mkdtemp()) - self.thumbnail_folder = Path(mkdtemp()) + self.temp_folder_path = Path(mkdtemp()) + self.thumbnail_folder_path = Path(mkdtemp()) self.mock_plugin = MagicMock() - self.mock_plugin.settings_section = self.temp_folder + self.mock_plugin.settings_section = self.temp_folder_path def tearDown(self): """ @@ -78,8 +78,8 @@ class TestPdfController(TestCase, TestMixin): """ del self.screens self.destroy_settings() - shutil.rmtree(str(self.thumbnail_folder)) - shutil.rmtree(str(self.temp_folder)) + self.thumbnail_folder_path.rmtree() + self.temp_folder_path.rmtree() def test_constructor(self): """ @@ -105,8 +105,8 @@ class TestPdfController(TestCase, TestMixin): controller = PdfController(plugin=self.mock_plugin) if not controller.check_available(): raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') - controller.temp_folder = self.temp_folder - controller.thumbnail_folder = self.thumbnail_folder + controller.temp_folder = self.temp_folder_path + controller.thumbnail_folder = self.thumbnail_folder_path document = PdfDocument(controller, test_file) loaded = document.load_presentation() @@ -125,14 +125,14 @@ class TestPdfController(TestCase, TestMixin): controller = PdfController(plugin=self.mock_plugin) if not controller.check_available(): raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') - controller.temp_folder = self.temp_folder - controller.thumbnail_folder = self.thumbnail_folder + controller.temp_folder = self.temp_folder_path + controller.thumbnail_folder = self.thumbnail_folder_path document = PdfDocument(controller, test_file) loaded = document.load_presentation() # 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.') - image = QtGui.QImage(os.path.join(str(self.temp_folder), 'pdf_test1.pdf', 'mainslide001.png')) + image = QtGui.QImage(os.path.join(str(self.temp_folder_path), 'pdf_test1.pdf', 'mainslide001.png')) # Based on the converter used the resolution will differ a bit if controller.gsbin: self.assertEqual(760, image.height(), 'The height should be 760') diff --git a/tests/interfaces/openlp_core/lib/test_pluginmanager.py b/tests/interfaces/openlp_core/lib/test_pluginmanager.py index 2e7d979a6..588c15520 100644 --- a/tests/interfaces/openlp_core/lib/test_pluginmanager.py +++ b/tests/interfaces/openlp_core/lib/test_pluginmanager.py @@ -23,7 +23,6 @@ Package to test the openlp.core.lib.pluginmanager package. """ import sys -import shutil import gc from tempfile import mkdtemp from unittest import TestCase @@ -50,8 +49,8 @@ class TestPluginManager(TestCase, TestMixin): """ self.setup_application() self.build_settings() - self.temp_dir = Path(mkdtemp('openlp')) - Settings().setValue('advanced/data path', self.temp_dir) + self.temp_dir_path = Path(mkdtemp('openlp')) + Settings().setValue('advanced/data path', self.temp_dir_path) Registry.create() Registry().register('service_list', MagicMock()) self.main_window = QtWidgets.QMainWindow() @@ -64,7 +63,7 @@ class TestPluginManager(TestCase, TestMixin): # On windows we need to manually garbage collect to close sqlalchemy files # to avoid errors when temporary files are deleted. gc.collect() - shutil.rmtree(str(self.temp_dir)) + self.temp_dir_path.rmtree() @patch('openlp.plugins.songusage.lib.db.init_schema') @patch('openlp.plugins.songs.lib.db.init_schema') From f07d6e736c855fb3ae51644420af523da57fc960 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sat, 18 Nov 2017 22:37:24 +0000 Subject: [PATCH 49/56] Moar pathlib refactors --- openlp/core/api/endpoint/core.py | 12 ---- openlp/core/lib/mediamanageritem.py | 42 ++++++------- openlp/core/lib/pluginmanager.py | 3 +- openlp/core/ui/maindisplay.py | 4 +- openlp/core/ui/mainwindow.py | 59 ++++++++----------- .../presentations/presentationplugin.py | 9 +-- 6 files changed, 53 insertions(+), 76 deletions(-) diff --git a/openlp/core/api/endpoint/core.py b/openlp/core/api/endpoint/core.py index 2988e03aa..8b9bcc4c0 100644 --- a/openlp/core/api/endpoint/core.py +++ b/openlp/core/api/endpoint/core.py @@ -172,15 +172,3 @@ def main_image(request): 'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image)) } return {'results': result} - - -def get_content_type(file_name): - """ - Examines the extension of the file and determines what the content_type should be, defaults to text/plain - Returns the extension and the content_type - - :param file_name: name of file - """ - ext = os.path.splitext(file_name)[1] - content_type = FILE_TYPES.get(ext, 'text/plain') - return ext, content_type diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index cc884279c..8091b1a2e 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -318,10 +318,10 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): self, self.on_new_prompt, Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks) - log.info('New files(s) {file_paths}'.format(file_paths=file_paths)) + log.info('New file(s) {file_paths}'.format(file_paths=file_paths)) if file_paths: self.application.set_busy_cursor() - self.validate_and_load([path_to_str(path) for path in file_paths]) + self.validate_and_load(file_paths) self.application.set_normal_cursor() def load_file(self, data): @@ -330,23 +330,24 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): :param data: A dictionary containing the list of files to be loaded and the target """ - new_files = [] + new_file_paths = [] error_shown = False for file_name in data['files']: - file_type = file_name.split('.')[-1] - if file_type.lower() not in self.on_new_file_masks: + file_path = str_to_path(file_name) # TODO: + if file_path.suffix[1:].lower() not in self.on_new_file_masks: if not error_shown: - critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'), - translate('OpenLP.MediaManagerItem', - 'Invalid File {name}.\n' - 'Suffix not supported').format(name=file_name)) + critical_error_message_box( + translate('OpenLP.MediaManagerItem', 'Invalid File Type'), + translate('OpenLP.MediaManagerItem', + 'Invalid File {file_path}.\nFile extension not supported').format( + file_path=file_path)) error_shown = True else: - new_files.append(file_name) - if new_files: + new_file_paths.append(file_path) + if new_file_paths: if 'target' in data: - self.validate_and_load(new_files, data['target']) - self.validate_and_load(new_files) + self.validate_and_load(new_file_paths, data['target']) + self.validate_and_load(new_file_paths) def dnd_move_internal(self, target): """ @@ -356,31 +357,30 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): """ pass - def validate_and_load(self, files, target_group=None): + def validate_and_load(self, file_paths, target_group=None): """ Process a list for files either from the File Dialog or from Drag and Drop - :param files: The files to be loaded. + :param list[openlp.core.common.path.Path] file_paths: The files to be loaded. :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files """ full_list = [] for count in range(self.list_view.count()): - full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) + full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) # TODO: Path objects duplicates_found = False files_added = False - for file_path in files: - if file_path in full_list: + for file_path in file_paths: + if path_to_str(file_path) in full_list: duplicates_found = True else: files_added = True - full_list.append(file_path) + full_list.append(path_to_str(file_path)) if full_list and files_added: if target_group is None: self.list_view.clear() self.load_list(full_list, target_group) - last_dir = os.path.split(files[0])[0] - Settings().setValue(self.settings_section + '/last directory', Path(last_dir)) + Settings().setValue(self.settings_section + '/last directory', file_paths[0].parent) Settings().setValue('{section}/{section} files'.format(section=self.settings_section), self.get_file_list()) if duplicates_found: critical_error_message_box(UiStrings().Duplicate, diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 061788b25..d4a02f8c0 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -43,8 +43,7 @@ class PluginManager(RegistryBase, LogMixin, RegistryProperties): """ super(PluginManager, self).__init__(parent) self.log_info('Plugin manager Initialising') - self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir))) - self.log_debug('Base path {path}'.format(path=self.base_path)) + self.log_debug('Base path {path}'.format(path=AppLocation.get_directory(AppLocation.PluginsDir))) self.plugins = [] self.log_info('Plugin manager Initialised') diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index daf6a165c..80cccdb4c 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -29,7 +29,6 @@ Some of the code for this form is based on the examples at: """ import html import logging -import os from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui, QtMultimedia @@ -490,8 +489,7 @@ class MainDisplay(Display, LogMixin, RegistryProperties): service_item = ServiceItem() service_item.title = 'webkit' service_item.processor = 'webkit' - path = os.path.join(str(AppLocation.get_section_data_path('themes')), - self.service_item.theme_data.theme_name) + path = str(AppLocation.get_section_data_path('themes') / self.service_item.theme_data.theme_name) service_item.add_from_command(path, path_to_str(self.service_item.theme_data.background_filename), ':/media/slidecontroller_multimedia.png') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 60fabdf58..5cdee786e 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -649,8 +649,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): self.application.process_events() plugin.first_time() self.application.process_events() - temp_dir = os.path.join(str(gettempdir()), 'openlp') - shutil.rmtree(temp_dir, True) + temp_path = Path(gettempdir(), 'openlp') + temp_path.rmtree(True) def on_first_time_wizard_clicked(self): """ @@ -1219,7 +1219,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): settings.remove('custom slide') settings.remove('service') settings.beginGroup(self.general_settings_section) - self.recent_files = [path_to_str(file_path) for file_path in settings.value('recent files')] + self.recent_files = settings.value('recent files') settings.endGroup() settings.beginGroup(self.ui_settings_section) self.move(settings.value('main window position')) @@ -1243,7 +1243,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): log.debug('Saving QSettings') settings = Settings() settings.beginGroup(self.general_settings_section) - settings.setValue('recent files', [str_to_path(file) for file in self.recent_files]) + settings.setValue('recent files', self.recent_files) settings.endGroup() settings.beginGroup(self.ui_settings_section) settings.setValue('main window position', self.pos()) @@ -1259,26 +1259,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): Updates the recent file menu with the latest list of service files accessed. """ recent_file_count = Settings().value('advanced/recent file count') - existing_recent_files = [recentFile for recentFile in self.recent_files if os.path.isfile(str(recentFile))] - recent_files_to_display = existing_recent_files[0:recent_file_count] self.recent_files_menu.clear() - for file_id, filename in enumerate(recent_files_to_display): - log.debug('Recent file name: {name}'.format(name=filename)) + count = 0 + for recent_path in self.recent_files: + if not recent_path.is_file(): + continue + count += 1 + log.debug('Recent file name: {name}'.format(name=recent_path)) action = create_action(self, '', - text='&{n} {name}'.format(n=file_id + 1, - name=os.path.splitext(os.path.basename(str(filename)))[0]), - data=filename, - triggers=self.service_manager_contents.on_recent_service_clicked) + text='&{n} {name}'.format(n=count, name=recent_path.name), + data=recent_path, triggers=self.service_manager_contents.on_recent_service_clicked) self.recent_files_menu.addAction(action) - clear_recent_files_action = create_action(self, '', - text=translate('OpenLP.MainWindow', 'Clear List', 'Clear List of ' - 'recent files'), - statustip=translate('OpenLP.MainWindow', 'Clear the list of recent ' - 'files.'), - enabled=bool(self.recent_files), - triggers=self.clear_recent_file_menu) + if count == recent_file_count: + break + clear_recent_files_action = \ + create_action(self, '', text=translate('OpenLP.MainWindow', 'Clear List', 'Clear List of recent files'), + statustip=translate('OpenLP.MainWindow', 'Clear the list of recent files.'), + enabled=bool(self.recent_files), triggers=self.clear_recent_file_menu) add_actions(self.recent_files_menu, (None, clear_recent_files_action)) - clear_recent_files_action.setEnabled(bool(self.recent_files)) def add_recent_file(self, filename): """ @@ -1290,20 +1288,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): # actually stored in the settings therefore the default value of 20 will # always be used. max_recent_files = Settings().value('advanced/max recent files') - if filename: - # Add some cleanup to reduce duplication in the recent file list - filename = os.path.abspath(filename) - # abspath() only capitalises the drive letter if it wasn't provided - # in the given filename which then causes duplication. - if filename[1:3] == ':\\': - filename = filename[0].upper() + filename[1:] - if filename in self.recent_files: - self.recent_files.remove(filename) - if not isinstance(self.recent_files, list): - self.recent_files = [self.recent_files] - self.recent_files.insert(0, filename) - while len(self.recent_files) > max_recent_files: - self.recent_files.pop() + file_path = Path(filename) + # Some cleanup to reduce duplication in the recent file list + file_path = file_path.resolve() + if file_path in self.recent_files: + self.recent_files.remove(file_path) + self.recent_files.insert(0, file_path) + self.recent_files = self.recent_files[:max_recent_files] def clear_recent_file_menu(self): """ diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 5e32af7b6..4c4ba5253 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -31,6 +31,7 @@ from PyQt5 import QtCore from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import translate +from openlp.core.common.path import path_to_str from openlp.core.common.settings import Settings from openlp.core.lib import Plugin, StringContent, build_icon from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint @@ -144,12 +145,12 @@ class PresentationPlugin(Plugin): # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in # PresentationDocument.get_thumbnail_folder and PresentationDocument.get_temp_folder is removed super().app_startup() - files_from_config = Settings().value('presentations/presentations files') - for file in files_from_config: - self.media_item.clean_up_thumbnails(file, clean_for_update=True) + presentation_paths = Settings().value('presentations/presentations files') + for path in presentation_paths: + self.media_item.clean_up_thumbnails(path, clean_for_update=True) self.media_item.list_view.clear() Settings().setValue('presentations/thumbnail_scheme', 'md5') - self.media_item.validate_and_load(files_from_config) + self.media_item.validate_and_load(presentation_paths) @staticmethod def about(): From 1b168dd7bfef74f739c144ccfdcf1cddc58aae7b Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sat, 18 Nov 2017 23:14:28 +0000 Subject: [PATCH 50/56] More pathlib refactors --- openlp/core/common/__init__.py | 11 ----- openlp/core/ui/servicemanager.py | 16 ++++--- .../openlp_core/common/test_init.py | 43 +------------------ .../openlp_core/ui/test_servicemanager.py | 4 +- 4 files changed, 12 insertions(+), 62 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index d280cbde2..99e222041 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -318,17 +318,6 @@ def get_filesystem_encoding(): return encoding -def split_filename(path): - """ - Return a list of the parts in a given path. - """ - path = os.path.abspath(path) - if not os.path.isfile(path): - return path, '' - else: - return os.path.split(path) - - def delete_file(file_path): """ Deletes a file from the system. diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 81edd4930..9c943afaf 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -32,7 +32,7 @@ from tempfile import mkstemp from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common import ThemeLevel, split_filename, delete_file +from openlp.core.common import ThemeLevel, delete_file from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, format_time, translate @@ -319,7 +319,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.service_id = 0 # is a new service and has not been saved self._modified = False - self._file_name = '' + self._service_path = None self.service_has_all_original_files = True self.list_double_clicked = False @@ -366,7 +366,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi :param openlp.core.common.path.Path file_path: The service file name :rtype: None """ - self._file_name = path_to_str(file_path) + self._service_path = file_path self.main_window.set_service_modified(self.is_modified(), self.short_file_name()) Settings().setValue('servicemanager/last file', file_path) if file_path and file_path.suffix == '.oszl': @@ -377,14 +377,16 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi def file_name(self): """ Return the current file name including path. + + :rtype: openlp.core.common.path.Path """ - return self._file_name + return self._service_path def short_file_name(self): """ Return the current file name, excluding the path. """ - return split_filename(self._file_name)[1] + return self._service_path.name def reset_supported_suffixes(self): """ @@ -706,13 +708,13 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi default_file_path = directory_path / default_file_path lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)') packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)') - if self._file_name.endswith('oszl'): + if self._service_path and self._service_path.suffix == '.oszl': default_filter = lite_filter else: default_filter = packaged_filter # 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. - if self._file_name.endswith('oszl') or self.service_has_all_original_files: + if self._service_path and self._service_path.suffix == '.oszl' or self.service_has_all_original_files: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter), diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 5acfe59bb..9b86c99aa 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -28,7 +28,7 @@ from unittest import TestCase from unittest.mock import MagicMock, PropertyMock, call, patch from openlp.core.common import add_actions, clean_filename, delete_file, get_file_encoding, get_filesystem_encoding, \ - get_uno_command, get_uno_instance, split_filename + get_uno_command, get_uno_instance from openlp.core.common.path import Path from tests.helpers.testmixin import TestMixin @@ -236,47 +236,6 @@ class TestInit(TestCase, TestMixin): mocked_getdefaultencoding.assert_called_with() self.assertEqual('utf-8', result, 'The result should be "utf-8"') - def test_split_filename_with_file_path(self): - """ - Test the split_filename() function with a path to a file - """ - # GIVEN: A path to a file. - if os.name == 'nt': - file_path = 'C:\\home\\user\\myfile.txt' - wanted_result = ('C:\\home\\user', 'myfile.txt') - else: - file_path = '/home/user/myfile.txt' - wanted_result = ('/home/user', 'myfile.txt') - with patch('openlp.core.common.os.path.isfile') as mocked_is_file: - mocked_is_file.return_value = True - - # WHEN: Split the file name. - result = split_filename(file_path) - - # THEN: A tuple should be returned. - self.assertEqual(wanted_result, result, 'A tuple with the dir and file name should have been returned') - - def test_split_filename_with_dir_path(self): - """ - Test the split_filename() function with a path to a directory - """ - # GIVEN: A path to a dir. - if os.name == 'nt': - file_path = 'C:\\home\\user\\mydir' - wanted_result = ('C:\\home\\user\\mydir', '') - else: - file_path = '/home/user/mydir' - wanted_result = ('/home/user/mydir', '') - with patch('openlp.core.common.os.path.isfile') as mocked_is_file: - mocked_is_file.return_value = False - - # WHEN: Split the file name. - result = split_filename(file_path) - - # THEN: A tuple should be returned. - self.assertEqual(wanted_result, result, - 'A two-entry tuple with the directory and file name (empty) should have been returned.') - def test_clean_filename(self): """ Test the clean_filename() function diff --git a/tests/functional/openlp_core/ui/test_servicemanager.py b/tests/functional/openlp_core/ui/test_servicemanager.py index adf241d35..3c0958506 100644 --- a/tests/functional/openlp_core/ui/test_servicemanager.py +++ b/tests/functional/openlp_core/ui/test_servicemanager.py @@ -637,7 +637,7 @@ class TestServiceManager(TestCase): Registry().register('main_window', mocked_main_window) Registry().register('application', MagicMock()) service_manager = ServiceManager(None) - service_manager._file_name = os.path.join('temp', 'filename.osz') + service_manager._service_path = os.path.join('temp', 'filename.osz') service_manager._save_lite = False service_manager.service_items = [] service_manager.service_theme = 'Default' @@ -666,7 +666,7 @@ class TestServiceManager(TestCase): Registry().register('main_window', mocked_main_window) Registry().register('application', MagicMock()) service_manager = ServiceManager(None) - service_manager._file_name = os.path.join('temp', 'filename.osz') + service_manager._service_path = os.path.join('temp', 'filename.osz') service_manager._save_lite = False service_manager.service_items = [] service_manager.service_theme = 'Default' From a864dbbbc99822cd79fbadef3a8e19b701bf5a11 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 19 Nov 2017 21:57:38 +0000 Subject: [PATCH 51/56] Minor tidyups --- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/servicemanager.py | 2 +- openlp/plugins/media/mediaplugin.py | 9 +++++---- openlp/plugins/presentations/presentationplugin.py | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 7c3881203..312e9d445 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -40,7 +40,7 @@ from openlp.core.common import is_win, is_macosx, add_actions from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import LanguageManager, UiStrings, translate -from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, str_to_path +from openlp.core.common.path import Path, copyfile, create_paths from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 9c943afaf..e6fb34e2f 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -37,7 +37,7 @@ from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, format_time, translate from openlp.core.common.mixins import LogMixin, RegistryProperties -from openlp.core.common.path import Path, create_paths, path_to_str, str_to_path +from openlp.core.common.path import Path, create_paths, str_to_path from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib import ServiceItem, ItemCapabilities, PluginStatus, build_icon diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 5efe8c910..461e78746 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -78,10 +78,10 @@ class MediaPlugin(Plugin): """ log.debug('check_installed Mediainfo') # Try to find mediainfo in the path - exists = process_check_binary('mediainfo') + exists = process_check_binary(Path('mediainfo')) # If mediainfo is not in the path, try to find it in the application folder if not exists: - exists = process_check_binary(os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'mediainfo')) + exists = process_check_binary(AppLocation.get_directory(AppLocation.AppDir) / 'mediainfo') return exists def app_startup(self): @@ -165,10 +165,11 @@ def process_check_binary(program_path): """ Function that checks whether a binary MediaInfo is present - :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: If exists or not + :rtype: bool """ - runlog = check_binary_exists(Path(program_path)) + runlog = check_binary_exists(program_path) # Analyse the output to see it the program is mediainfo for line in runlog.splitlines(): decoded_line = line.decode() diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 4c4ba5253..a68a22176 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -31,7 +31,6 @@ from PyQt5 import QtCore from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import translate -from openlp.core.common.path import path_to_str from openlp.core.common.settings import Settings from openlp.core.lib import Plugin, StringContent, build_icon from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint From 7d0b84126962632f46f54ae08f1859db3fbce1f2 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Mon, 20 Nov 2017 21:57:34 +0000 Subject: [PATCH 52/56] tidyups --- openlp/core/common/path.py | 28 ++++++------------- openlp/core/lib/mediamanageritem.py | 4 +-- openlp/core/ui/thememanager.py | 8 +++--- .../lib/presentationcontroller.py | 6 ++-- .../openlp_core/common/test_path.py | 20 +++++++------ .../songs/test_openlyricsexport.py | 5 ++-- 6 files changed, 32 insertions(+), 39 deletions(-) diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 05d2ea4eb..0e4b45c2a 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -69,8 +69,15 @@ class Path(PathVariant): path = path.relative_to(base_path) return {'__Path__': path.parts} - def rmtree(self, *args, **kwargs): - shutil.rmtree(str(self), *args, **kwargs) + def rmtree(self, ignore_errors=False, onerror=None): + """ + Provide an interface to :func:`shutil.rmtree` + + :param bool ignore_errors: Ignore errors + :param onerror: Handler function to handle any errors + :rtype: None + """ + shutil.rmtree(str(self), ignore_errors, onerror) def replace_params(args, kwargs, params): @@ -156,23 +163,6 @@ def copytree(*args, **kwargs): 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. diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 8091b1a2e..55306267c 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -333,7 +333,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): new_file_paths = [] error_shown = False for file_name in data['files']: - file_path = str_to_path(file_name) # TODO: + file_path = str_to_path(file_name) if file_path.suffix[1:].lower() not in self.on_new_file_masks: if not error_shown: critical_error_message_box( @@ -367,7 +367,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): """ full_list = [] for count in range(self.list_view.count()): - full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) # TODO: Path objects + full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) duplicates_found = False files_added = False for file_path in file_paths: diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index fa8a9e4d9..fae75f81d 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -32,7 +32,7 @@ from openlp.core.common import delete_file from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate, get_locale_key from openlp.core.common.mixins import LogMixin, RegistryProperties -from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, rmtree +from openlp.core.common.path import Path, copyfile, create_paths, path_to_str from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \ @@ -376,7 +376,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R delete_file(self.theme_path / thumb) delete_file(self.thumb_path / thumb) try: - rmtree(self.theme_path / theme) + (self.theme_path / theme).rmtree() except OSError: self.log_exception('Error deleting theme {name}'.format(name=theme)) @@ -431,7 +431,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R 'The theme_name export failed because this error occurred: {err}') .format(err=ose.strerror)) if theme_path.exists(): - rmtree(theme_path, True) + theme_path.rmtree(ignore_errors=True) return False def on_import_theme(self, checked=None): @@ -710,7 +710,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R Call the renderer to build a Sample Image :param theme_data: The theme to generated a preview for. - :param force_page: Flag to tell message lines per page need to be generated.7 + :param force_page: Flag to tell message lines per page need to be generated. :rtype: QtGui.QPixmap """ return self.renderer.generate_preview(theme_data, force_page) diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index db5786626..b54d8afa9 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -25,7 +25,7 @@ from PyQt5 import QtCore from openlp.core.common import md5_hash from openlp.core.common.applocation import AppLocation -from openlp.core.common.path import Path, create_paths, rmtree +from openlp.core.common.path import Path, create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import create_thumb, validate_thumb @@ -126,9 +126,9 @@ class PresentationDocument(object): thumbnail_folder_path = self.get_thumbnail_folder() temp_folder_path = self.get_temp_folder() if thumbnail_folder_path.exists(): - rmtree(thumbnail_folder_path) + thumbnail_folder_path.rmtree() if temp_folder_path.exists(): - rmtree(temp_folder_path) + temp_folder_path.rmtree() except OSError: log.exception('Failed to delete presentation controller files') diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 2ec89771b..498b7aaa0 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -26,7 +26,7 @@ import os from unittest import TestCase from unittest.mock import ANY, MagicMock, patch -from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, rmtree, \ +from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, \ str_to_path, which @@ -172,31 +172,35 @@ class TestShutil(TestCase): """ Test :func:`rmtree` """ - # GIVEN: A mocked :func:`shutil.rmtree` + # GIVEN: A mocked :func:`shutil.rmtree` and a test Path object with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree: + path = Path('test', 'path') # WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type - result = rmtree(Path('test', 'path')) + result = path.rmtree() # 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')) + mocked_shutil_rmtree.assert_called_once_with( + os.path.join('test', 'path'), False, None) 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: + # GIVEN: A mocked :func:`shutil.rmtree` and a test Path object. + with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree: + path = Path('test', 'path') 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) + path.rmtree(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) + mocked_shutil_rmtree.assert_called_once_with( + os.path.join('test', 'path'), True, mocked_on_error) def test_which_no_command(self): """ diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py index 0fd4767db..85bf0818c 100644 --- a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py +++ b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py @@ -22,13 +22,12 @@ """ This module contains tests for the OpenLyrics song importer. """ -import shutil from tempfile import mkdtemp from unittest import TestCase from unittest.mock import MagicMock, patch from openlp.core.common.registry import Registry -from openlp.core.common.path import Path, rmtree +from openlp.core.common.path import Path from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport from tests.helpers.testmixin import TestMixin @@ -49,7 +48,7 @@ class TestOpenLyricsExport(TestCase, TestMixin): """ Cleanup """ - rmtree(self.temp_folder) + self.temp_folder.rmtree() def test_export_same_filename(self): """ From 97e6e759bd457828c12ba8344d5d38e004f948ee Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Tue, 21 Nov 2017 07:15:05 +0000 Subject: [PATCH 53/56] test fix --- tests/functional/openlp_core/lib/test_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 67649fa49..15126c14e 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -320,7 +320,7 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path) From bd2efc8ec028fda8879a6a9e6c0be006ff3ea4aa Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Tue, 21 Nov 2017 07:23:02 +0000 Subject: [PATCH 54/56] PEP8 --- openlp/plugins/images/lib/mediaitem.py | 2 +- tests/functional/openlp_core/lib/test_lib.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 552a63d44..25a8d2353 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -377,7 +377,7 @@ class ImageMediaItem(MediaManagerItem): if validate_thumb(image.file_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(image.file_path,thumbnail_path) + icon = create_thumb(image.file_path, thumbnail_path) item_name = QtWidgets.QTreeWidgetItem([file_name]) item_name.setText(0, file_name) item_name.setIcon(0, icon) diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 15126c14e..e9788060e 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -401,7 +401,8 @@ class TestLib(TestCase): self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -436,7 +437,8 @@ class TestLib(TestCase): self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -474,7 +476,8 @@ class TestLib(TestCase): self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -484,7 +487,8 @@ class TestLib(TestCase): # THEN: Check if the thumb was created with aspect ratio of 1. self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: From 2b9d2a994b7d0f226adec8932422c81d2e7b8e43 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 24 Nov 2017 00:30:37 -0800 Subject: [PATCH 55/56] Bugfix 1734275 Nonstandard LAMP reply --- openlp/core/projectors/manager.py | 12 +- openlp/core/projectors/pjlink.py | 23 +-- .../projectors/test_projector_bugfixes_01.py | 134 ++++++++++++++++++ .../projectors/test_projector_pjlink_base.py | 54 +------ .../test_projector_pjlink_commands.py | 29 ---- 5 files changed, 155 insertions(+), 97 deletions(-) create mode 100644 tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index 0e00d602d..f9e3b191e 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -672,14 +672,16 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM data=projector.model_filter) count = 1 for item in projector.link.lamp: + if item['On'] is None: + onoff = translate('OpenLP.ProjectorManager', 'Unavailable') + elif item['On']: + onoff = translate('OpenLP.ProjectorManager', 'ON') + else: + onoff = translate('OpenLP.ProjectorManager', 'OFF') message += '{title} {count} {status} '.format(title=translate('OpenLP.ProjectorManager', 'Lamp'), count=count, - status=translate('OpenLP.ProjectorManager', - 'ON') - if item['On'] - else translate('OpenLP.ProjectorManager', - 'OFF')) + status=onoff) message += '{title}: {hours}
'.format(title=translate('OpenLP.ProjectorManager', 'Hours'), hours=item['Hours']) diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 38013097f..6ebc462f2 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -403,16 +403,19 @@ class PJLinkCommands(object): """ lamps = [] data_dict = data.split() - while data_dict: - try: - fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} - except ValueError: - # In case of invalid entry - log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) - return - lamps.append(fill) - data_dict.pop(0) # Remove lamp hours - data_dict.pop(0) # Remove lamp on/off + if len(data_dict) < 2: + lamps.append({'Hours': int(data_dict[0]), 'On': None}) + else: + while data_dict: + try: + fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} + except ValueError: + # In case of invalid entry + log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) + return + lamps.append(fill) + data_dict.pop(0) # Remove lamp hours + data_dict.pop(0) # Remove lamp on/off self.lamp = lamps return diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py new file mode 100644 index 000000000..6b5b4c54e --- /dev/null +++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2015 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.projectors.pjlink base package. +""" +from unittest import TestCase +from unittest.mock import patch + +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink + +from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA + +pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + + +class TestPJLinkBugs(TestCase): + """ + Tests for the PJLink module bugfixes + """ + def test_bug_1550891_process_clss_nonstandard_reply_1(self): + """ + Bugfix 1550891: CLSS request returns non-standard reply with Optoma/Viewsonic projector + """ + # GIVEN: Test object + pjlink = pjlink_test + + # WHEN: Process non-standard reply + pjlink.process_clss('Class 1') + + # THEN: Projector class should be set with proper value + self.assertEqual(pjlink.pjlink_class, '1', + 'Non-standard class reply should have set class=1') + + def test_bug_1550891_process_clss_nonstandard_reply_2(self): + """ + Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector + """ + # GIVEN: Test object + pjlink = pjlink_test + + # WHEN: Process non-standard reply + pjlink.process_clss('Version2') + + # THEN: Projector class should be set with proper value + # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify + self.assertEqual(pjlink.pjlink_class, '2', + 'Non-standard class reply should have set class=2') + + @patch.object(pjlink_test, 'send_command') + @patch.object(pjlink_test, 'waitForReadyRead') + @patch.object(pjlink_test, 'projectorAuthentication') + @patch.object(pjlink_test, 'timer') + @patch.object(pjlink_test, 'socket_timer') + def test_bug_1593882_no_pin_authenticated_connection(self, + mock_socket_timer, + mock_timer, + mock_authentication, + mock_ready_read, + mock_send_command): + """ + Test bug 1593882 no pin and authenticated request exception + """ + # GIVEN: Test object and mocks + pjlink = pjlink_test + pjlink.pin = None + mock_ready_read.return_value = True + + # WHEN: call with authentication request and pin not set + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) + + # THEN: 'No Authentication' signal should have been sent + mock_authentication.emit.assert_called_with(pjlink.ip) + + @patch.object(pjlink_test, 'waitForReadyRead') + @patch.object(pjlink_test, 'state') + @patch.object(pjlink_test, '_send_command') + @patch.object(pjlink_test, 'timer') + @patch.object(pjlink_test, 'socket_timer') + def test_bug_1593883_pjlink_authentication(self, + mock_socket_timer, + mock_timer, + mock_send_command, + mock_state, + mock_waitForReadyRead): + """ + Test bugfix 1593883 pjlink authentication + """ + # GIVEN: Test object and data + pjlink = pjlink_test + pjlink.pin = TEST_PIN + mock_state.return_value = pjlink.ConnectedState + mock_waitForReadyRead.return_value = True + + # WHEN: Athenticated connection is called + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) + + # THEN: send_command should have the proper authentication + self.assertEqual("{test}".format(test=mock_send_command.call_args), + "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) + + def test_bug_1734275_pjlink_nonstandard_lamp(self): + """ + Test bugfix 17342785 non-standard LAMP response + """ + # GIVEN: Test object + pjlink = pjlink_test + + # WHEN: Process lamp command called with only hours and no lamp power state + pjlink.process_lamp("45") + + # THEN: Lamp should show hours as 45 and lamp power as Unavailable + self.assertEqual(len(pjlink.lamp), 1, 'There should only be 1 lamp available') + self.assertEqual(pjlink.lamp[0]['Hours'], 45, 'Lamp hours should have equalled 45') + self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "Unavailable"') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 75b32d8c1..7253df032 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -29,7 +29,7 @@ from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_ from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink -from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA +from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) @@ -79,58 +79,6 @@ class TestPJLinkBase(TestCase): 'change_status should have been called with "{}"'.format( ERROR_STRING[E_PARAMETER])) - @patch.object(pjlink_test, 'send_command') - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'projectorAuthentication') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593882_no_pin_authenticated_connection(self, - mock_socket_timer, - mock_timer, - mock_authentication, - mock_ready_read, - mock_send_command): - """ - Test bug 1593882 no pin and authenticated request exception - """ - # GIVEN: Test object and mocks - pjlink = pjlink_test - pjlink.pin = None - mock_ready_read.return_value = True - - # WHEN: call with authentication request and pin not set - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: 'No Authentication' signal should have been sent - mock_authentication.emit.assert_called_with(pjlink.ip) - - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'state') - @patch.object(pjlink_test, '_send_command') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593883_pjlink_authentication(self, - mock_socket_timer, - mock_timer, - mock_send_command, - mock_state, - mock_waitForReadyRead): - """ - Test bugfix 1593883 pjlink authentication - """ - # GIVEN: Test object and data - pjlink = pjlink_test - pjlink.pin = TEST_PIN - mock_state.return_value = pjlink.ConnectedState - mock_waitForReadyRead.return_value = True - - # WHEN: Athenticated connection is called - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: send_command should have the proper authentication - self.assertEqual("{test}".format(test=mock_send_command.call_args), - "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) - @patch.object(pjlink_test, 'disconnect_from_host') def test_socket_abort(self, mock_disconnect): """ diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py index 584b63cf9..32544dd09 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py @@ -570,35 +570,6 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.pjlink_class, '2', 'Projector should have set class=2') - def test_projector_process_clss_nonstandard_reply_optoma(self): - """ - Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector - """ - # GIVEN: Test object - pjlink = pjlink_test - - # WHEN: Process non-standard reply - pjlink.process_clss('Class 1') - - # THEN: Projector class should be set with proper value - self.assertEqual(pjlink.pjlink_class, '1', - 'Non-standard class reply should have set class=1') - - def test_projector_process_clss_nonstandard_reply_benq(self): - """ - Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector - """ - # GIVEN: Test object - pjlink = pjlink_test - - # WHEN: Process non-standard reply - pjlink.process_clss('Version2') - - # THEN: Projector class should be set with proper value - # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify - self.assertEqual(pjlink.pjlink_class, '2', - 'Non-standard class reply should have set class=2') - @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_clss_invalid_nan(self, mock_log): """ From b650ef5730f231821ec58ee9d565365b8cf6464f Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 24 Nov 2017 11:08:23 -0800 Subject: [PATCH 56/56] Refactor mocks --- openlp/core/projectors/manager.py | 8 +-- openlp/core/projectors/pjlink.py | 14 ++-- .../projectors/test_projector_bugfixes_01.py | 64 +++++++++---------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index f9e3b191e..b352858b1 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -673,15 +673,15 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM count = 1 for item in projector.link.lamp: if item['On'] is None: - onoff = translate('OpenLP.ProjectorManager', 'Unavailable') + status = translate('OpenLP.ProjectorManager', 'Unavailable') elif item['On']: - onoff = translate('OpenLP.ProjectorManager', 'ON') + status = translate('OpenLP.ProjectorManager', 'ON') else: - onoff = translate('OpenLP.ProjectorManager', 'OFF') + status = translate('OpenLP.ProjectorManager', 'OFF') message += '{title} {count} {status} '.format(title=translate('OpenLP.ProjectorManager', 'Lamp'), count=count, - status=onoff) + status=status) message += '{title}: {hours}
'.format(title=translate('OpenLP.ProjectorManager', 'Hours'), hours=item['Hours']) diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 6ebc462f2..16a65bd11 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -402,20 +402,20 @@ class PJLinkCommands(object): :param data: Lamp(s) status. """ lamps = [] - data_dict = data.split() - if len(data_dict) < 2: - lamps.append({'Hours': int(data_dict[0]), 'On': None}) + lamp_list = data.split() + if len(lamp_list) < 2: + lamps.append({'Hours': int(lamp_list[0]), 'On': None}) else: - while data_dict: + while lamp_list: try: - fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} + fill = {'Hours': int(lamp_list[0]), 'On': False if lamp_list[1] == '0' else True} except ValueError: # In case of invalid entry log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) return lamps.append(fill) - data_dict.pop(0) # Remove lamp hours - data_dict.pop(0) # Remove lamp on/off + lamp_list.pop(0) # Remove lamp hours + lamp_list.pop(0) # Remove lamp on/off self.lamp = lamps return diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py index 6b5b4c54e..c33220d4a 100644 --- a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py +++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py @@ -30,19 +30,29 @@ from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA -pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) - class TestPJLinkBugs(TestCase): """ Tests for the PJLink module bugfixes """ + def setUp(self): + ''' + Initialization + ''' + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + + def tearDown(self): + ''' + Cleanups + ''' + self.pjlink_test = None + def test_bug_1550891_process_clss_nonstandard_reply_1(self): """ Bugfix 1550891: CLSS request returns non-standard reply with Optoma/Viewsonic projector """ # GIVEN: Test object - pjlink = pjlink_test + pjlink = self.pjlink_test # WHEN: Process non-standard reply pjlink.process_clss('Class 1') @@ -56,7 +66,7 @@ class TestPJLinkBugs(TestCase): Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector """ # GIVEN: Test object - pjlink = pjlink_test + pjlink = self.pjlink_test # WHEN: Process non-standard reply pjlink.process_clss('Version2') @@ -66,22 +76,17 @@ class TestPJLinkBugs(TestCase): self.assertEqual(pjlink.pjlink_class, '2', 'Non-standard class reply should have set class=2') - @patch.object(pjlink_test, 'send_command') - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'projectorAuthentication') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593882_no_pin_authenticated_connection(self, - mock_socket_timer, - mock_timer, - mock_authentication, - mock_ready_read, - mock_send_command): + def test_bug_1593882_no_pin_authenticated_connection(self): """ Test bug 1593882 no pin and authenticated request exception """ # GIVEN: Test object and mocks - pjlink = pjlink_test + mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() + mock_timer = patch.object(self.pjlink_test, 'timer').start() + mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start() + mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + pjlink = self.pjlink_test pjlink.pin = None mock_ready_read.return_value = True @@ -91,22 +96,17 @@ class TestPJLinkBugs(TestCase): # THEN: 'No Authentication' signal should have been sent mock_authentication.emit.assert_called_with(pjlink.ip) - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'state') - @patch.object(pjlink_test, '_send_command') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593883_pjlink_authentication(self, - mock_socket_timer, - mock_timer, - mock_send_command, - mock_state, - mock_waitForReadyRead): + def test_bug_1593883_pjlink_authentication(self): """ Test bugfix 1593883 pjlink authentication """ # GIVEN: Test object and data - pjlink = pjlink_test + mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() + mock_timer = patch.object(self.pjlink_test, 'timer').start() + mock_send_command = patch.object(self.pjlink_test, 'write').start() + mock_state = patch.object(self.pjlink_test, 'state').start() + mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start() + pjlink = self.pjlink_test pjlink.pin = TEST_PIN mock_state.return_value = pjlink.ConnectedState mock_waitForReadyRead.return_value = True @@ -116,14 +116,14 @@ class TestPJLinkBugs(TestCase): # THEN: send_command should have the proper authentication self.assertEqual("{test}".format(test=mock_send_command.call_args), - "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) + "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) - def test_bug_1734275_pjlink_nonstandard_lamp(self): + def test_bug_1734275_process_lamp_nonstandard_reply(self): """ Test bugfix 17342785 non-standard LAMP response """ # GIVEN: Test object - pjlink = pjlink_test + pjlink = self.pjlink_test # WHEN: Process lamp command called with only hours and no lamp power state pjlink.process_lamp("45") @@ -131,4 +131,4 @@ class TestPJLinkBugs(TestCase): # THEN: Lamp should show hours as 45 and lamp power as Unavailable self.assertEqual(len(pjlink.lamp), 1, 'There should only be 1 lamp available') self.assertEqual(pjlink.lamp[0]['Hours'], 45, 'Lamp hours should have equalled 45') - self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "Unavailable"') + self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "None"')