← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~tomasgroth/openlp/ftw-issues into lp:openlp

 

Tomas Groth has proposed merging lp:~tomasgroth/openlp/ftw-issues into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)
  Tim Bentley (trb143)
Related bugs:
  Bug #1194622 in OpenLP: "[support-system] Traceback when importing bible with disabled bible plugin in FTW"
  https://bugs.launchpad.net/openlp/+bug/1194622
  Bug #1222944 in OpenLP: "Tracebacks in First Time Wizard with no internet"
  https://bugs.launchpad.net/openlp/+bug/1222944
  Bug #1390818 in OpenLP: "Handle connection errors in FTW gracefully"
  https://bugs.launchpad.net/openlp/+bug/1390818

For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/ftw-issues/+merge/242883

Made the FTW able to handle if the downloaded config file is invalid (part of bug 1222944).
Improved FTW handling of connection issues.
Update thememanager widget after theme import.
Close song db session after import to unlock file on windows, so they can be deleted.
Fix for importing old bible DBs, like KJV
Removed old pyinstaller hooks which has been moved to the packaging branch.
When bible or song plugin is disabled in FTW don't show download for those plugins in FTW. Fixes bug 1194622.
-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/lib/renderer.py'
--- openlp/core/lib/renderer.py	2014-11-20 06:58:33 +0000
+++ openlp/core/lib/renderer.py	2014-11-26 09:06:53 +0000
@@ -302,7 +302,7 @@
                         lines = text.strip('\n').split('\n')
                         pages.extend(self._paginate_slide(lines, line_end))
                         break
-                    count =+ 1
+                    count += 1
             else:
                 # Clean up line endings.
                 pages = self._paginate_slide(text.split('\n'), line_end)

=== modified file 'openlp/core/ui/firsttimeform.py'
--- openlp/core/ui/firsttimeform.py	2014-11-20 06:58:33 +0000
+++ openlp/core/ui/firsttimeform.py	2014-11-26 09:06:53 +0000
@@ -37,14 +37,15 @@
 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
 
 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.utils import get_web_page
+from openlp.core.lib.ui import critical_error_message_box
+from openlp.core.utils import get_web_page, CONNECTION_RETRIES, CONNECTION_TIMEOUT
 from .firsttimewizard import UiFirstTimeWizard, FirstTimePage
 
 log = logging.getLogger(__name__)
@@ -89,27 +90,32 @@
         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():
+            print('Go for songs! %r' % 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.
         """
         self.application.process_events()
         if self.currentId() == FirstTimePage.Plugins:
-            if self.has_run_wizard:
-                self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
-                self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
-                self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name(
-                    'presentations').is_active())
-                self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
-                self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
-                self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
-                self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
-                self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
-                self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
             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:
@@ -124,7 +130,7 @@
             self.application.set_normal_cursor()
             return FirstTimePage.Defaults
         else:
-            return self.currentId() + 1
+            return self.get_next_page_id()
 
     def exec_(self):
         """
@@ -141,17 +147,23 @@
         """
         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
@@ -171,6 +183,17 @@
         self.no_internet_finish_button.setVisible(False)
         # Check if this is a re-run of the wizard.
         self.has_run_wizard = Settings().value('core/has run wizard')
+        if self.has_run_wizard:
+            self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
+            self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
+            self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active())
+            self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
+            self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
+            self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
+            self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
+            self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
+            self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
+        self.application.set_normal_cursor()
         # Sort out internet access for downloads
         if self.web_access:
             songs = self.config.get('songs', 'languages')
@@ -200,7 +223,6 @@
             # Download the theme screenshots.
             self.theme_screenshot_thread = ThemeScreenshotThread(self)
             self.theme_screenshot_thread.start()
-        self.application.set_normal_cursor()
 
     def update_screen_list_combo(self):
         """
@@ -286,24 +308,42 @@
     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()
+        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
+                    time.sleep(0.1)
+                    continue
+            break
         # 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):
         """
@@ -322,9 +362,19 @@
 
         :param url: The URL of the file we want to download.
         """
-        site = urllib.request.urlopen(url)
-        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
+                    time.sleep(0.1)
+                    continue
 
     def _download_progress(self, count, block_size):
         """
@@ -354,32 +404,41 @@
         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:
+            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
@@ -443,38 +502,11 @@
         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())
@@ -483,6 +515,46 @@
         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.

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2014-11-13 20:15:43 +0000
+++ openlp/core/ui/mainwindow.py	2014-11-26 09:06:53 +0000
@@ -706,7 +706,10 @@
                     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()

=== modified file 'openlp/core/utils/__init__.py'
--- openlp/core/utils/__init__.py	2014-10-22 20:47:47 +0000
+++ openlp/core/utils/__init__.py	2014-11-26 09:06:53 +0000
@@ -35,6 +35,7 @@
 import locale
 import os
 import re
+import time
 from subprocess import Popen, PIPE
 import sys
 import urllib.request
@@ -91,6 +92,8 @@
         '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 +253,19 @@
                 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
+                    time.sleep(0.1)
+                    continue
+            break
         if remote_version:
             version_string = remote_version
     return version_string
@@ -389,11 +401,19 @@
         req.add_header(header[0], header[1])
     page = None
     log.debug('Downloading URL = %s' % url)
-    try:
-        page = urllib.request.urlopen(req)
-        log.debug('Downloaded URL = %s' % page.geturl())
-    except urllib.error.URLError:
-        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:
+                time.sleep(0.1)
+                continue
+        break
     if not page:
         return None
     if update_openlp:

=== modified file 'openlp/plugins/bibles/bibleplugin.py'
--- openlp/plugins/bibles/bibleplugin.py	2014-04-12 20:19:22 +0000
+++ openlp/plugins/bibles/bibleplugin.py	2014-11-26 09:06:53 +0000
@@ -159,7 +159,7 @@
             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:

=== modified file 'openlp/plugins/bibles/lib/db.py'
--- openlp/plugins/bibles/lib/db.py	2014-05-02 06:42:17 +0000
+++ openlp/plugins/bibles/lib/db.py	2014-11-26 09:06:53 +0000
@@ -170,6 +170,9 @@
         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 @@
         """
         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):

