From 8f4ccc542697b88ea3f9c8958756914b2f2b40d0 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 6 Nov 2014 14:14:05 +0100 Subject: [PATCH 01/42] Close db session after import to unlock file on windows --- openlp/plugins/songs/lib/importers/openlp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/plugins/songs/lib/importers/openlp.py b/openlp/plugins/songs/lib/importers/openlp.py index 1a27e8d69..f3d0a74dc 100644 --- a/openlp/plugins/songs/lib/importers/openlp.py +++ b/openlp/plugins/songs/lib/importers/openlp.py @@ -217,4 +217,5 @@ class OpenLPSongImport(SongImport): self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title) if self.stop_import_flag: break + self.source_session.close() engine.dispose() From bc8afc9e4f2e8e7345b850de54467924605a7c32 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 6 Nov 2014 14:16:14 +0100 Subject: [PATCH 02/42] Update thememanager widget after theme import --- openlp/core/ui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 825a12889..48d00cc1f 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -706,7 +706,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): self.active_plugin.toggle_status(PluginStatus.Inactive) # Set global theme and Registry().execute('theme_update_global') + # Load the themes from files self.theme_manager_contents.load_first_time_themes() + # Update the theme widget + self.theme_manager_contents.load_themes() # Check if any Bibles downloaded. If there are, they will be processed. Registry().execute('bibles_load_list', True) self.application.set_normal_cursor() From f685572da244225d384f6074b87b2a3371e83739 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 6 Nov 2014 14:19:00 +0100 Subject: [PATCH 03/42] Improved FTW handling of connection issues --- openlp/core/ui/firsttimeform.py | 177 +++++++++++++++++++------------- 1 file changed, 105 insertions(+), 72 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 02d9e65f7..8a1c29dba 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -42,8 +42,9 @@ from configparser import ConfigParser from PyQt4 import QtCore, QtGui from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, \ - translate, clean_button_text + translate, clean_button_text, trace_error_handler from openlp.core.lib import PluginStatus, build_icon +from openlp.core.lib.ui import critical_error_message_box from openlp.core.utils import get_web_page from .firsttimewizard import UiFirstTimeWizard, FirstTimePage @@ -275,24 +276,34 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): def url_get_file(self, url, f_path): """" Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any - point. + point. Returns False on download error. + + :param url: URL to download + :param f_path: Destination file """ block_count = 0 block_size = 4096 - url_file = urllib.request.urlopen(url) - filename = open(f_path, "wb") - # Download until finished or canceled. - while not self.was_download_cancelled: - data = url_file.read(block_size) - if not data: - break - filename.write(data) - block_count += 1 - self._download_progress(block_count, block_size) - filename.close() + try: + url_file = urllib.request.urlopen(url, timeout=30) + filename = open(f_path, "wb") + # Download until finished or canceled. + while not self.was_download_cancelled: + data = url_file.read(block_size) + if not data: + break + filename.write(data) + block_count += 1 + self._download_progress(block_count, block_size) + filename.close() + except (ConnectionError, IOError): + trace_error_handler(log) + filename.close() + os.remove(f_path) + return False # Delete file if cancelled, it may be a partial file. if self.was_download_cancelled: os.remove(f_path) + return True def _build_theme_screenshots(self): """ @@ -311,7 +322,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): :param url: The URL of the file we want to download. """ - site = urllib.request.urlopen(url) + site = urllib.request.urlopen(url, timeout=30) meta = site.info() return int(meta.get("Content-Length")) @@ -343,32 +354,41 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.max_progress = 0 self.finish_button.setVisible(False) self.application.process_events() - # Loop through the songs list and increase for each selected item - for i in range(self.songs_list_widget.count()): - self.application.process_events() - item = self.songs_list_widget.item(i) - if item.checkState() == QtCore.Qt.Checked: - filename = item.data(QtCore.Qt.UserRole) - size = self._get_file_size('%s%s' % (self.songs_url, filename)) - self.max_progress += size - # Loop through the Bibles list and increase for each selected item - iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget) - while iterator.value(): - self.application.process_events() - item = iterator.value() - if item.parent() and item.checkState(0) == QtCore.Qt.Checked: - filename = item.data(0, QtCore.Qt.UserRole) - size = self._get_file_size('%s%s' % (self.bibles_url, filename)) - self.max_progress += size - iterator += 1 - # Loop through the themes list and increase for each selected item - for i in range(self.themes_list_widget.count()): - self.application.process_events() - item = self.themes_list_widget.item(i) - if item.checkState() == QtCore.Qt.Checked: - filename = item.data(QtCore.Qt.UserRole) - size = self._get_file_size('%s%s' % (self.themes_url, filename)) - self.max_progress += size + try: + # Loop through the songs list and increase for each selected item + for i in range(self.songs_list_widget.count()): + self.application.process_events() + item = self.songs_list_widget.item(i) + if item.checkState() == QtCore.Qt.Checked: + filename = item.data(QtCore.Qt.UserRole) + size = self._get_file_size('%s%s' % (self.songs_url, filename)) + self.max_progress += size + # Loop through the Bibles list and increase for each selected item + iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget) + while iterator.value(): + self.application.process_events() + item = iterator.value() + if item.parent() and item.checkState(0) == QtCore.Qt.Checked: + filename = item.data(0, QtCore.Qt.UserRole) + size = self._get_file_size('%s%s' % (self.bibles_url, filename)) + self.max_progress += size + iterator += 1 + # Loop through the themes list and increase for each selected item + for i in range(self.themes_list_widget.count()): + self.application.process_events() + item = self.themes_list_widget.item(i) + if item.checkState() == QtCore.Qt.Checked: + filename = item.data(QtCore.Qt.UserRole) + size = self._get_file_size('%s%s' % (self.themes_url, filename)) + self.max_progress += size + except (ConnectionError, IOError): + trace_error_handler(log) + critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), + translate('OpenLP.FirstTimeWizard', 'There was a connection problem during ' + 'download, so further downloads will be skipped. Try to re-run the ' + 'First Time Wizard later.')) + self.max_progress = 0 + self.web_access = None if self.max_progress: # Add on 2 for plugins status setting plus a "finished" point. self.max_progress += 2 @@ -432,38 +452,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self._set_plugin_status(self.song_usage_check_box, 'songusage/status') self._set_plugin_status(self.alert_check_box, 'alerts/status') if self.web_access: - # Build directories for downloads - songs_destination = os.path.join(gettempdir(), 'openlp') - bibles_destination = AppLocation.get_section_data_path('bibles') - themes_destination = AppLocation.get_section_data_path('themes') - # Download songs - for i in range(self.songs_list_widget.count()): - item = self.songs_list_widget.item(i) - if item.checkState() == QtCore.Qt.Checked: - filename = item.data(QtCore.Qt.UserRole) - self._increment_progress_bar(self.downloading % filename, 0) - self.previous_size = 0 - destination = os.path.join(songs_destination, str(filename)) - self.url_get_file('%s%s' % (self.songs_url, filename), destination) - # Download Bibles - bibles_iterator = QtGui.QTreeWidgetItemIterator( - self.bibles_tree_widget) - while bibles_iterator.value(): - item = bibles_iterator.value() - if item.parent() and item.checkState(0) == QtCore.Qt.Checked: - bible = item.data(0, QtCore.Qt.UserRole) - self._increment_progress_bar(self.downloading % bible, 0) - self.previous_size = 0 - self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible)) - bibles_iterator += 1 - # Download themes - for i in range(self.themes_list_widget.count()): - item = self.themes_list_widget.item(i) - if item.checkState() == QtCore.Qt.Checked: - theme = item.data(QtCore.Qt.UserRole) - self._increment_progress_bar(self.downloading % theme, 0) - self.previous_size = 0 - self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme)) + if not self._download_selected(): + critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), + translate('OpenLP.FirstTimeWizard', 'There was a connection problem while ' + 'downloading, so further downloads will be skipped. Try to re-run ' + 'the First Time Wizard later.')) # Set Default Display if self.display_combo_box.currentIndex() != -1: Settings().setValue('core/monitor', self.display_combo_box.currentIndex()) @@ -472,6 +465,46 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): if self.theme_combo_box.currentIndex() != -1: Settings().setValue('themes/global theme', self.theme_combo_box.currentText()) + def _download_selected(self): + """ + Download selected songs, bibles and themes. Returns False on download error + """ + # Build directories for downloads + songs_destination = os.path.join(gettempdir(), 'openlp') + bibles_destination = AppLocation.get_section_data_path('bibles') + themes_destination = AppLocation.get_section_data_path('themes') + # Download songs + for i in range(self.songs_list_widget.count()): + item = self.songs_list_widget.item(i) + if item.checkState() == QtCore.Qt.Checked: + filename = item.data(QtCore.Qt.UserRole) + self._increment_progress_bar(self.downloading % filename, 0) + self.previous_size = 0 + destination = os.path.join(songs_destination, str(filename)) + if not self.url_get_file('%s%s' % (self.songs_url, filename), destination): + return False + # Download Bibles + bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget) + while bibles_iterator.value(): + item = bibles_iterator.value() + if item.parent() and item.checkState(0) == QtCore.Qt.Checked: + bible = item.data(0, QtCore.Qt.UserRole) + self._increment_progress_bar(self.downloading % bible, 0) + self.previous_size = 0 + if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible)): + return False + bibles_iterator += 1 + # Download themes + for i in range(self.themes_list_widget.count()): + item = self.themes_list_widget.item(i) + if item.checkState() == QtCore.Qt.Checked: + theme = item.data(QtCore.Qt.UserRole) + self._increment_progress_bar(self.downloading % theme, 0) + self.previous_size = 0 + if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme)): + return False + return True + def _set_plugin_status(self, field, tag): """ Set the status of a plugin. From 529f94c504d84bda5a33e9a1198186bc7caa7162 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 7 Nov 2014 09:26:16 +0100 Subject: [PATCH 04/42] Remove old pyinstaller hooks which has been moved to the packaging branch --- .../pyinstaller/hook-openlp.core.ui.media.py | 32 ---------------- ...lugins.presentations.presentationplugin.py | 33 ---------------- resources/pyinstaller/hook-openlp.py | 38 ------------------- 3 files changed, 103 deletions(-) delete mode 100644 resources/pyinstaller/hook-openlp.core.ui.media.py delete mode 100644 resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py delete mode 100644 resources/pyinstaller/hook-openlp.py diff --git a/resources/pyinstaller/hook-openlp.core.ui.media.py b/resources/pyinstaller/hook-openlp.core.ui.media.py deleted file mode 100644 index a17856dca..000000000 --- a/resources/pyinstaller/hook-openlp.core.ui.media.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2014 Raoul Snyman # -# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # -# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # -# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # -# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # -# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # -# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # -# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # -# --------------------------------------------------------------------------- # -# 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 # -############################################################################### - -hiddenimports = ['openlp.core.ui.media.phononplayer', - 'openlp.core.ui.media.vlcplayer', - 'openlp.core.ui.media.webkitplayer'] diff --git a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py deleted file mode 100644 index 6ffb416fa..000000000 --- a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2014 Raoul Snyman # -# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # -# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # -# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # -# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # -# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # -# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # -# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # -# --------------------------------------------------------------------------- # -# 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 # -############################################################################### - -hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', - 'openlp.plugins.presentations.lib.powerpointcontroller', - 'openlp.plugins.presentations.lib.pptviewcontroller', - 'openlp.plugins.presentations.lib.pdfcontroller'] diff --git a/resources/pyinstaller/hook-openlp.py b/resources/pyinstaller/hook-openlp.py deleted file mode 100644 index c45dec009..000000000 --- a/resources/pyinstaller/hook-openlp.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2014 Raoul Snyman # -# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # -# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # -# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # -# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # -# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # -# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # -# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # -# --------------------------------------------------------------------------- # -# 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 # -############################################################################### - -hiddenimports = ['plugins.songs.songsplugin', - 'plugins.bibles.bibleplugin', - 'plugins.presentations.presentationplugin', - 'plugins.media.mediaplugin', - 'plugins.images.imageplugin', - 'plugins.custom.customplugin', - 'plugins.songusage.songusageplugin', - 'plugins.remotes.remoteplugin', - 'plugins.alerts.alertsplugin'] From 48c561e34bc1bd455161a53b287bc5ceed28d440 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 7 Nov 2014 10:32:45 +0100 Subject: [PATCH 05/42] Made the FTW able to handle if the downloaded config file is invalid. Fixes: https://launchpad.net/bugs/1222944 --- openlp/core/ui/firsttimeform.py | 28 ++++++----- openlp/core/utils/__init__.py | 4 +- .../openlp_core_ui/test_firsttimeform.py | 46 +++++++++++++++++++ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 8a1c29dba..20a18cb82 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -37,7 +37,7 @@ import urllib.request import urllib.parse import urllib.error from tempfile import gettempdir -from configparser import ConfigParser +from configparser import ConfigParser, MissingSectionHeaderError, NoSectionError, NoOptionError from PyQt4 import QtCore, QtGui @@ -131,17 +131,23 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): """ self.screens = screens # check to see if we have web access + self.web_access = False self.web = 'http://openlp.org/files/frw/' self.config = ConfigParser() user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() - self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent)) - if self.web_access: - files = self.web_access.read() - self.config.read_string(files.decode()) - self.web = self.config.get('general', 'base url') - self.songs_url = self.web + self.config.get('songs', 'directory') + '/' - self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' - self.themes_url = self.web + self.config.get('themes', 'directory') + '/' + web_config = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent)) + if web_config: + files = web_config.read() + try: + self.config.read_string(files.decode()) + self.web = self.config.get('general', 'base url') + self.songs_url = self.web + self.config.get('songs', 'directory') + '/' + self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' + self.themes_url = self.web + self.config.get('themes', 'directory') + '/' + self.web_access = True + except (NoSectionError, NoOptionError, MissingSectionHeaderError): + log.debug('A problem occured while parsing the downloaded config file') + trace_error_handler(log) self.update_screen_list_combo() self.was_download_cancelled = False self.theme_screenshot_thread = None @@ -295,7 +301,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): block_count += 1 self._download_progress(block_count, block_size) filename.close() - except (ConnectionError, IOError): + except ConnectionError: trace_error_handler(log) filename.close() os.remove(f_path) @@ -381,7 +387,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): filename = item.data(QtCore.Qt.UserRole) size = self._get_file_size('%s%s' % (self.themes_url, filename)) self.max_progress += size - except (ConnectionError, IOError): + except ConnectionError: trace_error_handler(log) critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), translate('OpenLP.FirstTimeWizard', 'There was a connection problem during ' diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 128c09b38..8f032cd7d 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -390,9 +390,9 @@ def get_web_page(url, header=None, update_openlp=False): page = None log.debug('Downloading URL = %s' % url) try: - page = urllib.request.urlopen(req) + page = urllib.request.urlopen(req, timeout=30) log.debug('Downloaded URL = %s' % page.geturl()) - except urllib.error.URLError: + except (urllib.error.URLError, ConnectionError): log.exception('The web page could not be downloaded') if not page: return None diff --git a/tests/functional/openlp_core_ui/test_firsttimeform.py b/tests/functional/openlp_core_ui/test_firsttimeform.py index ed7a7a9e8..ead9ef4cf 100644 --- a/tests/functional/openlp_core_ui/test_firsttimeform.py +++ b/tests/functional/openlp_core_ui/test_firsttimeform.py @@ -49,6 +49,22 @@ directory = bibles directory = themes """ +FAKE_BROKEN_CONFIG = b""" +[general] +base url = http://example.com/frw/ +[songs] +directory = songs +[bibles] +directory = bibles +""" + +FAKE_INVALID_CONFIG = b""" + +This is not a config file +Some text + +""" + class TestFirstTimeForm(TestCase, TestMixin): @@ -104,3 +120,33 @@ class TestFirstTimeForm(TestCase, TestMixin): self.assertEqual(expected_songs_url, first_time_form.songs_url, 'The songs URL should be correct') self.assertEqual(expected_bibles_url, first_time_form.bibles_url, 'The bibles URL should be correct') self.assertEqual(expected_themes_url, first_time_form.themes_url, 'The themes URL should be correct') + + def broken_config_test(self): + """ + Test if we can handle an config file with missing data + """ + # GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked broken config file + with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: + first_time_form = FirstTimeForm(None) + mocked_get_web_page.return_value.read.return_value = FAKE_BROKEN_CONFIG + + # WHEN: The First Time Wizard is initialised + first_time_form.initialize(MagicMock()) + + # THEN: The First Time Form should not have web access + self.assertFalse(first_time_form.web_access, 'There should not be web access with a broken config file') + + def invalid_config_test(self): + """ + Test if we can handle an config file in invalid format + """ + # GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked invalid config file + with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: + first_time_form = FirstTimeForm(None) + mocked_get_web_page.return_value.read.return_value = FAKE_INVALID_CONFIG + + # WHEN: The First Time Wizard is initialised + first_time_form.initialize(MagicMock()) + + # THEN: The First Time Form should not have web access + self.assertFalse(first_time_form.web_access, 'There should not be web access with an invalid config file') From 519119aecb2810e1efde88241c60b646f3a9c041 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 7 Nov 2014 10:41:37 +0100 Subject: [PATCH 06/42] Fixed test to reflect 30 sec timeout in get_web_page --- tests/functional/openlp_core_utils/test_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/openlp_core_utils/test_utils.py b/tests/functional/openlp_core_utils/test_utils.py index 075ecb14f..de3e68791 100644 --- a/tests/functional/openlp_core_utils/test_utils.py +++ b/tests/functional/openlp_core_utils/test_utils.py @@ -335,7 +335,7 @@ class TestUtils(TestCase): self.assertEqual(1, mocked_request_object.add_header.call_count, 'There should only be 1 call to add_header') mock_get_user_agent.assert_called_with() - mock_urlopen.assert_called_with(mocked_request_object) + mock_urlopen.assert_called_with(mocked_request_object, timeout=30) mocked_page_object.geturl.assert_called_with() self.assertEqual(0, MockRegistry.call_count, 'The Registry() object should have never been called') self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object') @@ -365,7 +365,7 @@ class TestUtils(TestCase): self.assertEqual(2, mocked_request_object.add_header.call_count, 'There should only be 2 calls to add_header') mock_get_user_agent.assert_called_with() - mock_urlopen.assert_called_with(mocked_request_object) + mock_urlopen.assert_called_with(mocked_request_object, timeout=30) mocked_page_object.geturl.assert_called_with() self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object') @@ -393,7 +393,7 @@ class TestUtils(TestCase): self.assertEqual(1, mocked_request_object.add_header.call_count, 'There should only be 1 call to add_header') self.assertEqual(0, mock_get_user_agent.call_count, '_get_user_agent should not have been called') - mock_urlopen.assert_called_with(mocked_request_object) + mock_urlopen.assert_called_with(mocked_request_object, timeout=30) mocked_page_object.geturl.assert_called_with() self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object') @@ -425,7 +425,7 @@ class TestUtils(TestCase): mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent') self.assertEqual(1, mocked_request_object.add_header.call_count, 'There should only be 1 call to add_header') - mock_urlopen.assert_called_with(mocked_request_object) + mock_urlopen.assert_called_with(mocked_request_object, timeout=30) mocked_page_object.geturl.assert_called_with() mocked_registry_object.get.assert_called_with('application') mocked_application_object.process_events.assert_called_with() From a8bd5f19964455a4fcfc685cf4393ca05e99f004 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 7 Nov 2014 13:06:58 +0100 Subject: [PATCH 07/42] Fix for importing old bible DBs, like KJV --- openlp/plugins/bibles/bibleplugin.py | 2 +- openlp/plugins/bibles/lib/db.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index e7f1fdd56..76dc75d35 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -159,7 +159,7 @@ class BiblePlugin(Plugin): self.upgrade_wizard = BibleUpgradeForm(self.main_window, self.manager, self) # If the import was not cancelled then reload. if self.upgrade_wizard.exec_(): - self.media_item.reloadBibles() + self.media_item.reload_bibles() def on_bible_import_click(self): if self.media_item: diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index b0d8d0e7d..8470c2765 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -170,6 +170,9 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): Returns the version name of the Bible. """ version_name = self.get_object(BibleMeta, 'name') + # Fallback to old way of naming + if not version_name: + version_name = self.get_object(BibleMeta, 'Version') self.name = version_name.value if version_name else None return self.name @@ -969,11 +972,15 @@ class OldBibleDB(QtCore.QObject, Manager): """ Returns the version name of the Bible. """ + self.name = None version_name = self.run_sql('SELECT value FROM metadata WHERE key = "name"') if version_name: self.name = version_name[0][0] else: - self.name = None + # Fallback to old way of naming + version_name = self.run_sql('SELECT value FROM metadata WHERE key = "Version"') + if version_name: + self.name = version_name[0][0] return self.name def get_metadata(self): From 5cc1fa2261f676b2b95d7d7652065d276b5a3159 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Mon, 10 Nov 2014 20:48:22 +0000 Subject: [PATCH 08/42] Fixes a few typos --- openlp/core/lib/colorbutton.py | 2 +- openlp/core/ui/aboutdialog.py | 4 ++-- openlp/core/ui/themeform.py | 8 ++++---- tests/functional/openlp_core_lib/test_color_button.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openlp/core/lib/colorbutton.py b/openlp/core/lib/colorbutton.py index 6ebf9ee99..30d2ad394 100644 --- a/openlp/core/lib/colorbutton.py +++ b/openlp/core/lib/colorbutton.py @@ -73,7 +73,7 @@ class ColorButton(QtGui.QPushButton): @color.setter def color(self, color): """ - Property setter to change the imstamce color + Property setter to change the instance color :param color: String representation of a hexidecimal color """ diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 251e0657c..9af4f55c2 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -225,10 +225,10 @@ class Ui_AboutDialog(object): '\n' 'Built With\n' ' Python: http://www.python.org/\n' - ' Qt4: http://qt.digia.com/\n' + ' Qt4: http://qt.io\n' ' PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/' 'intro\n' - ' Oxygen Icons: http://oxygen-icons.org/\n' + ' Oxygen Icons\n' '\n' 'Final Credit\n' ' "For God so loved the world that He gave\n' diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 611ac2d4d..0c90986e5 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -67,8 +67,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed) self.color_button.colorChanged.connect(self.on_color_changed) self.image_color_button.colorChanged.connect(self.on_image_color_changed) - self.gradient_start_button.colorChanged.connect(self.on_gradient_start_button_changed) - self.gradient_end_button.colorChanged.connect(self.on_gradient_end_button_changed) + self.gradient_start_button.colorChanged.connect(self.on_gradient_start_color_changed) + self.gradient_end_button.colorChanged.connect(self.on_gradient_end_color_changed) self.image_browse_button.clicked.connect(self.on_image_browse_button_clicked) self.image_file_edit.editingFinished.connect(self.on_image_file_edit_editing_finished) self.main_color_button.colorChanged.connect(self.on_main_color_changed) @@ -411,13 +411,13 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): """ self.theme.background_border_color = color - def on_gradient_start_button_changed(self, color): + def on_gradient_start_color_changed(self, color): """ Gradient 2 _color button pushed. """ self.theme.background_start_color = color - def on_gradient_end_button_changed(self, color): + def on_gradient_end_color_changed(self, color): """ Gradient 2 _color button pushed. """ diff --git a/tests/functional/openlp_core_lib/test_color_button.py b/tests/functional/openlp_core_lib/test_color_button.py index a7b743918..3d605b6d5 100644 --- a/tests/functional/openlp_core_lib/test_color_button.py +++ b/tests/functional/openlp_core_lib/test_color_button.py @@ -202,5 +202,5 @@ class TestColorDialog(TestCase): widget.on_clicked() # THEN: change_color should have been called and the colorChanged signal should have been emitted - self.mocked_change_color.assert_call_once_with('#ffffff') + self.mocked_change_color.assert_called_once_with('#ffffff') self.mocked_color_changed.emit.assert_called_once_with('#ffffff') From 75960ac8da0b0fa19e0f4cff4c42bfc3c327b6ac Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 11 Nov 2014 14:58:03 +0100 Subject: [PATCH 09/42] When bible or song plugin is disabled in FTW don't show download for those plugins in FTW. Fixes bug 1194622. Fixes: https://launchpad.net/bugs/1194622 --- openlp/core/ui/firsttimeform.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 20a18cb82..0688681a1 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -90,6 +90,21 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): super(FirstTimeForm, self).__init__(parent) self.setup_ui(self) + def get_next_page_id(self): + """ + Returns the id of the next FirstTimePage to go to based on enabled plugins + """ + # The songs plugin is enabled + if FirstTimePage.Welcome < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked(): + return FirstTimePage.Songs + # The Bibles plugin is enabled + elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked(): + return FirstTimePage.Bibles + elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Themes: + return FirstTimePage.Themes + else: + return self.currentId() + 1 + def nextId(self): """ Determine the next page in the Wizard to go to. @@ -99,7 +114,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): if not self.web_access: return FirstTimePage.NoInternet else: - return FirstTimePage.Songs + return self.get_next_page_id() elif self.currentId() == FirstTimePage.Progress: return -1 elif self.currentId() == FirstTimePage.NoInternet: @@ -114,7 +129,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.application.set_normal_cursor() return FirstTimePage.Defaults else: - return self.currentId() + 1 + return self.get_next_page_id() def exec_(self): """ From 0af6dc8eea5fcff46fd672a3e2bed8fdcc4ba9cc Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 23 Nov 2014 20:52:16 +0000 Subject: [PATCH 10/42] Always retry on connection failure. --- openlp/core/ui/firsttimeform.py | 58 +++++++++++++++++++++------------ openlp/core/utils/__init__.py | 34 ++++++++++++++----- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 0688681a1..7713aa948 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -45,7 +45,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, Settin translate, clean_button_text, trace_error_handler from openlp.core.lib import PluginStatus, build_icon from openlp.core.lib.ui import critical_error_message_box -from openlp.core.utils import get_web_page +from openlp.core.utils import get_web_page, CONNECTION_RETRIES, CONNECTION_TIMEOUT from .firsttimewizard import UiFirstTimeWizard, FirstTimePage log = logging.getLogger(__name__) @@ -304,23 +304,30 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): """ block_count = 0 block_size = 4096 - try: - url_file = urllib.request.urlopen(url, timeout=30) - filename = open(f_path, "wb") - # Download until finished or canceled. - while not self.was_download_cancelled: - data = url_file.read(block_size) - if not data: - break - filename.write(data) - block_count += 1 - self._download_progress(block_count, block_size) - filename.close() - except ConnectionError: - trace_error_handler(log) - filename.close() - os.remove(f_path) - return False + retries = 0 + while True: + try: + url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT) + filename = open(f_path, "wb") + # Download until finished or canceled. + while not self.was_download_cancelled: + data = url_file.read(block_size) + if not data: + break + filename.write(data) + block_count += 1 + self._download_progress(block_count, block_size) + filename.close() + except ConnectionError: + trace_error_handler(log) + filename.close() + os.remove(f_path) + if retries > CONNECTION_RETRIES: + return False + else: + retries += 1 + continue + break # Delete file if cancelled, it may be a partial file. if self.was_download_cancelled: os.remove(f_path) @@ -343,9 +350,18 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): :param url: The URL of the file we want to download. """ - site = urllib.request.urlopen(url, timeout=30) - meta = site.info() - return int(meta.get("Content-Length")) + retries = 0 + while True: + try: + site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT) + meta = site.info() + return int(meta.get("Content-Length")) + except ConnectionException: + if retries > CONNECTION_RETRIES: + raise + else: + retries += 1 + continue def _download_progress(self, count, block_size): """ diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 8f032cd7d..87dfc48cb 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -91,6 +91,8 @@ USER_AGENTS = { 'Mozilla/5.0 (X11; NetBSD amd64; rv:18.0) Gecko/20130120 Firefox/18.0' ] } +CONNECTION_TIMEOUT = 30 +CONNECTION_RETRIES = 2 class VersionThread(QtCore.QThread): @@ -250,10 +252,17 @@ def check_latest_version(current_version): req = urllib.request.Request('http://www.openlp.org/files/version.txt') req.add_header('User-Agent', 'OpenLP/%s' % current_version['full']) remote_version = None - try: - remote_version = str(urllib.request.urlopen(req, None).read().decode()).strip() - except IOError: - log.exception('Failed to download the latest OpenLP version file') + retries = 0 + while True: + try: + remote_version = str(urllib.request.urlopen(req, None, timeout=CONNECTION_TIMEOUT).read().decode()).strip() + except ConnectionException: + if retries > CONNECTION_RETRIES: + log.exception('Failed to download the latest OpenLP version file') + else: + retries += 1 + continue + break if remote_version: version_string = remote_version return version_string @@ -389,11 +398,18 @@ def get_web_page(url, header=None, update_openlp=False): req.add_header(header[0], header[1]) page = None log.debug('Downloading URL = %s' % url) - try: - page = urllib.request.urlopen(req, timeout=30) - log.debug('Downloaded URL = %s' % page.geturl()) - except (urllib.error.URLError, ConnectionError): - log.exception('The web page could not be downloaded') + retries = 0 + while True: + try: + page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT) + log.debug('Downloaded URL = %s' % page.geturl()) + except (urllib.error.URLError, ConnectionError): + if retries > CONNECTION_RETRIES: + log.exception('The web page could not be downloaded') + raise + else: + continue + break if not page: return None if update_openlp: From 5b794e57623ec4f872a700d4d23336dcf60fc7ed Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 24 Nov 2014 19:38:15 +0000 Subject: [PATCH 11/42] Added small pause before connection retry --- openlp/core/ui/firsttimeform.py | 2 ++ openlp/core/utils/__init__.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 7713aa948..b291ba249 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -326,6 +326,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): return False else: retries += 1 + time.sleep(0.1) continue break # Delete file if cancelled, it may be a partial file. @@ -361,6 +362,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): raise else: retries += 1 + time.sleep(0.1) continue def _download_progress(self, count, block_size): diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 87dfc48cb..df1fd2a9c 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -35,6 +35,7 @@ import logging import locale import os import re +import time from subprocess import Popen, PIPE import sys import urllib.request @@ -261,6 +262,7 @@ def check_latest_version(current_version): log.exception('Failed to download the latest OpenLP version file') else: retries += 1 + time.sleep(0.1) continue break if remote_version: @@ -408,6 +410,7 @@ def get_web_page(url, header=None, update_openlp=False): log.exception('The web page could not be downloaded') raise else: + time.sleep(0.1) continue break if not page: From c6e4c07a24374ec90978c632fd4fea25e4a91b8b Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 24 Nov 2014 23:12:04 +0000 Subject: [PATCH 12/42] Fix upgrade on song db with lost version. Fixes bug 1391638 Fixes: https://launchpad.net/bugs/1391638 --- openlp/plugins/songs/lib/upgrade.py | 57 +++++++++++++++++++---------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index 5b7255266..b8945f8a9 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -32,10 +32,11 @@ backend for the Songs plugin """ import logging -from sqlalchemy import Column, ForeignKey, types +from sqlalchemy import Table, Column, ForeignKey, types from sqlalchemy.sql.expression import func, false, null, text from openlp.core.lib.db import get_upgrade_op +from openlp.core.common import trace_error_handler log = logging.getLogger(__name__) __version__ = 4 @@ -57,12 +58,16 @@ def upgrade_1(session, metadata): :param metadata: """ op = get_upgrade_op(session) - op.drop_table('media_files_songs') - op.add_column('media_files', Column('song_id', types.Integer(), server_default=null())) - op.add_column('media_files', Column('weight', types.Integer(), server_default=text('0'))) - if metadata.bind.url.get_dialect().name != 'sqlite': - # SQLite doesn't support ALTER TABLE ADD CONSTRAINT - op.create_foreign_key('fk_media_files_song_id', 'media_files', 'songs', ['song_id', 'id']) + songs_table = Table('songs', metadata, autoload=True) + if 'media_files_songs' in [t.name for t in metadata.tables.values()]: + op.drop_table('media_files_songs') + op.add_column('media_files', Column('song_id', types.Integer(), server_default=null())) + op.add_column('media_files', Column('weight', types.Integer(), server_default=text('0'))) + if metadata.bind.url.get_dialect().name != 'sqlite': + # SQLite doesn't support ALTER TABLE ADD CONSTRAINT + op.create_foreign_key('fk_media_files_song_id', 'media_files', 'songs', ['song_id', 'id']) + else: + log.warning('Skipping upgrade_1 step of upgrading the song db') def upgrade_2(session, metadata): @@ -72,8 +77,12 @@ def upgrade_2(session, metadata): This upgrade adds a create_date and last_modified date to the songs table """ op = get_upgrade_op(session) - op.add_column('songs', Column('create_date', types.DateTime(), default=func.now())) - op.add_column('songs', Column('last_modified', types.DateTime(), default=func.now())) + songs_table = Table('songs', metadata, autoload=True) + if 'create_date' not in [col.name for col in songs_table.c.values()]: + op.add_column('songs', Column('create_date', types.DateTime(), default=func.now())) + op.add_column('songs', Column('last_modified', types.DateTime(), default=func.now())) + else: + log.warning('Skipping upgrade_2 step of upgrading the song db') def upgrade_3(session, metadata): @@ -83,10 +92,14 @@ def upgrade_3(session, metadata): This upgrade adds a temporary song flag to the songs table """ op = get_upgrade_op(session) - if metadata.bind.url.get_dialect().name == 'sqlite': - op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false())) + songs_table = Table('songs', metadata, autoload=True) + if 'temporary' not in [col.name for col in songs_table.c.values()]: + if metadata.bind.url.get_dialect().name == 'sqlite': + op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false())) + else: + op.add_column('songs', Column('temporary', types.Boolean(), server_default=false())) else: - op.add_column('songs', Column('temporary', types.Boolean(), server_default=false())) + log.warning('Skipping upgrade_3 step of upgrading the song db') def upgrade_4(session, metadata): @@ -98,11 +111,15 @@ def upgrade_4(session, metadata): # Since SQLite doesn't support changing the primary key of a table, we need to recreate the table # and copy the old values op = get_upgrade_op(session) - op.create_table('authors_songs_tmp', - Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True), - Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True), - Column('author_type', types.String(), primary_key=True, - nullable=False, server_default=text('""'))) - op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs') - op.drop_table('authors_songs') - op.rename_table('authors_songs_tmp', 'authors_songs') + songs_table = Table('songs', metadata) + if 'author_type' not in [col.name for col in songs_table.c.values()]: + op.create_table('authors_songs_tmp', + Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True), + Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True), + Column('author_type', types.String(), primary_key=True, + nullable=False, server_default=text('""'))) + op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs') + op.drop_table('authors_songs') + op.rename_table('authors_songs_tmp', 'authors_songs') + else: + log.warning('Skipping upgrade_4 step of upgrading the song db') From 4ca013e21148da0f4a2595cc005ce8dd7e167d2a Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 25 Nov 2014 12:25:41 +0100 Subject: [PATCH 13/42] Added test for song db upgrade --- .../openlp_plugins/songs/test_db.py | 35 ++++++++++++++++++ tests/resources/songs/songs-1.9.7.sqlite | Bin 0 -> 35840 bytes 2 files changed, 35 insertions(+) create mode 100755 tests/resources/songs/songs-1.9.7.sqlite diff --git a/tests/functional/openlp_plugins/songs/test_db.py b/tests/functional/openlp_plugins/songs/test_db.py index e696ea94b..e37cc58cb 100644 --- a/tests/functional/openlp_plugins/songs/test_db.py +++ b/tests/functional/openlp_plugins/songs/test_db.py @@ -29,9 +29,15 @@ """ This module contains tests for the db submodule of the Songs plugin. """ +import os +import shutil from unittest import TestCase +from tempfile import mkdtemp from openlp.plugins.songs.lib.db import Song, Author, AuthorType +from openlp.plugins.songs.lib import upgrade +from openlp.core.lib.db import upgrade_db +from tests.utils.constants import TEST_RESOURCES_PATH class TestDB(TestCase): @@ -39,6 +45,18 @@ class TestDB(TestCase): Test the functions in the :mod:`db` module. """ + def setUp(self): + """ + Setup for tests + """ + self.tmp_folder = mkdtemp() + + def tearDown(self): + """ + Clean up after tests + """ + shutil.rmtree(self.tmp_folder) + def test_add_author(self): """ Test adding an author to a song @@ -153,3 +171,20 @@ class TestDB(TestCase): # THEN: It should return the name with the type in brackets self.assertEqual("John Doe (Words)", display_name) + + def test_upgrade_song_db(self): + """ + Test that we can upgrade an old song db to the current schema + """ + # GIVEN: An old song db + old_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-1.9.7.sqlite') + old_db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite') + shutil.copyfile(old_db_path, old_db_tmp_path) + db_url = 'sqlite:///' + old_db_tmp_path + + # WHEN: upgrading the db + updated_to_version, latest_version = upgrade_db(db_url, upgrade) + + # Then the song db should have been upgraded to the latest version + self.assertEqual(updated_to_version, latest_version, + 'The song DB should have been upgrade to the latest version') diff --git a/tests/resources/songs/songs-1.9.7.sqlite b/tests/resources/songs/songs-1.9.7.sqlite new file mode 100755 index 0000000000000000000000000000000000000000..98505464bcec08c1a4cc6921e03d6f9ae1ff4f15 GIT binary patch literal 35840 zcmeHQ?Qh%08Rv<%6)Sbzq)EBN@p85i*aDo`a^j|GoHVMVBwih-bCjh)Pzbbiw%Aal zDpFD0Pj#|k12Syr3Je(bC+zz^uHQCbf5xznZGfT0u(eKmQccrF;l;O8@alorFuW4*YHp;t^P>5^MOb!}qA%#q0kbtZ;M* z5!fOE;`*;$)cF5PZNHr(M>H}%@Hrdco0fiNWwTngVt#5lYxG9VEV8+GA6IPph}AsH zbuN!*&Q6b0<`mr$l(;;8H-G)i+vD$ENzHjKoY5>c3dKgR%-QQTtLR;ss5An z^wB7_%@t-}9?d|Bkq7hFvia--Mdb)ByAA3!81t!LX4G@*PAQcy!`!B4K4K+m(ngK> z#WJ<6hm2O(rPKm-og$+R)1$WQ`IB^|?o$V(m=%~7h0`Zf_aJY@wqQ5RT%aCfXFvMr z%G{JNv6H1U5ljE^*&p&;%^C*>%mbr*v%XgLX=R<3nOTElm#)IjWGd^Ff^v|@lRR2u zw#C4QOR1Y+UA4xFmRt8|$tp=%Qmk363ITcWzU%2?r01A5<^hUjz?YhKjhUr&T68(I++j=ccSQ&ThoNfB zRSp7puxi#K{x&SzrZ!07g~}jj3n-sg3}$rEt(Cwa4^VO|mIw8|=DGlr3o<8DH^HU` zFTr7Vt6&N%*1-t?7TN(<4;fzBVm7oFaIxyvd}zUdU@FTS3A9!aZChYJb4vWoEtohP zG4VbF2peVQfd62Nta`3#)n;B9RGVPKs%hD>9aKQS37Bw&GOx%?4;+C;D2i5H&to1q zWq~{{ltFj0>{M2s6-^KbKK(OlWyO%P!B~%q~yN%+98jB77_gbKg22&Ex}Fp(PpWm|47g2b#Eo50)2h zWtZ>M_jC8@gjGtX(hZU{jvpr*3&IuOt-`n`-xG=hGOdg4irJZ&x1*NDDc%@PB#n^~ z@}yU27RIl*?nAG&rz;k;(tofO)jEu$UYXTG1i7K{z-(O5lg7{xd6E|&M}1J#;sYPLdT*-Vw|u+R+1qN~w0%}{ zOrI6n3-UHCZ(?XZ_aTfhkx@q6F%;km6w?oiZWV5Q_^1&bfw(ldrdF8adqSQ_DB4!R zsaIAaVqiexS;C?CEVw#UHjQ!3p5VcqL$Ms=jADIID&DywT>k-&61-8kJC z@o8VuIDMKtz0hh31vzN)vR<&?-`+5S9Ns>J)Yb^D_$_2b8ig-rA)h2Q_9 z!wK#q0{aVr6n7W-|NT|z=q(};ARy-d^#5w`j~gO@2<#UGdWlY8BpBd_oQ+jq!2vG#j0Oma{3rjI%sAsLUxT zS$tASCUlxP>Ne%1%qoRqCgtQ;%tTRGM44C$42qpv3bZTb#RO3>v2?M&&$u<-X<{jm z9854}ltTzICq-jwiafP8&w|VH5k^KP98zFx%jQtF68~>F6;YibRf;JvwQ}NT?Op^e zw7~hhQ=OoVNN>X1mNaMD=9qJvo656sp660e~zO zTzuUDDm^&gae?Cn%CcmxL#|6b5jEhJKssk_O(1PKZ4U~i){w+rt2<5pQrJD!j1-vS zcwaGt00cpXUsaCcLIc3Cj-dh6QX>OG?!OrrAYukYTtJv*@vs0&slPRzUvRRLw{N5Q zg}L2XegUf>`K=HiN%DgQF}ytht4+d#;`VQ{I_{r=0kXBjJG! z@6N#s?j5RnGY3zh^?}GOu4nwfs*T;`q3^x0{#bv!qR47xa}p;w5>hIIN#oKG z**L6-i9Wsp+Jh6`h?TjitfC;AH%WnZ%)j1eq{qlZp)JZvS*6I)F-1`%BS0dOBAhS} zCyfgR*&sr!^*Ertq7Nl-@Vqw_xv}NA`JybHh@PG<9Z4D&K}Bz@3dtmi^Ng-Kf=3_P zl*sh-+#7wyutDw!B|==#K)fF*Vw+hJYXMyQXwsNE-K@)i)Z;~i#}qgO!R~QITwBpl zZuo$MC<4Z`=uZ6p{|bi(6om-vG6dxJ|F1Ru>s@9znu`b^0xyTa5j{96Q!qSKUBCst8{KD&bW~u#jdqUU1`VpM}-8W}+dKQo-~sY`tylX>ON=Idqr(ni{BPF`V=rZB z<3!RJ9wr-dIgbkSO<1ND*7}y8qd_oLbGdMt$`_p&eaO~F!{vC@CBIYsk4(GH!Zmr7 z6+6U%x&MvPjnT#<0H zi&9=VMrG(|s5=b}w|njKmFQa);mO(^R&=*zizV_dt2@OF65P|JO;2_b52Kl5u{HEcI Date: Tue, 25 Nov 2014 15:11:18 +0100 Subject: [PATCH 14/42] Added one more test for song db upgrade --- .../openlp_plugins/songs/test_db.py | 19 +++++++++++++++++- tests/resources/songs/songs-1.9.7.sqlite | Bin .../resources/songs/songs-2.2-invalid.sqlite | Bin 0 -> 35840 bytes 3 files changed, 18 insertions(+), 1 deletion(-) mode change 100755 => 100644 tests/resources/songs/songs-1.9.7.sqlite create mode 100644 tests/resources/songs/songs-2.2-invalid.sqlite diff --git a/tests/functional/openlp_plugins/songs/test_db.py b/tests/functional/openlp_plugins/songs/test_db.py index e37cc58cb..690b89a18 100644 --- a/tests/functional/openlp_plugins/songs/test_db.py +++ b/tests/functional/openlp_plugins/songs/test_db.py @@ -172,7 +172,7 @@ class TestDB(TestCase): # THEN: It should return the name with the type in brackets self.assertEqual("John Doe (Words)", display_name) - def test_upgrade_song_db(self): + def test_upgrade_old_song_db(self): """ Test that we can upgrade an old song db to the current schema """ @@ -188,3 +188,20 @@ class TestDB(TestCase): # Then the song db should have been upgraded to the latest version self.assertEqual(updated_to_version, latest_version, 'The song DB should have been upgrade to the latest version') + + def test_upgrade_invalid_song_db(self): + """ + Test that we can upgrade an invalid song db to the current schema + """ + # GIVEN: A song db with invalid version + invalid_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-2.2-invalid.sqlite') + invalid_db_tmp_path = os.path.join(self.tmp_folder, 'songs-2.2-invalid.sqlite') + shutil.copyfile(invalid_db_path, invalid_db_tmp_path) + db_url = 'sqlite:///' + invalid_db_tmp_path + + # WHEN: upgrading the db + updated_to_version, latest_version = upgrade_db(db_url, upgrade) + + # Then the song db should have been upgraded to the latest version without errors + self.assertEqual(updated_to_version, latest_version, + 'The song DB should have been upgrade to the latest version') diff --git a/tests/resources/songs/songs-1.9.7.sqlite b/tests/resources/songs/songs-1.9.7.sqlite old mode 100755 new mode 100644 diff --git a/tests/resources/songs/songs-2.2-invalid.sqlite b/tests/resources/songs/songs-2.2-invalid.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..0c991d5a3245940e1cd6050098ac57b2713feb0d GIT binary patch literal 35840 zcmeHQ-*4N-9p{O*6f1Spq|G?T^?H^O*e>M6a^j|`9XE=jBw8K2bCjh)Pzbbive;0h zMp9AKm)2ef49LTDa(9KOW$3 zxFG_FK))e?6zbbMsur)Jq$Wp&3i_z`sv z@%#S(b~vhp2<#F8@%=BIm-y@d+J3W0j!9&4__u7BZ_@MwEuEIq74?11*rZo2wZInN zykF7jJ!aXOXyvLT#TRW8u4pz(K(oZsqTaByg1xlJ ziy3tqRdyk9FF|#6gXtF%DQGc%XZdnEm%bBVIZn%Fo!WKA9O{%AwauDQjOWVW+qBht ztVmT_x0q8XQ(e2uXoW4rv(z*SjMi0~>Za`^=|;_=22fEe;1-3;lkrx#LEIYK)pk=07aBk8P93v|O@kwJHeYK>M;;vuKu4SgtQISVm6>y27IZiTTCrBXu;&ra)q^kz8iuV zC=6YrmN*EYK}ofI`qnjFr#evKjmki07bu?%2+Zh;X%#^r8&EPUnhpKFX_^3&2{e=O zYammdx8SgwRS<<0YM=xF3wFTSLxo+|m=5*=DppO)0Sg8M<7sXrV68w}*Fb(|6#13A z5OLlo;%x>H*2~NQ{XrJD>p7>A&g?Rbau4dNh>ROJj z-dtHp()bXkkvCMZyq~Anm#?MQr{?D8rvpv+T-2(4?X16&Ph|NvW$0t3@!{=p;tbwi z&t6Zj-==S6Zqq5PI31s^lbA9xLbkF(700Z?yyw100t&cgJ!B7votk^yFIgDlt+7Z< z86PLxgMzaVe#12H+U+AbAVItQdrMKR!8~f0nbks&3mOj0);T$*jE<7+jF3;QIc3wb zy^n!_WL{wpvBVr0?Aaz~UA3L?nxbY`b+r+!>@bMVE`>4lM`51IGi9D(M!>)SW$9B% z`ZV$v`O9Z-@P9Np7*iC5{CcKkq{2S!XutrtY1%!yzh%0C;Ta!tG)Hfb=8mpab%$Ao z>acugLvFP0T?)-*-hnB^H;sr}f;@c1gcy3ktirtwpBDU645tQPniXa^wxA~p3c8jz zYLyKi8JKZ+l~5?W3a0NeaO6v`pW1f=&`g)3KpFL=PC2yYbbzcRV(^-L1( z%U}#u;Lfh3!Iy2jJmIhke0{1`BS9~%t*vCztMqc_O8VwXj?QvDb`Bn1c4cinlfAmi z=g294$4%4q%$3Y~W_3Ap1Mci#t`*ajH;-<;AB`y|Pm*7rYgUI6 zn~$K{p1}jK2wFb-2~-C3@Tlu7+C-+kSt z`C=%|9c=>){B=nR{;AvxQDu69+!fq>ffZolJ3Ag={rEp#SC_p!~9T#7;rBnXPr;Ep8%6VWh7|OzRc#7+ko;*CyXa^Iwe0uT4s4}LI z8-j@-2O5jd9|y=bt9;S|xcW;m<@CvBUwUTBn|f|apD%MHK(3={i*C!oDVl&VN!NV} z-1vBb2z$5i^=rPUAm$4JM+ijo_+^6!s*~|cu74Xss0LpQ z0Krlbi?10FO82feOo;J9$g-$5pw^_eST$f4fjW=cst~m0VS8Zca%}~%H)}>SektTW z?Sc+*c#hGM>McMJ!SIWLv#_NBAXwL>0aOdF3~0svn+pTPngOvcpjD;uWdW2@XLoUa z5tDV}_8nn;6>~mx_V~}o`UrO$z4R%h2&j=>VQbkm^xUyh{NxUsneFB_8uH` zqubbkp#@QUSa}jMFg9WFLUUMflMNjfbZ$0vb|#gYnVYAn**DIl-bl@!niX638nOOI z{%BG9J@937Y-#P{!C$n}LAB7g|=Edu!azi+!96-NXl_x}GY zN&X7KKm-th1C78jd71>4#9Z`#|9>LMpB!k-Q8`3lzaa1{d*Fc<9H&oa=^6ksj{g7s zViqVTBJem7X#4;1|G$qDSEPjq>>mWs|F?ho3MEAZ5CKF05kLeG0Ym^1Km>j$0 zTS@-*hf>1p5dlPCpCE8(Q2hIkzkLqR1N=aee-}wbB@IG!JiD%U5w zWq)?9ah|7 Date: Tue, 25 Nov 2014 16:00:27 +0100 Subject: [PATCH 15/42] Treat slide notes and servicemanager notes differently in the web remote and stage view. Fixes bug 1390015 Fixes: https://launchpad.net/bugs/1390015 --- openlp/plugins/remotes/html/openlp.js | 10 +++++++--- openlp/plugins/remotes/html/stage.js | 4 ++-- openlp/plugins/remotes/lib/httprouter.py | 4 +--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index 9f18c1552..2014988b8 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -67,8 +67,12 @@ window.OpenLP = { var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]"); ul.html(""); $.each(data.results.items, function (idx, value) { + var text = value["title"]; + if (value["notes"]) { + text += ' - ' + value["notes"]; + } var li = $("
  • ").append( - $("").attr("value", parseInt(idx, 10)).text(value["title"])); + $("").attr("value", parseInt(idx, 10)).text(text)); li.attr("uuid", value["id"]) li.children("a").click(OpenLP.setItem); ul.append(li); @@ -98,8 +102,8 @@ window.OpenLP = { } else { text += slide["text"]; } - if (slide["notes"]) { - text += ("
    " + slide["notes"] + "
    "); + if (slide["slide_notes"]) { + text += ("
    " + slide["slide_notes"] + "
    "); } text = text.replace(/\n/g, '
    '); if (slide["img"]) { diff --git a/openlp/plugins/remotes/html/stage.js b/openlp/plugins/remotes/html/stage.js index e63025b80..4834b4664 100644 --- a/openlp/plugins/remotes/html/stage.js +++ b/openlp/plugins/remotes/html/stage.js @@ -114,8 +114,8 @@ window.OpenLP = { text += "

    "; } // use notes if available - if (slide["notes"]) { - text += '
    ' + slide["notes"]; + if (slide["slide_notes"]) { + text += '
    ' + slide["slide_notes"]; } text = text.replace(/\n/g, "
    "); $("#currentslide").html(text); diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 22e2495c0..1313c9f9c 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -521,7 +521,7 @@ class HttpRouter(RegistryProperties): if current_item.is_capable(ItemCapabilities.HasDisplayTitle): item['title'] = str(frame['display_title']) if current_item.is_capable(ItemCapabilities.HasNotes): - item['notes'] = str(frame['notes']) + item['slide_notes'] = str(frame['notes']) if current_item.is_capable(ItemCapabilities.HasThumbnails) and \ Settings().value('remotes/thumbnails'): # If the file is under our app directory tree send the portion after the match @@ -531,8 +531,6 @@ class HttpRouter(RegistryProperties): item['text'] = str(frame['title']) item['html'] = str(frame['title']) item['selected'] = (self.live_controller.selected_row == index) - if current_item.notes: - item['notes'] = item.get('notes', '') + '\n' + current_item.notes data.append(item) json_data = {'results': {'slides': data}} if current_item: From 31018fb5aeb9c9dfe1e56172c237014af71b72d4 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 25 Nov 2014 22:22:28 +0000 Subject: [PATCH 16/42] When escaping live display stop looping to prevent display to reappear. Fixes bug 1266271 Fixes: https://launchpad.net/bugs/1266271 --- openlp/core/ui/slidecontroller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 85fe240d4..3cbdf4c33 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -493,6 +493,11 @@ class SlideController(DisplayController, RegistryProperties): """ self.display.setVisible(False) self.media_controller.media_stop(self) + # Stop looping if active + if self.play_slides_loop.isChecked(): + self.on_play_slides_loop(False) + elif self.play_slides_once.isChecked(): + self.on_play_slides_once(False) def toggle_display(self, action): """ From 92a6831b156a645a21321ab060a52ced342579d8 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 25 Nov 2014 22:31:18 +0000 Subject: [PATCH 17/42] Fix slidecontroller test --- tests/functional/openlp_core_ui/test_slidecontroller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index 1d241a317..d8663c20c 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -225,6 +225,10 @@ class TestSlideController(TestCase): Registry().register('media_controller', mocked_media_controller) slide_controller = SlideController(None) slide_controller.display = mocked_display + play_slides = MagicMock() + play_slides.isChecked.return_value = False + slide_controller.play_slides_loop = play_slides + slide_controller.play_slides_once = play_slides # WHEN: live_escape() is called slide_controller.live_escape() From 9b731aad7a0f93ba6e83cb9f75da0e27328f0e83 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 27 Nov 2014 21:40:56 +0000 Subject: [PATCH 18/42] Fix projector tests on windows --- tests/functional/openlp_core_lib/test_projectordb.py | 2 +- tests/interfaces/openlp_core_ui/test_projectormanager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_projectordb.py b/tests/functional/openlp_core_lib/test_projectordb.py index ba79b59c3..ecb78b3ef 100644 --- a/tests/functional/openlp_core_lib/test_projectordb.py +++ b/tests/functional/openlp_core_lib/test_projectordb.py @@ -85,7 +85,7 @@ class TestProjectorDB(TestCase): Set up anything necessary for all tests """ if not hasattr(self, 'projector'): - with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + with patch('openlp.core.lib.db.init_url') as mocked_init_url: mocked_init_url.start() mocked_init_url.return_value = 'sqlite:///%s' % tmpfile self.projector = ProjectorDB() diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core_ui/test_projectormanager.py index a46b0b93c..f21171fb5 100644 --- a/tests/interfaces/openlp_core_ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core_ui/test_projectormanager.py @@ -57,8 +57,8 @@ class TestProjectorManager(TestCase, TestMixin): self.setup_application() Registry.create() if not hasattr(self, 'projector_manager'): - with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: - mocked_init_url.start() + with patch('openlp.core.lib.db.init_url') as mocked_init_url: + #mocked_init_url.start() mocked_init_url.return_value = 'sqlite:///%s' % tmpfile self.projectordb = ProjectorDB() if not hasattr(self, 'projector_manager'): From 01cbd921e9618404e1e0560533ebdc89ae48439e Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 28 Nov 2014 08:13:21 +0000 Subject: [PATCH 19/42] Open opensong files in 'rb' mode. Fixes bug 1396958 Fixes: https://launchpad.net/bugs/1396958 --- openlp/plugins/songs/lib/importers/opensong.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/importers/opensong.py b/openlp/plugins/songs/lib/importers/opensong.py index e1502a903..96e35e4f5 100644 --- a/openlp/plugins/songs/lib/importers/opensong.py +++ b/openlp/plugins/songs/lib/importers/opensong.py @@ -126,7 +126,7 @@ class OpenSongImport(SongImport): for filename in self.import_source: if self.stop_import_flag: return - song_file = open(filename) + song_file = open(filename, 'rb') self.do_import_file(song_file) song_file.close() From 7d51352b2d1b48e9f7721c0470e457aaec8052c8 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 28 Nov 2014 08:15:59 +0000 Subject: [PATCH 20/42] Added divx and xvid as supported files for VLC. Fixes bug 1215302 Fixes: https://launchpad.net/bugs/1215302 --- openlp/core/ui/media/vlcplayer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 04afeb034..7abf2eac2 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -77,6 +77,7 @@ VIDEO_EXT = [ '*.asf', '*.wmv', '*.au', '*.avi', + '*.divx', '*.flv', '*.mov', '*.mp4', '*.m4v', @@ -95,7 +96,8 @@ VIDEO_EXT = [ '*.xa', '*.iso', '*.vob', - '*.webm' + '*.webm', + '*.xvid' ] From 9c64bd9d5621f3eabd07ad298f07efe91e6a7afb Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 28 Nov 2014 08:21:03 +0000 Subject: [PATCH 21/42] Added pptm as support file format for Powerpoint, Powerpoint Viewer and Libre/OpenOffice Impress. Fixes bug 1088800. Fixes: https://launchpad.net/bugs/1088800 --- openlp/plugins/presentations/lib/impresscontroller.py | 2 +- openlp/plugins/presentations/lib/powerpointcontroller.py | 2 +- openlp/plugins/presentations/lib/pptviewcontroller.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index a6e52411b..1375fb8fa 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -85,7 +85,7 @@ class ImpressController(PresentationController): log.debug('Initialising') super(ImpressController, self).__init__(plugin, 'Impress', ImpressDocument) self.supports = ['odp'] - self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx'] + self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] self.process = None self.desktop = None self.manager = None diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index cf0f5bb99..194bbacb2 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -64,7 +64,7 @@ class PowerpointController(PresentationController): """ log.debug('Initialising') super(PowerpointController, self).__init__(plugin, 'Powerpoint', PowerpointDocument) - self.supports = ['ppt', 'pps', 'pptx', 'ppsx'] + self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] self.process = None def check_available(self): diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 8bde0b213..42e034107 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -63,7 +63,7 @@ class PptviewController(PresentationController): log.debug('Initialising') self.process = None super(PptviewController, self).__init__(plugin, 'Powerpoint Viewer', PptviewDocument) - self.supports = ['ppt', 'pps', 'pptx', 'ppsx'] + self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] def check_available(self): """ From 612041daface397d71cb6495c2d7fa3bdf300c88 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 28 Nov 2014 08:26:33 +0000 Subject: [PATCH 22/42] Remove unnecessary start of patch in test --- tests/functional/openlp_core_lib/test_projectordb.py | 1 - tests/interfaces/openlp_core_ui/test_projectormanager.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_projectordb.py b/tests/functional/openlp_core_lib/test_projectordb.py index ecb78b3ef..d92258ff9 100644 --- a/tests/functional/openlp_core_lib/test_projectordb.py +++ b/tests/functional/openlp_core_lib/test_projectordb.py @@ -86,7 +86,6 @@ class TestProjectorDB(TestCase): """ if not hasattr(self, 'projector'): with patch('openlp.core.lib.db.init_url') as mocked_init_url: - mocked_init_url.start() mocked_init_url.return_value = 'sqlite:///%s' % tmpfile self.projector = ProjectorDB() diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core_ui/test_projectormanager.py index f21171fb5..9ecbaca38 100644 --- a/tests/interfaces/openlp_core_ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core_ui/test_projectormanager.py @@ -58,7 +58,6 @@ class TestProjectorManager(TestCase, TestMixin): Registry.create() if not hasattr(self, 'projector_manager'): with patch('openlp.core.lib.db.init_url') as mocked_init_url: - #mocked_init_url.start() mocked_init_url.return_value = 'sqlite:///%s' % tmpfile self.projectordb = ProjectorDB() if not hasattr(self, 'projector_manager'): From 6af0aef8bd659d1ad64a58ef48a340ce45148eee Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 28 Nov 2014 10:53:39 +0000 Subject: [PATCH 23/42] Another attempt to fix projector test across platforms and versions --- tests/functional/openlp_core_lib/test_projectordb.py | 7 +++++-- tests/interfaces/openlp_core_ui/test_projectormanager.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_projectordb.py b/tests/functional/openlp_core_lib/test_projectordb.py index d92258ff9..4dd5f52a3 100644 --- a/tests/functional/openlp_core_lib/test_projectordb.py +++ b/tests/functional/openlp_core_lib/test_projectordb.py @@ -85,8 +85,11 @@ class TestProjectorDB(TestCase): Set up anything necessary for all tests """ if not hasattr(self, 'projector'): - with patch('openlp.core.lib.db.init_url') as mocked_init_url: - mocked_init_url.return_value = 'sqlite:///%s' % tmpfile + # We need to patch this twice to make it work across multiple platforms and versions. + with patch('openlp.core.lib.db.init_url') as mocked_init_url1, \ + patch('openlp.core.lib.projector.db.init_url') as mocked_init_url2: + mocked_init_url1.return_value = 'sqlite:///%s' % tmpfile + mocked_init_url2.return_value = 'sqlite:///%s' % tmpfile self.projector = ProjectorDB() def find_record_by_ip_test(self): diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core_ui/test_projectormanager.py index 9ecbaca38..edfaa9a62 100644 --- a/tests/interfaces/openlp_core_ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core_ui/test_projectormanager.py @@ -57,8 +57,11 @@ class TestProjectorManager(TestCase, TestMixin): self.setup_application() Registry.create() if not hasattr(self, 'projector_manager'): - with patch('openlp.core.lib.db.init_url') as mocked_init_url: - mocked_init_url.return_value = 'sqlite:///%s' % tmpfile + # We need to patch this twice to make it work across multiple platforms and versions. + with patch('openlp.core.lib.db.init_url') as mocked_init_url1, \ + patch('openlp.core.lib.projector.db.init_url') as mocked_init_url2: + mocked_init_url1.return_value = 'sqlite:///%s' % tmpfile + mocked_init_url2.return_value = 'sqlite:///%s' % tmpfile self.projectordb = ProjectorDB() if not hasattr(self, 'projector_manager'): self.projector_manager = ProjectorManager(projectordb=self.projectordb) From 7d2f2d147c586f147e5346f35726563ec12e72c6 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 1 Dec 2014 08:22:17 +0000 Subject: [PATCH 24/42] Another projector test fix attempt --- .../openlp_core_lib/test_projectordb.py | 16 +++++++++------- .../openlp_core_ui/test_projectormanager.py | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_projectordb.py b/tests/functional/openlp_core_lib/test_projectordb.py index 4dd5f52a3..a1de8807f 100644 --- a/tests/functional/openlp_core_lib/test_projectordb.py +++ b/tests/functional/openlp_core_lib/test_projectordb.py @@ -32,7 +32,7 @@ record functions. PREREQUISITE: add_record() and get_all() functions validated. """ - +import sys from unittest import TestCase from tests.functional import MagicMock, patch @@ -85,12 +85,14 @@ class TestProjectorDB(TestCase): Set up anything necessary for all tests """ if not hasattr(self, 'projector'): - # We need to patch this twice to make it work across multiple platforms and versions. - with patch('openlp.core.lib.db.init_url') as mocked_init_url1, \ - patch('openlp.core.lib.projector.db.init_url') as mocked_init_url2: - mocked_init_url1.return_value = 'sqlite:///%s' % tmpfile - mocked_init_url2.return_value = 'sqlite:///%s' % tmpfile - self.projector = ProjectorDB() + # We need to patch this in different ways to make to work an all versions + if sys.version_info > (3, 4, 0): + mocked_init_url = patch('openlp.core.lib.db.init_url') + else: + mocked_init_url = patch('openlp.core.lib.projector.db.init_url') + mocked_init_url.start() + mocked_init_url.return_value = 'sqlite:///%s' % tmpfile + self.projector = ProjectorDB() def find_record_by_ip_test(self): """ diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core_ui/test_projectormanager.py index edfaa9a62..52587a44b 100644 --- a/tests/interfaces/openlp_core_ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core_ui/test_projectormanager.py @@ -31,6 +31,7 @@ Interface tests to test the themeManager class and related methods. """ import os +import sys from unittest import TestCase from openlp.core.common import Registry, Settings @@ -57,14 +58,16 @@ class TestProjectorManager(TestCase, TestMixin): self.setup_application() Registry.create() if not hasattr(self, 'projector_manager'): - # We need to patch this twice to make it work across multiple platforms and versions. - with patch('openlp.core.lib.db.init_url') as mocked_init_url1, \ - patch('openlp.core.lib.projector.db.init_url') as mocked_init_url2: - mocked_init_url1.return_value = 'sqlite:///%s' % tmpfile - mocked_init_url2.return_value = 'sqlite:///%s' % tmpfile - self.projectordb = ProjectorDB() - if not hasattr(self, 'projector_manager'): - self.projector_manager = ProjectorManager(projectordb=self.projectordb) + # We need to patch this in different ways to make to work an all versions + if sys.version_info > (3, 4, 0): + mocked_init_url = patch('openlp.core.lib.db.init_url') + else: + mocked_init_url = patch('openlp.core.lib.projector.db.init_url') + mocked_init_url.start() + mocked_init_url.return_value = 'sqlite:///%s' % tmpfile + self.projectordb = ProjectorDB() + if not hasattr(self, 'projector_manager'): + self.projector_manager = ProjectorManager(projectordb=self.projectordb) def tearDown(self): """ From e9410de13c1602c74a911b69bd5659b05a946520 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 1 Dec 2014 08:33:16 +0000 Subject: [PATCH 25/42] Revert attempted fixes to projector tests. Just don't use windows... --- .../openlp_core_lib/test_projectordb.py | 14 +++++--------- .../openlp_core_ui/test_projectormanager.py | 17 ++++++----------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_projectordb.py b/tests/functional/openlp_core_lib/test_projectordb.py index a1de8807f..ba79b59c3 100644 --- a/tests/functional/openlp_core_lib/test_projectordb.py +++ b/tests/functional/openlp_core_lib/test_projectordb.py @@ -32,7 +32,7 @@ record functions. PREREQUISITE: add_record() and get_all() functions validated. """ -import sys + from unittest import TestCase from tests.functional import MagicMock, patch @@ -85,14 +85,10 @@ class TestProjectorDB(TestCase): Set up anything necessary for all tests """ if not hasattr(self, 'projector'): - # We need to patch this in different ways to make to work an all versions - if sys.version_info > (3, 4, 0): - mocked_init_url = patch('openlp.core.lib.db.init_url') - else: - mocked_init_url = patch('openlp.core.lib.projector.db.init_url') - mocked_init_url.start() - mocked_init_url.return_value = 'sqlite:///%s' % tmpfile - self.projector = ProjectorDB() + with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + mocked_init_url.start() + mocked_init_url.return_value = 'sqlite:///%s' % tmpfile + self.projector = ProjectorDB() def find_record_by_ip_test(self): """ diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core_ui/test_projectormanager.py index 52587a44b..a46b0b93c 100644 --- a/tests/interfaces/openlp_core_ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core_ui/test_projectormanager.py @@ -31,7 +31,6 @@ Interface tests to test the themeManager class and related methods. """ import os -import sys from unittest import TestCase from openlp.core.common import Registry, Settings @@ -58,16 +57,12 @@ class TestProjectorManager(TestCase, TestMixin): self.setup_application() Registry.create() if not hasattr(self, 'projector_manager'): - # We need to patch this in different ways to make to work an all versions - if sys.version_info > (3, 4, 0): - mocked_init_url = patch('openlp.core.lib.db.init_url') - else: - mocked_init_url = patch('openlp.core.lib.projector.db.init_url') - mocked_init_url.start() - mocked_init_url.return_value = 'sqlite:///%s' % tmpfile - self.projectordb = ProjectorDB() - if not hasattr(self, 'projector_manager'): - self.projector_manager = ProjectorManager(projectordb=self.projectordb) + with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + mocked_init_url.start() + mocked_init_url.return_value = 'sqlite:///%s' % tmpfile + self.projectordb = ProjectorDB() + if not hasattr(self, 'projector_manager'): + self.projector_manager = ProjectorManager(projectordb=self.projectordb) def tearDown(self): """ From 1c71efc532f2464e897b3818f8a23d6bc94a8708 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 1 Dec 2014 15:08:13 +0000 Subject: [PATCH 26/42] Allow overwriting functions in registry, and use it when registring MainDisplay. Fixes bug 1397287 Fixes: https://launchpad.net/bugs/1397287 --- openlp/core/common/registry.py | 5 +++-- openlp/core/ui/maindisplay.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 218325823..70c2fe3bd 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -105,7 +105,7 @@ class Registry(object): if key in self.service_list: del self.service_list[key] - def register_function(self, event, function): + def register_function(self, event, function, overwrite=False): """ Register an event and associated function to be called @@ -113,8 +113,9 @@ class Registry(object): will/may need to respond to a single action and the caller does not need to understand or know about the recipients. :param function: The function to be called when the event happens. + :param overwrite: There should only be one function with the registred name. """ - if event in self.functions_list: + if not overwrite and event in self.functions_list: self.functions_list[event].append(function) else: self.functions_list[event] = [function] diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index f9f00a235..ceb94a2a9 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -153,9 +153,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.set_transparency(False) if self.is_live: - Registry().register_function('live_display_hide', self.hide_display) - Registry().register_function('live_display_show', self.show_display) - Registry().register_function('update_display_css', self.css_changed) + Registry().register_function('live_display_hide', self.hide_display, True) + Registry().register_function('live_display_show', self.show_display, True) + Registry().register_function('update_display_css', self.css_changed, True) def set_transparency(self, enabled): """ From af4715dc3c2a53c7f7a836c16377af7f894e0444 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 2 Dec 2014 09:46:19 +0000 Subject: [PATCH 27/42] Remove the registered functions instead of overwriting --- openlp/core/common/registry.py | 9 ++------- openlp/core/ui/maindisplay.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 70c2fe3bd..455c3385d 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -105,7 +105,7 @@ class Registry(object): if key in self.service_list: del self.service_list[key] - def register_function(self, event, function, overwrite=False): + def register_function(self, event, function): """ Register an event and associated function to be called @@ -113,9 +113,8 @@ class Registry(object): will/may need to respond to a single action and the caller does not need to understand or know about the recipients. :param function: The function to be called when the event happens. - :param overwrite: There should only be one function with the registred name. """ - if not overwrite and event in self.functions_list: + if event in self.functions_list: self.functions_list[event].append(function) else: self.functions_list[event] = [function] @@ -127,10 +126,6 @@ class Registry(object): :param event: The function description.. :param function: The function to be called when the event happens. """ - if not self.running_under_test: - trace_error_handler(log) - log.error('Invalid Method call for key %s' % event) - raise KeyError('Invalid Method call for key %s' % event) if event in self.functions_list: self.functions_list[event].remove(function) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index ceb94a2a9..d238a1f5e 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -153,9 +153,20 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.set_transparency(False) if self.is_live: - Registry().register_function('live_display_hide', self.hide_display, True) - Registry().register_function('live_display_show', self.show_display, True) - Registry().register_function('update_display_css', self.css_changed, True) + Registry().register_function('live_display_hide', self.hide_display) + Registry().register_function('live_display_show', self.show_display) + Registry().register_function('update_display_css', self.css_changed) + + def close(self): + """ + Remove registered function on close. + """ + if self.is_live: + Registry().remove_function('live_display_hide', self.hide_display) + Registry().remove_function('live_display_show', self.show_display) + Registry().remove_function('update_display_css', self.css_changed) + super().close() + def set_transparency(self, enabled): """ From 6ebc08d0d9df5d981b7308b891400b6f2f5d8487 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 2 Dec 2014 09:46:50 +0000 Subject: [PATCH 28/42] Attempt to fix crash in test --- .../openlp_core_ui/test_servicemanager.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/interfaces/openlp_core_ui/test_servicemanager.py b/tests/interfaces/openlp_core_ui/test_servicemanager.py index a08e4a1f1..2fe4c898e 100644 --- a/tests/interfaces/openlp_core_ui/test_servicemanager.py +++ b/tests/interfaces/openlp_core_ui/test_servicemanager.py @@ -49,7 +49,17 @@ class TestServiceManager(TestCase, TestMixin): self.setup_application() ScreenList.create(self.app.desktop()) Registry().register('application', MagicMock()) - with patch('openlp.core.lib.PluginManager'): + # Mock classes and methods used by mainwindow. + with patch('openlp.core.ui.mainwindow.SettingsForm') as mocked_settings_form, \ + patch('openlp.core.ui.mainwindow.ImageManager') as mocked_image_manager, \ + patch('openlp.core.ui.mainwindow.LiveController') as mocked_live_controller, \ + patch('openlp.core.ui.mainwindow.PreviewController') as mocked_preview_controller, \ + patch('openlp.core.ui.mainwindow.OpenLPDockWidget') as mocked_dock_widget, \ + patch('openlp.core.ui.mainwindow.QtGui.QToolBox') as mocked_q_tool_box_class, \ + patch('openlp.core.ui.mainwindow.QtGui.QMainWindow.addDockWidget') as mocked_add_dock_method, \ + patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \ + patch('openlp.core.ui.mainwindow.ProjectorManager') as mocked_projector_manager, \ + patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer: self.main_window = MainWindow() self.service_manager = Registry().get('service_manager') @@ -57,6 +67,7 @@ class TestServiceManager(TestCase, TestMixin): """ Delete all the C++ objects at the end so that we don't have a segfault """ + del self.main_window def basic_service_manager_test(self): """ From 20394de842d22eb3f67949237b3f9fb302902865 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 2 Dec 2014 10:02:13 +0000 Subject: [PATCH 29/42] pep8 fix --- openlp/core/ui/maindisplay.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index d238a1f5e..0d50e1d86 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -167,7 +167,6 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): Registry().remove_function('update_display_css', self.css_changed) super().close() - def set_transparency(self, enabled): """ Set the transparency of the window From 767e3d91f201d709f29afcd43f6c54d42fcab14d Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 2 Dec 2014 11:07:03 +0000 Subject: [PATCH 30/42] Added a refresh call to the hide-toolbar so that it gets updated if needed. Makes the blank-to-theme button visible in the toolbar. --- openlp/core/ui/slidecontroller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 85fe240d4..7066b4951 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -618,6 +618,9 @@ class SlideController(DisplayController, RegistryProperties): elif width < used_space - HIDE_MENU_THRESHOLD and not self.hide_menu.isVisible(): self.set_blank_menu(False) self.toolbar.set_widget_visible(NARROW_MENU) + # Fallback to the standard blank toolbar if the hide_menu is not visible. + elif not self.hide_menu.isVisible(): + self.set_blank_menu() def set_blank_menu(self, visible=True): """ From fc4eb5dd4da7dcd3226b45410c56f9f951a9964b Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Wed, 3 Dec 2014 17:45:34 +0000 Subject: [PATCH 31/42] URL updates. PEP fix --- openlp/core/ui/aboutdialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 9af4f55c2..99ddcc92d 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -226,9 +226,9 @@ class Ui_AboutDialog(object): 'Built With\n' ' Python: http://www.python.org/\n' ' Qt4: http://qt.io\n' - ' PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/' - 'intro\n' - ' Oxygen Icons\n' + ' PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/intro\n' + ' Oxygen Icons: http://techbase.kde.org/Projects/Oxygen/\n' + ' MuPDF: http://www.mupdf.com/\n' '\n' 'Final Credit\n' ' "For God so loved the world that He gave\n' From e94579e7f15a8ab7b2237c53e8924c53d835457b Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 3 Dec 2014 21:19:43 +0000 Subject: [PATCH 32/42] Only update blank menu if needed --- openlp/core/ui/slidecontroller.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 7066b4951..8bb985a3a 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -141,6 +141,7 @@ class SlideController(DisplayController, RegistryProperties): self.slide_list = {} self.slide_count = 0 self.slide_image = None + self.controller_width = 0 # Layout for holding panel self.panel_layout = QtGui.QVBoxLayout(self.panel) self.panel_layout.setSpacing(0) @@ -382,13 +383,11 @@ class SlideController(DisplayController, RegistryProperties): Registry().register_function('slidecontroller_live_spin_delay', self.receive_spin_delay) self.toolbar.set_widget_visible(LOOP_LIST, False) self.toolbar.set_widget_visible(WIDE_MENU, False) - else: - self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service) - self.toolbar.set_widget_visible(['editSong'], False) - if self.is_live: self.set_live_hot_keys(self) self.__add_actions_to_widget(self.controller) else: + self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service) + self.toolbar.set_widget_visible(['editSong'], False) self.controller.addActions([self.next_item, self.previous_item]) Registry().register_function('slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop) Registry().register_function('slidecontroller_%s_change' % self.type_prefix, self.on_slide_change) @@ -599,7 +598,10 @@ class SlideController(DisplayController, RegistryProperties): self.slide_preview.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio)) self.preview_display.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio)) self.preview_display.screen = {'size': self.preview_display.geometry()} - self.on_controller_size_changed(self.controller.width()) + # Only update controller layout if width has actually changed + if self.controller_width != self.controller.width(): + self.controller_width = self.controller.width() + self.on_controller_size_changed(self.controller_width) def on_controller_size_changed(self, width): """ From 304c489cd7d9863edbe0c226abc818cbe419a81e Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 3 Dec 2014 22:27:09 +0000 Subject: [PATCH 33/42] Add test for loading song with linked audio --- tests/resources/service/serviceitem-song-linked-audio.osj | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/resources/service/serviceitem-song-linked-audio.osj diff --git a/tests/resources/service/serviceitem-song-linked-audio.osj b/tests/resources/service/serviceitem-song-linked-audio.osj new file mode 100644 index 000000000..e361a8757 --- /dev/null +++ b/tests/resources/service/serviceitem-song-linked-audio.osj @@ -0,0 +1 @@ +[{"serviceitem": {"header": {"will_auto_start": false, "title": "Amazing Grace", "audit": ["Amazing Grace", ["John Newton"], "", ""], "processor": null, "theme_overwritten": false, "start_time": 0, "auto_play_slides_loop": false, "plugin": "songs", "auto_play_slides_once": false, "from_plugin": false, "media_length": 0, "xml_version": "\nAmazing GraceJohn NewtonAmazing Grace! how sweet the sound
    That saved a wretch like me;
    I once was lost, but now am found,
    Was blind, but now I see.
    \u2019Twas grace that taught my heart to fear,
    And grace my fears relieved;
    How precious did that grace appear,
    The hour I first believed!
    Through many dangers, toils and snares
    I have already come;
    \u2019Tis grace that brought me safe thus far,
    And grace will lead me home.
    The Lord has promised good to me,
    His word my hope secures;
    He will my shield and portion be
    As long as life endures.
    Yes, when this heart and flesh shall fail,
    And mortal life shall cease,
    I shall possess within the veil
    A life of joy and peace.
    When we\u2019ve been there a thousand years,
    Bright shining as the sun,
    We\u2019ve no less days to sing God\u2019s praise
    Than when we first begun.
    ", "timed_slide_interval": 0, "data": {"title": "amazing grace@", "authors": "John Newton"}, "type": 1, "background_audio": ["/home/tgc/.local/share/openlp/songs/audio/7/amazing_grace.mp3"], "theme": null, "footer": ["Amazing Grace", "Written by: John Newton"], "name": "songs", "capabilities": [2, 1, 5, 8, 9, 13, 15], "end_time": 0, "notes": "", "search": "", "icon": ":/plugins/plugin_songs.png"}, "data": [{"title": "Amazing Grace! how sweet the s", "verseTag": "V1", "raw_slide": "Amazing Grace! how sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see."}, {"title": "\u2019Twas grace that taught my hea", "verseTag": "V2", "raw_slide": "\u2019Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!"}, {"title": "Through many dangers, toils an", "verseTag": "V3", "raw_slide": "Through many dangers, toils and snares\nI have already come;\n\u2019Tis grace that brought me safe thus far,\nAnd grace will lead me home."}, {"title": "The Lord has promised good to ", "verseTag": "V4", "raw_slide": "The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures."}, {"title": "Yes, when this heart and flesh", "verseTag": "V5", "raw_slide": "Yes, when this heart and flesh shall fail,\nAnd mortal life shall cease,\nI shall possess within the veil\nA life of joy and peace."}, {"title": "When we\u2019ve been there a thousa", "verseTag": "V6", "raw_slide": "When we\u2019ve been there a thousand years,\nBright shining as the sun,\nWe\u2019ve no less days to sing God\u2019s praise\nThan when we first begun."}]}}] From 0038ffcbe63319e6f796f94cc2a86bf9450d33db Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 3 Dec 2014 22:29:20 +0000 Subject: [PATCH 34/42] Add test for loading song with linked audio - the actual test --- .../openlp_core_lib/test_serviceitem.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index 629b17114..f7c2d9e56 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -271,3 +271,36 @@ class TestServiceItem(TestCase): self.assertEqual(service_item.start_time, 654.375, 'Start time should be 654.375') self.assertEqual(service_item.end_time, 672.069, 'End time should be 672.069') self.assertEqual(service_item.media_length, 17.694, 'Media length should be 17.694') + + def service_item_load_song_and_audio_from_service_test(self): + """ + Test the Service Item - adding a song slide from a saved service + """ + # GIVEN: A new service item and a mocked add icon function + service_item = ServiceItem(None) + service_item.add_icon = MagicMock() + + # WHEN: We add a custom from a saved service + line = convert_file_service_item(TEST_PATH, 'service/serviceitem-song-linked-audio.osj') + service_item.set_from_service(line, '/test/') + + # THEN: We should get back a valid service item + self.assertTrue(service_item.is_valid, 'The new service item should be valid') + assert_length(0, service_item._display_frames, 'The service item should have no display frames') + assert_length(7, service_item.capabilities, 'There should be 7 default custom item capabilities') + + # WHEN: We render the frames of the service item + service_item.render(True) + + # THEN: The frames should also be valid + self.assertEqual('Amazing Grace', service_item.get_display_title(), 'The title should be "Amazing Grace"') + self.assertEqual(VERSE[:-1], service_item.get_frames()[0]['text'], + 'The returned text matches the input, except the last line feed') + self.assertEqual(VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1), + 'The first line has been returned') + self.assertEqual('Amazing Grace! how sweet the s', service_item.get_frame_title(0), + '"Amazing Grace! how sweet the s" has been returned as the title') + self.assertEqual('’Twas grace that taught my hea', service_item.get_frame_title(1), + '"’Twas grace that taught my hea" has been returned as the title') + self.assertEqual('/test/amazing_grace.mp3', service_item.background_audio[0], + '"/test/amazing_grace.mp3" should be in the background_audio list') From fd48879513b7699c12f0bcfdb4e7e7b6b0921f2f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 3 Dec 2014 22:31:23 +0000 Subject: [PATCH 35/42] The fix for loading songs with linked audio. Fixes bug 1398403 Fixes: https://launchpad.net/bugs/1398403 --- openlp/core/lib/serviceitem.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 4e7ff032a..a7051b9d9 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -36,6 +36,7 @@ import html import logging import os import uuid +import ntpath from PyQt4 import QtGui @@ -423,8 +424,12 @@ class ServiceItem(RegistryProperties): if 'background_audio' in header: self.background_audio = [] for filename in header['background_audio']: - # Give them real file paths - self.background_audio.append(os.path.join(path, filename)) + # Give them real file paths. + filepath = filename + if path: + # Windows can handle both forward and backward slashes, so we use ntpath to get the basename + filepath = os.path.join(path, ntpath.basename(filename)) + self.background_audio.append(filepath) self.theme_overwritten = header.get('theme_overwritten', False) if self.service_item_type == ServiceItemType.Text: for slide in service_item['serviceitem']['data']: From fd57f81376e9325489da1c5c3a4551d01f9cf066 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 4 Dec 2014 22:14:56 +0000 Subject: [PATCH 36/42] Remove dummy initialization of preview_display to solve issue of mediaplayer preview being hidden --- openlp/core/ui/slidecontroller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 8bb985a3a..3d9d8b29c 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -332,9 +332,6 @@ class SlideController(DisplayController, RegistryProperties): self.slide_layout.setMargin(0) self.slide_layout.setObjectName('SlideLayout') self.preview_display = Display(self) - self.preview_display.setGeometry(QtCore.QRect(0, 0, 300, 300)) - self.preview_display.screen = {'size': self.preview_display.geometry()} - self.preview_display.setup() self.slide_layout.insertWidget(0, self.preview_display) self.preview_display.hide() # Actual preview screen From e190bf30e9dbf4bce9767271bd988fcf98f2db0a Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 5 Dec 2014 17:04:44 +0000 Subject: [PATCH 37/42] Fixes 1397570 by 'feature detection' --- openlp/core/utils/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 128c09b38..df9d57fef 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -35,6 +35,7 @@ import logging import locale import os import re +from shutil import which from subprocess import Popen, PIPE import sys import urllib.request @@ -406,13 +407,18 @@ def get_uno_command(): """ Returns the UNO command to launch an openoffice.org instance. """ - COMMAND = 'soffice' + for command in ['libreoffice', 'soffice']: + if which(command): + break + else: + raise FileNotFoundError('Command not found') + OPTIONS = '--nologo --norestore --minimized --nodefault --nofirststartwizard' if UNO_CONNECTION_TYPE == 'pipe': CONNECTION = '"--accept=pipe,name=openlp_pipe;urp;"' else: CONNECTION = '"--accept=socket,host=localhost,port=2002;urp;"' - return '%s %s %s' % (COMMAND, OPTIONS, CONNECTION) + return '%s %s %s' % (command, OPTIONS, CONNECTION) def get_uno_instance(resolver): From be12ce54bbbd1f15ba14b3370a443b80d7ad4d3d Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 5 Dec 2014 21:10:58 +0000 Subject: [PATCH 38/42] Use shutil.which instead of calling which with check_output. --- openlp/plugins/presentations/lib/pdfcontroller.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index e1d0dc8f0..941be3fe1 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -31,6 +31,7 @@ import os import logging from tempfile import NamedTemporaryFile import re +import shutil from subprocess import check_output, CalledProcessError, STDOUT from openlp.core.utils import AppLocation @@ -144,17 +145,10 @@ class PdfController(PresentationController): else: DEVNULL = open(os.devnull, 'wb') # First try to find mupdf - try: - self.mudrawbin = check_output(['which', 'mudraw'], - stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n') - except CalledProcessError: - self.mudrawbin = '' + self.mudrawbin = shutil.which('mudraw') # if mupdf isn't installed, fallback to ghostscript if not self.mudrawbin: - try: - self.gsbin = check_output(['which', 'gs'], stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n') - except CalledProcessError: - self.gsbin = '' + self.gsbin = shutil.which('gs') # Last option: check if mudraw is placed in OpenLP base folder if not self.mudrawbin and not self.gsbin: application_path = AppLocation.get_directory(AppLocation.AppDir) From 402e91f688df76a6aa1c2786438d96d13877bde7 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 5 Dec 2014 22:36:49 +0000 Subject: [PATCH 39/42] When cloning a song copy the mediafiles as well. Fixes bug 1309998 Fixes: https://launchpad.net/bugs/1309998 --- openlp/plugins/songs/lib/mediaitem.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 18d964115..7426b032c 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -395,6 +395,18 @@ class SongMediaItem(MediaManagerItem): new_song = self.open_lyrics.xml_to_song(song_xml) new_song.title = '%s <%s>' % \ (new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning')) + # Copy audio files from the old to the new song + if len(old_song.media_files) > 0: + save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(new_song.id)) + check_directory_exists(save_path) + for media_file in old_song.media_files: + new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name)) + shutil.copyfile(media_file.file_name, new_media_file_name) + new_media_file = MediaFile() + new_media_file.file_name = new_media_file_name + new_media_file.type = media_file.type + new_media_file.weight = media_file.weight + new_song.media_files.append(new_media_file) self.plugin.manager.save_object(new_song) self.on_song_list_load() From 4123b9af1d7107f9d138699c2c17e701af1d5705 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 6 Dec 2014 20:08:42 +0000 Subject: [PATCH 40/42] Imported just 'which' from shutil, instead of the complete module. Also set the default song search to search as type. --- openlp/core/ui/servicemanager.py | 3 +-- openlp/plugins/presentations/lib/pdfcontroller.py | 6 +++--- openlp/plugins/songs/songsplugin.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index e8b201cf6..3bcbaa4d4 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -747,8 +747,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage 'File is not a valid service.\n The content encoding is not UTF-8.')) continue os_file = ucs_file.replace('/', os.path.sep) - if not os_file.startswith('audio'): - os_file = os.path.split(os_file)[1] + os_file = os.path.basename(os_file) self.log_debug('Extract file: %s' % os_file) zip_info.filename = os_file zip_file.extract(zip_info, self.service_path) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 941be3fe1..2550ae928 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -31,7 +31,7 @@ import os import logging from tempfile import NamedTemporaryFile import re -import shutil +from shutil import which from subprocess import check_output, CalledProcessError, STDOUT from openlp.core.utils import AppLocation @@ -145,10 +145,10 @@ class PdfController(PresentationController): else: DEVNULL = open(os.devnull, 'wb') # First try to find mupdf - self.mudrawbin = shutil.which('mudraw') + self.mudrawbin = which('mudraw') # if mupdf isn't installed, fallback to ghostscript if not self.mudrawbin: - self.gsbin = shutil.which('gs') + self.gsbin = which('gs') # Last option: check if mudraw is placed in OpenLP base folder if not self.mudrawbin and not self.gsbin: application_path = AppLocation.get_directory(AppLocation.AppDir) diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index b7daebaab..be04db9cc 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -60,7 +60,7 @@ __default_settings__ = { 'songs/last search type': SongSearch.Entire, 'songs/last import type': SongFormat.OpenLyrics, 'songs/update service on edit': False, - 'songs/search as type': False, + 'songs/search as type': True, 'songs/add song from service': True, 'songs/display songbar': True, 'songs/display songbook': False, From 56f4b8aa5e792f7b22dcce184db0cc411ccec375 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 6 Dec 2014 20:40:40 +0000 Subject: [PATCH 41/42] Another fix for blank menu. --- openlp/core/ui/slidecontroller.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 3d9d8b29c..784cc1ea6 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -141,7 +141,7 @@ class SlideController(DisplayController, RegistryProperties): self.slide_list = {} self.slide_count = 0 self.slide_image = None - self.controller_width = 0 + self.controller_width = -1 # Layout for holding panel self.panel_layout = QtGui.QVBoxLayout(self.panel) self.panel_layout.setSpacing(0) @@ -619,6 +619,7 @@ class SlideController(DisplayController, RegistryProperties): self.toolbar.set_widget_visible(NARROW_MENU) # Fallback to the standard blank toolbar if the hide_menu is not visible. elif not self.hide_menu.isVisible(): + self.toolbar.set_widget_visible(NARROW_MENU, False) self.set_blank_menu() def set_blank_menu(self, visible=True): @@ -694,7 +695,9 @@ class SlideController(DisplayController, RegistryProperties): self.mediabar.show() self.previous_item.setVisible(not item.is_media()) self.next_item.setVisible(not item.is_media()) - # The layout of the toolbar is size dependent, so make sure it fits + # The layout of the toolbar is size dependent, so make sure it fits. Reset stored controller_width. + if self.is_live: + self.controller_width = -1 self.on_controller_size_changed(self.controller.width()) # Work-around for OS X, hide and then show the toolbar # See bug #791050 From da7ff60697b4d496db4831ea792774582f091bde Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 6 Dec 2014 21:05:45 +0000 Subject: [PATCH 42/42] Moved test service files into appropriate folder. --- tests/functional/openlp_core_lib/test_serviceitem.py | 4 ++-- tests/resources/{ => service}/migrate_video_20_22.osd | 0 tests/resources/{ => service}/serviceitem-dvd.osj | 0 tests/resources/{ => service}/serviceitem_custom_1.osj | 0 tests/resources/{ => service}/serviceitem_image_1.osj | 0 tests/resources/{ => service}/serviceitem_image_2.osj | 0 tests/resources/{ => service}/serviceitem_image_3.osj | 0 tests/utils/osdinteraction.py | 2 +- 8 files changed, 3 insertions(+), 3 deletions(-) rename tests/resources/{ => service}/migrate_video_20_22.osd (100%) rename tests/resources/{ => service}/serviceitem-dvd.osj (100%) rename tests/resources/{ => service}/serviceitem_custom_1.osj (100%) rename tests/resources/{ => service}/serviceitem_image_1.osj (100%) rename tests/resources/{ => service}/serviceitem_image_2.osj (100%) rename tests/resources/{ => service}/serviceitem_image_3.osj (100%) diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index f7c2d9e56..8b7075d7c 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -46,7 +46,7 @@ VERSE = 'The Lord said to {r}Noah{/r}: \n'\ '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\ 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456'] -TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources', 'service')) class TestServiceItem(TestCase): @@ -281,7 +281,7 @@ class TestServiceItem(TestCase): service_item.add_icon = MagicMock() # WHEN: We add a custom from a saved service - line = convert_file_service_item(TEST_PATH, 'service/serviceitem-song-linked-audio.osj') + line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj') service_item.set_from_service(line, '/test/') # THEN: We should get back a valid service item diff --git a/tests/resources/migrate_video_20_22.osd b/tests/resources/service/migrate_video_20_22.osd similarity index 100% rename from tests/resources/migrate_video_20_22.osd rename to tests/resources/service/migrate_video_20_22.osd diff --git a/tests/resources/serviceitem-dvd.osj b/tests/resources/service/serviceitem-dvd.osj similarity index 100% rename from tests/resources/serviceitem-dvd.osj rename to tests/resources/service/serviceitem-dvd.osj diff --git a/tests/resources/serviceitem_custom_1.osj b/tests/resources/service/serviceitem_custom_1.osj similarity index 100% rename from tests/resources/serviceitem_custom_1.osj rename to tests/resources/service/serviceitem_custom_1.osj diff --git a/tests/resources/serviceitem_image_1.osj b/tests/resources/service/serviceitem_image_1.osj similarity index 100% rename from tests/resources/serviceitem_image_1.osj rename to tests/resources/service/serviceitem_image_1.osj diff --git a/tests/resources/serviceitem_image_2.osj b/tests/resources/service/serviceitem_image_2.osj similarity index 100% rename from tests/resources/serviceitem_image_2.osj rename to tests/resources/service/serviceitem_image_2.osj diff --git a/tests/resources/serviceitem_image_3.osj b/tests/resources/service/serviceitem_image_3.osj similarity index 100% rename from tests/resources/serviceitem_image_3.osj rename to tests/resources/service/serviceitem_image_3.osj diff --git a/tests/utils/osdinteraction.py b/tests/utils/osdinteraction.py index 8d12943e8..4b016d1ae 100644 --- a/tests/utils/osdinteraction.py +++ b/tests/utils/osdinteraction.py @@ -42,7 +42,7 @@ def read_service_from_file(file_name): @param file_name: File name of an OSD file residing in the tests/resources folder. @return: The service contained in the file. """ - service_file = os.path.join(TEST_RESOURCES_PATH, file_name) + service_file = os.path.join(TEST_RESOURCES_PATH, 'service', file_name) with open(service_file, 'r') as open_file: service = json.load(open_file) return service