forked from openlp/openlp
HEAD
This commit is contained in:
commit
a255faa95b
@ -20,7 +20,7 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.core.lib.historycombobox` module contains the HistoryComboBox widget
|
||||
The :mod:`~openlp.core.common.historycombobox` module contains the HistoryComboBox widget
|
||||
"""
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
@ -28,7 +28,7 @@ from PyQt4 import QtCore, QtGui
|
||||
|
||||
class HistoryComboBox(QtGui.QComboBox):
|
||||
"""
|
||||
The :class:`~openlp.core.lib.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed`` signal
|
||||
The :class:`~openlp.core.common.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed`` signal
|
||||
for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit box into
|
||||
its list.
|
||||
"""
|
||||
|
@ -228,7 +228,8 @@ class Settings(QtCore.QSettings):
|
||||
'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
|
||||
'shortcuts/nextTrackItem': [],
|
||||
'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down), QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
|
||||
'shortcuts/nextItem_preview': [],
|
||||
'shortcuts/nextItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
|
||||
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
|
||||
'shortcuts/newService': [],
|
||||
'shortcuts/offlineHelpItem': [],
|
||||
@ -242,7 +243,8 @@ class Settings(QtCore.QSettings):
|
||||
'shortcuts/playSlidesLoop': [],
|
||||
'shortcuts/playSlidesOnce': [],
|
||||
'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
|
||||
'shortcuts/previousItem_preview': [],
|
||||
'shortcuts/previousItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
|
||||
'shortcuts/printServiceItem': [QtGui.QKeySequence('Ctrl+P')],
|
||||
'shortcuts/songExportItem': [],
|
||||
'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
|
||||
@ -304,6 +306,10 @@ class Settings(QtCore.QSettings):
|
||||
'user interface/preview panel': True,
|
||||
'user interface/preview splitter geometry': QtCore.QByteArray(),
|
||||
'projector/db type': 'sqlite',
|
||||
'projector/db username': '',
|
||||
'projector/db password': '',
|
||||
'projector/db hostname': '',
|
||||
'projector/db database': '',
|
||||
'projector/enable': True,
|
||||
'projector/connect on start': False,
|
||||
'projector/last directory import': '',
|
||||
|
@ -115,6 +115,7 @@ class UiStrings(object):
|
||||
self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')
|
||||
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
|
||||
self.Preview = translate('OpenLP.Ui', 'Preview')
|
||||
self.PreviewToolbar = translate('OpenLP.Ui', 'Preview Toolbar')
|
||||
self.PrintService = translate('OpenLP.Ui', 'Print Service')
|
||||
self.Projector = translate('OpenLP.Ui', 'Projector', 'Singular')
|
||||
self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
|
||||
|
@ -312,6 +312,7 @@ def create_separated_list(string_list):
|
||||
|
||||
|
||||
from .colorbutton import ColorButton
|
||||
from .exceptions import ValidationError
|
||||
from .filedialog import FileDialog
|
||||
from .screen import ScreenList
|
||||
from .listwidgetwithdnd import ListWidgetWithDnD
|
||||
|
@ -60,6 +60,35 @@ def init_db(url, auto_flush=True, auto_commit=False, base=None):
|
||||
return session, metadata
|
||||
|
||||
|
||||
def get_db_path(plugin_name, db_file_name=None):
|
||||
"""
|
||||
Create a path to a database from the plugin name and database name
|
||||
|
||||
:param plugin_name: Name of plugin
|
||||
:param db_file_name: File name of database
|
||||
:return: The path to the database as type str
|
||||
"""
|
||||
if db_file_name is None:
|
||||
return 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
|
||||
else:
|
||||
return 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
|
||||
|
||||
|
||||
def handle_db_error(plugin_name, db_file_name):
|
||||
"""
|
||||
Log and report to the user that a database cannot be loaded
|
||||
|
||||
:param plugin_name: Name of plugin
|
||||
:param db_file_name: File name of database
|
||||
:return: None
|
||||
"""
|
||||
db_path = get_db_path(plugin_name, db_file_name)
|
||||
log.exception('Error loading database: %s', db_path)
|
||||
critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
|
||||
translate('OpenLP.Manager', 'OpenLP cannot load your database.\n\nDatabase: %s')
|
||||
% db_path)
|
||||
|
||||
|
||||
def init_url(plugin_name, db_file_name=None):
|
||||
"""
|
||||
Return the database URL.
|
||||
@ -69,21 +98,14 @@ def init_url(plugin_name, db_file_name=None):
|
||||
"""
|
||||
settings = Settings()
|
||||
settings.beginGroup(plugin_name)
|
||||
db_url = ''
|
||||
db_type = settings.value('db type')
|
||||
if db_type == 'sqlite':
|
||||
if db_file_name is None:
|
||||
db_url = 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
|
||||
else:
|
||||
db_url = 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
|
||||
db_url = get_db_path(plugin_name, db_file_name)
|
||||
else:
|
||||
db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),
|
||||
urlquote(settings.value('db password')),
|
||||
urlquote(settings.value('db hostname')),
|
||||
urlquote(settings.value('db database')))
|
||||
if db_type == 'mysql':
|
||||
db_encoding = settings.value('db encoding')
|
||||
db_url += '?charset=%s' % urlquote(db_encoding)
|
||||
settings.endGroup()
|
||||
return db_url
|
||||
|
||||
@ -212,7 +234,7 @@ class Manager(object):
|
||||
try:
|
||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||
except (SQLAlchemyError, DBAPIError):
|
||||
log.exception('Error loading database: %s', self.db_url)
|
||||
handle_db_error(plugin_name, db_file_name)
|
||||
return
|
||||
if db_ver > up_ver:
|
||||
critical_error_message_box(
|
||||
@ -225,10 +247,7 @@ class Manager(object):
|
||||
try:
|
||||
self.session = init_schema(self.db_url)
|
||||
except (SQLAlchemyError, DBAPIError):
|
||||
log.exception('Error loading database: %s', self.db_url)
|
||||
critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
|
||||
translate('OpenLP.Manager', 'OpenLP cannot load your database.\n\nDatabase: %s')
|
||||
% self.db_url)
|
||||
handle_db_error(plugin_name, db_file_name)
|
||||
|
||||
def save_object(self, object_instance, commit=True):
|
||||
"""
|
||||
|
32
openlp/core/lib/exceptions.py
Normal file
32
openlp/core/lib/exceptions.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2015 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.core.lib.exceptions` module contains custom exceptions
|
||||
"""
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
"""
|
||||
The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating
|
||||
import files.
|
||||
"""
|
||||
pass
|
@ -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'),
|
||||
|
@ -51,6 +51,12 @@ class SettingsTab(QtGui.QWidget, RegistryProperties):
|
||||
self.tab_visited = False
|
||||
if icon_path:
|
||||
self.icon_path = icon_path
|
||||
self._setup()
|
||||
|
||||
def _setup(self):
|
||||
"""
|
||||
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
|
||||
"""
|
||||
self.setupUi()
|
||||
self.retranslateUi()
|
||||
self.initialise()
|
||||
|
@ -22,6 +22,7 @@
|
||||
"""
|
||||
This module contains the first time wizard.
|
||||
"""
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
@ -47,10 +48,10 @@ class ThemeScreenshotWorker(QtCore.QObject):
|
||||
"""
|
||||
This thread downloads a theme's screenshot
|
||||
"""
|
||||
screenshot_downloaded = QtCore.pyqtSignal(str, str)
|
||||
screenshot_downloaded = QtCore.pyqtSignal(str, str, str)
|
||||
finished = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, themes_url, title, filename, screenshot):
|
||||
def __init__(self, themes_url, title, filename, sha256, screenshot):
|
||||
"""
|
||||
Set up the worker object
|
||||
"""
|
||||
@ -58,6 +59,7 @@ class ThemeScreenshotWorker(QtCore.QObject):
|
||||
self.themes_url = themes_url
|
||||
self.title = title
|
||||
self.filename = filename
|
||||
self.sha256 = sha256
|
||||
self.screenshot = screenshot
|
||||
super(ThemeScreenshotWorker, self).__init__()
|
||||
|
||||
@ -71,7 +73,7 @@ class ThemeScreenshotWorker(QtCore.QObject):
|
||||
urllib.request.urlretrieve('%s%s' % (self.themes_url, self.screenshot),
|
||||
os.path.join(gettempdir(), 'openlp', self.screenshot))
|
||||
# Signal that the screenshot has been downloaded
|
||||
self.screenshot_downloaded.emit(self.title, self.filename)
|
||||
self.screenshot_downloaded.emit(self.title, self.filename, self.sha256)
|
||||
except:
|
||||
log.exception('Unable to download screenshot')
|
||||
finally:
|
||||
@ -221,8 +223,9 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.application.process_events()
|
||||
title = self.config.get('songs_%s' % song, 'title')
|
||||
filename = self.config.get('songs_%s' % song, 'filename')
|
||||
sha256 = self.config.get('songs_%s' % song, 'sha256', fallback='')
|
||||
item = QtGui.QListWidgetItem(title, self.songs_list_widget)
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
item.setData(QtCore.Qt.UserRole, (filename, sha256))
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
bible_languages = self.config.get('bibles', 'languages')
|
||||
@ -237,8 +240,9 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.application.process_events()
|
||||
title = self.config.get('bible_%s' % bible, 'title')
|
||||
filename = self.config.get('bible_%s' % bible, 'filename')
|
||||
sha256 = self.config.get('bible_%s' % bible, 'sha256', fallback='')
|
||||
item = QtGui.QTreeWidgetItem(lang_item, [title])
|
||||
item.setData(0, QtCore.Qt.UserRole, filename)
|
||||
item.setData(0, QtCore.Qt.UserRole, (filename, sha256))
|
||||
item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
self.bibles_tree_widget.expandAll()
|
||||
@ -249,8 +253,9 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.application.process_events()
|
||||
title = self.config.get('theme_%s' % theme, 'title')
|
||||
filename = self.config.get('theme_%s' % theme, 'filename')
|
||||
sha256 = self.config.get('theme_%s' % theme, 'sha256', fallback='')
|
||||
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
|
||||
worker = ThemeScreenshotWorker(self.themes_url, title, filename, screenshot)
|
||||
worker = ThemeScreenshotWorker(self.themes_url, title, filename, sha256, screenshot)
|
||||
self.theme_screenshot_workers.append(worker)
|
||||
worker.screenshot_downloaded.connect(self.on_screenshot_downloaded)
|
||||
thread = QtCore.QThread(self)
|
||||
@ -356,7 +361,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
time.sleep(0.1)
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def on_screenshot_downloaded(self, title, filename):
|
||||
def on_screenshot_downloaded(self, title, filename, sha256):
|
||||
"""
|
||||
Add an item to the list when a theme has been downloaded
|
||||
|
||||
@ -364,7 +369,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
:param filename: The filename of the theme
|
||||
"""
|
||||
item = QtGui.QListWidgetItem(title, self.themes_list_widget)
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
item.setData(QtCore.Qt.UserRole, (filename, sha256))
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
|
||||
@ -385,7 +390,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.was_cancelled = True
|
||||
self.close()
|
||||
|
||||
def url_get_file(self, url, f_path):
|
||||
def url_get_file(self, url, f_path, sha256=None):
|
||||
""""
|
||||
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
|
||||
point. Returns False on download error.
|
||||
@ -400,16 +405,24 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
try:
|
||||
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
||||
filename = open(f_path, "wb")
|
||||
if sha256:
|
||||
hasher = hashlib.sha256()
|
||||
# Download until finished or canceled.
|
||||
while not self.was_cancelled:
|
||||
data = url_file.read(block_size)
|
||||
if not data:
|
||||
break
|
||||
filename.write(data)
|
||||
if sha256:
|
||||
hasher.update(data)
|
||||
block_count += 1
|
||||
self._download_progress(block_count, block_size)
|
||||
filename.close()
|
||||
except ConnectionError:
|
||||
if sha256 and hasher.hexdigest() != sha256:
|
||||
log.error('sha256 sums did not match for file: {}'.format(f_path))
|
||||
os.remove(f_path)
|
||||
return False
|
||||
except urllib.error.URLError:
|
||||
trace_error_handler(log)
|
||||
filename.close()
|
||||
os.remove(f_path)
|
||||
@ -449,7 +462,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
||||
meta = site.info()
|
||||
return int(meta.get("Content-Length"))
|
||||
except ConnectionException:
|
||||
except urllib.error.URLError:
|
||||
if retries > CONNECTION_RETRIES:
|
||||
raise
|
||||
else:
|
||||
@ -491,7 +504,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.application.process_events()
|
||||
item = self.songs_list_widget.item(i)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
filename = item.data(QtCore.Qt.UserRole)
|
||||
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||
size = self._get_file_size('%s%s' % (self.songs_url, filename))
|
||||
self.max_progress += size
|
||||
# Loop through the Bibles list and increase for each selected item
|
||||
@ -500,7 +513,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.application.process_events()
|
||||
item = iterator.value()
|
||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||
filename = item.data(0, QtCore.Qt.UserRole)
|
||||
filename, sha256 = item.data(0, QtCore.Qt.UserRole)
|
||||
size = self._get_file_size('%s%s' % (self.bibles_url, filename))
|
||||
self.max_progress += size
|
||||
iterator += 1
|
||||
@ -509,10 +522,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.application.process_events()
|
||||
item = self.themes_list_widget.item(i)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
filename = item.data(QtCore.Qt.UserRole)
|
||||
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||
size = self._get_file_size('%s%s' % (self.themes_url, filename))
|
||||
self.max_progress += size
|
||||
except ConnectionError:
|
||||
except urllib.error.URLError:
|
||||
trace_error_handler(log)
|
||||
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
||||
translate('OpenLP.FirstTimeWizard', 'There was a connection problem during '
|
||||
@ -608,31 +621,33 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
for i in range(self.songs_list_widget.count()):
|
||||
item = self.songs_list_widget.item(i)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
filename = item.data(QtCore.Qt.UserRole)
|
||||
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||
self._increment_progress_bar(self.downloading % filename, 0)
|
||||
self.previous_size = 0
|
||||
destination = os.path.join(songs_destination, str(filename))
|
||||
if not self.url_get_file('%s%s' % (self.songs_url, filename), destination):
|
||||
if not self.url_get_file('%s%s' % (self.songs_url, filename), destination, sha256):
|
||||
return False
|
||||
# Download Bibles
|
||||
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
|
||||
while bibles_iterator.value():
|
||||
item = bibles_iterator.value()
|
||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||
bible = item.data(0, QtCore.Qt.UserRole)
|
||||
bible, sha256 = item.data(0, QtCore.Qt.UserRole)
|
||||
self._increment_progress_bar(self.downloading % bible, 0)
|
||||
self.previous_size = 0
|
||||
if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible)):
|
||||
if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible),
|
||||
sha256):
|
||||
return False
|
||||
bibles_iterator += 1
|
||||
# Download themes
|
||||
for i in range(self.themes_list_widget.count()):
|
||||
item = self.themes_list_widget.item(i)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
theme = item.data(QtCore.Qt.UserRole)
|
||||
theme, sha256 = item.data(QtCore.Qt.UserRole)
|
||||
self._increment_progress_bar(self.downloading % theme, 0)
|
||||
self.previous_size = 0
|
||||
if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme)):
|
||||
if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme),
|
||||
sha256):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -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)
|
||||
|
@ -408,7 +408,7 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
self.set_live_hot_keys(self)
|
||||
self.__add_actions_to_widget(self.controller)
|
||||
else:
|
||||
self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service)
|
||||
self.preview_widget.doubleClicked.connect(self.on_preview_double_click)
|
||||
self.toolbar.set_widget_visible(['editSong'], False)
|
||||
self.controller.addActions([self.next_item, self.previous_item])
|
||||
Registry().register_function('slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
|
||||
@ -1309,18 +1309,21 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
if self.service_item:
|
||||
self.service_manager.add_service_item(self.service_item)
|
||||
|
||||
def on_go_live_click(self, field=None):
|
||||
def on_preview_double_click(self, field=None):
|
||||
"""
|
||||
triggered by clicking the Preview slide items
|
||||
Triggered when a preview slide item is doubleclicked
|
||||
"""
|
||||
if Settings().value('advanced/double click live'):
|
||||
# Live and Preview have issues if we have video or presentations
|
||||
# playing in both at the same time.
|
||||
if self.service_item.is_command():
|
||||
Registry().execute('%s_stop' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
||||
if self.service_item.is_media():
|
||||
self.on_media_close()
|
||||
self.on_go_live()
|
||||
if self.service_item:
|
||||
if Settings().value('advanced/double click live'):
|
||||
# Live and Preview have issues if we have video or presentations
|
||||
# playing in both at the same time.
|
||||
if self.service_item.is_command():
|
||||
Registry().execute('%s_stop' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
||||
if self.service_item.is_media():
|
||||
self.on_media_close()
|
||||
self.on_go_live()
|
||||
else:
|
||||
self.on_preview_add_to_service()
|
||||
|
||||
def on_go_live(self, field=None):
|
||||
"""
|
||||
@ -1418,7 +1421,7 @@ class PreviewController(RegistryMixin, OpenLPMixin, SlideController):
|
||||
super(PreviewController, self).__init__(parent)
|
||||
self.split = 0
|
||||
self.type_prefix = 'preview'
|
||||
self.category = None
|
||||
self.category = 'Preview Toolbar'
|
||||
|
||||
def bootstrap_post_set_up(self):
|
||||
"""
|
||||
|
@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||
check_directory_exists, UiStrings, translate, is_win
|
||||
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, get_text_file_string, build_icon, \
|
||||
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, ValidationError, get_text_file_string, build_icon, \
|
||||
check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.theme import ThemeXML, BackgroundType
|
||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||
@ -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,30 +391,35 @@ 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):
|
||||
"""
|
||||
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
|
||||
those files. This process will load both OpenLP version 1 and version 2 themes.
|
||||
those files. This process will only load version 2 themes.
|
||||
:param field:
|
||||
"""
|
||||
files = FileDialog.getOpenFileNames(self,
|
||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||
Settings().value(self.settings_section + '/last directory import'),
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)'))
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||
self.log_info('New Themes %s' % str(files))
|
||||
if not files:
|
||||
return
|
||||
@ -535,7 +534,6 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
||||
:param directory:
|
||||
"""
|
||||
self.log_debug('Unzipping theme %s' % file_name)
|
||||
file_name = str(file_name)
|
||||
theme_zip = None
|
||||
out_file = None
|
||||
file_xml = None
|
||||
@ -545,8 +543,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
||||
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
|
||||
if len(xml_file) != 1:
|
||||
self.log_error('Theme contains "%s" XML files' % len(xml_file))
|
||||
raise Exception('validation')
|
||||
raise ValidationError
|
||||
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
|
||||
theme_version = xml_tree.get('version', default=None)
|
||||
if not theme_version or float(theme_version) < 2.0:
|
||||
self.log_error('Theme version is less than 2.0')
|
||||
raise ValidationError
|
||||
theme_name = xml_tree.find('name').text.strip()
|
||||
theme_folder = os.path.join(directory, theme_name)
|
||||
theme_exists = os.path.exists(theme_folder)
|
||||
@ -573,13 +575,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
|
||||
out_file.close()
|
||||
except (IOError, zipfile.BadZipfile):
|
||||
self.log_exception('Importing theme from zip failed %s' % file_name)
|
||||
raise Exception('validation')
|
||||
except Exception as info:
|
||||
if str(info) == 'validation':
|
||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
|
||||
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
||||
else:
|
||||
raise
|
||||
raise ValidationError
|
||||
except ValidationError:
|
||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
|
||||
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
||||
finally:
|
||||
# Close the files, to be able to continue creating the theme.
|
||||
if theme_zip:
|
||||
|
@ -112,6 +112,10 @@ __default_settings__ = {
|
||||
'alerts/font face': QtGui.QFont().family(),
|
||||
'alerts/font size': 40,
|
||||
'alerts/db type': 'sqlite',
|
||||
'alerts/db username': '',
|
||||
'alerts/db password': '',
|
||||
'alerts/db hostname': '',
|
||||
'alerts/db database': '',
|
||||
'alerts/location': AlertLocation.Bottom,
|
||||
'alerts/background color': '#660000',
|
||||
'alerts/font color': '#ffffff',
|
||||
|
@ -37,6 +37,10 @@ log = logging.getLogger(__name__)
|
||||
|
||||
__default_settings__ = {
|
||||
'bibles/db type': 'sqlite',
|
||||
'bibles/db username': '',
|
||||
'bibles/db password': '',
|
||||
'bibles/db hostname': '',
|
||||
'bibles/db database': '',
|
||||
'bibles/last search type': BibleSearch.Reference,
|
||||
'bibles/verse layout style': LayoutStyle.VersePerSlide,
|
||||
'bibles/book name language': LanguageSelection.Bible,
|
||||
|
@ -73,7 +73,7 @@ class CSVBible(BibleDB):
|
||||
"""
|
||||
log.info(self.__class__.__name__)
|
||||
BibleDB.__init__(self, parent, **kwargs)
|
||||
self.books_file = kwargs['books_file']
|
||||
self.books_file = kwargs['booksfile']
|
||||
self.verses_file = kwargs['versefile']
|
||||
|
||||
def do_import(self, bible_name=None):
|
||||
@ -93,23 +93,20 @@ class CSVBible(BibleDB):
|
||||
# Populate the Tables
|
||||
try:
|
||||
details = get_file_encoding(self.books_file)
|
||||
books_file = open(self.books_file, 'r')
|
||||
if not books_file.read(3) == '\xEF\xBB\xBF':
|
||||
# no BOM was found
|
||||
books_file.seek(0)
|
||||
books_file = open(self.books_file, 'r', encoding=details['encoding'])
|
||||
books_reader = csv.reader(books_file, delimiter=',', quotechar='"')
|
||||
for line in books_reader:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing books... %s') %
|
||||
str(line[2], details['encoding']))
|
||||
book_ref_id = self.get_book_ref_id_by_name(str(line[2], details['encoding']), 67, language_id)
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing books... %s')
|
||||
% line[2])
|
||||
book_ref_id = self.get_book_ref_id_by_name(line[2], 67, language_id)
|
||||
if not book_ref_id:
|
||||
log.error('Importing books from "%s" failed' % self.books_file)
|
||||
return False
|
||||
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
||||
self.create_book(str(line[2], details['encoding']), book_ref_id, book_details['testament_id'])
|
||||
book_list[int(line[0])] = str(line[2], details['encoding'])
|
||||
self.create_book(line[2], book_ref_id, book_details['testament_id'])
|
||||
book_list.update({int(line[0]): line[2]})
|
||||
self.application.process_events()
|
||||
except (IOError, IndexError):
|
||||
log.exception('Loading books from file failed')
|
||||
@ -125,10 +122,7 @@ class CSVBible(BibleDB):
|
||||
try:
|
||||
book_ptr = None
|
||||
details = get_file_encoding(self.verses_file)
|
||||
verse_file = open(self.verses_file, 'rb')
|
||||
if not verse_file.read(3) == '\xEF\xBB\xBF':
|
||||
# no BOM was found
|
||||
verse_file.seek(0)
|
||||
verse_file = open(self.verses_file, 'r', encoding=details['encoding'])
|
||||
verse_reader = csv.reader(verse_file, delimiter=',', quotechar='"')
|
||||
for line in verse_reader:
|
||||
if self.stop_import_flag:
|
||||
@ -136,7 +130,7 @@ class CSVBible(BibleDB):
|
||||
try:
|
||||
line_book = book_list[int(line[0])]
|
||||
except ValueError:
|
||||
line_book = str(line[0], details['encoding'])
|
||||
line_book = line[0]
|
||||
if book_ptr != line_book:
|
||||
book = self.get_book(line_book)
|
||||
book_ptr = book.name
|
||||
@ -144,10 +138,7 @@ class CSVBible(BibleDB):
|
||||
translate('BiblesPlugin.CSVBible',
|
||||
'Importing verses from %s...' % book.name, 'Importing verses from <book name>...'))
|
||||
self.session.commit()
|
||||
try:
|
||||
verse_text = str(line[3], details['encoding'])
|
||||
except UnicodeError:
|
||||
verse_text = str(line[3], 'cp1252')
|
||||
verse_text = line[3]
|
||||
self.create_verse(book.id, line[1], line[2], verse_text)
|
||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.'))
|
||||
self.application.process_events()
|
||||
@ -170,7 +161,7 @@ def get_file_encoding(filename):
|
||||
"""
|
||||
detect_file = None
|
||||
try:
|
||||
detect_file = open(filename, 'r')
|
||||
detect_file = open(filename, 'rb')
|
||||
details = chardet.detect(detect_file.read(1024))
|
||||
except IOError:
|
||||
log.exception('Error detecting file encoding')
|
||||
|
@ -131,6 +131,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
||||
log.info('BibleDB loaded')
|
||||
QtCore.QObject.__init__(self)
|
||||
self.bible_plugin = parent
|
||||
self.session = None
|
||||
if 'path' not in kwargs:
|
||||
raise KeyError('Missing keyword argument "path".')
|
||||
if 'name' not in kwargs and 'file' not in kwargs:
|
||||
@ -144,8 +145,8 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
||||
if 'file' in kwargs:
|
||||
self.file = kwargs['file']
|
||||
Manager.__init__(self, 'bibles', init_schema, self.file, upgrade)
|
||||
if 'file' in kwargs:
|
||||
self.get_name()
|
||||
if self.session and 'file' in kwargs:
|
||||
self.get_name()
|
||||
if 'path' in kwargs:
|
||||
self.path = kwargs['path']
|
||||
self.wizard = None
|
||||
|
@ -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
|
||||
|
||||
|
@ -121,6 +121,8 @@ class BibleManager(RegistryProperties):
|
||||
self.old_bible_databases = []
|
||||
for filename in files:
|
||||
bible = BibleDB(self.parent, path=self.path, file=filename)
|
||||
if not bible.session:
|
||||
continue
|
||||
name = bible.get_name()
|
||||
# Remove corrupted files.
|
||||
if name is None:
|
||||
|
@ -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
|
||||
|
@ -82,9 +82,11 @@ class VerseReferenceList(object):
|
||||
if result[-1] not in [';', ',', '.']:
|
||||
result += ';'
|
||||
result += ' '
|
||||
result = '%s%s, %s' % (result, version['version'], version['copyright'])
|
||||
result += version['version']
|
||||
if version['copyright'].strip():
|
||||
result += ', ' + version['copyright']
|
||||
if version['permission'].strip():
|
||||
result = result + ', ' + version['permission']
|
||||
result += ', ' + version['permission']
|
||||
result = result.rstrip()
|
||||
if result.endswith(','):
|
||||
return result[:len(result) - 1]
|
||||
|
@ -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(
|
||||
|
@ -36,6 +36,10 @@ log = logging.getLogger(__name__)
|
||||
|
||||
__default_settings__ = {
|
||||
'custom/db type': 'sqlite',
|
||||
'custom/db username': '',
|
||||
'custom/db password': '',
|
||||
'custom/db hostname': '',
|
||||
'custom/db database': '',
|
||||
'custom/last search type': CustomSearch.Titles,
|
||||
'custom/display footer': True,
|
||||
'custom/add custom from service': True
|
||||
|
@ -35,6 +35,10 @@ log = logging.getLogger(__name__)
|
||||
|
||||
__default_settings__ = {
|
||||
'images/db type': 'sqlite',
|
||||
'images/db username': '',
|
||||
'images/db password': '',
|
||||
'images/db hostname': '',
|
||||
'images/db database': '',
|
||||
'images/background color': '#000000',
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -117,7 +117,7 @@ from urllib.parse import urlparse, parse_qs
|
||||
from mako.template import Template
|
||||
from PyQt4 import QtCore
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, translate
|
||||
from openlp.core.common import RegistryProperties, AppLocation, Settings, translate, UiStrings
|
||||
from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities, create_thumb
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -232,12 +232,18 @@ class HttpRouter(RegistryProperties):
|
||||
return func, args
|
||||
return None, None
|
||||
|
||||
def set_cache_headers(self):
|
||||
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
self.send_header("Pragma", "no-cache")
|
||||
self.send_header("Expires", "0")
|
||||
|
||||
def do_http_success(self):
|
||||
"""
|
||||
Create a success http header.
|
||||
"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_json_header(self):
|
||||
@ -246,6 +252,7 @@ class HttpRouter(RegistryProperties):
|
||||
"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_http_error(self):
|
||||
@ -254,6 +261,7 @@ class HttpRouter(RegistryProperties):
|
||||
"""
|
||||
self.send_response(404)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_authorisation(self):
|
||||
@ -261,8 +269,10 @@ class HttpRouter(RegistryProperties):
|
||||
Create a needs authorisation http header.
|
||||
"""
|
||||
self.send_response(401)
|
||||
self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"')
|
||||
header = 'Basic realm=\"{}\"'.format(UiStrings().OLPV2)
|
||||
self.send_header('WWW-Authenticate', header)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_not_found(self):
|
||||
@ -271,6 +281,7 @@ class HttpRouter(RegistryProperties):
|
||||
"""
|
||||
self.send_response(404)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
|
||||
|
||||
|
@ -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=''):
|
||||
"""
|
||||
|
@ -312,7 +312,7 @@ def init_schema(url):
|
||||
'authors_songs', metadata,
|
||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||
Column('author_type', types.String(), primary_key=True, nullable=False, server_default=text('""'))
|
||||
Column('author_type', types.Unicode(255), primary_key=True, nullable=False, server_default=text('""'))
|
||||
)
|
||||
|
||||
# Definition of the "songs_topics" table
|
||||
|
@ -109,7 +109,7 @@ def upgrade_4(session, metadata):
|
||||
op.create_table('authors_songs_tmp',
|
||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||
Column('author_type', types.String(), primary_key=True,
|
||||
Column('author_type', types.Unicode(255), primary_key=True,
|
||||
nullable=False, server_default=text('""')))
|
||||
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
||||
op.drop_table('authors_songs')
|
||||
|
@ -50,6 +50,10 @@ from openlp.plugins.songs.lib.songstab import SongsTab
|
||||
log = logging.getLogger(__name__)
|
||||
__default_settings__ = {
|
||||
'songs/db type': 'sqlite',
|
||||
'songs/db username': '',
|
||||
'songs/db password': '',
|
||||
'songs/db hostname': '',
|
||||
'songs/db database': '',
|
||||
'songs/last search type': SongSearch.Entire,
|
||||
'songs/last import type': SongFormat.OpenLyrics,
|
||||
'songs/update service on edit': False,
|
||||
|
@ -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()
|
||||
|
@ -43,6 +43,10 @@ if QtCore.QDate().currentDate().month() < 9:
|
||||
|
||||
__default_settings__ = {
|
||||
'songusage/db type': 'sqlite',
|
||||
'songusage/db username': '',
|
||||
'songuasge/db password': '',
|
||||
'songuasge/db hostname': '',
|
||||
'songuasge/db database': '',
|
||||
'songusage/active': False,
|
||||
'songusage/to date': QtCore.QDate(YEAR, 8, 31),
|
||||
'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1),
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1233
resources/i18n/sk.ts
1233
resources/i18n/sk.ts
File diff suppressed because it is too large
Load Diff
1189
resources/i18n/sv.ts
1189
resources/i18n/sv.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -83,7 +83,7 @@ MODULES = [
|
||||
|
||||
|
||||
OPTIONAL_MODULES = [
|
||||
('MySQLdb', '(MySQL support)', True),
|
||||
('mysql.connector', '(MySQL support)', True),
|
||||
('psycopg2', '(PostgreSQL support)', True),
|
||||
('nose', '(testing framework)', True),
|
||||
('mock', '(testing module)', sys.version_info[1] < 3),
|
||||
|
@ -27,14 +27,13 @@ import os
|
||||
import shutil
|
||||
|
||||
from unittest import TestCase
|
||||
from tests.functional import MagicMock
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from openlp.core.ui import ThemeManager
|
||||
from openlp.core.common import Registry
|
||||
|
||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||
from tests.functional import MagicMock, patch
|
||||
from tests.functional import ANY, MagicMock, patch
|
||||
|
||||
|
||||
class TestThemeManager(TestCase):
|
||||
@ -152,3 +151,23 @@ class TestThemeManager(TestCase):
|
||||
self.assertTrue(os.path.exists(os.path.join(folder, 'Moss on tree', 'Moss on tree.xml')))
|
||||
self.assertEqual(mocked_critical_error_message_box.call_count, 0, 'No errors should have happened')
|
||||
shutil.rmtree(folder)
|
||||
|
||||
def unzip_theme_invalid_version_test(self):
|
||||
"""
|
||||
Test that themes with invalid (< 2.0) or with no version attributes are rejected
|
||||
"""
|
||||
# GIVEN: An instance of ThemeManager whilst mocking a theme that returns a theme with no version attribute
|
||||
with patch('openlp.core.ui.thememanager.zipfile.ZipFile') as mocked_zip_file,\
|
||||
patch('openlp.core.ui.thememanager.ElementTree.getroot') as mocked_getroot,\
|
||||
patch('openlp.core.ui.thememanager.XML'),\
|
||||
patch('openlp.core.ui.thememanager.critical_error_message_box') as mocked_critical_error_message_box:
|
||||
|
||||
mocked_zip_file.return_value = MagicMock(**{'namelist.return_value': [os.path.join('theme', 'theme.xml')]})
|
||||
mocked_getroot.return_value = MagicMock(**{'get.return_value': None})
|
||||
theme_manager = ThemeManager(None)
|
||||
|
||||
# WHEN: unzip_theme is called
|
||||
theme_manager.unzip_theme('theme.file', 'folder')
|
||||
|
||||
# THEN: The critical_error_message_box should have been called
|
||||
mocked_critical_error_message_box.assert_called_once(ANY, ANY)
|
||||
|
97
tests/functional/openlp_plugins/bibles/test_csvimport.py
Normal file
97
tests/functional/openlp_plugins/bibles/test_csvimport.py
Normal file
@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2015 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the CSV Bible importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from unittest import TestCase
|
||||
|
||||
from tests.functional import MagicMock, patch
|
||||
from openlp.plugins.bibles.lib.csvbible import CSVBible
|
||||
from openlp.plugins.bibles.lib.db import BibleDB
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..', '..', '..', 'resources', 'bibles'))
|
||||
|
||||
|
||||
class TestCSVImport(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`csvimport` module.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
|
||||
self.registry_patcher.start()
|
||||
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
|
||||
self.manager_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.registry_patcher.stop()
|
||||
self.manager_patcher.stop()
|
||||
|
||||
def create_importer_test(self):
|
||||
"""
|
||||
Test creating an instance of the CSV file importer
|
||||
"""
|
||||
# GIVEN: A mocked out "manager"
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='.', versefile='.')
|
||||
|
||||
# THEN: The importer should be an instance of BibleDB
|
||||
self.assertIsInstance(importer, BibleDB)
|
||||
|
||||
def file_import_test(self):
|
||||
"""
|
||||
Test the actual import of CSV Bible file
|
||||
"""
|
||||
# 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, 'dk1933.json'), 'rb')
|
||||
test_data = json.loads(result_file.read().decode())
|
||||
books_file = os.path.join(TEST_PATH, 'dk1933-books.csv')
|
||||
verses_file = os.path.join(TEST_PATH, 'dk1933-verses.csv')
|
||||
with patch('openlp.plugins.bibles.lib.csvbible.CSVBible.application'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = CSVBible(mocked_manager, path='.', name='.', booksfile=books_file, versefile=verses_file)
|
||||
importer.wizard = mocked_import_wizard
|
||||
importer.get_book_ref_id_by_name = MagicMock()
|
||||
importer.create_verse = MagicMock()
|
||||
importer.create_book = MagicMock()
|
||||
importer.session = MagicMock()
|
||||
importer.get_language = MagicMock()
|
||||
importer.get_language.return_value = 'Danish'
|
||||
importer.get_book = MagicMock()
|
||||
|
||||
# WHEN: Importing 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.get_book().id, '1', verse_tag, verse_text)
|
||||
importer.create_book.assert_any_call('1. Mosebog', importer.get_book_ref_id_by_name(), 1)
|
||||
importer.create_book.assert_any_call('1. Krønikebog', importer.get_book_ref_id_by_name(), 1)
|
@ -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)
|
||||
|
@ -25,7 +25,6 @@ This module contains tests for the lib submodule of the Remotes plugin.
|
||||
import os
|
||||
import urllib.request
|
||||
from unittest import TestCase
|
||||
from PyQt4 import QtCore
|
||||
from openlp.core.common import Settings, Registry
|
||||
from openlp.core.ui import ServiceManager
|
||||
from openlp.plugins.remotes.lib.httpserver import HttpRouter
|
||||
@ -128,7 +127,7 @@ class TestRouter(TestCase, TestMixin):
|
||||
|
||||
# THEN: the function should have been called only once
|
||||
self.router.send_response.assert_called_once_with(401)
|
||||
self.assertEqual(self.router.send_header.call_count, 2, 'The header should have been called twice.')
|
||||
self.assertEqual(self.router.send_header.call_count, 5, 'The header should have been called five times.')
|
||||
|
||||
def get_content_type_test(self):
|
||||
"""
|
||||
@ -187,7 +186,6 @@ class TestRouter(TestCase, TestMixin):
|
||||
|
||||
# THEN: it should return a 404
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
self.router.send_header.assert_called_once_with('Content-type', 'text/html')
|
||||
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
|
||||
|
||||
def serve_file_with_valid_params_test(self):
|
||||
@ -217,14 +215,19 @@ class TestRouter(TestCase, TestMixin):
|
||||
"""
|
||||
Test the serve_thumbnail routine without params
|
||||
"""
|
||||
# GIVEN: mocked environment
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
|
||||
# WHEN: I request a thumbnail
|
||||
self.router.serve_thumbnail()
|
||||
|
||||
# THEN: The headers should be set correctly
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
|
||||
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
|
||||
self.assertEqual(self.router.send_response.call_count, 1, 'Send response should be called once')
|
||||
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers should be called once')
|
||||
|
||||
def serve_thumbnail_with_invalid_params_test(self):
|
||||
"""
|
||||
@ -240,7 +243,7 @@ class TestRouter(TestCase, TestMixin):
|
||||
self.router.serve_thumbnail('badcontroller', 'tecnologia 1.pptx/slide1.png')
|
||||
|
||||
# THEN: a 404 should be returned
|
||||
self.assertEqual(len(self.router.send_header.mock_calls), 1, 'One header')
|
||||
self.assertEqual(len(self.router.send_header.mock_calls), 4, 'header should be called 4 times')
|
||||
self.assertEqual(len(self.router.send_response.mock_calls), 1, 'One response')
|
||||
self.assertEqual(len(self.router.wfile.mock_calls), 1, 'Once call to write to the socket')
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
@ -284,7 +287,7 @@ class TestRouter(TestCase, TestMixin):
|
||||
mocked_location.get_section_data_path.return_value = ''
|
||||
|
||||
# WHEN: pass good controller and filename
|
||||
result = self.router.serve_thumbnail('presentations', '{0}x{1}'.format(width, height), file_name)
|
||||
self.router.serve_thumbnail('presentations', '{0}x{1}'.format(width, height), file_name)
|
||||
|
||||
# THEN: a file should be returned
|
||||
self.assertEqual(self.router.send_header.call_count, 1, 'One header')
|
||||
|
@ -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')
|
||||
|
@ -125,7 +125,7 @@ class TestInit(TestCase, TestMixin):
|
||||
# WHEN: Calling parse_options
|
||||
results = parse_options(options)
|
||||
|
||||
# THEN: A tuple should be returned with the parsed options and left over args
|
||||
# THEN: A tuple should be returned with the parsed options and left over options
|
||||
self.assertEqual(results, (Values({'no_error_form': True, 'dev_version': True, 'portable': True,
|
||||
'style': 'style', 'loglevel': 'debug'}), ['extra', 'qt', 'args']))
|
||||
|
||||
@ -136,10 +136,51 @@ class TestInit(TestCase, TestMixin):
|
||||
# GIVEN: A list of valid options
|
||||
options = ['openlp.py', '-e', '-l', 'debug', '-pd', '-s', 'style', 'extra', 'qt', 'args']
|
||||
|
||||
# WHEN: Passing in the options through sys.argv and calling parse_args with None
|
||||
# WHEN: Passing in the options through sys.argv and calling parse_options with None
|
||||
with patch.object(sys, 'argv', options):
|
||||
results = parse_options(None)
|
||||
|
||||
# THEN: parse_args should return a tuple of valid options and of left over options that OpenLP does not use
|
||||
# THEN: parse_options should return a tuple of valid options and of left over options that OpenLP does not use
|
||||
self.assertEqual(results, (Values({'no_error_form': True, 'dev_version': True, 'portable': True,
|
||||
'style': 'style', 'loglevel': 'debug'}), ['extra', 'qt', 'args']))
|
||||
|
||||
def test_parse_options_valid_long_options(self):
|
||||
"""
|
||||
Test that parse_options parses valid long options correctly
|
||||
"""
|
||||
# GIVEN: A list of valid options
|
||||
options = ['--no-error-form', 'extra', '--log-level', 'debug', 'qt', '--portable', '--dev-version', 'args',
|
||||
'--style=style']
|
||||
|
||||
# WHEN: Calling parse_options
|
||||
results = parse_options(options)
|
||||
|
||||
# THEN: parse_options should return a tuple of valid options and of left over options that OpenLP does not use
|
||||
self.assertEqual(results, (Values({'no_error_form': True, 'dev_version': True, 'portable': True,
|
||||
'style': 'style', 'loglevel': 'debug'}), ['extra', 'qt', 'args']))
|
||||
|
||||
def test_parse_options_help_option(self):
|
||||
"""
|
||||
Test that parse_options raises an SystemExit exception when called with invalid options
|
||||
"""
|
||||
# GIVEN: The --help option
|
||||
options = ['--help']
|
||||
|
||||
# WHEN: Calling parse_options
|
||||
# THEN: parse_options should raise an SystemExit exception with exception code 0
|
||||
with self.assertRaises(SystemExit) as raised_exception:
|
||||
parse_options(options)
|
||||
self.assertEqual(raised_exception.exception.code, 0)
|
||||
|
||||
def test_parse_options_invalid_option(self):
|
||||
"""
|
||||
Test that parse_options raises an SystemExit exception when called with invalid options
|
||||
"""
|
||||
# GIVEN: A list including invalid options
|
||||
options = ['-t']
|
||||
|
||||
# WHEN: Calling parse_options
|
||||
# THEN: parse_options should raise an SystemExit exception with exception code 2
|
||||
with self.assertRaises(SystemExit) as raised_exception:
|
||||
parse_options(options)
|
||||
self.assertEqual(raised_exception.exception.code, 2)
|
||||
|
22
tests/resources/bibles/dk1933-books.csv
Normal file
22
tests/resources/bibles/dk1933-books.csv
Normal file
@ -0,0 +1,22 @@
|
||||
1,1,1. Mosebog,1Mos
|
||||
2,1,2. Mosebog,2Mos
|
||||
3,1,3. Mosebog,3Mos
|
||||
4,1,4. Mosebog,4Mos
|
||||
5,1,5. Mosebog,5Mos
|
||||
6,1,Josvabogen,jos
|
||||
7,1,Dommerbogen,dom
|
||||
8,1,Ruths Bog,ruth
|
||||
9,1,1. Samuelsbog,1Sam
|
||||
10,1,2. Samuelsbog,2Sam
|
||||
11,1,1. Kongebog,1kong
|
||||
12,1,2. Kongebog,2kong
|
||||
13,1,1. Krønikebog,1kron
|
||||
14,1,2. Krønikebog,2kron
|
||||
15,1,Ezras Bog,ezra
|
||||
16,1,Nehemias' Bog,neh
|
||||
17,1,Esters Bog,est
|
||||
18,1,Jobs Bog,job
|
||||
19,1,Salmernes Bog,sl
|
||||
20,1,Ordsprogenes Bog,ordsp
|
||||
21,1,Prædikerens Bog,prad
|
||||
22,1,Højsangen,hojs
|
|
10
tests/resources/bibles/dk1933-verses.csv
Normal file
10
tests/resources/bibles/dk1933-verses.csv
Normal file
@ -0,0 +1,10 @@
|
||||
1,1,1,"I Begyndelsen skabte Gud Himmelen og Jorden."
|
||||
1,1,2,"Og Jorden var øde og tom, og der var Mørke over Verdensdybet. Men Guds Ånd svævede over Vandene."
|
||||
1,1,3,"Og Gud sagde: ""Der blive Lys!"" Og der blev Lys."
|
||||
1,1,4,"Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket,"
|
||||
1,1,5,"og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, og det blev Morgen, første Dag."
|
||||
1,1,6,"Derpå sagde Gud: ""Der blive en Hvælving midt i Vandene til at skille Vandene ad!"""
|
||||
1,1,7,"Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen fra Vandet over Hvælvingen;"
|
||||
1,1,8,"og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag."
|
||||
1,1,9,"Derpå sagde Gud: ""Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!"" Og således skete det;"
|
||||
1,1,10,"og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, at det var godt."
|
|
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>
|
Loading…
Reference in New Issue
Block a user