This commit is contained in:
Tomas Groth 2015-02-16 21:02:58 +00:00
commit 0617ce3dc9
27 changed files with 287 additions and 83 deletions

View File

@ -190,31 +190,32 @@ def verify_ip_address(addr):
return True if verify_ipv4(addr) else verify_ipv6(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 Returns the hashed output of md5sum on salt,data
using Python3 hashlib using Python3 hashlib
:param salt: Initial salt :param salt: Initial salt
:param data: Data to hash :param data: OPTIONAL Data to hash
:returns: str :returns: str
""" """
log.debug('md5_hash(salt="%s")' % salt) log.debug('md5_hash(salt="%s")' % salt)
hash_obj = hashlib.new('md5') hash_obj = hashlib.new('md5')
hash_obj.update(salt.encode('ascii')) hash_obj.update(salt)
hash_obj.update(data.encode('ascii')) if data:
hash_obj.update(data)
hash_value = hash_obj.hexdigest() hash_value = hash_obj.hexdigest()
log.debug('md5_hash() returning "%s"' % hash_value) log.debug('md5_hash() returning "%s"' % hash_value)
return 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 Returns the hashed output of MD5Sum on salt, data
using PyQt4.QCryptographicHash. using PyQt4.QCryptographicHash.
:param salt: Initial salt :param salt: Initial salt
:param data: Data to hash :param data: OPTIONAL Data to hash
:returns: str :returns: str
""" """
log.debug('qmd5_hash(salt="%s"' % salt) log.debug('qmd5_hash(salt="%s"' % salt)
@ -223,7 +224,7 @@ def qmd5_hash(salt, data):
hash_obj.addData(data) hash_obj.addData(data)
hash_value = hash_obj.result().toHex() hash_value = hash_obj.result().toHex()
log.debug('qmd5_hash() returning "%s"' % hash_value) log.debug('qmd5_hash() returning "%s"' % hash_value)
return decode(hash_value.data(), 'ascii') return hash_value.data()
def clean_button_text(button_text): def clean_button_text(button_text):

View File

@ -271,8 +271,8 @@ ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants', E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
'The proxy address set with setProxy() was not found'), 'The proxy address set with setProxy() was not found'),
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants', E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
'The connection negotiation with the proxy server because the response ' 'The connection negotiation with the proxy server failed because the '
'from the proxy server could not be understood'), 'response from the proxy server could not be understood'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'), E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'), S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'), S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),

View File

@ -343,7 +343,7 @@ class PJLink1(QTcpSocket):
# Authenticated login with salt # Authenticated login with salt
log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2])) log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
log.debug('(%s) pin="%s"' % (self.ip, self.pin)) 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: else:
salt = None salt = None
# We're connected at this point, so go ahead and do regular I/O # We're connected at this point, so go ahead and do regular I/O

View File

@ -268,9 +268,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.web = 'http://openlp.org/files/frw/' self.web = 'http://openlp.org/files/frw/'
self.cancel_button.clicked.connect(self.on_cancel_button_clicked) self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked)
self.currentIdChanged.connect(self.on_current_id_changed) self.currentIdChanged.connect(self.on_current_id_changed)
Registry().register_function('config_screen_changed', self.update_screen_list_combo) Registry().register_function('config_screen_changed', self.update_screen_list_combo)
self.no_internet_finish_button.setVisible(False) self.no_internet_finish_button.setVisible(False)
self.no_internet_cancel_button.setVisible(False)
# Check if this is a re-run of the wizard. # Check if this is a re-run of the wizard.
self.has_run_wizard = Settings().value('core/has run wizard') self.has_run_wizard = Settings().value('core/has run wizard')
check_directory_exists(os.path.join(gettempdir(), 'openlp')) check_directory_exists(os.path.join(gettempdir(), 'openlp'))
@ -327,6 +329,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.next_button.setVisible(False) self.next_button.setVisible(False)
self.cancel_button.setVisible(False) self.cancel_button.setVisible(False)
self.no_internet_finish_button.setVisible(True) 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: elif page_id == FirstTimePage.Plugins:
self.back_button.setVisible(False) self.back_button.setVisible(False)
elif page_id == FirstTimePage.Progress: elif page_id == FirstTimePage.Progress:
@ -372,6 +378,13 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
Settings().setValue('core/has run wizard', True) Settings().setValue('core/has run wizard', True)
self.close() 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): 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 Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any

