forked from openlp/openlp
Merge branch 'ftw-remote-download' into 'master'
Web Remote Version Checking and Downloads; Other Fixes See merge request openlp/openlp!159
This commit is contained in:
commit
92d67468e2
@ -23,16 +23,64 @@ Download and "install" the remote web client
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from datetime import date
|
||||
from distutils.version import LooseVersion
|
||||
from zipfile import ZipFile
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.httputils import download_file, get_web_page, get_openlp_user_agent
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.threading import ThreadWorker, run_thread
|
||||
|
||||
REMOTE_URL = 'https://get.openlp.org/remote/'
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RemoteVersionWorker(ThreadWorker):
|
||||
"""
|
||||
A worker class to fetch the version of the web remote. This is run from within a thread so that it
|
||||
doesn't affect the loading time of OpenLP.
|
||||
"""
|
||||
new_version = QtCore.pyqtSignal(str)
|
||||
no_internet = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, current_version):
|
||||
"""
|
||||
Constructor for the version check worker.
|
||||
|
||||
:param string current_version: The current version of the web remote
|
||||
"""
|
||||
log.debug('VersionWorker - Initialise')
|
||||
super().__init__(None)
|
||||
self.current_version = current_version or '0.0'
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Check the latest version of the web remote against the version file on the OpenLP server.
|
||||
"""
|
||||
log.debug('RemoteVersionWorker - Start')
|
||||
version_info = None
|
||||
retries = 0
|
||||
while retries < 3:
|
||||
try:
|
||||
version_info = download_version_info()
|
||||
log.debug('New version found: %s', version_info['latest']['version'])
|
||||
break
|
||||
except OSError:
|
||||
log.exception('Unable to connect to OpenLP server to download version file')
|
||||
retries += 1
|
||||
else:
|
||||
self.no_internet.emit()
|
||||
if version_info and LooseVersion(version_info['latest']['version']) > LooseVersion(self.current_version):
|
||||
Registry().get('settings').setValue('api/last version test', date.today().strftime('%Y-%m-%d'))
|
||||
Registry().get('settings_form').api_tab.master_version = version_info['latest']['version']
|
||||
self.new_version.emit(version_info['latest']['version'])
|
||||
self.quit.emit()
|
||||
|
||||
|
||||
def deploy_zipfile(app_root_path, zip_name):
|
||||
"""
|
||||
Process the downloaded zip file and add to the correct directory
|
||||
@ -60,7 +108,18 @@ def download_version_info():
|
||||
return json.loads(file_contents)
|
||||
|
||||
|
||||
def download_and_check(callback=None):
|
||||
def get_latest_size():
|
||||
"""
|
||||
Download the version info file and get the size of the latest file
|
||||
"""
|
||||
version_info = download_version_info()
|
||||
if not version_info:
|
||||
log.warning('Unable to access the version information, abandoning download')
|
||||
return 0
|
||||
return version_info['latest']['size']
|
||||
|
||||
|
||||
def download_and_check(callback=None, can_update_range=True):
|
||||
"""
|
||||
Download the web site and deploy it.
|
||||
"""
|
||||
@ -70,9 +129,27 @@ def download_and_check(callback=None):
|
||||
# Show the user an error message
|
||||
return None
|
||||
file_size = version_info['latest']['size']
|
||||
if can_update_range:
|
||||
callback.setRange(0, file_size)
|
||||
if download_file(callback, REMOTE_URL + '{version}/{filename}'.format(**version_info['latest']),
|
||||
AppLocation.get_section_data_path('remotes') / 'remote.zip'):
|
||||
deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'remote.zip')
|
||||
return version_info['latest']['version']
|
||||
return None
|
||||
|
||||
|
||||
def check_for_remote_update(main_window):
|
||||
"""
|
||||
Run a thread to download and check the version of OpenLP
|
||||
|
||||
:param MainWindow main_window: The OpenLP main window.
|
||||
"""
|
||||
last_check_date = Registry().get('settings').value('api/last version test')
|
||||
if date.today().strftime('%Y-%m-%d') <= last_check_date:
|
||||
log.debug('Version check skipped, last checked today')
|
||||
return
|
||||
worker = RemoteVersionWorker(Registry().get('settings').value('api/download version'))
|
||||
worker.new_version.connect(main_window.on_new_remote_version)
|
||||
# TODO: Use this to figure out if there's an Internet connection?
|
||||
# worker.no_internet.connect(main_window.on_no_internet)
|
||||
run_thread(worker, 'remote-version')
|
||||
|
@ -44,7 +44,7 @@ class ApiTab(SettingsTab):
|
||||
def __init__(self, parent):
|
||||
self.icon_path = UiIcons().remote
|
||||
advanced_translated = translate('OpenLP.APITab', 'API')
|
||||
self.master_version = None
|
||||
self._master_version = None
|
||||
super(ApiTab, self).__init__(parent, 'api', advanced_translated)
|
||||
|
||||
def setup_ui(self):
|
||||
@ -192,7 +192,37 @@ class ApiTab(SettingsTab):
|
||||
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
|
||||
self.current_version_label.setText(translate('RemotePlugin.RemoteTab', 'Current version:'))
|
||||
self.master_version_label.setText(translate('RemotePlugin.RemoteTab', 'Latest version:'))
|
||||
self.unknown_version = translate('RemotePlugin.RemoteTab', '(unknown)')
|
||||
self._unknown_version = translate('RemotePlugin.RemoteTab', '(unknown)')
|
||||
|
||||
@property
|
||||
def master_version(self):
|
||||
"""
|
||||
Property getter for the remote master version
|
||||
"""
|
||||
return self._master_version
|
||||
|
||||
@master_version.setter
|
||||
def master_version(self, value):
|
||||
"""
|
||||
Property setter for the remote master version
|
||||
"""
|
||||
self._master_version = value
|
||||
self.master_version_value.setText(self._master_version or self._unknown_version)
|
||||
self.upgrade_button.setEnabled(self.can_enable_upgrade_button())
|
||||
|
||||
def can_enable_upgrade_button(self):
|
||||
"""
|
||||
Do a couple checks to set the upgrade button state
|
||||
"""
|
||||
return self.master_version_value.text() != self._unknown_version and \
|
||||
self.master_version_value.text() != self.current_version_value.text()
|
||||
|
||||
def set_master_version(self):
|
||||
"""
|
||||
Check if the master version is not set, and set it to None to invoke the "unknown version" label
|
||||
"""
|
||||
if not self._master_version:
|
||||
self.master_version = None
|
||||
|
||||
def set_urls(self):
|
||||
"""
|
||||
@ -222,13 +252,6 @@ class ApiTab(SettingsTab):
|
||||
break
|
||||
return ip_address
|
||||
|
||||
def can_enable_upgrade_button(self):
|
||||
"""
|
||||
Do a couple checks to set the upgrade button state
|
||||
"""
|
||||
return self.master_version_value.text() != self.unknown_version and \
|
||||
self.master_version_value.text() != self.current_version_value.text()
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load the configuration and update the server configuration if necessary
|
||||
@ -243,8 +266,7 @@ class ApiTab(SettingsTab):
|
||||
self.user_id.setText(self.settings.value(self.settings_section + '/user id'))
|
||||
self.password.setText(self.settings.value(self.settings_section + '/password'))
|
||||
self.current_version_value.setText(self.settings.value(self.settings_section + '/download version'))
|
||||
self.master_version_value.setText(self.master_version or self.unknown_version)
|
||||
self.upgrade_button.setEnabled(self.can_enable_upgrade_button())
|
||||
self.set_master_version()
|
||||
self.set_urls()
|
||||
|
||||
def save(self):
|
||||
@ -287,8 +309,7 @@ class ApiTab(SettingsTab):
|
||||
app.process_events()
|
||||
version_info = download_version_info()
|
||||
app.process_events()
|
||||
self.master_version_value.setText(version_info['latest']['version'])
|
||||
self.upgrade_button.setEnabled(self.can_enable_upgrade_button())
|
||||
self.master_version = version_info['latest']['version']
|
||||
app.process_events()
|
||||
app.set_normal_cursor()
|
||||
app.process_events()
|
||||
|
@ -37,18 +37,19 @@ from traceback import format_exception
|
||||
|
||||
from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets # noqa
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.api.deploy import check_for_remote_update
|
||||
from openlp.core.common import is_macosx, is_win
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.loader import loader
|
||||
from openlp.core.common.i18n import LanguageManager, UiStrings, translate
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.common.path import create_paths
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.loader import loader
|
||||
from openlp.core.resources import qInitResources
|
||||
from openlp.core.server import Server
|
||||
from openlp.core.state import State
|
||||
from openlp.core.ui.exceptionform import ExceptionForm
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
|
||||
@ -140,6 +141,8 @@ class OpenLP(QtCore.QObject, LogMixin):
|
||||
self.main_window.first_time()
|
||||
if self.settings.value('core/update check'):
|
||||
check_for_update(self.main_window)
|
||||
if self.settings.value('api/update check'):
|
||||
check_for_remote_update(self.main_window)
|
||||
self.main_window.is_display_blank()
|
||||
Registry().execute('bootstrap_completion')
|
||||
return self.exec()
|
||||
|
@ -196,6 +196,8 @@ class Settings(QtCore.QSettings):
|
||||
'api/ip address': '0.0.0.0',
|
||||
'api/thumbnails': True,
|
||||
'api/download version': '0.0',
|
||||
'api/last version test': '',
|
||||
'api/update check': True,
|
||||
'bibles/db type': 'sqlite',
|
||||
'bibles/db username': '',
|
||||
'bibles/db password': '',
|
||||
|
@ -32,6 +32,7 @@ from tempfile import gettempdir
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.api.deploy import get_latest_size, download_and_check
|
||||
from openlp.core.common import trace_error_handler
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.httputils import DownloadWorker, download_file, get_url_file_size, get_web_page
|
||||
@ -113,13 +114,13 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
"""
|
||||
Returns the id of the next FirstTimePage to go to based on enabled plugins
|
||||
"""
|
||||
if FirstTimePage.Download < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():
|
||||
if FirstTimePage.Remote < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():
|
||||
# If the songs plugin is enabled then go to the songs page
|
||||
return FirstTimePage.Songs
|
||||
elif FirstTimePage.Download < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():
|
||||
elif FirstTimePage.Remote < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():
|
||||
# Otherwise, if the Bibles plugin is enabled then go to the Bibles page
|
||||
return FirstTimePage.Bibles
|
||||
elif FirstTimePage.Download < self.currentId() < FirstTimePage.Themes:
|
||||
elif FirstTimePage.Remote < self.currentId() < FirstTimePage.Themes:
|
||||
# Otherwise, if the current page is somewhere between the Welcome and the Themes pages, go to the themes
|
||||
return FirstTimePage.Themes
|
||||
else:
|
||||
@ -135,7 +136,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
if not self.has_web_access:
|
||||
return FirstTimePage.NoInternet
|
||||
else:
|
||||
return FirstTimePage.Songs
|
||||
return FirstTimePage.Remote
|
||||
elif self.currentId() == FirstTimePage.Progress:
|
||||
return -1
|
||||
elif self.currentId() == FirstTimePage.NoInternet:
|
||||
@ -237,6 +238,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.has_run_wizard = self.settings.value('core/has run wizard')
|
||||
create_paths(Path(gettempdir(), 'openlp'))
|
||||
self.theme_combo_box.clear()
|
||||
self.remote_page.can_download_remote = False
|
||||
self.button(QtWidgets.QWizard.CustomButton1).setVisible(False)
|
||||
if self.has_run_wizard:
|
||||
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
|
||||
@ -418,6 +420,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
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
|
||||
# If we're downloading the remote, add it in here too
|
||||
if self.remote_page.can_download_remote:
|
||||
self.max_progress += get_latest_size()
|
||||
except urllib.error.URLError:
|
||||
trace_error_handler(log)
|
||||
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
||||
@ -517,6 +522,15 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
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))
|
||||
# Remote
|
||||
if self.remote_page.can_download_remote:
|
||||
self._increment_progress_bar(self.downloading.format(name='Web Remote'), 0)
|
||||
self.previous_size = 0
|
||||
remote_version = download_and_check(self, can_update_range=False)
|
||||
if remote_version:
|
||||
self.settings.setValue('api/download version', remote_version)
|
||||
else:
|
||||
missed_files.append('Web Remote')
|
||||
if missed_files:
|
||||
file_list = ''
|
||||
for entry in missed_files:
|
||||
|
@ -29,6 +29,7 @@ from openlp.core.lib.ui import add_welcome_page
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.pages import GridLayoutPage
|
||||
from openlp.core.widgets.widgets import ScreenSelectionWidget
|
||||
|
||||
|
||||
@ -42,10 +43,53 @@ class FirstTimePage(object):
|
||||
SampleOption = 3
|
||||
Download = 4
|
||||
NoInternet = 5
|
||||
Songs = 6
|
||||
Bibles = 7
|
||||
Themes = 8
|
||||
Progress = 9
|
||||
Remote = 6
|
||||
Songs = 7
|
||||
Bibles = 8
|
||||
Themes = 9
|
||||
Progress = 10
|
||||
|
||||
|
||||
class RemotePage(GridLayoutPage):
|
||||
"""
|
||||
A page for the web remote
|
||||
"""
|
||||
def setup_ui(self):
|
||||
"""
|
||||
Set up the page
|
||||
"""
|
||||
self.remote_label = QtWidgets.QLabel(self)
|
||||
self.remote_label.setWordWrap(True)
|
||||
self.remote_label.setObjectName('remote_label')
|
||||
self.layout.addWidget(self.remote_label, 0, 0, 1, 4)
|
||||
self.download_checkbox = QtWidgets.QCheckBox(self)
|
||||
self.setObjectName('download_checkbox')
|
||||
self.layout.addWidget(self.download_checkbox, 1, 1, 1, 3)
|
||||
|
||||
def retranslate_ui(self):
|
||||
"""
|
||||
Translate the interface
|
||||
"""
|
||||
self.remote_label.setText(translate('OpenLP.FirstTimeWizard', 'OpenLP has a web remote, which enables you to '
|
||||
'control OpenLP from another computer, phone or tablet on the same network '
|
||||
'as the OpenLP computer. OpenLP can download this web remote for you now, '
|
||||
'or you can download it later via the remote settings.'))
|
||||
self.download_checkbox.setText(translate('OpenLP.FirstTimeWizard', 'Yes, download the remote now'))
|
||||
self.setTitle(translate('OpenLP.FirstTimeWizard', 'Web-based Remote Interface'))
|
||||
self.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please confirm if you want to download the web remote.'))
|
||||
|
||||
@property
|
||||
def can_download_remote(self):
|
||||
"""
|
||||
The get method of a property to determine if the user selected the "Download remote now" checkbox
|
||||
"""
|
||||
return self.download_checkbox.isChecked()
|
||||
|
||||
@can_download_remote.setter
|
||||
def can_download_remote(self, value):
|
||||
if not isinstance(value, bool):
|
||||
raise TypeError('Must be a bool')
|
||||
self.download_checkbox.setChecked(value)
|
||||
|
||||
|
||||
class ThemeListWidget(QtWidgets.QListWidget):
|
||||
@ -187,6 +231,9 @@ class UiFirstTimeWizard(object):
|
||||
self.alert_check_box.setObjectName('alert_check_box')
|
||||
self.plugin_layout.addWidget(self.alert_check_box)
|
||||
first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page)
|
||||
# Web Remote page
|
||||
self.remote_page = RemotePage(self)
|
||||
first_time_wizard.setPage(FirstTimePage.Remote, self.remote_page)
|
||||
# The song samples page
|
||||
self.songs_page = QtWidgets.QWizardPage()
|
||||
self.songs_page.setObjectName('songs_page')
|
||||
|
@ -622,8 +622,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
||||
|
||||
def on_new_version(self, version):
|
||||
"""
|
||||
Notifies the user that a newer version of OpenLP is available.
|
||||
Triggered by delay thread and cannot display popup.
|
||||
Notifies the user that a newer version of OpenLP is available. Triggered by delay thread and cannot display
|
||||
popup.
|
||||
|
||||
:param version: The Version to be displayed.
|
||||
"""
|
||||
@ -632,6 +632,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
||||
'https://openlp.org/.').format(new=version, current=get_version()[u'full'])
|
||||
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text)
|
||||
|
||||
def on_new_remote_version(self, version):
|
||||
"""
|
||||
Notifies the user that a newer version of the web remote is available. Triggered by delay thread and cannot
|
||||
display popup.
|
||||
|
||||
:param version: The Version to be displayed.
|
||||
"""
|
||||
version_text = translate('OpenLP.MainWindow', 'Version {version} of the web remote is now available for '
|
||||
'download.\nTo download this version, go to the Remote settings and click the Upgrade '
|
||||
'button.').format(version=version)
|
||||
self.information_message(translate('OpenLP.MainWindow', 'New Web Remote Version Available'), version_text)
|
||||
|
||||
def show(self):
|
||||
"""
|
||||
Show the main form, as well as the display form
|
||||
|
@ -61,7 +61,7 @@ class CustomPlugin(Plugin):
|
||||
|
||||
@staticmethod
|
||||
def about():
|
||||
about_text = translate('CustomPlugin', '<strong>Custom Slide Plugin </strong><br />The custom slide plugin '
|
||||
about_text = translate('CustomPlugin', '<strong>Custom Slide Plugin</strong><br />The custom slide plugin '
|
||||
'provides the ability to set up custom text slides that can be displayed on the screen '
|
||||
'the same way songs are. This plugin provides greater freedom over the songs plugin.')
|
||||
return about_text
|
||||
|
@ -70,7 +70,9 @@ class OpenLyricsExport(RegistryProperties):
|
||||
xml = open_lyrics.song_to_xml(song)
|
||||
tree = etree.ElementTree(etree.fromstring(xml.encode()))
|
||||
filename = '{title} ({author})'.format(title=song.title,
|
||||
author=', '.join([author.display_name for author in song.authors]))
|
||||
author=', '.join([author.display_name for author in
|
||||
sorted(song.authors,
|
||||
key=lambda a: a.display_name)]))
|
||||
filename = clean_filename(filename)
|
||||
# Ensure the filename isn't too long for some filesystems
|
||||
path_length = len(str(self.save_path))
|
||||
|
@ -187,7 +187,22 @@ class TestOpenLPJSONDecoder(TestCase):
|
||||
"""
|
||||
# GIVEN: A JSON encoded string
|
||||
json_string = '[{"parts": ["test", "path1"], "json_meta": {"class": "Path", "version": 1}}, ' \
|
||||
'{"parts": ["test", "path2"], "json_meta": {"class": "Path", "version": 1}}]'
|
||||
'{"parts": ["test", "path2"], "json_meta": {"class": "Path", "version": 1}}, ' \
|
||||
'{"key": "value", "json_meta": {"class": "Object"}}]'
|
||||
|
||||
# WHEN: Decoding the string using the OpenLPJsonDecoder class
|
||||
obj = json.loads(json_string, cls=OpenLPJSONDecoder)
|
||||
|
||||
# THEN: The object returned should be a python version of the JSON string
|
||||
assert obj == [Path('test', 'path1'), Path('test', 'path2'), {'key': 'value', 'json_meta': {'class': 'Object'}}]
|
||||
|
||||
def test_json_decode_old_style(self):
|
||||
"""
|
||||
Test the OpenLPJsonDecoder when decoding a JSON string with an old-style Path object
|
||||
"""
|
||||
# GIVEN: A JSON encoded string
|
||||
json_string = '[{"__Path__": ["test", "path1"]}, ' \
|
||||
'{"__Path__": ["test", "path2"]}]'
|
||||
|
||||
# WHEN: Decoding the string using the OpenLPJsonDecoder class
|
||||
obj = json.loads(json_string, cls=OpenLPJSONDecoder)
|
||||
@ -284,6 +299,19 @@ class TestPathSerializer(TestCase):
|
||||
# THEN: A JSON decodeable object should have been returned.
|
||||
assert obj == {'parts': (os.sep, 'base', 'path', 'to', 'fi.le'), "json_meta": {"class": "Path", "version": 1}}
|
||||
|
||||
def test_path_json_object_is_js(self):
|
||||
"""
|
||||
Test that `Path.json_object` creates a JSON decode-able object from a Path object
|
||||
"""
|
||||
# GIVEN: A Path object from openlp.core.common.path
|
||||
path = Path('/base', 'path', 'to', 'fi.le')
|
||||
|
||||
# WHEN: Calling json_object
|
||||
obj = PathSerializer().json_object(path, is_js=True, extra=1, args=2)
|
||||
|
||||
# THEN: A URI should be returned
|
||||
assert obj == 'file:///base/path/to/fi.le'
|
||||
|
||||
def test_path_json_object_base_path(self):
|
||||
"""
|
||||
Test that `Path.json_object` creates a JSON decode-able object from a Path object, that is relative to the
|
||||
|
@ -29,7 +29,7 @@ from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem
|
||||
from openlp.core.ui.firsttimewizard import ThemeListWidget
|
||||
from openlp.core.ui.firsttimewizard import RemotePage, ThemeListWidget
|
||||
|
||||
|
||||
INVALID_CONFIG = """
|
||||
@ -367,6 +367,50 @@ def test_on_projectors_check_box_unchecked(mock_settings):
|
||||
mock_settings.setValue.assert_called_once_with('projector/show after wizard', True)
|
||||
|
||||
|
||||
def test_remote_page_get_can_download_remote(ftf_app):
|
||||
"""
|
||||
Test that the `can_download_remote` property returns the correct value
|
||||
"""
|
||||
# GIVEN: A RemotePage object with a mocked out download_checkbox
|
||||
remote_page = RemotePage(None)
|
||||
remote_page.download_checkbox = MagicMock(**{"isChecked.return_value": True})
|
||||
|
||||
# WHEN: The can_download_remote property is accessed
|
||||
result = remote_page.can_download_remote
|
||||
|
||||
# THEN: The result should be True
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_remote_page_set_can_download_remote(ftf_app):
|
||||
"""
|
||||
Test that the `can_download_remote` property sets the correct value
|
||||
"""
|
||||
# GIVEN: A RemotePage object with a mocked out download_checkbox
|
||||
remote_page = RemotePage(None)
|
||||
remote_page.download_checkbox = MagicMock()
|
||||
|
||||
# WHEN: The can_download_remote property is set
|
||||
remote_page.can_download_remote = False
|
||||
|
||||
# THEN: The result should be True
|
||||
remote_page.download_checkbox.setChecked.assert_called_once_with(False)
|
||||
|
||||
|
||||
def test_remote_page_set_can_download_remote_not_bool(ftf_app):
|
||||
"""
|
||||
Test that the `can_download_remote` property throws an exception when the value is not a boolean
|
||||
"""
|
||||
# GIVEN: A RemotePage object with a mocked out download_checkbox
|
||||
remote_page = RemotePage(None)
|
||||
remote_page.download_checkbox = MagicMock()
|
||||
|
||||
# WHEN: The can_download_remote property is set
|
||||
# THEN: An exception is thrown
|
||||
with pytest.raises(TypeError, match='Must be a bool'):
|
||||
remote_page.can_download_remote = 'not a bool'
|
||||
|
||||
|
||||
def test_theme_list_widget_resize(ftf_app):
|
||||
"""
|
||||
Test that the resizeEvent() method in the ThemeListWidget works correctly
|
||||
@ -382,5 +426,3 @@ def test_theme_list_widget_resize(ftf_app):
|
||||
|
||||
# THEN: Check that the correct calculations were done
|
||||
mocked_setGridSize.assert_called_once_with(QtCore.QSize(149, 140))
|
||||
|
||||
# THEN: everything resizes correctly
|
||||
|
33
tests/functional/openlp_plugins/alerts/test_plugin.py
Normal file
33
tests/functional/openlp_plugins/alerts/test_plugin.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Alerts plugin.
|
||||
"""
|
||||
from openlp.plugins.alerts.alertsplugin import AlertsPlugin
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
result = AlertsPlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Alerts Plugin</strong>'
|
||||
'<br />The alert plugin controls the displaying of alerts on the display screen.'
|
||||
)
|
0
tests/functional/openlp_plugins/bibles/test_mediaitem.py
Executable file → Normal file
0
tests/functional/openlp_plugins/bibles/test_mediaitem.py
Executable file → Normal file
34
tests/functional/openlp_plugins/bibles/test_plugin.py
Normal file
34
tests/functional/openlp_plugins/bibles/test_plugin.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Bibles plugin.
|
||||
"""
|
||||
from openlp.plugins.bibles.bibleplugin import BiblePlugin
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
result = BiblePlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Bible Plugin</strong>'
|
||||
'<br />The Bible plugin provides the ability to display Bible '
|
||||
'verses from different sources during the service.'
|
||||
)
|
34
tests/functional/openlp_plugins/custom/test_plugin.py
Normal file
34
tests/functional/openlp_plugins/custom/test_plugin.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Custom plugin.
|
||||
"""
|
||||
from openlp.plugins.custom.customplugin import CustomPlugin
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
result = CustomPlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Custom Slide Plugin</strong><br />The custom slide plugin '
|
||||
'provides the ability to set up custom text slides that can be displayed on the screen '
|
||||
'the same way songs are. This plugin provides greater freedom over the songs plugin.'
|
||||
)
|
42
tests/functional/openlp_plugins/images/test_plugin.py
Normal file
42
tests/functional/openlp_plugins/images/test_plugin.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Images plugin.
|
||||
"""
|
||||
from openlp.plugins.images.imageplugin import ImagePlugin
|
||||
|
||||
|
||||
def test_image_plugin_about():
|
||||
result = ImagePlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Image Plugin</strong>'
|
||||
'<br />The image plugin provides displaying of images.<br />One '
|
||||
'of the distinguishing features of this plugin is the ability to '
|
||||
'group a number of images together in the service manager, making '
|
||||
'the displaying of multiple images easier. This plugin can also '
|
||||
'make use of OpenLP\'s "timed looping" feature to create a slide '
|
||||
'show that runs automatically. In addition to this, images from '
|
||||
'the plugin can be used to override the current theme\'s '
|
||||
'background, which renders text-based items like songs with the '
|
||||
'selected image as a background instead of the background '
|
||||
'provided by the theme.'
|
||||
)
|
33
tests/functional/openlp_plugins/media/test_plugin.py
Normal file
33
tests/functional/openlp_plugins/media/test_plugin.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Media plugin.
|
||||
"""
|
||||
from openlp.plugins.media.mediaplugin import MediaPlugin
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
result = MediaPlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Media Plugin</strong>'
|
||||
'<br />The media plugin provides playback of audio and video.'
|
||||
)
|
36
tests/functional/openlp_plugins/presentations/test_plugin.py
Normal file
36
tests/functional/openlp_plugins/presentations/test_plugin.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Presentation plugin.
|
||||
"""
|
||||
from openlp.plugins.presentations.presentationplugin import PresentationPlugin
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
result = PresentationPlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Presentation '
|
||||
'Plugin</strong><br />The presentation plugin provides the '
|
||||
'ability to show presentations using a number of different '
|
||||
'programs. The choice of available presentation programs is '
|
||||
'available to the user in a drop down box.'
|
||||
)
|
@ -24,34 +24,22 @@ This module contains tests for the OpenLyrics song importer.
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from tempfile import mkdtemp
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
import pytest
|
||||
|
||||
# from openlp.core.common.registry import Registry
|
||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
class TestOpenLyricsExport(TestCase, TestMixin):
|
||||
"""
|
||||
Test the functions in the :mod:`openlyricsexport` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the registry
|
||||
"""
|
||||
Registry.create()
|
||||
Registry().register('settings', Settings())
|
||||
self.temp_folder = Path(mkdtemp())
|
||||
@pytest.yield_fixture
|
||||
def temp_folder():
|
||||
temp_path = Path(mkdtemp())
|
||||
yield temp_path
|
||||
shutil.rmtree(temp_path)
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Cleanup
|
||||
"""
|
||||
shutil.rmtree(self.temp_folder)
|
||||
|
||||
def test_export_same_filename(self):
|
||||
def test_export_same_filename(registry, settings, temp_folder):
|
||||
"""
|
||||
Test that files is not overwritten if songs has same title and author
|
||||
"""
|
||||
@ -65,20 +53,21 @@ class TestOpenLyricsExport(TestCase, TestMixin):
|
||||
song.title = 'Test Title'
|
||||
parent = MagicMock()
|
||||
parent.stop_export_flag = False
|
||||
mocked_application_object = MagicMock()
|
||||
Registry().register('application', mocked_application_object)
|
||||
ol_export = OpenLyricsExport(parent, [song, song], self.temp_folder)
|
||||
# mocked_application_object = MagicMock()
|
||||
# Registry().register('application', mocked_application_object)
|
||||
ol_export = OpenLyricsExport(parent, [song, song], temp_folder)
|
||||
|
||||
# WHEN: Doing the export
|
||||
ol_export.do_export()
|
||||
|
||||
# THEN: The exporter should have created 2 files
|
||||
assert (self.temp_folder / '{title} ({display_name}).xml'.format(
|
||||
assert (temp_folder / '{title} ({display_name}).xml'.format(
|
||||
title=song.title, display_name=author.display_name)).exists() is True
|
||||
assert (self.temp_folder / '{title} ({display_name})-1.xml'.format(
|
||||
assert (temp_folder / '{title} ({display_name})-1.xml'.format(
|
||||
title=song.title, display_name=author.display_name)).exists() is True
|
||||
|
||||
def test_export_sort_of_authers_filename(self):
|
||||
|
||||
def test_export_sort_of_authers_filename(registry, settings, temp_folder):
|
||||
"""
|
||||
Test that files is not overwritten if songs has same title and author
|
||||
"""
|
||||
@ -98,19 +87,45 @@ class TestOpenLyricsExport(TestCase, TestMixin):
|
||||
|
||||
parent = MagicMock()
|
||||
parent.stop_export_flag = False
|
||||
mocked_application_object = MagicMock()
|
||||
Registry().register('application', mocked_application_object)
|
||||
ol_export = OpenLyricsExport(parent, [songA, songB], self.temp_folder)
|
||||
# mocked_application_object = MagicMock()
|
||||
# Registry().register('application', mocked_application_object)
|
||||
ol_export = OpenLyricsExport(parent, [songA, songB], temp_folder)
|
||||
|
||||
# WHEN: Doing the export
|
||||
ol_export.do_export()
|
||||
|
||||
# THEN: The exporter orders authers
|
||||
assert (self.temp_folder / '{title} ({display_name}).xml'.format(
|
||||
title=song.title,
|
||||
assert (temp_folder / '{title} ({display_name}).xml'.format(
|
||||
title=songA.title,
|
||||
display_name=", ".join([authorA.display_name, authorB.display_name])
|
||||
)).exists() is True
|
||||
assert (self.temp_folder / '{title} ({display_name})-1.xml'.format(
|
||||
title=song.title,
|
||||
assert (temp_folder / '{title} ({display_name})-1.xml'.format(
|
||||
title=songB.title,
|
||||
display_name=", ".join([authorA.display_name, authorB.display_name])
|
||||
)).exists() is True
|
||||
|
||||
|
||||
def test_export_is_stopped(registry, settings, temp_folder):
|
||||
"""
|
||||
Test that the exporter stops when the flag is set
|
||||
"""
|
||||
# GIVEN: A mocked song_to_xml, a mocked song, a mocked application and an OpenLyricsExport instance
|
||||
with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml:
|
||||
mocked_song_to_xml.return_value = '<?xml version="1.0" encoding="UTF-8"?>\n<empty/>'
|
||||
author = MagicMock()
|
||||
author.display_name = 'Test Author'
|
||||
song = MagicMock()
|
||||
song.authors = [author]
|
||||
song.title = 'Test Title'
|
||||
parent = MagicMock()
|
||||
parent.stop_export_flag = True
|
||||
# mocked_application_object = MagicMock()
|
||||
# Registry().register('application', mocked_application_object)
|
||||
ol_export = OpenLyricsExport(parent, [song, song], temp_folder)
|
||||
|
||||
# WHEN: Doing the export
|
||||
ol_export.do_export()
|
||||
|
||||
# THEN: The exporter should not have created any files
|
||||
assert (temp_folder / '{title} ({display_name}).xml'.format(
|
||||
title=song.title, display_name=author.display_name)).exists() is False
|
||||
|
33
tests/functional/openlp_plugins/songs/test_plugin.py
Normal file
33
tests/functional/openlp_plugins/songs/test_plugin.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
This module contains tests for the plugin class Song plugin.
|
||||
"""
|
||||
from openlp.plugins.songs.songsplugin import SongsPlugin
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
result = SongsPlugin.about()
|
||||
|
||||
assert result == (
|
||||
'<strong>Songs Plugin</strong>'
|
||||
'<br />The songs plugin provides the ability to display and manage songs.'
|
||||
)
|
@ -22,7 +22,7 @@
|
||||
Package to test the openlp.plugins.planningcenter.planningcenterplugin package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
@ -152,3 +152,16 @@ class TestPlanningCenterPlugin(TestCase, TestMixin):
|
||||
return_value = self.plugin.about()
|
||||
# THEN:
|
||||
self.assertGreater(len(return_value), 0, "About function returned some text")
|
||||
|
||||
def test_finalise(self):
|
||||
"""
|
||||
Test that the finalise function cleans up after the plugin
|
||||
"""
|
||||
# GIVEN: A PlanningcenterPlugin Class with a bunch of mocks
|
||||
self.plugin.import_planning_center = MagicMock()
|
||||
|
||||
# WHEN: finalise has been called on the class
|
||||
self.plugin.finalise()
|
||||
|
||||
# THEN: it cleans up after itself
|
||||
self.plugin.import_planning_center.setVisible.assert_called_once_with(False)
|
||||
|
Loading…
Reference in New Issue
Block a user