Code change for json config file. Titulate themes ftw page

This commit is contained in:
Philip Ridout 2019-02-15 20:12:28 +00:00
parent d99c2f6996
commit 097225c9d7
5 changed files with 387 additions and 252 deletions

View File

@ -27,12 +27,16 @@ import logging
import sys import sys
import time import time
from random import randint from random import randint
from tempfile import gettempdir
import requests import requests
from PyQt5 import QtCore
from openlp.core.common import trace_error_handler from openlp.core.common import trace_error_handler
from openlp.core.common.path import Path
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.common.settings import ProxyMode, Settings from openlp.core.common.settings import ProxyMode, Settings
from openlp.core.threading import ThreadWorker
log = logging.getLogger(__name__ + '.__init__') log = logging.getLogger(__name__ + '.__init__')
@ -227,4 +231,46 @@ def download_file(update_object, url, file_path, sha256=None):
return True return True
__all__ = ['get_web_page'] class DownloadWorker(ThreadWorker):
"""
This worker allows a file to be downloaded in a thread
"""
download_failed = QtCore.pyqtSignal()
download_succeeded = QtCore.pyqtSignal(Path)
def __init__(self, base_url, file_name):
"""
Set up the worker object
"""
self._base_url = base_url
self._file_name = file_name
self._download_cancelled = False
super().__init__()
def start(self):
"""
Download the url to the temporary directory
"""
if self._download_cancelled:
self.quit.emit()
return
try:
dest_path = Path(gettempdir()) / 'openlp' / self._file_name
url = f'{self._base_url}{self._file_name}'
is_success = download_file(self, url, dest_path)
if is_success and not self._download_cancelled:
self.download_succeeded.emit(dest_path)
else:
self.download_failed.emit()
except: # noqa
log.exception('Unable to download %s', url)
self.download_failed.emit()
finally:
self.quit.emit()
@QtCore.pyqtSlot()
def cancel_download(self):
"""
A slot to allow the download to be cancelled from outside of the thread
"""
self._download_cancelled = True

View File