View File

@ -59,7 +59,8 @@ class UiFirstTimeWizard(object):
first_time_wizard.resize(550, 386) first_time_wizard.resize(550, 386)
first_time_wizard.setModal(True) first_time_wizard.setModal(True)
first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage | 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(): if is_macosx():
first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap, first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap,
QtGui.QPixmap(':/wizards/openlp-osx-wizard.png')) QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
@ -69,6 +70,7 @@ class UiFirstTimeWizard(object):
self.finish_button = self.button(QtGui.QWizard.FinishButton) self.finish_button = self.button(QtGui.QWizard.FinishButton)
self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1) self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1)
self.cancel_button = self.button(QtGui.QWizard.CancelButton) 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.next_button = self.button(QtGui.QWizard.NextButton)
self.back_button = self.button(QtGui.QWizard.BackButton) self.back_button = self.button(QtGui.QWizard.BackButton)
add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp') add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp')
@ -271,3 +273,4 @@ class UiFirstTimeWizard(object):
'and OpenLP is configured.')) 'and OpenLP is configured.'))
self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...')) 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.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton2, translate('OpenLP.FirstTimeWizard', 'Cancel'))

View File

@ -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 # Read the temp file and output the user's CONF file with blanks to
# make it more readable. # make it more readable.
temp_conf = open(temp_file, 'r') temp_conf = open(temp_file, 'r')
export_conf = open(export_file_name, 'w') try:
for file_record in temp_conf: export_conf = open(export_file_name, 'w')
# Get rid of any invalid entries. for file_record in temp_conf:
if file_record.find('@Invalid()') == -1: # Get rid of any invalid entries.
file_record = file_record.replace('%20', ' ') if file_record.find('@Invalid()') == -1:
export_conf.write(file_record) file_record = file_record.replace('%20', ' ')
temp_conf.close() export_conf.write(file_record)
export_conf.close() temp_conf.close()
os.remove(temp_file) 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): def on_mode_default_item_clicked(self):
""" """

View File

@ -601,6 +601,12 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
shutil.copy(temp_file_name, path_file_name) shutil.copy(temp_file_name, path_file_name)
except shutil.Error: except shutil.Error:
return self.save_file_as() 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.main_window.add_recent_file(path_file_name)
self.set_modified(False) self.set_modified(False)
delete_file(temp_file_name) delete_file(temp_file_name)

View File

@ -377,17 +377,11 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
self.application.set_busy_cursor() self.application.set_busy_cursor()
if path: if path:
Settings().setValue(self.settings_section + '/last directory export', path) Settings().setValue(self.settings_section + '/last directory export', path)
try: if self._export_theme(path, theme):
self._export_theme(path, theme)
QtGui.QMessageBox.information(self, QtGui.QMessageBox.information(self,
translate('OpenLP.ThemeManager', 'Theme Exported'), translate('OpenLP.ThemeManager', 'Theme Exported'),
translate('OpenLP.ThemeManager', translate('OpenLP.ThemeManager',
'Your theme has been successfully exported.')) '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() self.application.set_normal_cursor()
def _export_theme(self, path, theme): 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 :param theme: The name of the theme to be exported
""" """
theme_path = os.path.join(path, theme + '.otz') theme_path = os.path.join(path, theme + '.otz')
theme_zip = None
try: try:
theme_zip = zipfile.ZipFile(theme_path, 'w') theme_zip = zipfile.ZipFile(theme_path, 'w')
source = os.path.join(self.path, theme) source = os.path.join(self.path, theme)
for files in os.walk(source): for files in os.walk(source):
for name in files[2]: for name in files[2]:
theme_zip.write(os.path.join(source, name), os.path.join(theme, name)) 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: if theme_zip:
theme_zip.close() theme_zip.close()
shutil.rmtree(theme_path, True) shutil.rmtree(theme_path, True)
raise return False
else:
theme_zip.close()
def on_import_theme(self, field=None): def on_import_theme(self, field=None):
""" """

View File

@ -371,7 +371,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
from_chapter = from_verse from_chapter = from_verse
from_verse = None from_verse = None
if to_chapter: if to_chapter:
if to_chapter < from_chapter: if from_chapter and to_chapter < from_chapter:
continue continue
else: else:
chapter = to_chapter chapter = to_chapter
@ -387,7 +387,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
from_verse = 1 from_verse = 1
if not to_verse: if not to_verse:
to_verse = -1 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)) ref_list.append((book_ref_id, from_chapter, from_verse, -1))
for i in range(from_chapter + 1, to_chapter): for i in range(from_chapter + 1, to_chapter):
ref_list.append((book_ref_id, i, 1, -1)) ref_list.append((book_ref_id, i, 1, -1))