=== modified file 'openlp/plugins/songs/lib/importers/openlp.py'
--- openlp/plugins/songs/lib/importers/openlp.py	2014-07-04 09:35:10 +0000
+++ openlp/plugins/songs/lib/importers/openlp.py	2014-11-26 09:06:53 +0000
@@ -217,4 +217,5 @@
                 self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
             if self.stop_import_flag:
                 break
+        self.source_session.close()
         engine.dispose()

=== removed directory 'resources/pyinstaller'
=== removed file 'resources/pyinstaller/hook-openlp.core.ui.media.py'
--- resources/pyinstaller/hook-openlp.core.ui.media.py	2013-12-24 08:56:50 +0000
+++ resources/pyinstaller/hook-openlp.core.ui.media.py	1970-01-01 00:00:00 +0000
@@ -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']

=== removed file 'resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py'
--- resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py	2013-12-29 19:47:54 +0000
+++ resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py	1970-01-01 00:00:00 +0000
@@ -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']

=== removed file 'resources/pyinstaller/hook-openlp.py'
--- resources/pyinstaller/hook-openlp.py	2013-12-24 08:56:50 +0000
+++ resources/pyinstaller/hook-openlp.py	1970-01-01 00:00:00 +0000
@@ -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']

=== modified file 'tests/functional/openlp_core_ui/test_firsttimeform.py'
--- tests/functional/openlp_core_ui/test_firsttimeform.py	2014-10-22 20:43:05 +0000
+++ tests/functional/openlp_core_ui/test_firsttimeform.py	2014-11-26 09:06:53 +0000
@@ -49,6 +49,22 @@
 directory = themes
 """
 
+FAKE_BROKEN_CONFIG = b"""
+[general]
+base url = http://example.com/frw/
+[songs]
+directory = songs
+[bibles]
+directory = bibles
+"""
+
+FAKE_INVALID_CONFIG = b"""
+<html>
+<head><title>This is not a config file</title></head>
+<body>Some text</body>
+</html>
+"""
+
 
 class TestFirstTimeForm(TestCase, TestMixin):
 
@@ -104,3 +120,33 @@
             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')

=== modified file 'tests/functional/openlp_core_utils/test_utils.py'
--- tests/functional/openlp_core_utils/test_utils.py	2014-05-02 06:42:17 +0000
+++ tests/functional/openlp_core_utils/test_utils.py	2014-11-26 09:06:53 +0000
@@ -335,7 +335,7 @@
             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 @@
             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 @@
             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 @@
             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()


Follow ups