@ -22,19 +22,19 @@
""" """
This module contains the first time wizard. This module contains the first time wizard.
""" """
import json
import logging import logging
import time import time
import urllib.error import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
from configparser import ConfigParser, MissingSectionHeaderError, NoOptionError, NoSectionError
from tempfile import gettempdir from tempfile import gettempdir
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import clean_button_text, trace_error_handler from openlp.core.common import clean_button_text, trace_error_handler
from openlp.core.common.applocation import AppLocation from openlp.core.common.applocation import AppLocation
from openlp.core.common.httputils import download_file, get_url_file_size, get_web_page from openlp.core.common.httputils import DownloadWorker, download_file, get_url_file_size, get_web_page
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.core.common.mixins import RegistryProperties from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.path import Path, create_paths from openlp.core.common.path import Path, create_paths
@ -43,58 +43,50 @@ from openlp.core.common.settings import Settings
from openlp.core.lib import build_icon from openlp.core.lib import build_icon
from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.plugin import PluginStatus
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.threading import ThreadWorker, get_thread_worker, is_thread_finished, run_thread from openlp.core.threading import get_thread_worker, is_thread_finished, run_thread
from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard
from openlp.core.ui.icons import UiIcons
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ThemeScreenshotWorker(ThreadWorker): class ThemeListWidgetItem(QtWidgets.QListWidgetItem):
""" """
This thread downloads a theme's screenshot Subclass a QListWidgetItem to allow dynamic loading of thumbnails from an online resource
""" """
screenshot_downloaded = QtCore.pyqtSignal(str, str, str) def __init__(self, themes_url, sample_theme_data, ftw, *args, **kwargs):
super().__init__(*args, **kwargs)
title = sample_theme_data['title']
thumbnail = sample_theme_data['thumbnail']
self.file_name = sample_theme_data['file_name']
self.sha256 = sample_theme_data['sha256']
self.setIcon(UiIcons().picture) # Set a place holder icon whilst the thumbnails download
self.setText(title)
self.setToolTip(title)
worker = DownloadWorker(themes_url, thumbnail)
worker.download_failed.connect(self._on_download_failed)
worker.download_succeeded.connect(self._on_thumbnail_downloaded)
thread_name = f'thumbnail_download_{thumbnail}'
run_thread(worker, thread_name)
ftw.thumbnail_download_threads.append(thread_name) # TODO: Already in the application que
def __init__(self, themes_url, title, filename, sha256, screenshot): def _on_download_failed(self):
""" """
Set up the worker object Set an icon to indicate that the thumbnail download has failed.
"""
self.was_cancelled = False
self.themes_url = themes_url
self.title = title
self.filename = filename
self.sha256 = sha256
self.screenshot = screenshot
super().__init__()
def start(self): :rtype: None
""" """
Run the worker self.setIcon(UiIcons().exception)
"""
if self.was_cancelled:
return
try:
download_path = Path(gettempdir()) / 'openlp' / self.screenshot
is_success = download_file(self, '{host}{name}'.format(host=self.themes_url, name=self.screenshot),
download_path)
if is_success and not self.was_cancelled:
# Signal that the screenshot has been downloaded
self.screenshot_downloaded.emit(self.title, self.filename, self.sha256)
except: # noqa
log.exception('Unable to download screenshot')
finally:
self.quit.emit()
@QtCore.pyqtSlot(bool) def _on_thumbnail_downloaded(self, thumbnail_path):
def set_download_canceled(self, toggle):
""" """
Externally set if the download was canceled Load the thumbnail as the icon when it has downloaded.
:param toggle: Set if the download was canceled or not :param Path thumbnail_path: Path to the file to use as a thumbnail
:rtype: None
""" """
self.was_download_cancelled = toggle self.setIcon(build_icon(thumbnail_path))
class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
""" """
@ -110,6 +102,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
self.web_access = True self.web_access = True
self.web = '' self.web = ''
self.setup_ui(self) self.setup_ui(self)
self.themes_list_widget.itemSelectionChanged.connect(self.on_themes_list_widget_selection_changed)
self.themes_deselect_all_button.clicked.connect(self.themes_list_widget.clearSelection)
self.themes_select_all_button.clicked.connect(self.themes_list_widget.selectAll)
def get_next_page_id(self): def get_next_page_id(self):
""" """
@ -144,18 +139,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
return -1 return -1
elif self.currentId() == FirstTimePage.NoInternet: elif self.currentId() == FirstTimePage.NoInternet:
return FirstTimePage.Progress return FirstTimePage.Progress
elif self.currentId() == FirstTimePage.Themes: return self.get_next_page_id()
self.application.set_busy_cursor()
while not all([is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]):
time.sleep(0.1)
self.application.process_events()
# Build the screenshot icons, as this can not be done in the thread.
self._build_theme_screenshots()
self.application.set_normal_cursor()
self.theme_screenshot_threads = []
return self.get_next_page_id()
else:
return self.get_next_page_id()
def exec(self): def exec(self):
""" """
@ -172,104 +156,83 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
""" """
self.screens = screens self.screens = screens
self.was_cancelled = False self.was_cancelled = False
self.theme_screenshot_threads = [] self.thumbnail_download_threads = []
self.has_run_wizard = False self.has_run_wizard = False
self.themes_list_widget.itemChanged.connect(self.on_theme_selected)
def _download_index(self): def _download_index(self):
""" """
Download the configuration file and kick off the theme screenshot download threads Download the configuration file and kick off the theme screenshot download threads
""" """
# check to see if we have web access # check to see if we have web access
self.web_access = False self.web_access = False
self.config = ConfigParser() self.config = ''
web_config = None
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
self.application.process_events() self.application.process_events()
try: try:
web_config = get_web_page('{host}{name}'.format(host=self.web, name='download.cfg'), web_config = get_web_page('{host}{name}'.format(host=self.web, name='download_3.0.json'),
headers={'User-Agent': user_agent}) headers={'User-Agent': user_agent})
except ConnectionError: web_config = Path(
'C:\\Users\\sroom\\Documents\\Phill Ridout\\play_ground\\openlp\\ftw-json\\download_3.0.json'
).read_text(encoding='utf-8') # TODO: Remove!!!!!
except ConnectionError as e:
QtWidgets.QMessageBox.critical(self, translate('OpenLP.FirstTimeWizard', 'Network Error'), QtWidgets.QMessageBox.critical(self, translate('OpenLP.FirstTimeWizard', 'Network Error'),
translate('OpenLP.FirstTimeWizard', 'There was a network error attempting ' translate('OpenLP.FirstTimeWizard', 'There was a network error attempting '
'to connect to retrieve initial configuration information'), 'to connect to retrieve initial configuration information'),
QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.Ok)
web_config = False if web_config and self._parse_config(web_config):
if web_config: self.web_access = True
try:
self.config.read_string(web_config)
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 occurred while parsing the downloaded config file')
trace_error_handler(log)
self.application.process_events() self.application.process_events()
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...') self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...')
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.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() self.application.set_normal_cursor()
# Sort out internet access for downloads
if self.web_access: def _parse_config(self, web_config):
songs = self.config.get('songs', 'languages') try:
songs = songs.split(',') config = json.loads(web_config)
for song in songs: meta = config['_meta']
self.web = meta['base_url']
self.songs_url = self.web + meta['songs_dir'] + '/'
self.bibles_url = self.web + meta['bibles_dir'] + '/'
self.themes_url = self.web + meta['themes_dir'] + '/'
for song in config['songs'].values():
self.application.process_events() self.application.process_events()
title = self.config.get('songs_{song}'.format(song=song), 'title') item = QtWidgets.QListWidgetItem(song['title'], self.songs_list_widget)
filename = self.config.get('songs_{song}'.format(song=song), 'filename') item.setData(QtCore.Qt.UserRole, (song['file_name'], song['sha256']))
sha256 = self.config.get('songs_{song}'.format(song=song), 'sha256', fallback='')
item = QtWidgets.QListWidgetItem(title, self.songs_list_widget)
item.setData(QtCore.Qt.UserRole, (filename, sha256))
item.setCheckState(QtCore.Qt.Unchecked) item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
bible_languages = self.config.get('bibles', 'languages')
bible_languages = bible_languages.split(',') for lang in config['bibles'].values():
for lang in bible_languages:
self.application.process_events() self.application.process_events()
language = self.config.get('bibles_{lang}'.format(lang=lang), 'title') lang_item = QtWidgets.QTreeWidgetItem(self.bibles_tree_widget, [lang['title']])
lang_item = QtWidgets.QTreeWidgetItem(self.bibles_tree_widget, [language]) for translation in lang['translations'].values():
bibles = self.config.get('bibles_{lang}'.format(lang=lang), 'translations')
bibles = bibles.split(',')
for bible in bibles:
self.application.process_events() self.application.process_events()
title = self.config.get('bible_{bible}'.format(bible=bible), 'title') item = QtWidgets.QTreeWidgetItem(lang_item, [translation['title']])
filename = self.config.get('bible_{bible}'.format(bible=bible), 'filename') item.setData(0, QtCore.Qt.UserRole, (translation['file_name'], translation['sha256']))
sha256 = self.config.get('bible_{bible}'.format(bible=bible), 'sha256', fallback='')
item = QtWidgets.QTreeWidgetItem(lang_item, [title])
item.setData(0, QtCore.Qt.UserRole, (filename, sha256))
item.setCheckState(0, QtCore.Qt.Unchecked) item.setCheckState(0, QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
self.bibles_tree_widget.expandAll() self.bibles_tree_widget.expandAll()
self.application.process_events() self.application.process_events()
# Download the theme screenshots
themes = self.config.get('themes', 'files').split(',') for theme in config['themes'].values():
for theme in themes: ThemeListWidgetItem(self.themes_url, theme, self, self.themes_list_widget)
title = self.config.get('theme_{theme}'.format(theme=theme), 'title')
filename = self.config.get('theme_{theme}'.format(theme=theme), 'filename')
sha256 = self.config.get('theme_{theme}'.format(theme=theme), 'sha256', fallback='')
screenshot = self.config.get('theme_{theme}'.format(theme=theme), 'screenshot')
worker = ThemeScreenshotWorker(self.themes_url, title, filename, sha256, screenshot)
worker.screenshot_downloaded.connect(self.on_screenshot_downloaded)
thread_name = 'theme_screenshot_{title}'.format(title=title)
run_thread(worker, thread_name)
self.theme_screenshot_threads.append(thread_name)
self.application.process_events() self.application.process_events()
except Exception:
log.exception('Unable to parse sample config file %s', web_config)
critical_error_message_box(
translate('OpenLP.FirstTimeWizard', 'Invalid index file'),
translate('OpenLP.FirstTimeWizard', 'OpenLP was unable to read the resource index file. '
'Please try again later.'))
return False
return True
def set_defaults(self): def set_defaults(self):
""" """
Set up display at start of theme edit. Set up display at start of theme edit.
""" """
self.restart() self.restart()
self.web = 'http://openlp.org/files/frw/' self.web = 'https://openlp.org/files/frw/'
self.cancel_button.clicked.connect(self.on_cancel_button_clicked) self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked) self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked)
@ -282,9 +245,18 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
create_paths(Path(gettempdir(), 'openlp')) create_paths(Path(gettempdir(), 'openlp'))
self.theme_combo_box.clear() self.theme_combo_box.clear()
if self.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.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())
# Add any existing themes to list. # Add any existing themes to list.
for theme in self.theme_manager.get_themes(): self.theme_combo_box.insertSeparator(0)
self.theme_combo_box.addItem(theme) self.theme_combo_box.addItems(sorted(self.theme_manager.get_themes()))
default_theme = Settings().value('themes/global theme') default_theme = Settings().value('themes/global theme')
# Pre-select the current default theme. # Pre-select the current default theme.
index = self.theme_combo_box.findText(default_theme) index = self.theme_combo_box.findText(default_theme)
@ -335,49 +307,34 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
Process the triggering of the cancel button. Process the triggering of the cancel button.
""" """
self.was_cancelled = True self.was_cancelled = True
if self.theme_screenshot_threads: if self.thumbnail_download_threads: # TODO: Use main thread list
for thread_name in self.theme_screenshot_threads: for thread_name in self.thumbnail_download_threads:
worker = get_thread_worker(thread_name) worker = get_thread_worker(thread_name)
if worker: if worker:
worker.set_download_canceled(True) worker.cancel_download()
# Was the thread created. # Was the thread created.
if self.theme_screenshot_threads: if self.thumbnail_download_threads:
while any([not is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]): while any([not is_thread_finished(thread_name) for thread_name in self.thumbnail_download_threads]):
time.sleep(0.1) time.sleep(0.1)
self.application.set_normal_cursor() self.application.set_normal_cursor()
def on_screenshot_downloaded(self, title, filename, sha256): def on_themes_list_widget_selection_changed(self):
""" """
Add an item to the list when a theme has been downloaded Update the `theme_combo_box` with the selected items
:param title: The title of the theme
:param filename: The filename of the theme
"""
self.themes_list_widget.blockSignals(True)
item = QtWidgets.QListWidgetItem(title, self.themes_list_widget)
item.setData(QtCore.Qt.UserRole, (filename, sha256))
item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
self.themes_list_widget.blockSignals(False)
def on_theme_selected(self, item):
"""
Add or remove a de/selected sample theme from the theme_combo_box
:param QtWidgets.QListWidgetItem item: The item that has been de/selected
:rtype: None :rtype: None
""" """
theme_name = item.text() existing_themes = []
if self.theme_manager and theme_name in self.theme_manager.get_themes(): if self.theme_manager:
return True existing_themes = self.theme_manager.get_themes()
if item.checkState() == QtCore.Qt.Checked: for list_index in range(self.themes_list_widget.count()):
self.theme_combo_box.addItem(theme_name) item = self.themes_list_widget.item(list_index)
return True if item.text() not in existing_themes:
else: cbox_index = self.theme_combo_box.findText(item.text())
index = self.theme_combo_box.findText(theme_name) if item.isSelected() and cbox_index == -1:
if index != -1: self.theme_combo_box.insertItem(0, item.text())
self.theme_combo_box.removeItem(index) elif not item.isSelected() and cbox_index != -1:
return True self.theme_combo_box.removeItem(cbox_index)
def on_no_internet_finish_button_clicked(self): def on_no_internet_finish_button_clicked(self):
""" """
@ -396,18 +353,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
self.was_cancelled = True self.was_cancelled = True
self.close() self.close()
def _build_theme_screenshots(self):
"""
This method builds the theme screenshots' icons for all items in the ``self.themes_list_widget``.
"""
themes = self.config.get('themes', 'files')
themes = themes.split(',')
for index, theme in enumerate(themes):
screenshot = self.config.get('theme_{theme}'.format(theme=theme), 'screenshot')
item = self.themes_list_widget.item(index)
if item:
item.setIcon(build_icon(Path(gettempdir(), 'openlp', screenshot)))
def update_progress(self, count, block_size): def update_progress(self, count, block_size):
""" """
Calculate and display the download progress. This method is called by download_file(). Calculate and display the download progress. This method is called by download_file().
@ -456,13 +401,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
self.max_progress += size self.max_progress += size
iterator += 1 iterator += 1
# Loop through the themes list and increase for each selected item # Loop through the themes list and increase for each selected item
for i in range(self.themes_list_widget.count()): for item in self.themes_list_widget.selectedItems():
self.application.process_events() size = get_url_file_size(f'{self.themes_url}{item.file_name}')
item = self.themes_list_widget.item(i) self.max_progress += size
if item.checkState() == QtCore.Qt.Checked:
filename, sha256 = item.data(QtCore.Qt.UserRole)
size = get_url_file_size('{path}{name}'.format(path=self.themes_url, name=filename))
self.max_progress += size
except urllib.error.URLError: except urllib.error.URLError:
trace_error_handler(log) trace_error_handler(log)
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
@ -579,15 +520,12 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
missed_files.append('Bible: {name}'.format(name=bible)) missed_files.append('Bible: {name}'.format(name=bible))
bibles_iterator += 1 bibles_iterator += 1
# Download themes # Download themes
for i in range(self.themes_list_widget.count()): for item in self.themes_list_widget.selectedItems():
item = self.themes_list_widget.item(i) self._increment_progress_bar(self.downloading.format(name=item.file_name), 0)
if item.checkState() == QtCore.Qt.Checked: self.previous_size = 0
theme, sha256 = item.data(QtCore.Qt.UserRole) if not download_file(
self._increment_progress_bar(self.downloading.format(name=theme), 0) self, f'{self.themes_url}{item.file_name}', themes_destination_path / item.file_name, item.sha256):
self.previous_size = 0 missed_files.append(f'Theme: {item.file_name}')
if not download_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
themes_destination_path / theme, sha256):
missed_files.append('Theme: {name}'.format(name=theme))
if missed_files: if missed_files:
file_list = '' file_list = ''
for entry in missed_files: for entry in missed_files:

