forked from openlp/openlp
move ftw to new json config format. spruce up theme list page
bzr-revno: 2846
This commit is contained in:
commit
1434751591
@ -27,12 +27,16 @@ import logging
|
||||
import sys
|
||||
import time
|
||||
from random import randint
|
||||
from tempfile import gettempdir
|
||||
|
||||
import requests
|
||||
from PyQt5 import QtCore
|
||||
|
||||
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.settings import ProxyMode, Settings
|
||||
from openlp.core.threading import ThreadWorker
|
||||
|
||||
|
||||
log = logging.getLogger(__name__ + '.__init__')
|
||||
@ -227,4 +231,46 @@ def download_file(update_object, url, file_path, sha256=None):
|
||||
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 = '{url}{name}'.format(url=self._base_url, name=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 Exception:
|
||||
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
|
||||
|
@ -22,19 +22,19 @@
|
||||
"""
|
||||
This module contains the first time wizard.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from configparser import ConfigParser, MissingSectionHeaderError, NoOptionError, NoSectionError
|
||||
from tempfile import gettempdir
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import clean_button_text, trace_error_handler
|
||||
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.mixins import RegistryProperties
|
||||
from openlp.core.common.path import Path, create_paths
|
||||
@ -43,57 +43,50 @@ from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
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.icons import UiIcons
|
||||
|
||||
|
||||
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 = 'thumbnail_download_{thumbnail}'.format(thumbnail=thumbnail)
|
||||
run_thread(worker, thread_name)
|
||||
ftw.thumbnail_download_threads.append(thread_name)
|
||||
|
||||
def __init__(self, themes_url, title, filename, sha256, screenshot):
|
||||
def _on_download_failed(self):
|
||||
"""
|
||||
Set up the worker object
|
||||
"""
|
||||
self.was_cancelled = False
|
||||
self.themes_url = themes_url
|
||||
self.title = title
|
||||
self.filename = filename
|
||||
self.sha256 = sha256
|
||||
self.screenshot = screenshot
|
||||
super().__init__()
|
||||
Set an icon to indicate that the thumbnail download has failed.
|
||||
|
||||
def start(self):
|
||||
:rtype: None
|
||||
"""
|
||||
Run the worker
|
||||
"""
|
||||
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()
|
||||
self.setIcon(UiIcons().exception)
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def set_download_canceled(self, toggle):
|
||||
def _on_thumbnail_downloaded(self, thumbnail_path):
|
||||
"""
|
||||
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):
|
||||
@ -110,6 +103,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.web_access = True
|
||||
self.web = ''
|
||||
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):
|
||||
"""
|
||||
@ -144,18 +140,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
return -1
|
||||
elif self.currentId() == FirstTimePage.NoInternet:
|
||||
return FirstTimePage.Progress
|
||||
elif self.currentId() == FirstTimePage.Themes:
|
||||
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()
|
||||
return self.get_next_page_id()
|
||||
|
||||
def exec(self):
|
||||
"""
|
||||
@ -172,104 +157,76 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
"""
|
||||
self.screens = screens
|
||||
self.was_cancelled = False
|
||||
self.theme_screenshot_threads = []
|
||||
self.thumbnail_download_threads = []
|
||||
self.has_run_wizard = False
|
||||
|
||||
self.themes_list_widget.itemChanged.connect(self.on_theme_selected)
|
||||
|
||||
def _download_index(self):
|
||||
"""
|
||||
Download the configuration file and kick off the theme screenshot download threads
|
||||
"""
|
||||
# check to see if we have web access
|
||||
self.web_access = False
|
||||
self.config = ConfigParser()
|
||||
self.config = ''
|
||||
web_config = None
|
||||
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
|
||||
self.application.process_events()
|
||||
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})
|
||||
except ConnectionError:
|
||||
QtWidgets.QMessageBox.critical(self, translate('OpenLP.FirstTimeWizard', 'Network Error'),
|
||||
translate('OpenLP.FirstTimeWizard', 'There was a network error attempting '
|
||||
'to connect to retrieve initial configuration information'),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
web_config = False
|
||||
if web_config:
|
||||
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)
|
||||
if web_config and self._parse_config(web_config):
|
||||
self.web_access = True
|
||||
self.application.process_events()
|
||||
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()
|
||||
# Sort out internet access for downloads
|
||||
if self.web_access:
|
||||
songs = self.config.get('songs', 'languages')
|
||||
songs = songs.split(',')
|
||||
for song in songs:
|
||||
|
||||
def _parse_config(self, web_config):
|
||||
try:
|
||||
config = json.loads(web_config)
|
||||
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()
|
||||
title = self.config.get('songs_{song}'.format(song=song), 'title')
|
||||
filename = self.config.get('songs_{song}'.format(song=song), 'filename')
|
||||
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 = QtWidgets.QListWidgetItem(song['title'], self.songs_list_widget)
|
||||
item.setData(QtCore.Qt.UserRole, (song['file_name'], song['sha256']))
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
bible_languages = self.config.get('bibles', 'languages')
|
||||
bible_languages = bible_languages.split(',')
|
||||
for lang in bible_languages:
|
||||
for lang in config['bibles'].values():
|
||||
self.application.process_events()
|
||||
language = self.config.get('bibles_{lang}'.format(lang=lang), 'title')
|
||||
lang_item = QtWidgets.QTreeWidgetItem(self.bibles_tree_widget, [language])
|
||||
bibles = self.config.get('bibles_{lang}'.format(lang=lang), 'translations')
|
||||
bibles = bibles.split(',')
|
||||
for bible in bibles:
|
||||
lang_item = QtWidgets.QTreeWidgetItem(self.bibles_tree_widget, [lang['title']])
|
||||
for translation in lang['translations'].values():
|
||||
self.application.process_events()
|
||||
title = self.config.get('bible_{bible}'.format(bible=bible), 'title')
|
||||
filename = self.config.get('bible_{bible}'.format(bible=bible), 'filename')
|
||||
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 = QtWidgets.QTreeWidgetItem(lang_item, [translation['title']])
|
||||
item.setData(0, QtCore.Qt.UserRole, (translation['file_name'], translation['sha256']))
|
||||
item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
self.bibles_tree_widget.expandAll()
|
||||
self.application.process_events()
|
||||
# Download the theme screenshots
|
||||
themes = self.config.get('themes', 'files').split(',')
|
||||
for theme in themes:
|
||||
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)
|
||||
for theme in config['themes'].values():
|
||||
ThemeListWidgetItem(self.themes_url, theme, self, self.themes_list_widget)
|
||||
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):
|
||||
"""
|
||||
Set up display at start of theme edit.
|
||||
"""
|
||||
self.restart()
|
||||
self.web = 'http://openlp.org/files/frw/'
|
||||
self.web = 'https://get.openlp.org/ftw/'
|
||||
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_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked)
|
||||
@ -282,9 +239,18 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
create_paths(Path(gettempdir(), 'openlp'))
|
||||
self.theme_combo_box.clear()
|
||||
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.
|
||||
for theme in self.theme_manager.get_themes():
|
||||
self.theme_combo_box.addItem(theme)
|
||||
self.theme_combo_box.insertSeparator(0)
|
||||
self.theme_combo_box.addItems(sorted(self.theme_manager.get_themes()))
|
||||
default_theme = Settings().value('themes/global theme')
|
||||
# Pre-select the current default theme.
|
||||
index = self.theme_combo_box.findText(default_theme)
|
||||
@ -335,49 +301,34 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
Process the triggering of the cancel button.
|
||||
"""
|
||||
self.was_cancelled = True
|
||||
if self.theme_screenshot_threads:
|
||||
for thread_name in self.theme_screenshot_threads:
|
||||
if self.thumbnail_download_threads: # TODO: Use main thread list
|
||||
for thread_name in self.thumbnail_download_threads:
|
||||
worker = get_thread_worker(thread_name)
|
||||
if worker:
|
||||
worker.set_download_canceled(True)
|
||||
worker.cancel_download()
|
||||
# Was the thread created.
|
||||
if self.theme_screenshot_threads:
|
||||
while any([not is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]):
|
||||
if self.thumbnail_download_threads:
|
||||
while any([not is_thread_finished(thread_name) for thread_name in self.thumbnail_download_threads]):
|
||||
time.sleep(0.1)
|
||||
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
|
||||
"""
|
||||
theme_name = item.text()
|
||||
if self.theme_manager and theme_name in self.theme_manager.get_themes():
|
||||
return True
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
self.theme_combo_box.addItem(theme_name)
|
||||
return True
|
||||
else:
|
||||
index = self.theme_combo_box.findText(theme_name)
|
||||
if index != -1:
|
||||
self.theme_combo_box.removeItem(index)
|
||||
return True
|
||||
existing_themes = []
|
||||
if self.theme_manager:
|
||||
existing_themes = self.theme_manager.get_themes()
|
||||
for list_index in range(self.themes_list_widget.count()):
|
||||
item = self.themes_list_widget.item(list_index)
|
||||
if item.text() not in existing_themes:
|
||||
cbox_index = self.theme_combo_box.findText(item.text())
|
||||
if item.isSelected() and cbox_index == -1:
|
||||
self.theme_combo_box.insertItem(0, item.text())
|
||||
elif not item.isSelected() and cbox_index != -1:
|
||||
self.theme_combo_box.removeItem(cbox_index)
|
||||
|
||||
def on_no_internet_finish_button_clicked(self):
|
||||
"""
|
||||
@ -396,18 +347,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.was_cancelled = True
|
||||
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):
|
||||
"""
|
||||
Calculate and display the download progress. This method is called by download_file().
|
||||
@ -456,13 +395,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
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, sha256 = item.data(QtCore.Qt.UserRole)
|
||||
size = get_url_file_size('{path}{name}'.format(path=self.themes_url, name=filename))
|
||||
self.max_progress += size
|
||||
for item in self.themes_list_widget.selectedItems():
|
||||
size = get_url_file_size('{url}{file}'.format(url=self.themes_url, file=item.file_name))
|
||||
self.max_progress += size
|
||||
except urllib.error.URLError:
|
||||
trace_error_handler(log)
|
||||
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
||||
@ -579,15 +514,12 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
missed_files.append('Bible: {name}'.format(name=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, sha256 = item.data(QtCore.Qt.UserRole)
|
||||
self._increment_progress_bar(self.downloading.format(name=theme), 0)
|
||||
self.previous_size = 0
|
||||
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))
|
||||
for item in self.themes_list_widget.selectedItems():
|
||||
self._increment_progress_bar(self.downloading.format(name=item.file_name), 0)
|
||||
self.previous_size = 0
|
||||
if not download_file(self, '{url}{file}'.format(url=self.themes_url, file=item.file_name),
|
||||
themes_destination_path / item.file_name, item.sha256):
|
||||
missed_files.append('Theme: name'.format(name=item.file_name))
|
||||
if missed_files:
|
||||
file_list = ''
|
||||
for entry in missed_files:
|
||||
|
@ -49,6 +49,39 @@ class FirstTimePage(object):
|
||||
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)
|
||||
|
||||
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):
|
||||
"""
|
||||
The UI widgets for the first time wizard.
|
||||
@ -175,27 +208,26 @@ class UiFirstTimeWizard(object):
|
||||
self.themes_page = QtWidgets.QWizardPage()
|
||||
self.themes_page.setObjectName('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_list_widget = QtWidgets.QListWidget(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_list_widget = ThemeListWidget(self.themes_page)
|
||||
self.themes_layout.addWidget(self.themes_list_widget)
|
||||
self.theme_options_layout = QtWidgets.QHBoxLayout()
|
||||
self.default_theme_layout = QtWidgets.QHBoxLayout()
|
||||
self.theme_label = QtWidgets.QLabel(self.themes_page)
|
||||
self.default_theme_layout.addWidget(self.theme_label)
|
||||
self.theme_combo_box = QtWidgets.QComboBox(self.themes_page)
|
||||
self.theme_combo_box.setEditable(False)
|
||||
self.theme_combo_box.setInsertPolicy(QtWidgets.QComboBox.NoInsert)
|
||||
self.theme_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
|
||||
self.default_theme_layout.addWidget(self.theme_combo_box)
|
||||
self.themes_layout.addLayout(self.default_theme_layout)
|
||||
self.default_theme_layout.addWidget(self.theme_combo_box, stretch=1)
|
||||
self.theme_options_layout.addLayout(self.default_theme_layout, stretch=1)
|
||||
self.select_buttons_layout = QtWidgets.QHBoxLayout()
|
||||
self.themes_select_all_button = QtWidgets.QToolButton(self.themes_page)
|
||||
self.themes_select_all_button.setIcon(UiIcons().select_all)
|
||||
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().select_none)
|
||||
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)
|
||||
# Progress page
|
||||
self.progress_page = QtWidgets.QWizardPage()
|
||||
@ -271,9 +303,12 @@ class UiFirstTimeWizard(object):
|
||||
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.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.'))
|
||||
# Themes Page
|
||||
self.themes_page.setTitle(translate('OpenLP.FirstTimeWizard', '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.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded '
|
||||
'and OpenLP is configured.'))
|
||||
|
@ -138,6 +138,8 @@ class UiIcons(object):
|
||||
'search_plus': {'icon': 'fa.search-plus'},
|
||||
'search_ref': {'icon': 'fa.institution'},
|
||||
'search_text': {'icon': 'op.search-text'},
|
||||
'select_all': {'icon': 'fa.check-square-o'},
|
||||
'select_none': {'icon': 'fa.square-o'},
|
||||
'settings': {'icon': 'fa.cogs'},
|
||||
'shortcuts': {'icon': 'fa.wrench'},
|
||||
'song_usage': {'icon': 'fa.line-chart'},
|
||||
|
@ -25,40 +25,69 @@ Package to test the openlp.core.ui.firsttimeform package.
|
||||
import os
|
||||
import tempfile
|
||||
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.registry import Registry
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
FAKE_CONFIG = """
|
||||
[general]
|
||||
base url = http://example.com/frw/
|
||||
[songs]
|
||||
directory = songs
|
||||
[bibles]
|
||||
directory = bibles
|
||||
[themes]
|
||||
directory = themes
|
||||
INVALID_CONFIG = """
|
||||
{
|
||||
"_comments": "The most recent version should be added to https://openlp.org/files/frw/download_3.0.json",
|
||||
"_meta": {
|
||||
}
|
||||
"""
|
||||
|
||||
FAKE_BROKEN_CONFIG = """
|
||||
[general]
|
||||
base url = http://example.com/frw/
|
||||
[songs]
|
||||
directory = songs
|
||||
[bibles]
|
||||
directory = bibles
|
||||
"""
|
||||
|
||||
FAKE_INVALID_CONFIG = """
|
||||
<html>
|
||||
<head><title>This is not a config file</title></head>
|
||||
<body>Some text</body>
|
||||
</html>
|
||||
"""
|
||||
class TestThemeListWidgetItem(TestCase):
|
||||
"""
|
||||
Test the :class:`ThemeListWidgetItem` class
|
||||
"""
|
||||
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):
|
||||
@ -92,7 +121,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
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.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'
|
||||
|
||||
def test_set_defaults(self):
|
||||
@ -109,6 +138,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
|
||||
patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
|
||||
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('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \
|
||||
patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \
|
||||
@ -122,7 +152,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
|
||||
# THEN: The default values should have been set
|
||||
mocked_restart.assert_called_once()
|
||||
assert 'http://openlp.org/files/frw/' == frw.web, 'The default URL should be set'
|
||||
assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'
|
||||
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(
|
||||
frw.on_no_internet_finish_button_clicked)
|
||||
@ -134,6 +164,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))
|
||||
mocked_theme_combo_box.clear.assert_called_once()
|
||||
mocked_theme_manager.assert_not_called()
|
||||
mocked_songs_check_box.assert_not_called()
|
||||
|
||||
def test_set_defaults_rerun(self):
|
||||
"""
|
||||
@ -150,12 +181,17 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
|
||||
patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
|
||||
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), \
|
||||
patch.object(Registry, 'register_function') as mocked_register_function, \
|
||||
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.create_paths') as mocked_create_paths, \
|
||||
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)
|
||||
|
||||
# WHEN: The set_defaults() method is run
|
||||
@ -163,7 +199,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
|
||||
# THEN: The default values should have been set
|
||||
mocked_restart.assert_called_once()
|
||||
assert 'http://openlp.org/files/frw/' == frw.web, 'The default URL should be set'
|
||||
assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'
|
||||
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(
|
||||
frw.on_no_internet_finish_button_clicked)
|
||||
@ -173,9 +209,13 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')])
|
||||
mocked_gettempdir.assert_called_once()
|
||||
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.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.setCurrentIndex(3)
|
||||
|
||||
@ -192,7 +232,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
mocked_is_thread_finished.side_effect = [False, True]
|
||||
frw = FirstTimeForm(None)
|
||||
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:
|
||||
|
||||
# WHEN: on_cancel_button_clicked() is called
|
||||
@ -201,43 +241,26 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||
# 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'
|
||||
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')
|
||||
assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice'
|
||||
mocked_time.sleep.assert_called_once_with(0.1)
|
||||
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
|
||||
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_BROKEN_CONFIG
|
||||
# GIVEN: An instance of `FirstTimeForm`
|
||||
first_time_form = FirstTimeForm(None)
|
||||
|
||||
# WHEN: The First Time Wizard is downloads the config file
|
||||
first_time_form._download_index()
|
||||
# WHEN: Calling _parse_config with a string containing invalid data
|
||||
result = first_time_form._parse_config(INVALID_CONFIG)
|
||||
|
||||
# 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 a broken config file'
|
||||
|
||||
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'
|
||||
# THEN: _parse_data should return False and the user should have should have been informed.
|
||||
assert result is False
|
||||
mocked_critical_error_message_box.assert_called_once()
|
||||
|
||||
@patch('openlp.core.ui.firsttimeform.get_web_page')
|
||||
@patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox')
|
||||
|
88
tests/interfaces/openlp_core/ui/test_firsttimeform.py
Normal file
88
tests/interfaces/openlp_core/ui/test_firsttimeform.py
Normal file
@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2019 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
|
||||
|
||||
|
||||
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()) # noqa Overcome GC issue
|
||||
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()) # noqa Overcome GC issue
|
||||
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())])
|
Loading…
Reference in New Issue
Block a user