View File

@ -365,31 +365,20 @@ class CWExtract(RegistryProperties):
if not soup: if not soup:
return None return None
self.application.process_events() self.application.process_events()
html_verses = soup.find_all('span', 'versetext') verses_div = soup.find_all('div', 'verse')
if not html_verses: if not verses_div:
log.error('No verses found in the CrossWalk response.') log.error('No verses found in the CrossWalk response.')
send_error_message('parse') send_error_message('parse')
return None return None
verses = {} verses = {}
for verse in html_verses: for verse in verses_div:
self.application.process_events() self.application.process_events()
verse_number = int(verse.contents[0].contents[0]) verse_number = int(verse.find('strong').contents[0])
verse_text = '' verse_span = verse.find('span')
for part in verse.contents: tags_to_remove = verse_span.find_all(['a', 'sup'])
self.application.process_events() for tag in tags_to_remove:
if isinstance(part, NavigableString): tag.decompose()
verse_text += part verse_text = verse_span.get_text()
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
self.application.process_events() self.application.process_events()
# Fix up leading and trailing spaces, multiple spaces, and spaces between text and , and . # Fix up leading and trailing spaces, multiple spaces, and spaces between text and , and .
verse_text = verse_text.strip('\n\r\t ') verse_text = verse_text.strip('\n\r\t ')
@ -409,16 +398,13 @@ class CWExtract(RegistryProperties):
soup = get_soup_for_bible_ref(chapter_url) soup = get_soup_for_bible_ref(chapter_url)
if not soup: if not soup:
return None return None
content = soup.find('div', {'class': 'Body'}) content = soup.find_all(('h4', {'class': 'small-header'}))
content = content.find('ul', {'class': 'parent'})
if not content: if not content:
log.error('No books found in the Crosswalk response.') log.error('No books found in the Crosswalk response.')
send_error_message('parse') send_error_message('parse')
return None return None
content = content.find_all('li')
books = [] books = []
for book in content: for book in content:
book = book.find('a')
books.append(book.contents[0]) books.append(book.contents[0])
return books return books

View File

@ -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 # 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. # detection, and the two mechanisms together interfere with each other.
import_file = open(self.filename, 'rb') 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'} namespace = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
# Find bible language # Find bible language
language_id = None language_id = None

View File

