diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 98e2bf78f..22218f983 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -25,6 +25,7 @@ This module contains the first time wizard. import hashlib import logging import os +import socket import time import urllib.request import urllib.parse @@ -61,6 +62,7 @@ class ThemeScreenshotWorker(QtCore.QObject): self.filename = filename self.sha256 = sha256 self.screenshot = screenshot + socket.setdefaulttimeout(CONNECTION_TIMEOUT) super(ThemeScreenshotWorker, self).__init__() def run(self): @@ -250,7 +252,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): # Download the theme screenshots themes = self.config.get('themes', 'files').split(',') for theme in themes: - self.application.process_events() title = self.config.get('theme_%s' % theme, 'title') filename = self.config.get('theme_%s' % theme, 'filename') sha256 = self.config.get('theme_%s' % theme, 'sha256', fallback='') @@ -264,6 +265,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): worker.finished.connect(thread.quit) worker.moveToThread(thread) thread.start() + self.application.process_events() def set_defaults(self): """ @@ -403,8 +405,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): retries = 0 while True: try: - url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT) filename = open(f_path, "wb") + url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT) if sha256: hasher = hashlib.sha256() # Download until finished or canceled. @@ -422,7 +424,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): log.error('sha256 sums did not match for file: {}'.format(f_path)) os.remove(f_path) return False - except urllib.error.URLError: + except (urllib.error.URLError, socket.timeout) as err: trace_error_handler(log) filename.close() os.remove(f_path) @@ -447,8 +449,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): for index, theme in enumerate(themes): screenshot = self.config.get('theme_%s' % theme, 'screenshot') item = self.themes_list_widget.item(index) - # if item: - item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot))) + if item: + item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot))) def _get_file_size(self, url): """ @@ -617,6 +619,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): songs_destination = os.path.join(gettempdir(), 'openlp') bibles_destination = AppLocation.get_section_data_path('bibles') themes_destination = AppLocation.get_section_data_path('themes') + missed_files = [] # Download songs for i in range(self.songs_list_widget.count()): item = self.songs_list_widget.item(i) @@ -626,7 +629,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): 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, sha256): - return False + missed_files.append('Song: {}'.format(filename)) # Download Bibles bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget) while bibles_iterator.value(): @@ -637,7 +640,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.previous_size = 0 if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible), sha256): - return False + missed_files.append('Bible: {}'.format(bible)) bibles_iterator += 1 # Download themes for i in range(self.themes_list_widget.count()): @@ -648,7 +651,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.previous_size = 0 if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme), sha256): - return False + missed_files.append('Theme: {}'.format(theme)) + if missed_files: + file_list = '' + for entry in missed_files: + file_list += '{}
'.format(entry) + msg = QtGui.QMessageBox() + msg.setIcon(QtGui.QMessageBox.Warning) + msg.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'Network Error')) + msg.setText(translate('OpenLP.FirstTimeWizard', 'Unable to download some files')) + msg.setInformativeText(translate('OpenLP.FirstTimeWizard', + 'The following files were not able to be ' + 'downloaded:
{}'.format(file_list))) + msg.setStandardButtons(msg.Ok) + ans = msg.exec_() return True def _set_plugin_status(self, field, tag): diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 44399eb60..ea896fad7 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -29,6 +29,7 @@ import locale import os import platform import re +import socket import time from shutil import which from subprocess import Popen, PIPE @@ -394,26 +395,44 @@ def get_web_page(url, header=None, update_openlp=False): req.add_header('User-Agent', user_agent) if header: req.add_header(header[0], header[1]) - page = None log.debug('Downloading URL = %s' % url) - retries = 1 - while True: + retries = 0 + while retries <= CONNECTION_RETRIES: + retries += 1 + time.sleep(0.1) try: page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT) - log.debug('Downloaded URL = %s' % page.geturl()) - except (urllib.error.URLError, ConnectionError): + log.debug('Downloaded page {}'.format(page.geturl())) + except urllib.error.URLError as err: + log.exception('URLError on {}'.format(url)) + log.exception('URLError: {}'.format(err.reason)) + page = None if retries > CONNECTION_RETRIES: - log.exception('The web page could not be downloaded') raise - else: - retries += 1 - time.sleep(0.1) - continue - break - if not page: - return None + except socket.timeout: + log.exception('Socket timeout: {}'.format(url)) + page = None + if retries > CONNECTION_RETRIES: + raise + except ConnectionRefusedError: + log.exception('ConnectionRefused: {}'.format(url)) + page = None + if retries > CONNECTION_RETRIES: + raise + break + except ConnectionError: + log.exception('Connection error: {}'.format(url)) + page = None + if retries > CONNECTION_RETRIES: + raise + except: + # Don't know what's happening, so reraise the original + raise if update_openlp: Registry().get('application').process_events() + if not page: + log.exception('{} could not be downloaded'.format(url)) + return None log.debug(page) return page diff --git a/tests/functional/openlp_core_ui/test_firsttimeform.py b/tests/functional/openlp_core_ui/test_firsttimeform.py index 4be8ec063..54c1a7b1d 100644 --- a/tests/functional/openlp_core_ui/test_firsttimeform.py +++ b/tests/functional/openlp_core_ui/test_firsttimeform.py @@ -23,6 +23,8 @@ Package to test the openlp.core.ui.firsttimeform package. """ import os +import socket +import tempfile import urllib from unittest import TestCase @@ -70,6 +72,11 @@ class TestFirstTimeForm(TestCase, TestMixin): self.app.process_events = lambda: None Registry.create() Registry().register('application', self.app) + self.tempfile = os.path.join(tempfile.gettempdir(), 'testfile') + + def tearDown(self): + if os.path.isfile(self.tempfile): + os.remove(self.tempfile) def initialise_test(self): """ @@ -229,3 +236,20 @@ class TestFirstTimeForm(TestCase, TestMixin): # THEN: the critical_error_message_box should have been called self.assertEquals(mocked_message_box.mock_calls[1][1][0], 'Network Error 407', 'first_time_form should have caught Network Error') + + @patch('openlp.core.ui.firsttimeform.urllib.request.urlopen') + def socket_timeout_test(self, mocked_urlopen): + """ + Test socket timeout gets caught + """ + # GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download + first_time_form = FirstTimeForm(None) + first_time_form.initialize(MagicMock()) + mocked_urlopen.side_effect = socket.timeout() + + # WHEN: Attempt to retrieve a file + first_time_form.url_get_file(url='http://localhost/test', f_path=self.tempfile) + + # THEN: socket.timeout should have been caught + # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files + self.assertFalse(os.path.exists(self.tempfile), 'FTW url_get_file should have caught socket.timeout')