View File

@ -49,6 +49,40 @@ class FirstTimePage(object):
Progress = 8 Progress = 8
class ThemeListWidget(QtWidgets.QListWidget):
"""
Subclass a QListWidget so we can make it look better when it resizes.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.setIconSize(QtCore.QSize(133, 100))
self.setMovement(QtWidgets.QListView.Static)
self.setFlow(QtWidgets.QListView.LeftToRight)
self.setProperty("isWrapping", True)
self.setResizeMode(QtWidgets.QListView.Adjust)
self.setViewMode(QtWidgets.QListView.IconMode)
self.setUniformItemSizes(True)
self.setWordWrap(True)
def resizeEvent(self, event):
"""
Resize the grid so the list looks better when its resized/
:param QtGui.QResizeEvent event: Not used
:return: None
"""
nominal_width = 141 # Icon width of 133 + 4 each side
max_items_per_row = self.viewport().width() // nominal_width or 1 # or 1 to avoid divide by 0 errors
col_size = (self.viewport().width() - 1) / max_items_per_row
self.setGridSize(QtCore.QSize(col_size, 140))
class UiFirstTimeWizard(object): class UiFirstTimeWizard(object):
""" """
The UI widgets for the first time wizard. The UI widgets for the first time wizard.
@ -175,27 +209,26 @@ class UiFirstTimeWizard(object):
self.themes_page = QtWidgets.QWizardPage() self.themes_page = QtWidgets.QWizardPage()
self.themes_page.setObjectName('themes_page') self.themes_page.setObjectName('themes_page')
self.themes_layout = QtWidgets.QVBoxLayout(self.themes_page) self.themes_layout = QtWidgets.QVBoxLayout(self.themes_page)
self.themes_layout.setContentsMargins(20, 50, 20, 60)
self.themes_layout.setObjectName('themes_layout') self.themes_layout.setObjectName('themes_layout')
self.themes_list_widget = QtWidgets.QListWidget(self.themes_page) self.themes_list_widget = ThemeListWidget(self.themes_page)
self.themes_list_widget.setViewMode(QtWidgets.QListView.IconMode)
self.themes_list_widget.setMovement(QtWidgets.QListView.Static)
self.themes_list_widget.setFlow(QtWidgets.QListView.LeftToRight)
self.themes_list_widget.setSpacing(4)
self.themes_list_widget.setUniformItemSizes(True)
self.themes_list_widget.setIconSize(QtCore.QSize(133, 100))
self.themes_list_widget.setWrapping(False)
self.themes_list_widget.setObjectName('themes_list_widget')
self.themes_layout.addWidget(self.themes_list_widget) self.themes_layout.addWidget(self.themes_list_widget)
self.theme_options_layout = QtWidgets.QHBoxLayout()
self.default_theme_layout = QtWidgets.QHBoxLayout() self.default_theme_layout = QtWidgets.QHBoxLayout()
self.theme_label = QtWidgets.QLabel(self.themes_page) self.theme_label = QtWidgets.QLabel(self.themes_page)
self.default_theme_layout.addWidget(self.theme_label) self.default_theme_layout.addWidget(self.theme_label)
self.theme_combo_box = QtWidgets.QComboBox(self.themes_page) self.theme_combo_box = QtWidgets.QComboBox(self.themes_page)
self.theme_combo_box.setEditable(False) self.theme_combo_box.setEditable(False)
self.theme_combo_box.setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.default_theme_layout.addWidget(self.theme_combo_box, stretch=1)
self.theme_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.theme_options_layout.addLayout(self.default_theme_layout, stretch=1)
self.default_theme_layout.addWidget(self.theme_combo_box) self.select_buttons_layout = QtWidgets.QHBoxLayout(self.themes_page)
self.themes_layout.addLayout(self.default_theme_layout) self.themes_select_all_button = QtWidgets.QToolButton(self.themes_page)
self.themes_select_all_button.setIcon(UiIcons().plus)
self.select_buttons_layout.addWidget(self.themes_select_all_button, stretch=1, alignment=QtCore.Qt.AlignRight)
self.themes_deselect_all_button = QtWidgets.QToolButton(self.themes_page)
self.themes_deselect_all_button.setIcon(UiIcons().minus)
self.select_buttons_layout.addWidget(self.themes_deselect_all_button)
self.theme_options_layout.addLayout(self.select_buttons_layout, stretch=1)
self.themes_layout.addLayout(self.theme_options_layout)
first_time_wizard.setPage(FirstTimePage.Themes, self.themes_page) first_time_wizard.setPage(FirstTimePage.Themes, self.themes_page)
# Progress page # Progress page
self.progress_page = QtWidgets.QWizardPage() self.progress_page = QtWidgets.QWizardPage()
@ -271,9 +304,12 @@ class UiFirstTimeWizard(object):
self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.')) self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))
self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles')) self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))
self.bibles_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.')) self.bibles_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.'))
# Themes Page
self.themes_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Themes')) self.themes_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Themes'))
self.themes_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download sample themes.')) self.themes_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download sample themes.'))
self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Select default theme:')) self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Default theme:'))
self.themes_select_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Select all'))
self.themes_deselect_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Deselect all'))
self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring')) self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring'))
self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded ' self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded '
'and OpenLP is configured.')) 'and OpenLP is configured.'))

View File

@ -25,40 +25,70 @@ Package to test the openlp.core.ui.firsttimeform package.
import os import os
import tempfile import tempfile
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call, patch, DEFAULT
from openlp.core.common.path import Path from openlp.core.common.path import Path
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
FAKE_CONFIG = """ INVALID_CONFIG = """
[general] {
base url = http://example.com/frw/ "_comments": "The most recent version should be added to https://openlp.org/files/frw/download_3.0.json",
[songs] "_meta": {
directory = songs }
[bibles]
directory = bibles
[themes]
directory = themes
""" """
FAKE_BROKEN_CONFIG = """
[general]
base url = http://example.com/frw/
[songs]
directory = songs
[bibles]
directory = bibles
"""
FAKE_INVALID_CONFIG = """ class TestThemeListWidgetItem(TestCase):
<html> """
<head><title>This is not a config file</title></head> Test the :class:`ThemeListWidgetItem` class
<body>Some text</body> """
</html> def setUp(self):
""" self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
download_worker_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker')
self.addCleanup(download_worker_patcher.stop)
self.mocked_download_worker = download_worker_patcher.start()
run_thread_patcher = patch('openlp.core.ui.firsttimeform.run_thread')
self.addCleanup(run_thread_patcher.stop)
self.mocked_run_thread = run_thread_patcher.start()
def test_init_sample_data(self):
"""
Test that the theme data is loaded correctly in to a ThemeListWidgetItem object when instantiated
"""
# GIVEN: A sample theme dictanary object
# WHEN: Creating an instance of `ThemeListWidgetItem`
instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock())
# THEN: The data should have been set correctly
assert instance.file_name == 'BlueBurst.otz'
assert instance.sha256 == 'sha_256_hash'
assert instance.text() == 'Blue Burst'
assert instance.toolTip() == 'Blue Burst'
self.mocked_download_worker.assert_called_once_with('url', 'BlueBurst.png')
def test_init_download_worker(self):
"""
Test that the `DownloadWorker` worker is set up correctly and that the thread is started.
"""
# GIVEN: A sample theme dictanary object
mocked_ftw = MagicMock(spec=FirstTimeForm)
mocked_ftw.thumbnail_download_threads = []
# WHEN: Creating an instance of `ThemeListWidgetItem`
instance = ThemeListWidgetItem('url', self.sample_theme_data, mocked_ftw)
# THEN: The `DownloadWorker` should have been set up with the appropriate data
self.mocked_download_worker.assert_called_once_with('url', 'BlueBurst.png')
self.mocked_download_worker.download_failed.connect.called_once_with(instance._on_download_failed())
self.mocked_download_worker.download_succeeded.connect.called_once_with(instance._on_thumbnail_downloaded)
self.mocked_run_thread.assert_called_once_with(
self.mocked_download_worker(), 'thumbnail_download_BlueBurst.png')
assert mocked_ftw.thumbnail_download_threads == ['thumbnail_download_BlueBurst.png']
class TestFirstTimeForm(TestCase, TestMixin): class TestFirstTimeForm(TestCase, TestMixin):
@ -92,7 +122,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
assert expected_screens == frw.screens, 'The screens should be correct' assert expected_screens == frw.screens, 'The screens should be correct'
assert frw.web_access is True, 'The default value of self.web_access should be True' assert frw.web_access is True, 'The default value of self.web_access should be True'
assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False' assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False'
assert [] == frw.theme_screenshot_threads, 'The list of threads should be empty' assert [] == frw.thumbnail_download_threads, 'The list of threads should be empty'
assert frw.has_run_wizard is False, 'has_run_wizard should be False' assert frw.has_run_wizard is False, 'has_run_wizard should be False'
def test_set_defaults(self): def test_set_defaults(self):
@ -109,6 +139,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \ patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \
patch.object(frw, 'songs_check_box') as mocked_songs_check_box, \
patch.object(Registry, 'register_function') as mocked_register_function, \ patch.object(Registry, 'register_function') as mocked_register_function, \
patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \ patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \
patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \ patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \
@ -122,7 +153,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
# THEN: The default values should have been set # THEN: The default values should have been set
mocked_restart.assert_called_once() mocked_restart.assert_called_once()
assert 'http://openlp.org/files/frw/' == frw.web, 'The default URL should be set' assert 'https://openlp.org/files/frw/' == frw.web, 'The default URL should be set'
mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked)
mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with(
frw.on_no_internet_finish_button_clicked) frw.on_no_internet_finish_button_clicked)
@ -134,6 +165,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))
mocked_theme_combo_box.clear.assert_called_once() mocked_theme_combo_box.clear.assert_called_once()
mocked_theme_manager.assert_not_called() mocked_theme_manager.assert_not_called()
mocked_songs_check_box.assert_not_called()
def test_set_defaults_rerun(self): def test_set_defaults_rerun(self):
""" """
@ -150,12 +182,17 @@ class TestFirstTimeForm(TestCase, TestMixin):
patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
patch.object(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \ patch.object(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \
patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT,
image_check_box=DEFAULT, media_check_box=DEFAULT, custom_check_box=DEFAULT,
song_usage_check_box=DEFAULT, alert_check_box=DEFAULT) as mocked_check_boxes, \
patch.object(Registry, 'register_function') as mocked_register_function, \ patch.object(Registry, 'register_function') as mocked_register_function, \
patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \ patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \
patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \ patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \
patch('openlp.core.ui.firsttimeform.create_paths') as mocked_create_paths, \ patch('openlp.core.ui.firsttimeform.create_paths') as mocked_create_paths, \
patch.object(frw.application, 'set_normal_cursor'): patch.object(frw.application, 'set_normal_cursor'):
mocked_theme_manager = MagicMock(**{'get_themes.return_value': ['a', 'b', 'c']}) mocked_plugin_manager = MagicMock()
mocked_theme_manager = MagicMock(**{'get_themes.return_value': ['b', 'a', 'c']})
Registry().register('plugin_manager', mocked_plugin_manager)
Registry().register('theme_manager', mocked_theme_manager) Registry().register('theme_manager', mocked_theme_manager)
# WHEN: The set_defaults() method is run # WHEN: The set_defaults() method is run
@ -163,7 +200,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
# THEN: The default values should have been set # THEN: The default values should have been set
mocked_restart.assert_called_once() mocked_restart.assert_called_once()
assert 'http://openlp.org/files/frw/' == frw.web, 'The default URL should be set' assert 'https://openlp.org/files/frw/' == frw.web, 'The default URL should be set'
mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked)
mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with(
frw.on_no_internet_finish_button_clicked) frw.on_no_internet_finish_button_clicked)
@ -173,9 +210,13 @@ class TestFirstTimeForm(TestCase, TestMixin):
mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')]) mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')])
mocked_gettempdir.assert_called_once() mocked_gettempdir.assert_called_once()
mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))
mocked_theme_manager.assert_not_called() mocked_theme_manager.get_themes.assert_called_once()
mocked_theme_combo_box.clear.assert_called_once() mocked_theme_combo_box.clear.assert_called_once()
mocked_theme_combo_box.addItem.assert_has_calls([call('a'), call('b'), call('c')]) mocked_plugin_manager.get_plugin_by_name.assert_has_calls(
[call('songs'), call('bibles'), call('presentations'), call('images'), call('media'), call('custom'),
call('songusage'), call('alerts')], any_order=True)
mocked_plugin_manager.get_plugin_by_name.assert_has_calls([call().is_active()] * 8, any_order=True)
mocked_theme_combo_box.addItems.assert_called_once_with(['a', 'b', 'c'])
mocked_theme_combo_box.findText.assert_called_once_with('Default Theme') mocked_theme_combo_box.findText.assert_called_once_with('Default Theme')
mocked_theme_combo_box.setCurrentIndex(3) mocked_theme_combo_box.setCurrentIndex(3)
@ -192,7 +233,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
mocked_is_thread_finished.side_effect = [False, True] mocked_is_thread_finished.side_effect = [False, True]
frw = FirstTimeForm(None) frw = FirstTimeForm(None)
frw.initialize(MagicMock()) frw.initialize(MagicMock())
frw.theme_screenshot_threads = ['test_thread'] frw.thumbnail_download_threads = ['test_thread']
with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:
# WHEN: on_cancel_button_clicked() is called # WHEN: on_cancel_button_clicked() is called
@ -201,43 +242,26 @@ class TestFirstTimeForm(TestCase, TestMixin):
# THEN: The right things should be called in the right order # THEN: The right things should be called in the right order
assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True' assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True'
mocked_get_thread_worker.assert_called_once_with('test_thread') mocked_get_thread_worker.assert_called_once_with('test_thread')
mocked_worker.set_download_canceled.assert_called_with(True) mocked_worker.cancel_download.assert_called_once()
mocked_is_thread_finished.assert_called_with('test_thread') mocked_is_thread_finished.assert_called_with('test_thread')
assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice' assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice'
mocked_time.sleep.assert_called_once_with(0.1) mocked_time.sleep.assert_called_once_with(0.1)
mocked_set_normal_cursor.assert_called_once_with() mocked_set_normal_cursor.assert_called_once_with()
def test_broken_config(self): @patch('openlp.core.ui.firsttimeform.critical_error_message_box')
def test__parse_config_invalid_config(self, mocked_critical_error_message_box):
""" """
Test if we can handle an config file with missing data Test `FirstTimeForm._parse_config` when called with invalid data
""" """
# GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked broken config file # GIVEN: An instance of `FirstTimeForm`
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: first_time_form = FirstTimeForm(None)
first_time_form = FirstTimeForm(None)
first_time_form.initialize(MagicMock())
mocked_get_web_page.return_value = FAKE_BROKEN_CONFIG
# WHEN: The First Time Wizard is downloads the config file # WHEN: Calling _parse_config with a string containing invalid data
first_time_form._download_index() result = first_time_form._parse_config(INVALID_CONFIG)
# THEN: The First Time Form should not have web access # THEN: _parse_data should return False and the user should have should have been informed.
assert first_time_form.web_access is False, 'There should not be web access with a broken config file' assert result is False
mocked_critical_error_message_box.assert_called_once()
def test_invalid_config(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)
first_time_form.initialize(MagicMock())
mocked_get_web_page.return_value = FAKE_INVALID_CONFIG
# WHEN: The First Time Wizard is downloads the config file
first_time_form._download_index()
# THEN: The First Time Form should not have web access
assert first_time_form.web_access is False, 'There should not be web access with an invalid config file'
@patch('openlp.core.ui.firsttimeform.get_web_page') @patch('openlp.core.ui.firsttimeform.get_web_page')
@patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox') @patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox')

View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.ui.firsttimeform package.
"""
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from openlp.core.common.path import Path
from openlp.core.common.registry import Registry
from openlp.core.ui.firsttimeform import ThemeListWidgetItem
from openlp.core.ui.icons import UiIcons
from tests.helpers.testmixin import TestMixin
# TODO: BZR ADD!!!!!!!!!!!!!
class TestThemeListWidgetItem(TestCase, TestMixin):
def setUp(self):
self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
Registry.create()
self.registry = Registry()
mocked_app = MagicMock()
mocked_app.worker_threads = {}
Registry().register('application', mocked_app)
self.setup_application()
move_to_thread_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker.moveToThread')
self.addCleanup(move_to_thread_patcher.stop)
move_to_thread_patcher.start()
set_icon_patcher = patch('openlp.core.ui.firsttimeform.ThemeListWidgetItem.setIcon')
self.addCleanup(set_icon_patcher.stop)
self.mocked_set_icon = set_icon_patcher.start()
q_thread_patcher = patch('openlp.core.ui.firsttimeform.QtCore.QThread')
self.addCleanup(q_thread_patcher.stop)
q_thread_patcher.start()
def test_failed_download(self):
"""
Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock())
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
# WHEN: `DownloadWorker` emits the `download_failed` signal
worker.download_failed.emit()
# THEN: Then the initial loading icon should have been replaced by the exception icon
self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(UiIcons().exception)])
@patch('openlp.core.ui.firsttimeform.build_icon')
def test_successful_download(self, mocked_build_icon):
"""
Test that the downloaded thumbnail is set as the icon when `DownloadWorker` emits the `download_succeeded`
signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock())
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
test_path = Path('downlaoded', 'file')
# WHEN: `DownloadWorker` emits the `download_succeeded` signal
worker.download_succeeded.emit(test_path)
# THEN: An icon should have been built from the downloaded file and used to replace the loading icon
mocked_build_icon.assert_called_once_with(test_path)
self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())])