@ -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 # 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. # detection, and the two mechanisms together interfere with each other.
import_file = open(self.filename, 'rb') 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 # Find bible language
language_id = None language_id = None
language = zefania_bible_tree.xpath("/XMLBIBLE/INFORMATION/language/text()") 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) etree.strip_elements(zefania_bible_tree, ('PROLOG', 'REMARK', 'CAPTION', 'MEDIA'), with_tail=False)
xmlbible = zefania_bible_tree.getroot() xmlbible = zefania_bible_tree.getroot()
for BIBLEBOOK in xmlbible: for BIBLEBOOK in xmlbible:
book_ref_id = self.get_book_ref_id_by_name(str(BIBLEBOOK.get('bname')), num_books) if self.stop_import_flag:
if not book_ref_id: break
book_ref_id = self.get_book_ref_id_by_localised_name(str(BIBLEBOOK.get('bname'))) 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: if not book_ref_id:
log.error('Importing books from "%s" failed' % self.filename) log.error('Importing books from "%s" failed' % self.filename)
return False return False
@ -94,7 +104,7 @@ class ZefaniaBible(BibleDB):
self.wizard.increment_progress_bar( self.wizard.increment_progress_bar(
translate('BiblesPlugin.Zefnia', 'Importing %(bookname)s %(chapter)s...' % translate('BiblesPlugin.Zefnia', 'Importing %(bookname)s %(chapter)s...' %
{'bookname': db_book.name, 'chapter': chapter_number})) {'bookname': db_book.name, 'chapter': chapter_number}))
self.session.commit() self.session.commit()
self.application.process_events() self.application.process_events()
except Exception as e: except Exception as e:
critical_error_message_box( critical_error_message_box(

View File

@ -225,10 +225,10 @@ class PresentationMediaItem(MediaManagerItem):
self.clean_up_thumbnails(filepath) self.clean_up_thumbnails(filepath)
self.main_window.increment_progress_bar() self.main_window.increment_progress_bar()
self.main_window.finished_progress_bar() self.main_window.finished_progress_bar()
self.application.set_busy_cursor()
for row in row_list: for row in row_list:
self.list_view.takeItem(row) self.list_view.takeItem(row)
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
self.application.set_normal_cursor()
def clean_up_thumbnails(self, filepath): def clean_up_thumbnails(self, filepath):
""" """

View File

@ -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 # 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': if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash('', self.file_path) folder = md5_hash(self.file_path.encode('utf-8'))
else: else:
folder = self.get_file_name() folder = self.get_file_name()
return os.path.join(self.controller.thumbnail_folder, folder) 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 # 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': if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash('', self.file_path) folder = md5_hash(self.file_path.encode('utf-8'))
else: else:
folder = folder = self.get_file_name() folder = folder = self.get_file_name()
return os.path.join(self.controller.temp_folder, folder) return os.path.join(self.controller.temp_folder, folder)

View File

@ -244,12 +244,16 @@ class SongExportForm(OpenLPWizard):
for song in self._find_list_widget_items(self.selected_list_widget) for song in self._find_list_widget_items(self.selected_list_widget)
] ]
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text()) exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
if exporter.do_export(): try:
self.progress_label.setText( if exporter.do_export():
translate('SongsPlugin.SongExportForm', self.progress_label.setText(
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.')) translate('SongsPlugin.SongExportForm',
else: 'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.')) 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=''): def _find_list_widget_items(self, list_widget, text=''):
""" """

View File

@ -25,7 +25,9 @@ Presentationmanager song files into the current database.
""" """
import os import os
from lxml import objectify import re
import chardet
from lxml import objectify, etree
from openlp.core.ui.wizard import WizardStrings from openlp.core.ui.wizard import WizardStrings
from .songimport import SongImport from .songimport import SongImport
@ -42,7 +44,18 @@ class PresentationManagerImport(SongImport):
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) 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) self.process_song(root)
def process_song(self, root): def process_song(self, root):

View File

@ -27,6 +27,7 @@ from PyQt4 import QtGui
from sqlalchemy.sql import and_ from sqlalchemy.sql import and_
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate 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 openlp.plugins.songusage.lib.db import SongUsageItem
from .songusagedetaildialog import Ui_SongUsageDetailDialog from .songusagedetaildialog import Ui_SongUsageDetailDialog
@ -104,8 +105,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog, RegistryPrope
translate('SongUsagePlugin.SongUsageDetailForm', translate('SongUsagePlugin.SongUsageDetailForm',
'Report \n%s \nhas been successfully created. ') % report_file_name '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') 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: finally:
if file_handle: if file_handle:
file_handle.close() file_handle.close()

View File

@ -83,6 +83,7 @@
<file>openlp-logo-64x64.png</file> <file>openlp-logo-64x64.png</file>
<file>openlp-logo-128x128.png</file> <file>openlp-logo-128x128.png</file>
<file>openlp-logo-256x256.png</file> <file>openlp-logo-256x256.png</file>
<file>openlp-logo.svg</file>
</qresource> </qresource>
<qresource prefix="graphics"> <qresource prefix="graphics">
<file>exception.png</file> <file>exception.png</file>

