forked from openlp/openlp
trunk
This commit is contained in:
commit
0617ce3dc9
@ -190,31 +190,32 @@ def verify_ip_address(addr):
|
||||
return True if verify_ipv4(addr) else verify_ipv6(addr)
|
||||
|
||||
|
||||
def md5_hash(salt, data):
|
||||
def md5_hash(salt, data=None):
|
||||
"""
|
||||
Returns the hashed output of md5sum on salt,data
|
||||
using Python3 hashlib
|
||||
|
||||
:param salt: Initial salt
|
||||
:param data: Data to hash
|
||||
:param data: OPTIONAL Data to hash
|
||||
:returns: str
|
||||
"""
|
||||
log.debug('md5_hash(salt="%s")' % salt)
|
||||
hash_obj = hashlib.new('md5')
|
||||
hash_obj.update(salt.encode('ascii'))
|
||||
hash_obj.update(data.encode('ascii'))
|
||||
hash_obj.update(salt)
|
||||
if data:
|
||||
hash_obj.update(data)
|
||||
hash_value = hash_obj.hexdigest()
|
||||
log.debug('md5_hash() returning "%s"' % hash_value)
|
||||
return hash_value
|
||||
|
||||
|
||||
def qmd5_hash(salt, data):
|
||||
def qmd5_hash(salt, data=None):
|
||||
"""
|
||||
Returns the hashed output of MD5Sum on salt, data
|
||||
using PyQt4.QCryptographicHash.
|
||||
|
||||
:param salt: Initial salt
|
||||
:param data: Data to hash
|
||||
:param data: OPTIONAL Data to hash
|
||||
:returns: str
|
||||
"""
|
||||
log.debug('qmd5_hash(salt="%s"' % salt)
|
||||
@ -223,7 +224,7 @@ def qmd5_hash(salt, data):
|
||||
hash_obj.addData(data)
|
||||
hash_value = hash_obj.result().toHex()
|
||||
log.debug('qmd5_hash() returning "%s"' % hash_value)
|
||||
return decode(hash_value.data(), 'ascii')
|
||||
return hash_value.data()
|
||||
|
||||
|
||||
def clean_button_text(button_text):
|
||||
|
@ -271,8 +271,8 @@ ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
|
||||
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
|
||||
'The proxy address set with setProxy() was not found'),
|
||||
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
|
||||
'The connection negotiation with the proxy server because the response '
|
||||
'from the proxy server could not be understood'),
|
||||
'The connection negotiation with the proxy server failed because the '
|
||||
'response from the proxy server could not be understood'),
|
||||
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
|
||||
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
|
||||
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
|
||||
|
@ -343,7 +343,7 @@ class PJLink1(QTcpSocket):
|
||||
# Authenticated login with salt
|
||||
log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
|
||||
log.debug('(%s) pin="%s"' % (self.ip, self.pin))
|
||||
salt = qmd5_hash(salt=data_check[2], data=self.pin)
|
||||
salt = qmd5_hash(salt=data_check[2].endcode('ascii'), data=self.pin.encode('ascii'))
|
||||
else:
|
||||
salt = None
|
||||
# We're connected at this point, so go ahead and do regular I/O
|
||||
|
@ -268,9 +268,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.web = 'http://openlp.org/files/frw/'
|
||||
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)
|
||||
self.currentIdChanged.connect(self.on_current_id_changed)
|
||||
Registry().register_function('config_screen_changed', self.update_screen_list_combo)
|
||||
self.no_internet_finish_button.setVisible(False)
|
||||
self.no_internet_cancel_button.setVisible(False)
|
||||
# Check if this is a re-run of the wizard.
|
||||
self.has_run_wizard = Settings().value('core/has run wizard')
|
||||
check_directory_exists(os.path.join(gettempdir(), 'openlp'))
|
||||
@ -327,6 +329,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.next_button.setVisible(False)
|
||||
self.cancel_button.setVisible(False)
|
||||
self.no_internet_finish_button.setVisible(True)
|
||||
if self.has_run_wizard:
|
||||
self.no_internet_cancel_button.setVisible(False)
|
||||
else:
|
||||
self.no_internet_cancel_button.setVisible(True)
|
||||
elif page_id == FirstTimePage.Plugins:
|
||||
self.back_button.setVisible(False)
|
||||
elif page_id == FirstTimePage.Progress:
|
||||
@ -372,6 +378,13 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
Settings().setValue('core/has run wizard', True)
|
||||
self.close()
|
||||
|
||||
def on_no_internet_cancel_button_clicked(self):
|
||||
"""
|
||||
Process the triggering of the "Cancel" button on the No Internet page.
|
||||
"""
|
||||
self.was_cancelled = True
|
||||
self.close()
|
||||
|
||||
def url_get_file(self, url, f_path):
|
||||
""""
|
||||
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
|
||||
|
@ -59,7 +59,8 @@ class UiFirstTimeWizard(object):
|
||||
first_time_wizard.resize(550, 386)
|
||||
first_time_wizard.setModal(True)
|
||||
first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage |
|
||||
QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1)
|
||||
QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1 |
|
||||
QtGui.QWizard.HaveCustomButton2)
|
||||
if is_macosx():
|
||||
first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap,
|
||||
QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
|
||||
@ -69,6 +70,7 @@ class UiFirstTimeWizard(object):
|
||||
self.finish_button = self.button(QtGui.QWizard.FinishButton)
|
||||
self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1)
|
||||
self.cancel_button = self.button(QtGui.QWizard.CancelButton)
|
||||
self.no_internet_cancel_button = self.button(QtGui.QWizard.CustomButton2)
|
||||
self.next_button = self.button(QtGui.QWizard.NextButton)
|
||||
self.back_button = self.button(QtGui.QWizard.BackButton)
|
||||
add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp')
|
||||
@ -271,3 +273,4 @@ class UiFirstTimeWizard(object):
|
||||
'and OpenLP is configured.'))
|
||||
self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
|
||||
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))
|
||||
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton2, translate('OpenLP.FirstTimeWizard', 'Cancel'))
|
||||
|
@ -989,15 +989,21 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
# Read the temp file and output the user's CONF file with blanks to
|
||||
# make it more readable.
|
||||
temp_conf = open(temp_file, 'r')
|
||||
export_conf = open(export_file_name, 'w')
|
||||
for file_record in temp_conf:
|
||||
# Get rid of any invalid entries.
|
||||
if file_record.find('@Invalid()') == -1:
|
||||
file_record = file_record.replace('%20', ' ')
|
||||
export_conf.write(file_record)
|
||||
temp_conf.close()
|
||||
export_conf.close()
|
||||
os.remove(temp_file)
|
||||
try:
|
||||
export_conf = open(export_file_name, 'w')
|
||||
for file_record in temp_conf:
|
||||
# Get rid of any invalid entries.
|
||||
if file_record.find('@Invalid()') == -1:
|
||||
file_record = file_record.replace('%20', ' ')
|
||||
export_conf.write(file_record)
|
||||
temp_conf.close()
|
||||
export_conf.close()
|
||||
os.remove(temp_file)
|
||||
except OSError as ose:
|
||||
QtGui.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
|
||||
translate('OpenLP.MainWindow', 'An error occurred while exporting the '
|
||||
'settings: %s') % ose.strerror,
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||
|
||||
def on_mode_default_item_clicked(self):
|
||||
"""
|
||||
|
@ -601,6 +601,12 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
|
||||
shutil.copy(temp_file_name, path_file_name)
|
||||
except shutil.Error:
|
||||
return self.save_file_as()
|
||||
except OSError as ose:
|
||||
QtGui.QMessageBox.critical(self, translate('OpenLP.ServiceManager', 'Error Saving File'),
|
||||
translate('OpenLP.ServiceManager', 'An error occurred while writing the '
|
||||
'service file: %s') % ose.strerror,
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||
success = False
|
||||
self.main_window.add_recent_file(path_file_name)
|
||||
self.set_modified(False)
|
||||
delete_file(temp_file_name)
|
||||
|
@ -377,17 +377,11 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
||||
self.application.set_busy_cursor()
|
||||
if path:
|
||||
Settings().setValue(self.settings_section + '/last directory export', path)
|
||||
try:
|
||||
self._export_theme(path, theme)
|
||||
if self._export_theme(path, theme):
|
||||
QtGui.QMessageBox.information(self,
|
||||
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
||||
translate('OpenLP.ThemeManager',
|
||||
'Your theme has been successfully exported.'))
|
||||
except (IOError, OSError):
|
||||
self.log_exception('Export Theme Failed')
|
||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
||||
translate('OpenLP.ThemeManager',
|
||||
'Your theme could not be exported due to an error.'))
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def _export_theme(self, path, theme):
|
||||
@ -397,19 +391,24 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
||||
:param theme: The name of the theme to be exported
|
||||
"""
|
||||
theme_path = os.path.join(path, theme + '.otz')
|
||||
theme_zip = None
|
||||
try:
|
||||
theme_zip = zipfile.ZipFile(theme_path, 'w')
|
||||
source = os.path.join(self.path, theme)
|
||||
for files in os.walk(source):
|
||||
for name in files[2]:
|
||||
theme_zip.write(os.path.join(source, name), os.path.join(theme, name))
|
||||
except (IOError, OSError):
|
||||
theme_zip.close()
|
||||
return True
|
||||
except OSError as ose:
|
||||
self.log_exception('Export Theme Failed')
|
||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
||||
translate('OpenLP.ThemeManager', 'The theme export failed because this error '
|
||||
'occurred: %s') % ose.strerror)
|
||||
if theme_zip:
|
||||
theme_zip.close()
|
||||
shutil.rmtree(theme_path, True)
|
||||
raise
|
||||
else:
|
||||
theme_zip.close()
|
||||
return False
|
||||
|
||||
def on_import_theme(self, field=None):
|
||||
"""
|
||||
|
@ -371,7 +371,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
||||
from_chapter = from_verse
|
||||
from_verse = None
|
||||
if to_chapter:
|
||||
if to_chapter < from_chapter:
|
||||
if from_chapter and to_chapter < from_chapter:
|
||||
continue
|
||||
else:
|
||||
chapter = to_chapter
|
||||
@ -387,7 +387,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
||||
from_verse = 1
|
||||
if not to_verse:
|
||||
to_verse = -1
|
||||
if to_chapter > from_chapter:
|
||||
if to_chapter and to_chapter > from_chapter:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, -1))
|
||||
for i in range(from_chapter + 1, to_chapter):
|
||||
ref_list.append((book_ref_id, i, 1, -1))
|
||||
|
@ -365,31 +365,20 @@ class CWExtract(RegistryProperties):
|
||||
if not soup:
|
||||
return None
|
||||
self.application.process_events()
|
||||
html_verses = soup.find_all('span', 'versetext')
|
||||
if not html_verses:
|
||||
verses_div = soup.find_all('div', 'verse')
|
||||
if not verses_div:
|
||||
log.error('No verses found in the CrossWalk response.')
|
||||
send_error_message('parse')
|
||||
return None
|
||||
verses = {}
|
||||
for verse in html_verses:
|
||||
for verse in verses_div:
|
||||
self.application.process_events()
|
||||
verse_number = int(verse.contents[0].contents[0])
|
||||
verse_text = ''
|
||||
for part in verse.contents:
|
||||
self.application.process_events()
|
||||
if isinstance(part, NavigableString):
|
||||
verse_text += part
|
||||
elif part and part.attrMap and \
|
||||
(part.attrMap['class'] == 'WordsOfChrist' or part.attrMap['class'] == 'strongs'):
|
||||
for subpart in part.contents:
|
||||
self.application.process_events()
|
||||
if isinstance(subpart, NavigableString):
|
||||
verse_text += subpart
|
||||
elif subpart and subpart.attrMap and subpart.attrMap['class'] == 'strongs':
|
||||
for subsub in subpart.contents:
|
||||
self.application.process_events()
|
||||
if isinstance(subsub, NavigableString):
|
||||
verse_text += subsub
|
||||
verse_number = int(verse.find('strong').contents[0])
|
||||
verse_span = verse.find('span')
|
||||
tags_to_remove = verse_span.find_all(['a', 'sup'])
|
||||
for tag in tags_to_remove:
|
||||
tag.decompose()
|
||||
verse_text = verse_span.get_text()
|
||||
self.application.process_events()
|
||||
# Fix up leading and trailing spaces, multiple spaces, and spaces between text and , and .
|
||||
verse_text = verse_text.strip('\n\r\t ')
|
||||
@ -409,16 +398,13 @@ class CWExtract(RegistryProperties):
|
||||
soup = get_soup_for_bible_ref(chapter_url)
|
||||
if not soup:
|
||||
return None
|
||||
content = soup.find('div', {'class': 'Body'})
|
||||
content = content.find('ul', {'class': 'parent'})
|
||||
content = soup.find_all(('h4', {'class': 'small-header'}))
|
||||
if not content:
|
||||
log.error('No books found in the Crosswalk response.')
|
||||
send_error_message('parse')
|
||||
return None
|
||||
content = content.find_all('li')
|
||||
books = []
|
||||
for book in content:
|
||||
book = book.find('a')
|
||||
books.append(book.contents[0])
|
||||
return books
|
||||
|
||||
|
@ -58,7 +58,7 @@ class OSISBible(BibleDB):
|
||||
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
|
||||
# detection, and the two mechanisms together interfere with each other.
|
||||
import_file = open(self.filename, 'rb')
|
||||
osis_bible_tree = etree.parse(import_file)
|
||||
osis_bible_tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
|
||||
namespace = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
|
||||
# Find bible language
|
||||
language_id = None
|
||||
|
@ -57,7 +57,7 @@ class ZefaniaBible(BibleDB):
|
||||
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
|
||||
# detection, and the two mechanisms together interfere with each other.
|
||||
import_file = open(self.filename, 'rb')
|
||||
zefania_bible_tree = etree.parse(import_file)
|
||||
zefania_bible_tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
|
||||
# Find bible language
|
||||
language_id = None
|
||||
language = zefania_bible_tree.xpath("/XMLBIBLE/INFORMATION/language/text()")
|
||||
@ -76,9 +76,19 @@ class ZefaniaBible(BibleDB):
|
||||
etree.strip_elements(zefania_bible_tree, ('PROLOG', 'REMARK', 'CAPTION', 'MEDIA'), with_tail=False)
|
||||
xmlbible = zefania_bible_tree.getroot()
|
||||
for BIBLEBOOK in xmlbible:
|
||||
book_ref_id = self.get_book_ref_id_by_name(str(BIBLEBOOK.get('bname')), num_books)
|
||||
if not book_ref_id:
|
||||
book_ref_id = self.get_book_ref_id_by_localised_name(str(BIBLEBOOK.get('bname')))
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
bname = BIBLEBOOK.get('bname')
|
||||
bnumber = BIBLEBOOK.get('bnumber')
|
||||
if not bname and not bnumber:
|
||||
continue
|
||||
if bname:
|
||||
book_ref_id = self.get_book_ref_id_by_name(bname, num_books)
|
||||
if not book_ref_id:
|
||||
book_ref_id = self.get_book_ref_id_by_localised_name(bname)
|
||||
else:
|
||||
log.debug('Could not find a name, will use number, basically a guess.')
|
||||
book_ref_id = int(bnumber)
|
||||
if not book_ref_id:
|
||||
log.error('Importing books from "%s" failed' % self.filename)
|
||||
return False
|
||||
@ -94,7 +104,7 @@ class ZefaniaBible(BibleDB):
|
||||
self.wizard.increment_progress_bar(
|
||||
translate('BiblesPlugin.Zefnia', 'Importing %(bookname)s %(chapter)s...' %
|
||||
{'bookname': db_book.name, 'chapter': chapter_number}))
|
||||
self.session.commit()
|
||||
self.session.commit()
|
||||
self.application.process_events()
|
||||
except Exception as e:
|
||||
critical_error_message_box(
|
||||
|
@ -225,10 +225,10 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
self.clean_up_thumbnails(filepath)
|
||||
self.main_window.increment_progress_bar()
|
||||
self.main_window.finished_progress_bar()
|
||||
self.application.set_busy_cursor()
|
||||
for row in row_list:
|
||||
self.list_view.takeItem(row)
|
||||
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def clean_up_thumbnails(self, filepath):
|
||||
"""
|
||||
|
@ -134,7 +134,7 @@ class PresentationDocument(object):
|
||||
"""
|
||||
# TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
||||
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
||||
folder = md5_hash('', self.file_path)
|
||||
folder = md5_hash(self.file_path.encode('utf-8'))
|
||||
else:
|
||||
folder = self.get_file_name()
|
||||
return os.path.join(self.controller.thumbnail_folder, folder)
|
||||
@ -145,7 +145,7 @@ class PresentationDocument(object):
|
||||
"""
|
||||
# TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
||||
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
||||
folder = md5_hash('', self.file_path)
|
||||
folder = md5_hash(self.file_path.encode('utf-8'))
|
||||
else:
|
||||
folder = folder = self.get_file_name()
|
||||
return os.path.join(self.controller.temp_folder, folder)
|
||||
|
@ -244,12 +244,16 @@ class SongExportForm(OpenLPWizard):
|
||||
for song in self._find_list_widget_items(self.selected_list_widget)
|
||||
]
|
||||
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
|
||||
if exporter.do_export():
|
||||
self.progress_label.setText(
|
||||
translate('SongsPlugin.SongExportForm',
|
||||
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
|
||||
else:
|
||||
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.'))
|
||||
try:
|
||||
if exporter.do_export():
|
||||
self.progress_label.setText(
|
||||
translate('SongsPlugin.SongExportForm',
|
||||
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
|
||||
else:
|
||||
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.'))
|
||||
except OSError as ose:
|
||||
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed because this '
|
||||
'error occurred: %s') % ose.strerror)
|
||||
|
||||
def _find_list_widget_items(self, list_widget, text=''):
|
||||
"""
|
||||
|
@ -25,7 +25,9 @@ Presentationmanager song files into the current database.
|
||||
"""
|
||||
|
||||
import os
|
||||
from lxml import objectify
|
||||
import re
|
||||
import chardet
|
||||
from lxml import objectify, etree
|
||||
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from .songimport import SongImport
|
||||
@ -42,7 +44,18 @@ class PresentationManagerImport(SongImport):
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
|
||||
root = objectify.parse(open(file_path, 'rb')).getroot()
|
||||
try:
|
||||
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
|
||||
except etree.XMLSyntaxError:
|
||||
# Try to detect encoding and use it
|
||||
file = open(file_path, mode='rb')
|
||||
encoding = chardet.detect(file.read())['encoding']
|
||||
file.close()
|
||||
# Open file with detected encoding and remove encoding declaration
|
||||
text = open(file_path, mode='r', encoding=encoding).read()
|
||||
text = re.sub('.+\?>\n', '', text)
|
||||
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
|
||||
root = objectify.fromstring(etree.tostring(tree))
|
||||
self.process_song(root)
|
||||
|
||||
def process_song(self, root):
|
||||
|
@ -27,6 +27,7 @@ from PyQt4 import QtGui
|
||||
from sqlalchemy.sql import and_
|
||||
|
||||
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.plugins.songusage.lib.db import SongUsageItem
|
||||
from .songusagedetaildialog import Ui_SongUsageDetailDialog
|
||||
|
||||
@ -104,8 +105,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog, RegistryPrope
|
||||
translate('SongUsagePlugin.SongUsageDetailForm',
|
||||
'Report \n%s \nhas been successfully created. ') % report_file_name
|
||||
)
|
||||
except IOError:
|
||||
except OSError as ose:
|
||||
log.exception('Failed to write out song usage records')
|
||||
critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
|
||||
translate('SongUsagePlugin.SongUsageDetailForm',
|
||||
'An error occurred while creating the report: %s') % ose.strerror)
|
||||
finally:
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
|
@ -83,6 +83,7 @@
|
||||
<file>openlp-logo-64x64.png</file>
|
||||
<file>openlp-logo-128x128.png</file>
|
||||
<file>openlp-logo-256x256.png</file>
|
||||
<file>openlp-logo.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="graphics">
|
||||
<file>exception.png</file>
|
||||
|
@ -23,6 +23,8 @@
|
||||
Package to test the openlp.core.ui.projector.networkutils package.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
|
||||
@ -30,6 +32,8 @@ from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
|
||||
salt = '498e4a67'
|
||||
pin = 'JBMIAProjectorLink'
|
||||
test_hash = '5d8409bc1c3fa39749434aa3a5c38682'
|
||||
test_non_ascii_string = '이것은 한국어 시험 문자열'
|
||||
test_non_ascii_hash = 'fc00c7912976f6e9c19099b514ced201'
|
||||
|
||||
ip4_loopback = '127.0.0.1'
|
||||
ip4_local = '192.168.1.1'
|
||||
@ -120,7 +124,7 @@ class testProjectorUtilities(TestCase):
|
||||
Test MD5 hash from salt+data pass (python)
|
||||
"""
|
||||
# WHEN: Given a known salt+data
|
||||
hash_ = md5_hash(salt=salt, data=pin)
|
||||
hash_ = md5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii'))
|
||||
|
||||
# THEN: Validate return has is same
|
||||
self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash')
|
||||
@ -130,7 +134,7 @@ class testProjectorUtilities(TestCase):
|
||||
Test MD5 hash from salt+data fail (python)
|
||||
"""
|
||||
# WHEN: Given a different salt+hash
|
||||
hash_ = md5_hash(salt=pin, data=salt)
|
||||
hash_ = md5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii'))
|
||||
|
||||
# THEN: return data is different
|
||||
self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash')
|
||||
@ -140,17 +144,37 @@ class testProjectorUtilities(TestCase):
|
||||
Test MD5 hash from salt+data pass (Qt)
|
||||
"""
|
||||
# WHEN: Given a known salt+data
|
||||
hash_ = qmd5_hash(salt=salt, data=pin)
|
||||
hash_ = qmd5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii'))
|
||||
|
||||
# THEN: Validate return has is same
|
||||
self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash')
|
||||
self.assertEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a good hash')
|
||||
|
||||
def test_qmd5_hash_bad(self):
|
||||
"""
|
||||
Test MD5 hash from salt+hash fail (Qt)
|
||||
"""
|
||||
# WHEN: Given a different salt+hash
|
||||
hash_ = qmd5_hash(salt=pin, data=salt)
|
||||
hash_ = qmd5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii'))
|
||||
|
||||
# THEN: return data is different
|
||||
self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash')
|
||||
self.assertNotEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a bad hash')
|
||||
|
||||
def test_md5_non_ascii_string(self):
|
||||
"""
|
||||
Test MD5 hash with non-ascii string - bug 1417809
|
||||
"""
|
||||
# WHEN: Non-ascii string is hashed
|
||||
hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None)
|
||||
|
||||
# THEN: Valid MD5 hash should be returned
|
||||
self.assertEqual(hash_, test_non_ascii_hash, 'MD5 should have returned a valid hash')
|
||||
|
||||
def test_qmd5_non_ascii_string(self):
|
||||
"""
|
||||
Test MD5 hash with non-ascii string - bug 1417809
|
||||
"""
|
||||
# WHEN: Non-ascii string is hashed
|
||||
hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None)
|
||||
|
||||
# THEN: Valid MD5 hash should be returned
|
||||
self.assertEqual(hash_, test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash')
|
||||
|
@ -77,7 +77,6 @@ class TestZefaniaImport(TestCase):
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
|
||||
importer.wizard = mocked_import_wizard
|
||||
importer.get_book_ref_id_by_name = MagicMock()
|
||||
importer.create_verse = MagicMock()
|
||||
importer.create_book = MagicMock()
|
||||
importer.session = MagicMock()
|
||||
@ -92,3 +91,34 @@ class TestZefaniaImport(TestCase):
|
||||
self.assertTrue(importer.create_verse.called)
|
||||
for verse_tag, verse_text in test_data['verses']:
|
||||
importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
|
||||
importer.create_book.assert_any_call('Genesis', 1, 1)
|
||||
|
||||
def file_import_no_book_name_test(self):
|
||||
"""
|
||||
Test the import of Zefania Bible file without book names
|
||||
"""
|
||||
# GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
|
||||
# get_book_ref_id_by_name, create_verse, create_book, session and get_language.
|
||||
result_file = open(os.path.join(TEST_PATH, 'rst.json'), 'rb')
|
||||
test_data = json.loads(result_file.read().decode())
|
||||
bible_file = 'zefania-rst.xml'
|
||||
with patch('openlp.plugins.bibles.lib.zefania.ZefaniaBible.application'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
|
||||
importer.wizard = mocked_import_wizard
|
||||
importer.create_verse = MagicMock()
|
||||
importer.create_book = MagicMock()
|
||||
importer.session = MagicMock()
|
||||
importer.get_language = MagicMock()
|
||||
importer.get_language.return_value = 'Russian'
|
||||
|
||||
# WHEN: Importing bible file
|
||||
importer.filename = os.path.join(TEST_PATH, bible_file)
|
||||
importer.do_import()
|
||||
|
||||
# THEN: The create_verse() method should have been called with each verse in the file.
|
||||
self.assertTrue(importer.create_verse.called)
|
||||
for verse_tag, verse_text in test_data['verses']:
|
||||
importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
|
||||
importer.create_book.assert_any_call('Exodus', 2, 1)
|
||||
|
@ -46,3 +46,5 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Agnus Dei.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Agnus Dei.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -506,7 +506,6 @@ class TestSongSelectFileImport(TestCase, TestMixin):
|
||||
|
||||
# WHEN: We call the song importer
|
||||
song_import.do_import()
|
||||
print(song_import.verses)
|
||||
# THEN: Song values should be equal to test values in setUp
|
||||
self.assertEquals(song_import.title, self.title, 'Song title should match')
|
||||
self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match')
|
||||
|
@ -109,3 +109,13 @@ class TestBibleManager(TestCase, TestMixin):
|
||||
results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock())
|
||||
# THEN a verse array should be returned
|
||||
self.assertEqual(False, results, "The bible Search should return False")
|
||||
|
||||
def parse_reference_five_test(self):
|
||||
"""
|
||||
Test the parse_reference method with 1 Timothy 1:3-end
|
||||
"""
|
||||
# GIVEN given a bible in the bible manager
|
||||
# WHEN asking to parse the bible reference
|
||||
results = parse_reference('1 Timothy 1:3-end', self.manager.db_cache['tests'], MagicMock(), 54)
|
||||
# THEN a verse array should be returned
|
||||
self.assertEqual([(54, 1, 3, -1)], results, "The bible verses should matches the expected results")
|
||||
|
16
tests/resources/bibles/rst.json
Normal file
16
tests/resources/bibles/rst.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"book": "Exodus",
|
||||
"chapter": 1,
|
||||
"verses": [
|
||||
[ "1", "Вот имена сынов Израилевых, которые вошли в Египет с Иаковом, вошли каждый с домом своим:" ],
|
||||
[ "2", "Рувим, Симеон, Левий и Иуда," ],
|
||||
[ "3", "Иссахар, Завулон и Вениамин," ],
|
||||
[ "4", "Дан и Неффалим, Гад и Асир." ],
|
||||
[ "5", "Всех же душ, происшедших от чресл Иакова, было семьдесят, а Иосиф был [уже] в Египте." ],
|
||||
[ "6", "И умер Иосиф и все братья его и весь род их;" ],
|
||||
[ "7", "а сыны Израилевы расплодились и размножились, и возросли и усилились чрезвычайно, и наполнилась ими земля та." ],
|
||||
[ "8", "И восстал в Египте новый царь, который не знал Иосифа," ],
|
||||
[ "9", "и сказал народу своему: вот, народ сынов Израилевых многочислен и сильнее нас;" ],
|
||||
[ "10", "перехитрим же его, чтобы он не размножался; иначе, когда случится война, соединится и он с нашими неприятелями, и вооружится против нас, и выйдет из земли [нашей]." ]
|
||||
]
|
||||
}
|
48
tests/resources/bibles/zefania-rst.xml
Normal file
48
tests/resources/bibles/zefania-rst.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--Visit the online documentation for Zefania XML Markup-->
|
||||
<!--http://bgfdb.de/zefaniaxml/bml/-->
|
||||
<!--Download another Zefania XML files from-->
|
||||
<!--http://sourceforge.net/projects/zefania-sharp-->
|
||||
<XMLBIBLE xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="zef2005.xsd" version="2.0.1.18" status="v" biblename="Russian Synodal Translation" type="x-bible" revision="0">
|
||||
<INFORMATION>
|
||||
<title>Russian Synodal Translation</title>
|
||||
<format>Zefania XML Bible Markup Language</format>
|
||||
<date>2009-01-20</date>
|
||||
<creator>Jens Grabner</creator>
|
||||
<source>http://www.agape-biblia.org
|
||||
http://www.crosswire.org/sword/modules/</source>
|
||||
<language>RUS</language>
|
||||
<publisher />
|
||||
<identifier>RST</identifier>
|
||||
<contributors>
|
||||
1876 Russian Synodal Translation, 1956 Edition
|
||||
The text was supplied by "Light in East Germany".</contributors>
|
||||
<rights>
|
||||
</rights>
|
||||
<description>
|
||||
"Light in East Germany" Tel +49 711 83 30 57
|
||||
Postfach 1340 Fax +49 711 83 13 51
|
||||
7015 Korntal
|
||||
Munchingen 1
|
||||
Germany
|
||||
</description>
|
||||
<subject />
|
||||
<type />
|
||||
<coverage />
|
||||
</INFORMATION>
|
||||
<BIBLEBOOK bnumber="2">
|
||||
<CHAPTER cnumber="1">
|
||||
<CAPTION vref="1">Вторая книга Моисеева. Исход</CAPTION>
|
||||
<VERS vnumber="1">Вот имена сынов Израилевых, которые вошли в Египет с Иаковом, вошли каждый с домом своим:</VERS>
|
||||
<VERS vnumber="2">Рувим, Симеон, Левий и Иуда,</VERS>
|
||||
<VERS vnumber="3">Иссахар, Завулон и Вениамин,</VERS>
|
||||
<VERS vnumber="4">Дан и Неффалим, Гад и Асир.</VERS>
|
||||
<VERS vnumber="5">Всех же душ, происшедших от чресл Иакова, было семьдесят, а Иосиф был [уже] в Египте.</VERS>
|
||||
<VERS vnumber="6">И умер Иосиф и все братья его и весь род их;</VERS>
|
||||
<VERS vnumber="7">а сыны Израилевы расплодились и размножились, и возросли и усилились чрезвычайно, и наполнилась ими земля та.</VERS>
|
||||
<VERS vnumber="8">И восстал в Египте новый царь, который не знал Иосифа,</VERS>
|
||||
<VERS vnumber="9">и сказал народу своему: вот, народ сынов Израилевых многочислен и сильнее нас;</VERS>
|
||||
<VERS vnumber="10">перехитрим же его, чтобы он не размножался; иначе, когда случится война, соединится и он с нашими неприятелями, и вооружится против нас, и выйдет из земли [нашей].</VERS>
|
||||
</CHAPTER>
|
||||
</BIBLEBOOK>
|
||||
</XMLBIBLE>
|
29
tests/resources/presentationmanagersongs/Amazing Grace.json
Normal file
29
tests/resources/presentationmanagersongs/Amazing Grace.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"title": "Amazing Grace",
|
||||
"authors": [
|
||||
"John Newton"
|
||||
],
|
||||
"verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
|
||||
"verses": [
|
||||
[
|
||||
"Amazing grace! How sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
|
||||
"v1"
|
||||
],
|
||||
[
|
||||
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
|
||||
"v2"
|
||||
],
|
||||
[
|
||||
"The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
|
||||
"v3"
|
||||
],
|
||||
[
|
||||
"Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
|
||||
"v4"
|
||||
],
|
||||
[
|
||||
"When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.",
|
||||
"v5"
|
||||
]
|
||||
]
|
||||
}
|
BIN
tests/resources/presentationmanagersongs/Amazing Grace.sng
Normal file
BIN
tests/resources/presentationmanagersongs/Amazing Grace.sng
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user