Fix bug 1422683: Added exception checks to get_web_page() to help with changed description. Rearrange calls so thumbnail downloads don't hang other event threads. Reraise exception when retries > CONNECTION_RETRIES.

bzr-revno: 2512
Fixes: https://launchpad.net/bugs/1422683
This commit is contained in:
Ken Roberts 2015-02-21 21:11:25 +02:00 committed by Raoul Snyman
commit 85b8cb2c50
3 changed files with 80 additions and 21 deletions

View File

@ -25,6 +25,7 @@ This module contains the first time wizard.
import hashlib import hashlib
import logging import logging
import os import os
import socket
import time import time
import urllib.request import urllib.request
import urllib.parse import urllib.parse
@ -61,6 +62,7 @@ class ThemeScreenshotWorker(QtCore.QObject):
self.filename = filename self.filename = filename
self.sha256 = sha256 self.sha256 = sha256
self.screenshot = screenshot self.screenshot = screenshot
socket.setdefaulttimeout(CONNECTION_TIMEOUT)
super(ThemeScreenshotWorker, self).__init__() super(ThemeScreenshotWorker, self).__init__()
def run(self): def run(self):
@ -250,7 +252,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
# Download the theme screenshots # Download the theme screenshots
themes = self.config.get('themes', 'files').split(',') themes = self.config.get('themes', 'files').split(',')
for theme in themes: for theme in themes:
self.application.process_events()
title = self.config.get('theme_%s' % theme, 'title') title = self.config.get('theme_%s' % theme, 'title')
filename = self.config.get('theme_%s' % theme, 'filename') filename = self.config.get('theme_%s' % theme, 'filename')
sha256 = self.config.get('theme_%s' % theme, 'sha256', fallback='') 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.finished.connect(thread.quit)
worker.moveToThread(thread) worker.moveToThread(thread)
thread.start() thread.start()
self.application.process_events()
def set_defaults(self): def set_defaults(self):
""" """
@ -403,8 +405,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
retries = 0 retries = 0
while True: while True:
try: try:
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
filename = open(f_path, "wb") filename = open(f_path, "wb")
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
if sha256: if sha256:
hasher = hashlib.sha256() hasher = hashlib.sha256()
# Download until finished or canceled. # 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)) log.error('sha256 sums did not match for file: {}'.format(f_path))
os.remove(f_path) os.remove(f_path)
return False return False
except urllib.error.URLError: except (urllib.error.URLError, socket.timeout) as err:
trace_error_handler(log) trace_error_handler(log)
filename.close() filename.close()
os.remove(f_path) os.remove(f_path)
@ -447,7 +449,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
for index, theme in enumerate(themes): for index, theme in enumerate(themes):
screenshot = self.config.get('theme_%s' % theme, 'screenshot') screenshot = self.config.get('theme_%s' % theme, 'screenshot')
item = self.themes_list_widget.item(index) item = self.themes_list_widget.item(index)
# if item: if item:
item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot))) item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot)))
def _get_file_size(self, url): def _get_file_size(self, url):
@ -617,6 +619,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
songs_destination = os.path.join(gettempdir(), 'openlp') songs_destination = os.path.join(gettempdir(), 'openlp')
bibles_destination = AppLocation.get_section_data_path('bibles') bibles_destination = AppLocation.get_section_data_path('bibles')
themes_destination = AppLocation.get_section_data_path('themes') themes_destination = AppLocation.get_section_data_path('themes')
missed_files = []
# Download songs # Download songs
for i in range(self.songs_list_widget.count()): for i in range(self.songs_list_widget.count()):
item = self.songs_list_widget.item(i) item = self.songs_list_widget.item(i)
@ -626,7 +629,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.previous_size = 0 self.previous_size = 0
destination = os.path.join(songs_destination, str(filename)) destination = os.path.join(songs_destination, str(filename))
if not self.url_get_file('%s%s' % (self.songs_url, filename), destination, sha256): if not self.url_get_file('%s%s' % (self.songs_url, filename), destination, sha256):
return False missed_files.append('Song: {}'.format(filename))
# Download Bibles # Download Bibles
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget) bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
while bibles_iterator.value(): while bibles_iterator.value():
@ -637,7 +640,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.previous_size = 0 self.previous_size = 0
if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible), if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible),
sha256): sha256):
return False missed_files.append('Bible: {}'.format(bible))
bibles_iterator += 1 bibles_iterator += 1
# Download themes # Download themes
for i in range(self.themes_list_widget.count()): for i in range(self.themes_list_widget.count()):
@ -648,7 +651,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.previous_size = 0 self.previous_size = 0
if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme), if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme),
sha256): sha256):
return False missed_files.append('Theme: {}'.format(theme))
if missed_files:
file_list = ''
for entry in missed_files:
file_list += '{}<br \>'.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:<br \>{}'.format(file_list)))
msg.setStandardButtons(msg.Ok)
ans = msg.exec_()
return True return True
def _set_plugin_status(self, field, tag): def _set_plugin_status(self, field, tag):

View File

@ -29,6 +29,7 @@ import locale
import os import os
import platform import platform
import re import re
import socket
import time import time
from shutil import which from shutil import which
from subprocess import Popen, PIPE 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) req.add_header('User-Agent', user_agent)
if header: if header:
req.add_header(header[0], header[1]) req.add_header(header[0], header[1])
page = None
log.debug('Downloading URL = %s' % url) log.debug('Downloading URL = %s' % url)
retries = 1 retries = 0
while True: while retries <= CONNECTION_RETRIES:
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:
retries += 1 retries += 1
time.sleep(0.1) time.sleep(0.1)
continue try:
page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
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:
raise
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 break
if not page: except ConnectionError:
return None 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: if update_openlp:
Registry().get('application').process_events() Registry().get('application').process_events()
if not page:
log.exception('{} could not be downloaded'.format(url))
return None
log.debug(page) log.debug(page)
return page return page

View File

@ -23,6 +23,8 @@
Package to test the openlp.core.ui.firsttimeform package. Package to test the openlp.core.ui.firsttimeform package.
""" """
import os import os
import socket
import tempfile
import urllib import urllib
from unittest import TestCase from unittest import TestCase
@ -70,6 +72,11 @@ class TestFirstTimeForm(TestCase, TestMixin):
self.app.process_events = lambda: None self.app.process_events = lambda: None
Registry.create() Registry.create()
Registry().register('application', self.app) 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): def initialise_test(self):
""" """
@ -229,3 +236,20 @@ class TestFirstTimeForm(TestCase, TestMixin):
# THEN: the critical_error_message_box should have been called # THEN: the critical_error_message_box should have been called
self.assertEquals(mocked_message_box.mock_calls[1][1][0], 'Network Error 407', self.assertEquals(mocked_message_box.mock_calls[1][1][0], 'Network Error 407',
'first_time_form should have caught Network Error') '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')