View File

@ -23,6 +23,8 @@
Package to test the openlp.core.ui.projector.networkutils package. Package to test the openlp.core.ui.projector.networkutils package.
""" """
import os
from unittest import TestCase from unittest import TestCase
from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash 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' salt = '498e4a67'
pin = 'JBMIAProjectorLink' pin = 'JBMIAProjectorLink'
test_hash = '5d8409bc1c3fa39749434aa3a5c38682' test_hash = '5d8409bc1c3fa39749434aa3a5c38682'
test_non_ascii_string = '이것은 한국어 시험 문자열'
test_non_ascii_hash = 'fc00c7912976f6e9c19099b514ced201'
ip4_loopback = '127.0.0.1' ip4_loopback = '127.0.0.1'
ip4_local = '192.168.1.1' ip4_local = '192.168.1.1'
@ -120,7 +124,7 @@ class testProjectorUtilities(TestCase):
Test MD5 hash from salt+data pass (python) Test MD5 hash from salt+data pass (python)
""" """
# WHEN: Given a known salt+data # 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 # THEN: Validate return has is same
self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash') 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) Test MD5 hash from salt+data fail (python)
""" """
# WHEN: Given a different salt+hash # 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 # THEN: return data is different
self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash') 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) Test MD5 hash from salt+data pass (Qt)
""" """
# WHEN: Given a known salt+data # 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 # 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): def test_qmd5_hash_bad(self):
""" """
Test MD5 hash from salt+hash fail (Qt) Test MD5 hash from salt+hash fail (Qt)
""" """
# WHEN: Given a different salt+hash # 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 # 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')

View File

@ -77,7 +77,6 @@ class TestZefaniaImport(TestCase):
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
importer.wizard = mocked_import_wizard importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock() importer.create_verse = MagicMock()
importer.create_book = MagicMock() importer.create_book = MagicMock()
importer.session = MagicMock() importer.session = MagicMock()
@ -92,3 +91,34 @@ class TestZefaniaImport(TestCase):
self.assertTrue(importer.create_verse.called) self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']: 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_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)

View File

@ -46,3 +46,5 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json'))) 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.file_import([os.path.join(TEST_PATH, 'Agnus Dei.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Agnus Dei.json'))) 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')))

View File

@ -506,7 +506,6 @@ class TestSongSelectFileImport(TestCase, TestMixin):
# WHEN: We call the song importer # WHEN: We call the song importer
song_import.do_import() song_import.do_import()
print(song_import.verses)
# THEN: Song values should be equal to test values in setUp # 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.title, self.title, 'Song title should match')
self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match') self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match')

View File

@ -109,3 +109,13 @@ class TestBibleManager(TestCase, TestMixin):
results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock()) results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock())
# THEN a verse array should be returned # THEN a verse array should be returned
self.assertEqual(False, results, "The bible Search should return False") 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")

View File

@ -0,0 +1,16 @@
{
"book": "Exodus",
"chapter": 1,
"verses": [
[ "1", "Вот имена сынов Израилевых, которые вошли в Египет с Иаковом, вошли каждый с домом своим:" ],
[ "2", "Рувим, Симеон, Левий и Иуда," ],
[ "3", "Иссахар, Завулон и Вениамин," ],
[ "4", "Дан и Неффалим, Гад и Асир." ],
[ "5", "Всех же душ, происшедших от чресл Иакова, было семьдесят, а Иосиф был [уже] в Египте." ],
[ "6", "И умер Иосиф и все братья его и весь род их;" ],
[ "7", "а сыны Израилевы расплодились и размножились, и возросли и усилились чрезвычайно, и наполнилась ими земля та." ],
[ "8", "И восстал в Египте новый царь, который не знал Иосифа," ],
[ "9", "и сказал народу своему: вот, народ сынов Израилевых многочислен и сильнее нас;" ],
[ "10", "перехитрим же его, чтобы он не размножался; иначе, когда случится война, соединится и он с нашими неприятелями, и вооружится против нас, и выйдет из земли [нашей]." ]
]
}

View 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>

View 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"
]
]
}