mirror of https://gitlab.com/openlp/openlp.git
Merge branch 'master' into openlp-migrate-to-pyside6
This commit is contained in:
commit
eba83f1ab9
|
@ -39,7 +39,7 @@ def index(path):
|
|||
'index.html', mimetype='text/html')
|
||||
|
||||
|
||||
@main_views.route('/assets/<path>')
|
||||
@main_views.route('/assets/<path:path>')
|
||||
def assets(path):
|
||||
return send_from_directory(str(AppLocation.get_section_data_path('remotes') / 'assets'),
|
||||
path, mimetype=get_mime_type(path))
|
||||
|
|
|
@ -24,6 +24,7 @@ from flask import jsonify, request, abort, Blueprint
|
|||
from PySide6 import QtCore
|
||||
|
||||
from openlp.core.api.lib import login_required
|
||||
from openlp.core.common.i18n import LanguageManager
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib.plugin import PluginStatus, StringContent
|
||||
from openlp.core.state import State
|
||||
|
@ -82,6 +83,12 @@ def system_information():
|
|||
return jsonify(data)
|
||||
|
||||
|
||||
@core.route('/language')
|
||||
def language():
|
||||
language = LanguageManager.get_language()
|
||||
return jsonify({'language': language})
|
||||
|
||||
|
||||
@core.route('/login', methods=['POST'])
|
||||
def login():
|
||||
data = request.json
|
||||
|
|
|
@ -275,6 +275,8 @@ def sha256_file_hash(filename):
|
|||
"""
|
||||
Returns the hashed output of sha256 on the file content using Python3 hashlib
|
||||
|
||||
This method allows PermissionError to bubble up, while supressing other exceptions
|
||||
|
||||
:param filename: Name of the file to hash
|
||||
:returns: str
|
||||
"""
|
||||
|
@ -288,6 +290,8 @@ def sha256_file_hash(filename):
|
|||
hash_obj.update(chunk)
|
||||
return hash_obj.hexdigest()
|
||||
except PermissionError:
|
||||
raise
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
@ -356,10 +356,12 @@ class UiStrings(metaclass=Singleton):
|
|||
self.BibleNoBibles = translate('OpenLP.Ui', '<strong>There are no Bibles currently installed.</strong><br><br>'
|
||||
'Please use the Import Wizard to install one or more Bibles.')
|
||||
self.Bottom = translate('OpenLP.Ui', 'Bottom')
|
||||
self.Bridge = translate('SongsPlugin.VerseType', 'Bridge')
|
||||
self.Browse = translate('OpenLP.Ui', 'Browse...')
|
||||
self.Cancel = translate('OpenLP.Ui', 'Cancel')
|
||||
self.CCLINumberLabel = translate('OpenLP.Ui', 'CCLI number:')
|
||||
self.CCLISongNumberLabel = translate('OpenLP.Ui', 'CCLI song number:')
|
||||
self.Chorus = translate('SongsPlugin.VerseType', 'Chorus')
|
||||
self.CreateService = translate('OpenLP.Ui', 'Create a new service.')
|
||||
self.ConfirmDelete = translate('OpenLP.Ui', 'Confirm Delete')
|
||||
self.Continuous = translate('OpenLP.Ui', 'Continuous')
|
||||
|
@ -371,14 +373,20 @@ class UiStrings(metaclass=Singleton):
|
|||
'.html#strftime-strptime-behavior for more information.')
|
||||
self.Delete = translate('OpenLP.Ui', '&Delete')
|
||||
self.DisplayStyle = translate('OpenLP.Ui', 'Display style:')
|
||||
self.Down = translate('SongsPlugin.EditVerseForm', 'Down')
|
||||
self.Duplicate = translate('OpenLP.Ui', 'Duplicate Error')
|
||||
self.Edit = translate('OpenLP.Ui', '&Edit')
|
||||
self.EditVerse = translate('SongsPlugin.EditVerseForm', 'Edit Verse')
|
||||
self.EmptyField = translate('OpenLP.Ui', 'Empty Field')
|
||||
self.Ending = translate('SongsPlugin.VerseType', 'Ending')
|
||||
self.Error = translate('OpenLP.Ui', 'Error')
|
||||
self.Export = translate('OpenLP.Ui', 'Export')
|
||||
self.File = translate('OpenLP.Ui', 'File')
|
||||
self.FileCorrupt = translate('OpenLP.Ui', 'File appears to be corrupt.')
|
||||
self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font point size unit')
|
||||
self.ForcedSplit = translate('SongsPlugin.EditVerseForm', '&Forced Split')
|
||||
self.ForcedSplitToolTip = translate('SongsPlugin.EditVerseForm', 'Split the verse when displayed '
|
||||
'regardless of the screen size.')
|
||||
self.Help = translate('OpenLP.Ui', 'Help')
|
||||
self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
|
||||
self.IFdSs = translate('OpenLP.Ui', 'Invalid Folder Selected', 'Singular')
|
||||
|
@ -386,6 +394,10 @@ class UiStrings(metaclass=Singleton):
|
|||
self.IFSp = translate('OpenLP.Ui', 'Invalid Files Selected', 'Plural')
|
||||
self.Image = translate('OpenLP.Ui', 'Image')
|
||||
self.Import = translate('OpenLP.Ui', 'Import')
|
||||
self.Insert = translate('SongsPlugin.EditVerseForm', '&Insert')
|
||||
self.InsertToolTip = translate('SongsPlugin.EditVerseForm',
|
||||
'Split a slide into two by inserting a verse splitter.')
|
||||
self.Intro = translate('SongsPlugin.VerseType', 'Intro')
|
||||
self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:')
|
||||
self.Live = translate('OpenLP.Ui', 'Live')
|
||||
self.LiveStream = translate('OpenLP.Ui', 'Live Stream')
|
||||
|
@ -414,8 +426,11 @@ class UiStrings(metaclass=Singleton):
|
|||
self.OpenService = translate('OpenLP.Ui', 'Open service.')
|
||||
self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
|
||||
self.OptionalHideInFooter = translate('OpenLP.Ui', 'Optional, this won\'t be displayed in footer.')
|
||||
self.Other = translate('SongsPlugin.VerseType', 'Other')
|
||||
self.PermissionError = translate('OpenLP.Ui', 'Permission Error')
|
||||
self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')
|
||||
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
|
||||
self.PreChorus = translate('SongsPlugin.VerseType', 'Pre-Chorus')
|
||||
self.Preview = translate('OpenLP.Ui', 'Preview')
|
||||
self.PreviewToolbar = translate('OpenLP.Ui', 'Preview Toolbar')
|
||||
self.PrintService = translate('OpenLP.Ui', 'Print Service')
|
||||
|
@ -431,6 +446,11 @@ class UiStrings(metaclass=Singleton):
|
|||
self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
|
||||
self.SaveAndClose = translate('OpenLP.ui', translate('SongsPlugin.EditSongForm', '&Save && Close'))
|
||||
self.SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview')
|
||||
self.ScreenSetupHasChangedTitle = translate('OpenLP.MainWindow', 'Screen setup has changed')
|
||||
self.ScreenSetupHasChanged = translate('OpenLP.MainWindow',
|
||||
'The screen setup has changed. OpenLP will try to '
|
||||
'automatically select a display screen, but '
|
||||
'you should consider updating the screen settings.')
|
||||
self.Search = translate('OpenLP.Ui', 'Search')
|
||||
self.SearchThemes = translate('OpenLP.Ui', 'Search Themes...', 'Search bar place holder text ')
|
||||
self.SelectDelete = translate('OpenLP.Ui', 'You must select an item to delete.')
|
||||
|
@ -449,9 +469,16 @@ class UiStrings(metaclass=Singleton):
|
|||
self.Themes = translate('OpenLP.Ui', 'Themes', 'Plural')
|
||||
self.Tools = translate('OpenLP.Ui', 'Tools')
|
||||
self.Top = translate('OpenLP.Ui', 'Top')
|
||||
self.Transpose = translate('SongsPlugin.EditVerseForm', 'Transpose:')
|
||||
self.UnableToRead = translate('OpenLP.Ui', 'Unable to read the file(s) listed below, please check that '
|
||||
'your user has permission to read the file(s) or that the '
|
||||
'file(s) are not using cloud storage (e.g. Dropbox, OneDrive).')
|
||||
self.UnsupportedFile = translate('OpenLP.Ui', 'Unsupported File')
|
||||
self.Up = translate('SongsPlugin.EditVerseForm', 'Up')
|
||||
self.Verse = translate('SongsPlugin.VerseType', 'Verse')
|
||||
self.VersePerSlide = translate('OpenLP.Ui', 'Verse Per Slide')
|
||||
self.VersePerLine = translate('OpenLP.Ui', 'Verse Per Line')
|
||||
self.VerseType = translate('SongsPlugin.EditVerseForm', '&Verse type:')
|
||||
self.Version = translate('OpenLP.Ui', 'Version')
|
||||
self.View = translate('OpenLP.Ui', 'View')
|
||||
self.ViewMode = translate('OpenLP.Ui', 'View Mode')
|
||||
|
|
|
@ -35,6 +35,9 @@ from openlp.core.common.i18n import UiStrings, translate
|
|||
log = logging.getLogger(__name__ + '.__init__')
|
||||
|
||||
|
||||
DEFAULT_THUMBNAIL_HEIGHT = 88
|
||||
|
||||
|
||||
class DataType(IntEnum):
|
||||
U8 = 1
|
||||
U16 = 2
|
||||
|
@ -301,8 +304,8 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
|
|||
:param Path image_path: The image file to create the icon from.
|
||||
:param Path thumb_path: The filename to save the thumbnail to.
|
||||
:param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``.
|
||||
:param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default
|
||||
height of 88 is used.
|
||||
:param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means it uses the value
|
||||
from DEFAULT_THUMBNAIL_HEIGHT.
|
||||
:return: The final icon.
|
||||
"""
|
||||
reader = QtGui.QImageReader(str(image_path))
|
||||
|
@ -312,7 +315,7 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
|
|||
ratio = 1
|
||||
else:
|
||||
ratio = reader.size().width() / reader.size().height()
|
||||
reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
|
||||
reader.setScaledSize(QtCore.QSize(int(ratio * DEFAULT_THUMBNAIL_HEIGHT), DEFAULT_THUMBNAIL_HEIGHT))
|
||||
elif size.isValid():
|
||||
# Complete size given
|
||||
reader.setScaledSize(size)
|
||||
|
@ -330,7 +333,7 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
|
|||
reader.setScaledSize(QtCore.QSize(int(ratio * size.height()), size.height()))
|
||||
else:
|
||||
# Invalid; use default height of 88
|
||||
reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
|
||||
reader.setScaledSize(QtCore.QSize(int(ratio * DEFAULT_THUMBNAIL_HEIGHT), DEFAULT_THUMBNAIL_HEIGHT))
|
||||
thumb = reader.read()
|
||||
thumb.save(str(thumb_path), thumb_path.suffix[1:].lower())
|
||||
if not return_icon:
|
||||
|
|
|
@ -1040,12 +1040,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
and (datetime.now() - self.screen_change_timestamp).seconds < 5
|
||||
should_show_messagebox = self.settings_form.isHidden() and not has_shown_messagebox_recently
|
||||
if should_show_messagebox:
|
||||
QtWidgets.QMessageBox.information(self, translate('OpenLP.MainWindow', 'Screen setup has changed'),
|
||||
translate('OpenLP.MainWindow',
|
||||
'The screen setup has changed. OpenLP will try to '
|
||||
'automatically select a display screen, but '
|
||||
'you should consider updating the screen settings.'),
|
||||
QtWidgets.QMessageBox.StandardButton(
|
||||
self.live_controller.toggle_display('desktop')
|
||||
QtWidgets.QMessageBox.information(self,
|
||||
UiStrings().ScreenSetupHasChangedTitle,
|
||||
UiStrings().ScreenSetupHasChanged,
|
||||
QtWidgets.QMessageBox.StandardButtons(
|
||||
QtWidgets.QMessageBox.StandardButton.Ok))
|
||||
self.screen_change_timestamp = datetime.now()
|
||||
self.application.set_busy_cursor()
|
||||
|
|
|
@ -672,7 +672,12 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
|||
missing_list = []
|
||||
|
||||
if not self._save_lite:
|
||||
write_list, missing_list = self.get_write_file_list()
|
||||
try:
|
||||
write_list, missing_list = self.get_write_file_list()
|
||||
except PermissionError as pe:
|
||||
self.main_window.error_message(UiStrings.PermissionError,
|
||||
UiStrings.UnableToRead + '\n\n' + pe.filename)
|
||||
return False
|
||||
if missing_list:
|
||||
self.application.set_normal_cursor()
|
||||
title = translate('OpenLP.ServiceManager', 'Service File(s) Missing')
|
||||
|
|
|
@ -1268,8 +1268,8 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
|||
fallback_to_windowed = display_above_horizontal or display_above_vertical \
|
||||
or display_beyond_horizontal or display_beyond_vertical
|
||||
if fallback_to_windowed:
|
||||
if self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or self.service_item.is_media() or \
|
||||
self.service_item.is_command():
|
||||
if self.service_item and (self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
|
||||
self.service_item.is_media() or self.service_item.is_command()):
|
||||
if self.service_item.is_command():
|
||||
# Attempting to get screenshot from command handler
|
||||
service_item_name = self.service_item.name.lower()
|
||||
|
|
|
@ -162,11 +162,23 @@ def get_version():
|
|||
except OSError:
|
||||
log.exception('Error in version file.')
|
||||
full_version = '0.0.0'
|
||||
bits = full_version.split('.dev')
|
||||
|
||||
if '.dev' in full_version:
|
||||
# Old way of doing build numbers, but also how hatch does them
|
||||
version_number, build_number = full_version.split('.dev', 1)
|
||||
build_number = build_number.split('+', 1)[1]
|
||||
elif '+' in full_version:
|
||||
# Current way of doing build numbers, may be replaced by hatch later
|
||||
version_number, build_number = full_version.split('+', 1)
|
||||
else:
|
||||
# If this is a release, there is no build number
|
||||
version_number = full_version
|
||||
build_number = None
|
||||
|
||||
APPLICATION_VERSION = {
|
||||
'full': full_version,
|
||||
'version': bits[0],
|
||||
'build': full_version.split('+')[1] if '+' in full_version else None
|
||||
'version': version_number,
|
||||
'build': build_number
|
||||
}
|
||||
if APPLICATION_VERSION['build']:
|
||||
log.info('OpenLP version {version} build {build}'.format(version=APPLICATION_VERSION['version'],
|
||||
|
|
|
@ -27,6 +27,7 @@ import logging
|
|||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.db.manager import DBManager
|
||||
from openlp.core.lib.plugin import Plugin, StringContent
|
||||
|
@ -53,6 +54,7 @@ class CustomPlugin(Plugin):
|
|||
self.weight = -5
|
||||
self.db_manager = DBManager('custom', init_schema)
|
||||
self.icon_path = UiIcons().custom
|
||||
Registry().register('custom_manager', self.db_manager)
|
||||
self.icon = build_icon(self.icon_path)
|
||||
State().add_service(self.name, self.weight, is_plugin=True)
|
||||
State().update_pre_conditions(self.name, self.check_pre_conditions())
|
||||
|
|
|
@ -121,6 +121,7 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
|
|||
self.custom_slide.theme_name = self.theme_combo_box.currentText()
|
||||
success = self.manager.save_object(self.custom_slide)
|
||||
self.media_item.auto_select_id = self.custom_slide.id
|
||||
Registry().execute('custom_changed', self.custom_slide.id)
|
||||
return success
|
||||
|
||||
def on_up_button_clicked(self):
|
||||
|
|
|
@ -112,7 +112,7 @@ class CustomMediaItem(MediaManagerItem):
|
|||
self.load_list(self.plugin.db_manager.get_all_objects(CustomSlide, order_by_ref=CustomSlide.title))
|
||||
self.config_update()
|
||||
|
||||
def load_list(self, custom_slides, target_group=None):
|
||||
def load_list(self, custom_slides=None, target_group=None):
|
||||
# Sort out what custom we want to select after loading the list.
|
||||
"""
|
||||
|
||||
|
@ -121,6 +121,8 @@ class CustomMediaItem(MediaManagerItem):
|
|||
"""
|
||||
self.save_auto_select_id()
|
||||
self.list_view.clear()
|
||||
if not custom_slides:
|
||||
custom_slides = self.plugin.db_manager.get_all_objects(CustomSlide, order_by_ref=CustomSlide.title)
|
||||
custom_slides.sort()
|
||||
for custom_slide in custom_slides:
|
||||
custom_name = QtWidgets.QListWidgetItem(custom_slide.title)
|
||||
|
@ -201,6 +203,7 @@ class CustomMediaItem(MediaManagerItem):
|
|||
id_list = [(item.data(QtCore.Qt.ItemDataRole.UserRole)) for item in self.list_view.selectedIndexes()]
|
||||
for id in id_list:
|
||||
self.plugin.db_manager.delete_object(CustomSlide, id)
|
||||
Registry().execute('custom_deleted', id)
|
||||
self.on_search_text_button_clicked()
|
||||
|
||||
def on_focus(self):
|
||||
|
@ -257,6 +260,7 @@ class CustomMediaItem(MediaManagerItem):
|
|||
credits=old_custom_slide.credits,
|
||||
theme_name=old_custom_slide.theme_name)
|
||||
self.plugin.db_manager.save_object(new_custom_slide)
|
||||
Registry().execute('custom_changed', new_custom_slide.id)
|
||||
self.on_search_text_button_clicked()
|
||||
|
||||
def on_search_text_button_clicked(self):
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from PySide6 import QtCore, QtWidgets
|
||||
|
||||
|
@ -159,7 +160,11 @@ class ImageMediaItem(FolderLibraryItem):
|
|||
if validate_thumb(file_path, thumbnail_path):
|
||||
icon = build_icon(thumbnail_path)
|
||||
else:
|
||||
icon = create_thumb(file_path, thumbnail_path)
|
||||
size: Union[QtCore.QSize, None] = None
|
||||
slide_height: Union[int, None] = self.settings.value('advanced/slide max height')
|
||||
if slide_height and slide_height > 0:
|
||||
size = QtCore.QSize(-1, slide_height)
|
||||
icon = create_thumb(file_path, thumbnail_path, size=size)
|
||||
tree_item = QtWidgets.QTreeWidgetItem([file_name])
|
||||
tree_item.setData(0, QtCore.Qt.ItemDataRole.UserRole, item)
|
||||
tree_item.setIcon(0, icon)
|
||||
|
|
|
@ -1128,6 +1128,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||
clean_song(self.manager, self.song)
|
||||
self.manager.save_object(self.song)
|
||||
self.media_item.auto_select_id = self.song.id
|
||||
Registry().execute('song_changed', self.song.id)
|
||||
|
||||
def provide_help(self):
|
||||
"""
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from PySide6 import QtWidgets
|
||||
|
||||
from openlp.core.common.i18n import UiStrings, translate
|
||||
from openlp.core.common.i18n import UiStrings
|
||||
from openlp.core.lib.ui import create_button_box
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.core.widgets.edits import SpellTextEdit
|
||||
|
@ -87,8 +87,18 @@ class Ui_EditVerseDialog(object):
|
|||
self.retranslate_ui(edit_verse_dialog)
|
||||
|
||||
def retranslate_ui(self, edit_verse_dialog):
|
||||
edit_verse_dialog.setWindowTitle(translate('SongsPlugin.EditVerseForm', 'Edit Verse'))
|
||||
self.verse_type_label.setText(translate('SongsPlugin.EditVerseForm', '&Verse type:'))
|
||||
VerseType.translated_names = [
|
||||
UiStrings().Verse,
|
||||
UiStrings().Chorus,
|
||||
UiStrings().Bridge,
|
||||
UiStrings().PreChorus,
|
||||
UiStrings().Intro,
|
||||
UiStrings().Ending,
|
||||
UiStrings().Other
|
||||
]
|
||||
VerseType.translated_tags = [name[0].lower() for name in VerseType.translated_names]
|
||||
edit_verse_dialog.setWindowTitle(UiStrings().EditVerse)
|
||||
self.verse_type_label.setText(UiStrings().VerseType)
|
||||
self.verse_type_combo_box.setItemText(VerseType.Verse, VerseType.translated_names[VerseType.Verse])
|
||||
self.verse_type_combo_box.setItemText(VerseType.Chorus, VerseType.translated_names[VerseType.Chorus])
|
||||
self.verse_type_combo_box.setItemText(VerseType.Bridge, VerseType.translated_names[VerseType.Bridge])
|
||||
|
@ -98,12 +108,10 @@ class Ui_EditVerseDialog(object):
|
|||
self.verse_type_combo_box.setItemText(VerseType.Other, VerseType.translated_names[VerseType.Other])
|
||||
self.overflow_split_button.setText(UiStrings().Split)
|
||||
self.overflow_split_button.setToolTip(UiStrings().SplitToolTip)
|
||||
self.forced_split_button.setText(translate('SongsPlugin.EditVerseForm', '&Forced Split'))
|
||||
self.forced_split_button.setToolTip(translate('SongsPlugin.EditVerseForm', 'Split the verse when displayed '
|
||||
'regardless of the screen size.'))
|
||||
self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert'))
|
||||
self.insert_button.setToolTip(translate('SongsPlugin.EditVerseForm',
|
||||
'Split a slide into two by inserting a verse splitter.'))
|
||||
self.transpose_label.setText(translate('SongsPlugin.EditVerseForm', 'Transpose:'))
|
||||
self.transpose_up_button.setText(translate('SongsPlugin.EditVerseForm', 'Up'))
|
||||
self.transpose_down_button.setText(translate('SongsPlugin.EditVerseForm', 'Down'))
|
||||
self.forced_split_button.setText(UiStrings().ForcedSplit)
|
||||
self.forced_split_button.setToolTip(UiStrings().ForcedSplitToolTip)
|
||||
self.insert_button.setText(UiStrings().Insert)
|
||||
self.insert_button.setToolTip(UiStrings().InsertToolTip)
|
||||
self.transpose_label.setText(UiStrings().Transpose)
|
||||
self.transpose_up_button.setText(UiStrings().Up)
|
||||
self.transpose_down_button.setText(UiStrings().Down)
|
||||
|
|
|
@ -144,17 +144,8 @@ class VerseType(object):
|
|||
|
||||
names = ['Verse', 'Chorus', 'Bridge', 'Pre-Chorus', 'Intro', 'Ending', 'Other']
|
||||
tags = [name[0].lower() for name in names]
|
||||
|
||||
translated_names = [
|
||||
translate('SongsPlugin.VerseType', 'Verse'),
|
||||
translate('SongsPlugin.VerseType', 'Chorus'),
|
||||
translate('SongsPlugin.VerseType', 'Bridge'),
|
||||
translate('SongsPlugin.VerseType', 'Pre-Chorus'),
|
||||
translate('SongsPlugin.VerseType', 'Intro'),
|
||||
translate('SongsPlugin.VerseType', 'Ending'),
|
||||
translate('SongsPlugin.VerseType', 'Other')]
|
||||
|
||||
translated_tags = [name[0].lower() for name in translated_names]
|
||||
translated_names = names
|
||||
translated_tags = tags
|
||||
|
||||
@staticmethod
|
||||
def translated_tag(verse_tag, default=Other):
|
||||
|
@ -524,27 +515,30 @@ def strip_rtf(text, default_encoding=None):
|
|||
return text, default_encoding
|
||||
|
||||
|
||||
def delete_song(song_id, song_plugin):
|
||||
def delete_song(song_id, trigger_event=True):
|
||||
"""
|
||||
Deletes a song from the database. Media files associated to the song are removed prior to the deletion of the song.
|
||||
|
||||
:param song_id: The ID of the song to delete.
|
||||
:param song_plugin: The song plugin instance.
|
||||
:param trigger_event: If True the song_deleted event is triggered through the registry
|
||||
"""
|
||||
save_path = ''
|
||||
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
||||
songs_manager = Registry().get('songs_manager')
|
||||
media_files = songs_manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
||||
for media_file in media_files:
|
||||
try:
|
||||
media_file.file_path.unlink()
|
||||
except OSError:
|
||||
log.exception('Could not remove file: {name}'.format(name=media_file.file_path))
|
||||
try:
|
||||
save_path = AppLocation.get_section_data_path(song_plugin.name) / 'audio' / str(song_id)
|
||||
save_path = AppLocation.get_section_data_path('songs') / 'audio' / str(song_id)
|
||||
if save_path.exists():
|
||||
save_path.rmdir()
|
||||
except OSError:
|
||||
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
||||
song_plugin.manager.delete_object(Song, song_id)
|
||||
songs_manager.delete_object(Song, song_id)
|
||||
if trigger_event:
|
||||
Registry().execute('song_deleted', song_id)
|
||||
|
||||
|
||||
def transpose_lyrics(lyrics, transpose_value):
|
||||
|
|
|
@ -166,28 +166,29 @@ class SongFormat(object):
|
|||
EasyWorshipDB = 7
|
||||
EasyWorshipSqliteDB = 8
|
||||
EasyWorshipService = 9
|
||||
FoilPresenter = 10
|
||||
LiveWorship = 11
|
||||
Lyrix = 12
|
||||
MediaShout = 13
|
||||
OpenSong = 14
|
||||
OPSPro = 15
|
||||
PowerPraise = 16
|
||||
PowerSong = 17
|
||||
PresentationManager = 18
|
||||
ProPresenter = 19
|
||||
SingingTheFaith = 20
|
||||
SongBeamer = 21
|
||||
SongPro = 22
|
||||
SongShowPlus = 23
|
||||
SongsOfFellowship = 24
|
||||
SundayPlus = 25
|
||||
VideoPsalm = 26
|
||||
WordsOfWorship = 27
|
||||
WorshipAssistant = 28
|
||||
WorshipCenterPro = 29
|
||||
ZionWorx = 30
|
||||
Datasoul = 31
|
||||
EasyWorshipServiceSqliteDB = 10
|
||||
FoilPresenter = 11
|
||||
LiveWorship = 12
|
||||
Lyrix = 13
|
||||
MediaShout = 14
|
||||
OpenSong = 15
|
||||
OPSPro = 16
|
||||
PowerPraise = 17
|
||||
PowerSong = 18
|
||||
PresentationManager = 19
|
||||
ProPresenter = 20
|
||||
SingingTheFaith = 21
|
||||
SongBeamer = 22
|
||||
SongPro = 23
|
||||
SongShowPlus = 24
|
||||
SongsOfFellowship = 25
|
||||
SundayPlus = 26
|
||||
VideoPsalm = 27
|
||||
WordsOfWorship = 28
|
||||
WorshipAssistant = 29
|
||||
WorshipCenterPro = 30
|
||||
ZionWorx = 31
|
||||
Datasoul = 32
|
||||
|
||||
# Set optional attribute defaults
|
||||
__defaults__ = {
|
||||
|
@ -278,6 +279,14 @@ class SongFormat(object):
|
|||
'filter': '{text} (*.ews)'.format(text=translate('SongsPlugin.ImportWizardForm',
|
||||
'EasyWorship 2007/2009 Service File'))
|
||||
},
|
||||
EasyWorshipServiceSqliteDB: {
|
||||
'class': EasyWorshipSongImport,
|
||||
'name': 'EasyWorship 6/7 Service File',
|
||||
'prefix': 'ew',
|
||||
'selectMode': SongFormatSelect.SingleFile,
|
||||
'filter': '{text} (*.ewsx)'.format(text=translate('SongsPlugin.ImportWizardForm',
|
||||
'EasyWorship 6/7 Service File'))
|
||||
},
|
||||
FoilPresenter: {
|
||||
'class': FoilPresenterImport,
|
||||
'name': 'Foilpresenter',
|
||||
|
@ -487,6 +496,7 @@ class SongFormat(object):
|
|||
SongFormat.EasyWorshipDB,
|
||||
SongFormat.EasyWorshipSqliteDB,
|
||||
SongFormat.EasyWorshipService,
|
||||
SongFormat.EasyWorshipServiceSqliteDB,
|
||||
SongFormat.FoilPresenter,
|
||||
SongFormat.LiveWorship,
|
||||
SongFormat.Lyrix,
|
||||
|
|
|
@ -28,6 +28,8 @@ import sqlite3
|
|||
import struct
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from zipfile import ZipFile
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding, strip_rtf
|
||||
|
@ -83,6 +85,8 @@ class EasyWorshipSongImport(SongImport):
|
|||
self.import_ews()
|
||||
elif ext == '.db':
|
||||
self.import_db()
|
||||
elif ext == '.ewsx':
|
||||
self.import_ewsx()
|
||||
else:
|
||||
self.import_sqlite_db()
|
||||
except Exception:
|
||||
|
@ -346,6 +350,65 @@ class EasyWorshipSongImport(SongImport):
|
|||
db_file.close()
|
||||
self.memo_file.close()
|
||||
|
||||
def import_ewsx(self):
|
||||
"""
|
||||
Imports songs from an EasyWorship 6/7 service file, which is just a zip file with an Sqlite DB with text
|
||||
resources. Non-text recources is also in the zip file, but is ignored.
|
||||
"""
|
||||
invalid_ewsx_msg = translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'This is not a valid Easy Worship 6/7 service file.')
|
||||
# Open ewsx file if it exists
|
||||
if not self.import_source.is_file():
|
||||
log.debug('Given ewsx file does not exists.')
|
||||
return
|
||||
tmp_db_file = NamedTemporaryFile(delete=False)
|
||||
with ZipFile(self.import_source, 'r') as eswx_file:
|
||||
db_zfile = eswx_file.open('main.db')
|
||||
# eswx has bad CRC for the database for some reason (custom CRC?), so skip the CRC
|
||||
db_zfile._expected_crc = None
|
||||
db_data = db_zfile.read()
|
||||
tmp_db_file.write(db_data)
|
||||
tmp_db_file.close()
|
||||
ewsx_conn = sqlite3.connect(tmp_db_file.file.name)
|
||||
if ewsx_conn is None:
|
||||
self.log_error(self.import_source, invalid_ewsx_msg)
|
||||
return
|
||||
ewsx_db = ewsx_conn.cursor()
|
||||
# Take a stab at how text is encoded
|
||||
self.encoding = 'cp1252'
|
||||
self.encoding = retrieve_windows_encoding(self.encoding)
|
||||
if not self.encoding:
|
||||
log.debug('No encoding set.')
|
||||
return
|
||||
# get list of songs in service file, presentation_type=6 means songs
|
||||
songs_exec = ewsx_db.execute('SELECT rowid, title, author, copyright, reference_number '
|
||||
'FROM presentation WHERE presentation_type=6;')
|
||||
songs = songs_exec.fetchall()
|
||||
for song in songs:
|
||||
self.title = title = song[1]
|
||||
self.author = song[2]
|
||||
self.copyright = song[3]
|
||||
self.ccli_number = song[4]
|
||||
# get slides for the song, element_type=6 means songs, element_style_type=4 means song text
|
||||
slides = ewsx_db.execute('SELECT rt.rtf '
|
||||
'FROM element as e '
|
||||
'JOIN slide as s ON e.slide_id = s.rowid '
|
||||
'JOIN resource_text as rt ON rt.resource_id = e.foreground_resource_id '
|
||||
'WHERE e.element_type=6 AND e.element_style_type=4 AND s.presentation_id = ? '
|
||||
'ORDER BY s.order_index;', (song[0],))
|
||||
for slide in slides:
|
||||
if slide:
|
||||
self.set_song_import_object(self.author, slide[0].encode())
|
||||
# save song
|
||||
if not self.finish():
|
||||
self.log_error(self.import_source,
|
||||
translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'"{title}" could not be imported. {entry}').
|
||||
format(title=title, entry=self.entry_error_log))
|
||||
# close database handles
|
||||
ewsx_conn.close()
|
||||
Path(tmp_db_file.file.name).unlink()
|
||||
|
||||
def import_sqlite_db(self):
|
||||
"""
|
||||
Import the songs from an EasyWorship 6 SQLite database
|
||||
|
|
|
@ -546,6 +546,7 @@ class SongMediaItem(MediaManagerItem):
|
|||
new_song.media_files.append(new_media_file)
|
||||
self.plugin.manager.save_object(new_song)
|
||||
new_song.init_on_load()
|
||||
Registry().execute('song_changed', new_song.id)
|
||||
self.on_song_list_load()
|
||||
|
||||
def generate_slide_data(self, service_item, *, item=None, context=ServiceItemContext.Service, **kwargs):
|
||||
|
@ -627,9 +628,11 @@ class SongMediaItem(MediaManagerItem):
|
|||
if State().check_preconditions('media'):
|
||||
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
|
||||
total_length = 0
|
||||
# We could have stored multiple files but only the first one will be played.
|
||||
for m in song.media_files:
|
||||
total_length += self.media_controller.media_length(m.file_path)
|
||||
service_item.background_audio = [(m.file_path, m.file_hash) for m in song.media_files]
|
||||
service_item.background_audio = [(m.file_path, m.file_hash)]
|
||||
break
|
||||
service_item.set_media_length(total_length)
|
||||
service_item.metadata.append('<em>{label}:</em> {media}'.
|
||||
format(label=translate('SongsPlugin.MediaItem', 'Media'),
|
||||
|
|
|
@ -229,7 +229,7 @@ class OpenLyrics(object):
|
|||
self.manager = manager
|
||||
FormattingTags.load_tags()
|
||||
|
||||
def song_to_xml(self, song):
|
||||
def song_to_xml(self, song, version=None):
|
||||
"""
|
||||
Convert the song to OpenLyrics Format.
|
||||
"""
|
||||
|
@ -258,6 +258,9 @@ class OpenLyrics(object):
|
|||
'verseOrder', properties, song.verse_order.lower())
|
||||
if song.ccli_number:
|
||||
self._add_text_to_element('ccliNo', properties, song.ccli_number)
|
||||
# Add a custom version
|
||||
if version:
|
||||
self._add_text_to_element('version', properties, version)
|
||||
if song.authors_songs:
|
||||
authors = etree.SubElement(properties, 'authors')
|
||||
for author_song in song.authors_songs:
|
||||
|
@ -376,7 +379,7 @@ class OpenLyrics(object):
|
|||
end_tags.reverse()
|
||||
return ''.join(start_tags), ''.join(end_tags)
|
||||
|
||||
def xml_to_song(self, xml, parse_and_temporary_save=False):
|
||||
def xml_to_song(self, xml, parse_and_temporary_save=False, update_song_id=None):
|
||||
"""
|
||||
Create and save a song from OpenLyrics format xml to the database. Since we also export XML from external
|
||||
sources (e. g. OpenLyrics import), we cannot ensure, that it completely conforms to the OpenLyrics standard.
|
||||
|
@ -398,7 +401,10 @@ class OpenLyrics(object):
|
|||
# Formatting tags are new in OpenLyrics 0.8
|
||||
if float(song_xml.get('version')) > 0.7:
|
||||
self._process_formatting_tags(song_xml, parse_and_temporary_save)
|
||||
song = Song()
|
||||
if update_song_id:
|
||||
song = self.manager.get_object(Song, update_song_id)
|
||||
else:
|
||||
song = Song()
|
||||
# Values will be set when cleaning the song.
|
||||
song.search_lyrics = ''
|
||||
song.verse_order = ''
|
||||
|
|
|
@ -121,6 +121,7 @@ class SongsPlugin(Plugin):
|
|||
"""
|
||||
super(SongsPlugin, self).__init__('songs', SongMediaItem, SongsTab)
|
||||
self.manager = DBManager('songs', init_schema, upgrade_mod=upgrade)
|
||||
Registry().register('songs_manager', self.manager)
|
||||
self.weight = -10
|
||||
self.icon_path = UiIcons().music
|
||||
self.icon = build_icon(self.icon_path)
|
||||
|
|
|
@ -69,6 +69,12 @@ def test_shortcuts(flask_client: FlaskClient, settings: Settings):
|
|||
assert res.get_json()[0]['shortcut'] == shortcut
|
||||
|
||||
|
||||
def test_language(flask_client: FlaskClient, settings: Settings):
|
||||
res = flask_client.get('/api/v2/core/language')
|
||||
assert res.status_code == 200
|
||||
assert res.get_json()['language']
|
||||
|
||||
|
||||
def test_poll_backend(settings: Settings):
|
||||
"""
|
||||
Test the raw poll function returns the correct JSON
|
||||
|
|
|
@ -428,12 +428,26 @@ def test_sha256_file_hash_no_exist():
|
|||
|
||||
def test_sha256_file_hash_permission_error():
|
||||
"""
|
||||
Test SHA256 file hash when there is a permission error
|
||||
Test that SHA256 file hash re-raises a permission error
|
||||
"""
|
||||
# GIVEN: A mocked Path object
|
||||
mocked_path = MagicMock()
|
||||
mocked_path.open.side_effect = PermissionError
|
||||
|
||||
# WHEN: Generating a hash for the file
|
||||
# THEN: The PermissionError should be bubbled up
|
||||
with pytest.raises(PermissionError):
|
||||
sha256_file_hash(mocked_path)
|
||||
|
||||
|
||||
def test_sha256_file_hash_other_error():
|
||||
"""
|
||||
Test SHA256 file hash when there is an error other than permission error
|
||||
"""
|
||||
# GIVEN: A mocked Path object
|
||||
mocked_path = MagicMock()
|
||||
mocked_path.open.side_effect = NotADirectoryError
|
||||
|
||||
# WHEN: Generating a hash for the file
|
||||
result = sha256_file_hash(mocked_path)
|
||||
|
||||
|
|
|
@ -30,7 +30,9 @@ from unittest.mock import MagicMock, patch
|
|||
from PySide6 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import DataType, build_icon, check_item_selected, create_separated_list, create_thumb, \
|
||||
get_text_file_string, image_to_byte, read_or_fail, read_int, resize_image, seek_or_fail, str_to_bool, validate_thumb
|
||||
get_text_file_string, image_to_byte, read_or_fail, read_int, resize_image, seek_or_fail, str_to_bool, \
|
||||
validate_thumb
|
||||
from openlp.core.common.registry import Registry
|
||||
from tests.utils.constants import RESOURCE_PATH
|
||||
|
||||
|
||||
|
@ -275,7 +277,7 @@ def test_image_to_byte_base_64():
|
|||
assert 'byte_array base64ified' == result, 'The result should be the return value of the mocked base64 method'
|
||||
|
||||
|
||||
def test_create_thumb_with_size(registry):
|
||||
def test_create_thumb_with_size(registry: Registry):
|
||||
"""
|
||||
Test the create_thumb() function with a given size.
|
||||
"""
|
||||
|
@ -310,7 +312,7 @@ def test_create_thumb_with_size(registry):
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_no_size(registry):
|
||||
def test_create_thumb_no_size(registry: Registry):
|
||||
"""
|
||||
Test the create_thumb() function with no size specified.
|
||||
"""
|
||||
|
@ -345,7 +347,7 @@ def test_create_thumb_no_size(registry):
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_invalid_size(registry):
|
||||
def test_create_thumb_invalid_size(registry: Registry):
|
||||
"""
|
||||
Test the create_thumb() function with invalid size specified.
|
||||
"""
|
||||
|
@ -381,7 +383,7 @@ def test_create_thumb_invalid_size(registry):
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_width_only(registry):
|
||||
def test_create_thumb_width_only(registry: Registry):
|
||||
"""
|
||||
Test the create_thumb() function with a size of only width specified.
|
||||
"""
|
||||
|
@ -417,7 +419,7 @@ def test_create_thumb_width_only(registry):
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_height_only(registry):
|
||||
def test_create_thumb_height_only(registry: Registry):
|
||||
"""
|
||||
Test the create_thumb() function with a size of only height specified.
|
||||
"""
|
||||
|
@ -453,7 +455,7 @@ def test_create_thumb_height_only(registry):
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_empty_img(registry):
|
||||
def test_create_thumb_empty_img(registry: Registry):
|
||||
"""
|
||||
Test the create_thumb() function with a size of only height specified.
|
||||
"""
|
||||
|
@ -504,7 +506,7 @@ def test_create_thumb_empty_img(registry):
|
|||
|
||||
@patch('openlp.core.lib.QtGui.QImageReader')
|
||||
@patch('openlp.core.lib.build_icon')
|
||||
def test_create_thumb_path_fails(mocked_build_icon, MockQImageReader, registry):
|
||||
def test_create_thumb_path_fails(mocked_build_icon: MagicMock, MockQImageReader: MagicMock, registry: Registry):
|
||||
"""
|
||||
Test that build_icon() is run against the image_path when the thumbnail fails to be created
|
||||
"""
|
||||
|
@ -539,7 +541,7 @@ def test_check_item_selected_true():
|
|||
assert result is True, 'The result should be True'
|
||||
|
||||
|
||||
def test_check_item_selected_false(registry):
|
||||
def test_check_item_selected_false(registry: Registry):
|
||||
"""
|
||||
Test that the check_item_selected() function returns False when there are no selected indexes.
|
||||
"""
|
||||
|
@ -610,7 +612,7 @@ def test_validate_thumb_file_exists_and_older():
|
|||
assert result is False, 'The result should be False'
|
||||
|
||||
|
||||
def test_resize_thumb(registry):
|
||||
def test_resize_thumb(registry: Registry):
|
||||
"""
|
||||
Test the resize_thumb() function
|
||||
"""
|
||||
|
@ -632,7 +634,7 @@ def test_resize_thumb(registry):
|
|||
assert image.pixel(0, 0) == wanted_background_rgb, 'The background should be white.'
|
||||
|
||||
|
||||
def test_resize_thumb_ignoring_aspect_ratio(registry):
|
||||
def test_resize_thumb_ignoring_aspect_ratio(registry: Registry):
|
||||
"""
|
||||
Test the resize_thumb() function ignoring aspect ratio
|
||||
"""
|
||||
|
@ -654,7 +656,7 @@ def test_resize_thumb_ignoring_aspect_ratio(registry):
|
|||
assert image.pixel(0, 0) == wanted_background_rgb, 'The background should be white.'
|
||||
|
||||
|
||||
def test_resize_thumb_width_aspect_ratio(registry):
|
||||
def test_resize_thumb_width_aspect_ratio(registry: Registry):
|
||||
"""
|
||||
Test the resize_thumb() function using the image's width as the reference
|
||||
"""
|
||||
|
@ -672,7 +674,7 @@ def test_resize_thumb_width_aspect_ratio(registry):
|
|||
assert wanted_width == result_size.width(), 'The image should have the requested width.'
|
||||
|
||||
|
||||
def test_resize_thumb_same_aspect_ratio(registry):
|
||||
def test_resize_thumb_same_aspect_ratio(registry: Registry):
|
||||
"""
|
||||
Test the resize_thumb() function when the image and the wanted aspect ratio are the same
|
||||
"""
|
||||
|
@ -691,7 +693,7 @@ def test_resize_thumb_same_aspect_ratio(registry):
|
|||
|
||||
|
||||
@patch('openlp.core.lib.QtCore.QLocale.createSeparatedList')
|
||||
def test_create_separated_list_qlocate(mocked_createSeparatedList):
|
||||
def test_create_separated_list_qlocate(mocked_createSeparatedList: MagicMock):
|
||||
"""
|
||||
Test the create_separated_list function using the Qt provided method
|
||||
"""
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
"""
|
||||
Package to test the openlp.core.version package.
|
||||
"""
|
||||
import sys
|
||||
from datetime import date
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import ConnectionError
|
||||
|
||||
from openlp.core.version import VersionWorker, check_for_update, get_version, update_check_date
|
||||
|
@ -251,15 +251,25 @@ def test_check_for_update_skipped(mocked_run_thread, mock_settings):
|
|||
assert mocked_run_thread.call_count == 0
|
||||
|
||||
|
||||
def test_get_version_dev_version():
|
||||
@pytest.mark.parametrize('in_version, out_version', [
|
||||
('3.1.1', {'full': '3.1.1', 'version': '3.1.1', 'build': None}),
|
||||
('3.0.2+git.cb1db9f43', {'full': '3.0.2+git.cb1db9f43', 'version': '3.0.2', 'build': 'git.cb1db9f43'}),
|
||||
('3.1.2.dev15+gff6b05ed3', {'full': '3.1.2.dev15+gff6b05ed3', 'version': '3.1.2', 'build': 'gff6b05ed3'})
|
||||
])
|
||||
@patch('openlp.core.version.AppLocation.get_directory')
|
||||
def test_get_version(mocked_get_directory: MagicMock, in_version: str, out_version: dict):
|
||||
"""
|
||||
Test the get_version() function
|
||||
"""
|
||||
# GIVEN: We're in dev mode
|
||||
with patch.object(sys, 'argv', ['--dev-version']), \
|
||||
patch('openlp.core.version.APPLICATION_VERSION', None):
|
||||
# WHEN: get_version() is run
|
||||
# GIVEN: Some mocks and predefined versions
|
||||
mocked_path = MagicMock()
|
||||
mocked_path.__truediv__.return_value = mocked_path
|
||||
mocked_path.read_text.return_value = in_version
|
||||
mocked_get_directory.return_value = mocked_path
|
||||
|
||||
# WHEN: get_version() is run
|
||||
with patch('openlp.core.version.APPLICATION_VERSION', None):
|
||||
version = get_version()
|
||||
|
||||
# THEN: version is something
|
||||
assert version
|
||||
assert version == out_version
|
||||
|
|
|
@ -22,17 +22,20 @@
|
|||
This module contains tests for the lib submodule of the Images plugin.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from PySide6 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.enum import ImageThemeMode
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.db.manager import DBManager
|
||||
from openlp.core.lib import build_icon, create_thumb
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities
|
||||
from openlp.core.widgets.views import TreeWidgetWithDnD
|
||||
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
|
||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -258,3 +261,64 @@ def test_generate_thumbnail_path_filename(media_item):
|
|||
|
||||
# THEN: The path should be correct
|
||||
assert result == Path('.') / 'myimage.jpg'
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.create_thumb')
|
||||
def test_load_item_file_not_exist(mocked_create_thumb: MagicMock, media_item: ImageMediaItem):
|
||||
"""Test the load_item method when the file does not exist"""
|
||||
# GIVEN: A media item and an Item to load
|
||||
item = MagicMock(file_path=Path('myimage.jpg'), file_hash=None)
|
||||
|
||||
# WHEN load_item() is called with the Item
|
||||
result = media_item.load_item(item)
|
||||
|
||||
# THEN: A QTreeWidgetItem with a "delete" icon should be returned
|
||||
assert isinstance(result, QtWidgets.QTreeWidgetItem)
|
||||
assert result.text(0) == 'myimage.jpg'
|
||||
mocked_create_thumb.assert_not_called()
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.validate_thumb')
|
||||
@patch('openlp.plugins.images.lib.mediaitem.create_thumb', wraps=create_thumb)
|
||||
@patch('openlp.plugins.images.lib.mediaitem.build_icon', wraps=build_icon)
|
||||
def test_load_item_valid_thumbnail(mocked_build_icon: MagicMock, mocked_create_thumb: MagicMock,
|
||||
mocked_validate_thumb: MagicMock, media_item: ImageMediaItem, registry: Registry):
|
||||
"""Test the load_item method with an existing thumbnail"""
|
||||
# GIVEN: A media item and an Item to load
|
||||
media_item.service_path = Path(TEST_RESOURCES_PATH) / 'images'
|
||||
mocked_validate_thumb.return_value = True
|
||||
image_path = Path(TEST_RESOURCES_PATH) / 'images' / 'tractor.jpg'
|
||||
item = MagicMock(file_path=image_path, file_hash=None)
|
||||
|
||||
# WHEN load_item() is called with the Item
|
||||
result = media_item.load_item(item)
|
||||
|
||||
# THEN: A QTreeWidgetItem with a "delete" icon should be returned
|
||||
assert isinstance(result, QtWidgets.QTreeWidgetItem)
|
||||
assert result.text(0) == 'tractor.jpg'
|
||||
assert result.toolTip(0) == str(image_path)
|
||||
mocked_create_thumb.assert_not_called()
|
||||
mocked_build_icon.assert_called_once_with(image_path)
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.validate_thumb')
|
||||
@patch('openlp.plugins.images.lib.mediaitem.create_thumb', wraps=create_thumb)
|
||||
def test_load_item_missing_thumbnail(mocked_create_thumb: MagicMock, mocked_validate_thumb: MagicMock,
|
||||
media_item: ImageMediaItem, registry: Registry):
|
||||
"""Test the load_item method with no valid thumbnails"""
|
||||
# GIVEN: A media item and an Item to load
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
media_item.service_path = Path(tmpdir)
|
||||
mocked_validate_thumb.return_value = False
|
||||
image_path = Path(TEST_RESOURCES_PATH) / 'images' / 'tractor.jpg'
|
||||
item = MagicMock(file_path=image_path, file_hash=None)
|
||||
registry.get('settings').value.return_value = 400
|
||||
|
||||
# WHEN load_item() is called with the Item
|
||||
result = media_item.load_item(item)
|
||||
|
||||
# THEN: A QTreeWidgetItem with a "delete" icon should be returned
|
||||
assert isinstance(result, QtWidgets.QTreeWidgetItem)
|
||||
assert result.text(0) == 'tractor.jpg'
|
||||
assert result.toolTip(0) == str(image_path)
|
||||
mocked_create_thumb.assert_called_once_with(image_path, Path(tmpdir, 'tractor.jpg'), size=QtCore.QSize(-1, 400))
|
||||
|
|
|
@ -498,6 +498,48 @@ def test_ews_file_import(mocked_retrieve_windows_encoding: MagicMock, MockSongIm
|
|||
mocked_finish.assert_called_with()
|
||||
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.easyworship.SongImport')
|
||||
@patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding')
|
||||
def test_ewsx_file_import(mocked_retrieve_windows_encoding: MagicMock, MockSongImport: MagicMock,
|
||||
registry: Registry, settings: Settings):
|
||||
"""
|
||||
Test the actual import of song from ewsx file and check that the imported data is correct.
|
||||
"""
|
||||
|
||||
# GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard",
|
||||
# and mocked out "author", "add_copyright", "add_verse", "finish" methods.
|
||||
mocked_retrieve_windows_encoding.return_value = 'cp1252'
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
mocked_add_author = MagicMock()
|
||||
mocked_add_verse = MagicMock()
|
||||
mocked_finish = MagicMock()
|
||||
mocked_title = MagicMock()
|
||||
mocked_finish.return_value = True
|
||||
importer = EasyWorshipSongImportLogger(mocked_manager)
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = False
|
||||
importer.add_author = mocked_add_author
|
||||
importer.add_verse = mocked_add_verse
|
||||
importer.title = mocked_title
|
||||
importer.finish = mocked_finish
|
||||
importer.topics = []
|
||||
|
||||
# WHEN: Importing ews file
|
||||
importer.import_source = str(TEST_PATH / 'test1.ewsx')
|
||||
import_result = importer.do_import()
|
||||
|
||||
# THEN: do_import should return none, the song data should be as expected, and finish should have been
|
||||
# called.
|
||||
title = EWS_SONG_TEST_DATA['title']
|
||||
assert import_result is None, 'do_import should return None when it has completed'
|
||||
assert title in importer._title_assignment_list, 'title for should be "%s"' % title
|
||||
mocked_add_author.assert_any_call(EWS_SONG_TEST_DATA['authors'][0])
|
||||
for verse_text, verse_tag in EWS_SONG_TEST_DATA['verses']:
|
||||
mocked_add_verse.assert_any_call(verse_text, verse_tag)
|
||||
mocked_finish.assert_called_with()
|
||||
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.easyworship.SongImport')
|
||||
def test_import_rtf_unescaped_unicode(MockSongImport: MagicMock, registry: Registry, settings: Settings):
|
||||
"""
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 675 KiB |
Binary file not shown.
Loading…
Reference in New Issue