Fixes a few bugs, and some path lib refactors

bzr-revno: 2850
This commit is contained in:
Phill 2019-03-17 21:14:38 +00:00 committed by Philip Ridout
commit 0669a6c5d4
47 changed files with 187 additions and 208 deletions

View File

@ -39,8 +39,8 @@ def deploy_zipfile(app_root_path, zip_name):
:return: None :return: None
""" """
zip_path = app_root_path / zip_name zip_path = app_root_path / zip_name
web_zip = ZipFile(str(zip_path)) web_zip = ZipFile(zip_path)
web_zip.extractall(str(app_root_path)) web_zip.extractall(app_root_path)
def download_sha256(): def download_sha256():

View File

@ -317,8 +317,7 @@ def set_up_logging(log_path):
""" """
create_paths(log_path, do_not_log=True) create_paths(log_path, do_not_log=True)
file_path = log_path / 'openlp.log' file_path = log_path / 'openlp.log'
# TODO: FileHandler accepts a Path object in Py3.6 logfile = logging.FileHandler(file_path, 'w', encoding='UTF-8')
logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8')
logfile.setFormatter(logging.Formatter('%(asctime)s %(threadName)s %(name)-55s %(levelname)-8s %(message)s')) logfile.setFormatter(logging.Formatter('%(asctime)s %(threadName)s %(name)-55s %(levelname)-8s %(message)s'))
log.addHandler(logfile) log.addHandler(logfile)
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
@ -364,7 +363,7 @@ def main(args=None):
portable_settings_path = data_path / 'OpenLP.ini' portable_settings_path = data_path / 'OpenLP.ini'
# Make this our settings file # Make this our settings file
log.info('INI file: {name}'.format(name=portable_settings_path)) log.info('INI file: {name}'.format(name=portable_settings_path))
Settings.set_filename(str(portable_settings_path)) Settings.set_filename(portable_settings_path)
portable_settings = Settings() portable_settings = Settings()
# Set our data path # Set our data path
log.info('Data path: {name}'.format(name=data_path)) log.info('Data path: {name}'.format(name=data_path))
@ -405,7 +404,12 @@ def main(args=None):
None, translate('OpenLP', 'Settings Upgrade'), None, translate('OpenLP', 'Settings Upgrade'),
translate('OpenLP', 'Your settings are about to be upgraded. A backup will be created at ' translate('OpenLP', 'Your settings are about to be upgraded. A backup will be created at '
'{back_up_path}').format(back_up_path=back_up_path)) '{back_up_path}').format(back_up_path=back_up_path))
settings.export(back_up_path) try:
settings.export(back_up_path)
except OSError:
QtWidgets.QMessageBox.warning(
None, translate('OpenLP', 'Settings Upgrade'),
translate('OpenLP', 'Settings back up failed.\n\nContinuining to upgrade.'))
settings.upgrade_settings() settings.upgrade_settings()
# First time checks in settings # First time checks in settings
if not Settings().value('core/has run wizard'): if not Settings().value('core/has run wizard'):

View File

@ -474,10 +474,10 @@ def get_file_encoding(file_path):
if not chunk: if not chunk:
break break
detector.feed(chunk) detector.feed(chunk)
detector.close()
return detector.result
except OSError: except OSError:
log.exception('Error detecting file encoding') log.exception('Error detecting file encoding')
finally:
return detector.close()
def normalize_str(irregular_string): def normalize_str(irregular_string):

View File

@ -78,7 +78,7 @@ class Path(PathVariant):
:param onerror: Handler function to handle any errors :param onerror: Handler function to handle any errors
:rtype: None :rtype: None
""" """
shutil.rmtree(str(self), ignore_errors, onerror) shutil.rmtree(self, ignore_errors, onerror)
def replace_params(args, kwargs, params): def replace_params(args, kwargs, params):

View File

@ -540,7 +540,7 @@ class Settings(QtCore.QSettings):
old_values = [self._convert_value(old_value, default_value) old_values = [self._convert_value(old_value, default_value)
for old_value, default_value in zip(old_values, default_values)] for old_value, default_value in zip(old_values, default_values)]
# Iterate over our rules and check what the old_value should be "converted" to. # Iterate over our rules and check what the old_value should be "converted" to.
new_value = None new_value = old_values[0]
for new_rule, old_rule in rules: for new_rule, old_rule in rules:
# If the value matches with the condition (rule), then use the provided value. This is used to # If the value matches with the condition (rule), then use the provided value. This is used to
# convert values. E. g. an old value 1 results in True, and 0 in False. # convert values. E. g. an old value 1 results in True, and 0 in False.

View File

@ -6,7 +6,7 @@
<style type="text/css"> <style type="text/css">
body { body {
background: transparent !important; background: transparent !important;
color: #fff !important; color: rgb(255, 255, 255) !important;
} }
sup { sup {
vertical-align: super !important; vertical-align: super !important;

View File

@ -315,7 +315,7 @@ var Display = {
section.setAttribute("style", "height: 100%; width: 100%; position: relative;"); section.setAttribute("style", "height: 100%; width: 100%; position: relative;");
var img = document.createElement('img'); var img = document.createElement('img');
img.src = image; img.src = image;
img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;"); img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-height: 100%; max-width: 100%");
section.appendChild(img); section.appendChild(img);
slidesDiv.appendChild(section); slidesDiv.appendChild(section);
Display._slides['0'] = 0; Display._slides['0'] = 0;

View File

@ -242,16 +242,16 @@ def image_to_byte(image, base_64=True):
""" """
Resize an image to fit on the current screen for the web and returns it as a byte stream. Resize an image to fit on the current screen for the web and returns it as a byte stream.
:param image: The image to converted. :param image: The image to be converted.
:param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array. :param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
To preserve original intention, this defaults to True To preserve original intention, this defaults to True
""" """
log.debug('image_to_byte - start') log.debug('image_to_byte - start')
byte_array = QtCore.QByteArray() byte_array = QtCore.QByteArray()
# use buffer to store pixmap into byteArray # use buffer to store pixmap into byteArray
buffie = QtCore.QBuffer(byte_array) buffer = QtCore.QBuffer(byte_array)
buffie.open(QtCore.QIODevice.WriteOnly) buffer.open(QtCore.QIODevice.WriteOnly)
image.save(buffie, "PNG") image.save(buffer, "PNG")
log.debug('image_to_byte - end') log.debug('image_to_byte - end')
if not base_64: if not base_64:
return byte_array return byte_array
@ -263,15 +263,13 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
""" """
Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb. Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb.
:param image_path: The image file to create the icon from. :param openlp.core.common.path.Path image_path: The image file to create the icon from.
:param thumb_path: The filename to save the thumbnail to. :param openlp.core.common.path.Path thumb_path: The filename to save the thumbnail to.
:param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``. :param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``.
:param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default :param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default
height of 88 is used. height of 88 is used.
:return: The final icon. :return: The final icon.
""" """
# TODO: To path object
thumb_path = Path(thumb_path)
reader = QtGui.QImageReader(str(image_path)) reader = QtGui.QImageReader(str(image_path))
if size is None: if size is None:
# No size given; use default height of 88 # No size given; use default height of 88

View File

@ -132,8 +132,9 @@ def get_db_path(plugin_name, db_file_name=None):
Create a path to a database from the plugin name and database name Create a path to a database from the plugin name and database name
:param plugin_name: Name of plugin :param plugin_name: Name of plugin
:param db_file_name: File name of database :param openlp.core.common.path.Path | str | None db_file_name: File name of database
:return: The path to the database as type str :return: The path to the database
:rtype: str
""" """
if db_file_name is None: if db_file_name is None:
return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name), return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
@ -144,15 +145,15 @@ def get_db_path(plugin_name, db_file_name=None):
return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name) return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name)
def handle_db_error(plugin_name, db_file_name): def handle_db_error(plugin_name, db_file_path):
""" """
Log and report to the user that a database cannot be loaded Log and report to the user that a database cannot be loaded
:param plugin_name: Name of plugin :param plugin_name: Name of plugin
:param db_file_name: File name of database :param openlp.core.common.path.Path db_file_path: File name of database
:return: None :return: None
""" """
db_path = get_db_path(plugin_name, db_file_name) db_path = get_db_path(plugin_name, db_file_path)
log.exception('Error loading database: {db}'.format(db=db_path)) log.exception('Error loading database: {db}'.format(db=db_path))
critical_error_message_box(translate('OpenLP.Manager', 'Database Error'), critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
translate('OpenLP.Manager', translate('OpenLP.Manager',
@ -161,10 +162,13 @@ def handle_db_error(plugin_name, db_file_name):
def init_url(plugin_name, db_file_name=None): def init_url(plugin_name, db_file_name=None):
""" """
Return the database URL. Construct the connection string for a database.
:param plugin_name: The name of the plugin for the database creation. :param plugin_name: The name of the plugin for the database creation.
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used. :param openlp.core.common.path.Path | str | None db_file_name: The database file name. Defaults to None resulting
in the plugin_name being used.
:return: The database URL
:rtype: str
""" """
settings = Settings() settings = Settings()
settings.beginGroup(plugin_name) settings.beginGroup(plugin_name)
@ -345,7 +349,7 @@ class Manager(object):
Runs the initialisation process that includes creating the connection to the database and the tables if they do Runs the initialisation process that includes creating the connection to the database and the tables if they do
not exist. not exist.
:param plugin_name: The name to setup paths and settings section names :param plugin_name: The name to setup paths and settings section names
:param init_schema: The init_schema function for this database :param init_schema: The init_schema function for this database
:param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None :param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None
resulting in the plugin_name being used. resulting in the plugin_name being used.
@ -357,14 +361,14 @@ class Manager(object):
self.db_url = None self.db_url = None
if db_file_path: if db_file_path:
log.debug('Manager: Creating new DB url') log.debug('Manager: Creating new DB url')
self.db_url = init_url(plugin_name, str(db_file_path)) self.db_url = init_url(plugin_name, str(db_file_path)) # TOdO :PATHLIB
else: else:
self.db_url = init_url(plugin_name) self.db_url = init_url(plugin_name)
if upgrade_mod: if upgrade_mod:
try: try:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod) db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
except (SQLAlchemyError, DBAPIError): except (SQLAlchemyError, DBAPIError):
handle_db_error(plugin_name, str(db_file_path)) handle_db_error(plugin_name, db_file_path)
return return
if db_ver > up_ver: if db_ver > up_ver:
critical_error_message_box( critical_error_message_box(
@ -379,7 +383,7 @@ class Manager(object):
try: try:
self.session = init_schema(self.db_url) self.session = init_schema(self.db_url)
except (SQLAlchemyError, DBAPIError): except (SQLAlchemyError, DBAPIError):
handle_db_error(plugin_name, str(db_file_path)) handle_db_error(plugin_name, db_file_path)
else: else:
self.session = session self.session = session

View File

@ -454,15 +454,16 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
""" """
pass pass
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Live,
context=ServiceItemContext.Live): file_path=None):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service Item to be processed :param service_item: The service Item to be processed
:param item: The database item to be used to build the service item :param item: The database item to be used to build the service item
:param xml_version:
:param remote: Was this remote triggered (False) :param remote: Was this remote triggered (False)
:param context: The service context :param context: The service context
:param openlp.core.common.path.Path file_path:
""" """
raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin') raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin')
@ -624,17 +625,16 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
'You must select a {title} ' 'You must select a {title} '
'service item.').format(title=self.title)) 'service item.').format(title=self.title))
def build_service_item(self, item=None, xml_version=False, remote=False, context=ServiceItemContext.Live): def build_service_item(self, item=None, remote=False, context=ServiceItemContext.Live):
""" """
Common method for generating a service item Common method for generating a service item
:param item: Service Item to be built. :param item: Service Item to be built.
:param xml_version: version of XML (False)
:param remote: Remote triggered (False) :param remote: Remote triggered (False)
:param context: The context on which this is called :param context: The context on which this is called
""" """
service_item = ServiceItem(self.plugin) service_item = ServiceItem(self.plugin)
service_item.add_icon() service_item.add_icon()
if self.generate_slide_data(service_item, item, xml_version, remote, context): if self.generate_slide_data(service_item, item=item, remote=remote, context=context):
return service_item return service_item
else: else:
return None return None

View File

@ -263,7 +263,7 @@ class ServiceItem(RegistryProperties):
file_location = os.path.join(path, file_name) file_location = os.path.join(path, file_name)
file_location_hash = md5_hash(file_location.encode('utf-8')) file_location_hash = md5_hash(file_location.encode('utf-8'))
image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails', image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
file_location_hash, ntpath.basename(image)) file_location_hash, ntpath.basename(image)) # TODO: Pathlib
self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title, self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title,
'notes': notes, 'thumbnail': image}) 'notes': notes, 'thumbnail': image})
# if self.is_capable(ItemCapabilities.HasThumbnails): # if self.is_capable(ItemCapabilities.HasThumbnails):
@ -361,7 +361,7 @@ class ServiceItem(RegistryProperties):
if isinstance(file_path, str): if isinstance(file_path, str):
# Handle service files prior to OpenLP 3.0 # Handle service files prior to OpenLP 3.0
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename # Windows can handle both forward and backward slashes, so we use ntpath to get the basename
file_path = Path(path, ntpath.basename(file_path)) file_path = path / ntpath.basename(file_path)
self.background_audio.append(file_path) self.background_audio.append(file_path)
self.theme_overwritten = header.get('theme_overwritten', False) self.theme_overwritten = header.get('theme_overwritten', False)
if self.service_item_type == ServiceItemType.Text: if self.service_item_type == ServiceItemType.Text:
@ -374,7 +374,7 @@ class ServiceItem(RegistryProperties):
if path: if path:
self.has_original_files = False self.has_original_files = False
for text_image in service_item['serviceitem']['data']: for text_image in service_item['serviceitem']['data']:
file_path = os.path.join(path, text_image) file_path = path / text_image
self.add_from_image(file_path, text_image, background) self.add_from_image(file_path, text_image, background)
else: else:
for text_image in service_item['serviceitem']['data']: for text_image in service_item['serviceitem']['data']:

View File

@ -478,7 +478,7 @@ class AdvancedTab(SettingsTab):
minute=self.service_name_time.time().minute() minute=self.service_name_time.time().minute()
) )
try: try:
service_name_example = format_time(str(self.service_name_edit.text()), local_time) service_name_example = format_time(self.service_name_edit.text(), local_time)
except ValueError: except ValueError:
preset_is_valid = False preset_is_valid = False
service_name_example = translate('OpenLP.AdvancedTab', 'Syntax error.') service_name_example = translate('OpenLP.AdvancedTab', 'Syntax error.')

View File

@ -77,11 +77,11 @@ class Ui_ExceptionDialog(object):
self.save_report_button = create_button(exception_dialog, 'save_report_button', self.save_report_button = create_button(exception_dialog, 'save_report_button',
icon=UiIcons().save, icon=UiIcons().save,
click=self.on_save_report_button_clicked) click=self.on_save_report_button_clicked)
self.attach_tile_button = create_button(exception_dialog, 'attach_tile_button', self.attach_file_button = create_button(exception_dialog, 'attach_file_button',
icon=UiIcons().open, icon=UiIcons().open,
click=self.on_attach_file_button_clicked) click=self.on_attach_file_button_clicked)
self.button_box = create_button_box(exception_dialog, 'button_box', ['close'], self.button_box = create_button_box(exception_dialog, 'button_box', ['close'],
[self.send_report_button, self.save_report_button, self.attach_tile_button]) [self.send_report_button, self.save_report_button, self.attach_file_button])
self.exception_layout.addWidget(self.button_box) self.exception_layout.addWidget(self.button_box)
self.retranslate_ui(exception_dialog) self.retranslate_ui(exception_dialog)
@ -112,4 +112,4 @@ class Ui_ExceptionDialog(object):
).format(first_part=exception_part1)) ).format(first_part=exception_part1))
self.send_report_button.setText(translate('OpenLP.ExceptionDialog', 'Send E-Mail')) self.send_report_button.setText(translate('OpenLP.ExceptionDialog', 'Send E-Mail'))
self.save_report_button.setText(translate('OpenLP.ExceptionDialog', 'Save to File')) self.save_report_button.setText(translate('OpenLP.ExceptionDialog', 'Save to File'))
self.attach_tile_button.setText(translate('OpenLP.ExceptionDialog', 'Attach File')) self.attach_file_button.setText(translate('OpenLP.ExceptionDialog', 'Attach File'))

View File

@ -95,12 +95,14 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
""" """
Saving exception log and system information to a file. Saving exception log and system information to a file.
""" """
file_path, filter_used = FileDialog.getSaveFileName( while True:
self, file_path, filter_used = FileDialog.getSaveFileName(
translate('OpenLP.ExceptionForm', 'Save Crash Report'), self,
Settings().value(self.settings_section + '/last directory'), translate('OpenLP.ExceptionForm', 'Save Crash Report'),
translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)')) Settings().value(self.settings_section + '/last directory'),
if file_path: translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))
if file_path is None:
break
Settings().setValue(self.settings_section + '/last directory', file_path.parent) Settings().setValue(self.settings_section + '/last directory', file_path.parent)
opts = self._create_report() opts = self._create_report()
report_text = self.report_text.format(version=opts['version'], description=opts['description'], report_text = self.report_text.format(version=opts['version'], description=opts['description'],
@ -108,8 +110,13 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
try: try:
with file_path.open('w') as report_file: with file_path.open('w') as report_file:
report_file.write(report_text) report_file.write(report_text)
except OSError: break
except OSError as e:
log.exception('Failed to write crash report') log.exception('Failed to write crash report')
QtWidgets.QMessageBox.warning(
self, translate('OpenLP.ExceptionDialog', 'Failed to Save Report'),
translate('OpenLP.ExceptionDialog', 'The following error occured when saving the report.\n\n'
'{exception}').format(file_name=file_path, exception=e))
def on_send_report_button_clicked(self): def on_send_report_button_clicked(self):
""" """

View File

@ -47,7 +47,6 @@ class GeneralTab(SettingsTab):
""" """
Initialise the general settings tab Initialise the general settings tab
""" """
self.logo_file = ':/graphics/openlp-splash-screen.png'
self.logo_background_color = '#ffffff' self.logo_background_color = '#ffffff'
self.screens = ScreenList() self.screens = ScreenList()
self.icon_path = ':/icon/openlp-logo.svg' self.icon_path = ':/icon/openlp-logo.svg'

View File

@ -1334,7 +1334,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
self.show_status_message( self.show_status_message(
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} ' translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
'- Please wait for copy to finish').format(path=self.new_data_path)) '- Please wait for copy to finish').format(path=self.new_data_path))
dir_util.copy_tree(str(old_data_path), str(self.new_data_path)) dir_util.copy_tree(old_data_path, self.new_data_path)
self.log_info('Copy successful') self.log_info('Copy successful')
except (OSError, DistutilsFileError) as why: except (OSError, DistutilsFileError) as why:
self.application.set_normal_cursor() self.application.set_normal_cursor()

View File

@ -25,7 +25,6 @@ The service manager sets up, loads, saves and manages services.
import html import html
import json import json
import os import os
import shutil
import zipfile import zipfile
from contextlib import suppress from contextlib import suppress
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -234,7 +233,7 @@ class Ui_ServiceManager(object):
self.service_manager_list.itemExpanded.connect(self.expanded) self.service_manager_list.itemExpanded.connect(self.expanded)
# Last little bits of setting up # Last little bits of setting up
self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme') self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
self.service_path = str(AppLocation.get_section_data_path('servicemanager')) self.service_path = AppLocation.get_section_data_path('servicemanager')
# build the drag and drop context menu # build the drag and drop context menu
self.dnd_menu = QtWidgets.QMenu() self.dnd_menu = QtWidgets.QMenu()
self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item')) self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
@ -590,11 +589,11 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
self.main_window.increment_progress_bar(service_content_size) self.main_window.increment_progress_bar(service_content_size)
# Finally add all the listed media files. # Finally add all the listed media files.
for write_path in write_list: for write_path in write_list:
zip_file.write(str(write_path), str(write_path)) zip_file.write(write_path, write_path)
self.main_window.increment_progress_bar(write_path.stat().st_size) self.main_window.increment_progress_bar(write_path.stat().st_size)
with suppress(FileNotFoundError): with suppress(FileNotFoundError):
file_path.unlink() file_path.unlink()
os.link(temp_file.name, str(file_path)) os.link(temp_file.name, file_path)
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', file_path.parent) Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', file_path.parent)
except (PermissionError, OSError) as error: except (PermissionError, OSError) as error:
self.log_exception('Failed to save service to disk: {name}'.format(name=file_path)) self.log_exception('Failed to save service to disk: {name}'.format(name=file_path))
@ -679,7 +678,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
service_data = None service_data = None
self.application.set_busy_cursor() self.application.set_busy_cursor()
try: try:
with zipfile.ZipFile(str(file_path)) as zip_file: with zipfile.ZipFile(file_path) as zip_file:
compressed_size = 0 compressed_size = 0
for zip_info in zip_file.infolist(): for zip_info in zip_file.infolist():
compressed_size += zip_info.compress_size compressed_size += zip_info.compress_size
@ -692,7 +691,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
service_data = json_file.read() service_data = json_file.read()
else: else:
zip_info.filename = os.path.basename(zip_info.filename) zip_info.filename = os.path.basename(zip_info.filename)
zip_file.extract(zip_info, str(self.service_path)) zip_file.extract(zip_info, self.service_path)
self.main_window.increment_progress_bar(zip_info.compress_size) self.main_window.increment_progress_bar(zip_info.compress_size)
if service_data: if service_data:
items = json.loads(service_data, cls=OpenLPJsonDecoder) items = json.loads(service_data, cls=OpenLPJsonDecoder)
@ -705,11 +704,13 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
else: else:
raise ValidationError(msg='No service data found') raise ValidationError(msg='No service data found')
except (NameError, OSError, ValidationError, zipfile.BadZipFile): except (NameError, OSError, ValidationError, zipfile.BadZipFile):
self.application.set_normal_cursor()
self.log_exception('Problem loading service file {name}'.format(name=file_path)) self.log_exception('Problem loading service file {name}'.format(name=file_path))
critical_error_message_box( critical_error_message_box(
message=translate('OpenLP.ServiceManager', message=translate('OpenLP.ServiceManager',
'The service file {file_path} could not be loaded because it is either corrupt, or ' 'The service file {file_path} could not be loaded because it is either corrupt, '
'not a valid OpenLP 2 or OpenLP 3 service file.'.format(file_path=file_path))) 'inaccessible, or not a valid OpenLP 2 or OpenLP 3 service file.'
).format(file_path=file_path))
self.main_window.finished_progress_bar() self.main_window.finished_progress_bar()
self.application.set_normal_cursor() self.application.set_normal_cursor()
self.repaint_service_list(-1, -1) self.repaint_service_list(-1, -1)
@ -1237,11 +1238,11 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
""" """
Empties the service_path of temporary files on system exit. Empties the service_path of temporary files on system exit.
""" """
for file_name in os.listdir(self.service_path): for file_path in self.service_path.iterdir():
file_path = Path(self.service_path, file_name)
delete_file(file_path) delete_file(file_path)
if os.path.exists(os.path.join(self.service_path, 'audio')): audio_path = self.service_path / 'audio'
shutil.rmtree(os.path.join(self.service_path, 'audio'), True) if audio_path.exists():
audio_path.rmtree(True)
def on_theme_combo_box_selected(self, current_index): def on_theme_combo_box_selected(self, current_index):
""" """

View File

@ -150,7 +150,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
self.global_theme = Settings().value(self.settings_section + '/global theme') self.global_theme = Settings().value(self.settings_section + '/global theme')
self.build_theme_path() self.build_theme_path()
self.load_first_time_themes() self.load_first_time_themes()
self.upgrade_themes() self.upgrade_themes() # TODO: Can be removed when upgrade path from OpenLP 2.4 no longer needed
def bootstrap_post_set_up(self): def bootstrap_post_set_up(self):
""" """
@ -422,10 +422,10 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
:rtype: bool :rtype: bool
""" """
try: try:
with zipfile.ZipFile(str(theme_path), 'w') as theme_zip: with zipfile.ZipFile(theme_path, 'w') as theme_zip:
source_path = self.theme_path / theme_name source_path = self.theme_path / theme_name
for file_path in source_path.iterdir(): for file_path in source_path.iterdir():
theme_zip.write(str(file_path), os.path.join(theme_name, file_path.name)) theme_zip.write(file_path, Path(theme_name, file_path.name))
return True return True
except OSError as ose: except OSError as ose:
self.log_exception('Export Theme Failed') self.log_exception('Export Theme Failed')
@ -567,10 +567,10 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
json_theme = False json_theme = False
theme_name = "" theme_name = ""
try: try:
with zipfile.ZipFile(str(file_path)) as theme_zip: with zipfile.ZipFile(file_path) as theme_zip:
json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json'] json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json']
if len(json_file) != 1: if len(json_file) != 1:
# TODO: remove XML handling after the 2.6 release. # TODO: remove XML handling after once the upgrade path from 2.4 is no longer required
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml'] xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
if len(xml_file) != 1: if len(xml_file) != 1:
self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file))) self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file)))
@ -607,12 +607,12 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
else: else:
with full_name.open('wb') as out_file: with full_name.open('wb') as out_file:
out_file.write(theme_zip.read(zipped_file)) out_file.write(theme_zip.read(zipped_file))
except (OSError, zipfile.BadZipFile): except (OSError, ValidationError, zipfile.BadZipFile):
self.log_exception('Importing theme from zip failed {name}'.format(name=file_path)) self.log_exception('Importing theme from zip failed {name}'.format(name=file_path))
raise ValidationError critical_error_message_box(
except ValidationError: translate('OpenLP.ThemeManager', 'Import Error'),
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'), translate('OpenLP.ThemeManager', 'There was a problem imoorting {file_name}.\n\nIt is corrupt,'
translate('OpenLP.ThemeManager', 'File is not a valid theme.')) 'inaccessible or not a valid theme.').format(file_name=file_path))
finally: finally:
if not abort_import: if not abort_import:
# As all files are closed, we can create the Theme. # As all files are closed, we can create the Theme.

View File

@ -200,7 +200,7 @@ def get_library_versions():
""" """
library_versions = OrderedDict([(library, _get_lib_version(*args)) for library, args in LIBRARIES.items()]) library_versions = OrderedDict([(library, _get_lib_version(*args)) for library, args in LIBRARIES.items()])
# Just update the dict for display purposes, changing the None to a '-' # Just update the dict for display purposes, changing the None to a '-'
for library, version in library_versions: for library, version in library_versions.items():
if version is None: if version is None:
library_versions[library] = '-' library_versions[library] = '-'
return library_versions return library_versions

View File

@ -352,7 +352,7 @@ class PathEdit(QtWidgets.QWidget):
:rtype: None :rtype: None
""" """
if self._path != path: if self._path != path:
self._path = path self.path = path
self.pathChanged.emit(path) self.pathChanged.emit(path)

View File

@ -27,13 +27,11 @@ import logging
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_macosx from openlp.core.common import is_macosx
from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.i18n import translate
from openlp.core.common.mixins import RegistryProperties from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.ui import add_welcome_page from openlp.core.lib.ui import add_welcome_page
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.dialogs import FileDialog
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -280,41 +278,3 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
self.finish_button.setVisible(True) self.finish_button.setVisible(True)
self.cancel_button.setVisible(False) self.cancel_button.setVisible(False)
self.application.process_events() self.application.process_events()
def get_file_name(self, title, editbox, setting_name, filters=''):
"""
Opens a FileDialog and saves the filename to the given editbox.
:param str title: The title of the dialog.
:param QtWidgets.QLineEdit editbox: An QLineEdit.
:param str setting_name: The place where to save the last opened directory.
:param str filters: The file extension filters. It should contain the file description
as well as the file extension. For example::
'OpenLP 2 Databases (*.sqlite)'
:rtype: None
"""
if filters:
filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles
file_path, filter_used = FileDialog.getOpenFileName(
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name), filters)
if file_path:
editbox.setText(str(file_path))
Settings().setValue(self.plugin.settings_section + '/' + setting_name, file_path.parent)
def get_folder(self, title, editbox, setting_name):
"""
Opens a FileDialog and saves the selected folder to the given editbox.
:param str title: The title of the dialog.
:param QtWidgets.QLineEdit editbox: An QLineEditbox.
:param str setting_name: The place where to save the last opened directory.
:rtype: None
"""
folder_path = FileDialog.getExistingDirectory(
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
FileDialog.ShowDirsOnly)
if folder_path:
editbox.setText(str(folder_path))
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)

View File

@ -463,14 +463,14 @@ class BibleImportForm(OpenLPWizard):
UiStrings().NFSs, translate('BiblesPlugin.ImportWizardForm', UiStrings().NFSs, translate('BiblesPlugin.ImportWizardForm',
'You need to specify a file with books of the Bible to use in the ' 'You need to specify a file with books of the Bible to use in the '
'import.')) 'import.'))
self.csv_books_edit.setFocus() self.csv_books_path_edit.setFocus()
return False return False
elif not self.field('csv_versefile'): elif not self.field('csv_versefile'):
critical_error_message_box( critical_error_message_box(
UiStrings().NFSs, UiStrings().NFSs,
translate('BiblesPlugin.ImportWizardForm', 'You need to specify a file of Bible verses to ' translate('BiblesPlugin.ImportWizardForm', 'You need to specify a file of Bible verses to '
'import.')) 'import.'))
self.csv_verses_edit.setFocus() self.csv_verses_pathedit.setFocus()
return False return False
elif self.field('source_format') == BibleFormat.OpenSong: elif self.field('source_format') == BibleFormat.OpenSong:
if not self.field('opensong_file'): if not self.field('opensong_file'):

View File

@ -48,9 +48,9 @@ class BibleImport(BibleDB, LogMixin, RegistryProperties):
""" """
Check if the supplied file is compressed Check if the supplied file is compressed
:param file_path: A path to the file to check :param openlp.core.common.path.Path file_path: A path to the file to check
""" """
if is_zipfile(str(file_path)): if is_zipfile(file_path):
critical_error_message_box( critical_error_message_box(
message=translate('BiblesPlugin.BibleImport', message=translate('BiblesPlugin.BibleImport',
'The file "{file}" you supplied is compressed. You must decompress it before import.' 'The file "{file}" you supplied is compressed. You must decompress it before import.'

View File

@ -158,11 +158,10 @@ class BibleDB(Manager):
self.name = kwargs['name'] self.name = kwargs['name']
if not isinstance(self.name, str): if not isinstance(self.name, str):
self.name = str(self.name, 'utf-8') self.name = str(self.name, 'utf-8')
# TODO: To path object self.file_path = Path(clean_filename(self.name) + '.sqlite')
file_path = Path(clean_filename(self.name) + '.sqlite')
if 'file' in kwargs: if 'file' in kwargs:
file_path = kwargs['file'] self.file_path = kwargs['file']
Manager.__init__(self, 'bibles', init_schema, file_path, upgrade) Manager.__init__(self, 'bibles', init_schema, self.file_path, upgrade)
if self.session and 'file' in kwargs: if self.session and 'file' in kwargs:
self.get_name() self.get_name()
self._is_web_bible = None self._is_web_bible = None
@ -750,7 +749,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
] ]
class AlternativeBookNamesDB(QtCore.QObject, Manager): class AlternativeBookNamesDB(Manager):
""" """
This class represents a database-bound alternative book names system. This class represents a database-bound alternative book names system.
""" """
@ -765,8 +764,9 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
""" """
if AlternativeBookNamesDB.cursor is None: if AlternativeBookNamesDB.cursor is None:
file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite' file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite'
exists = file_path.exists()
AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path)) AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path))
if not file_path.exists(): if not exists:
# create new DB, create table alternative_book_names # create new DB, create table alternative_book_names
AlternativeBookNamesDB.conn.execute( AlternativeBookNamesDB.conn.execute(
'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, ' 'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, '

View File

@ -51,7 +51,7 @@ class WordProjectBible(BibleImport):
Unzip the file to a temporary directory Unzip the file to a temporary directory
""" """
self.tmp = TemporaryDirectory() self.tmp = TemporaryDirectory()
with ZipFile(str(self.file_path)) as zip_file: with ZipFile(self.file_path) as zip_file:
zip_file.extractall(self.tmp.name) zip_file.extractall(self.tmp.name)
self.base_path = Path(self.tmp.name, self.file_path.stem) self.base_path = Path(self.tmp.name, self.file_path.stem)

View File

@ -187,7 +187,7 @@ class BibleManager(LogMixin, RegistryProperties):
bible = self.db_cache[name] bible = self.db_cache[name]
bible.session.close_all() bible.session.close_all()
bible.session = None bible.session = None
return delete_file(Path(bible.path, bible.file)) return delete_file(bible.path, bible.file_path)
def get_bibles(self): def get_bibles(self):
""" """

View File

@ -911,16 +911,16 @@ class BibleMediaItem(MediaManagerItem):
list_widget_items.append(bible_verse) list_widget_items.append(bible_verse)
return list_widget_items return list_widget_items
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
context=ServiceItemContext.Service): **kwargs):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from remote
:param context: Why is it being generated :param context: Why is it being generated
:param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
""" """
log.debug('generating slide data') log.debug('generating slide data')
if item: if item:

View File

@ -28,7 +28,7 @@ from sqlalchemy.sql import and_, func, or_
from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings from openlp.core.common.settings import Settings
from openlp.core.lib import ServiceItemContext, check_item_selected from openlp.core.lib import check_item_selected
from openlp.core.lib.mediamanageritem import MediaManagerItem from openlp.core.lib.mediamanageritem import MediaManagerItem
from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.plugin import PluginStatus
from openlp.core.lib.serviceitem import ItemCapabilities from openlp.core.lib.serviceitem import ItemCapabilities
@ -219,15 +219,12 @@ class CustomMediaItem(MediaManagerItem):
self.search_text_edit.setFocus() self.search_text_edit.setFocus()
self.search_text_edit.selectAll() self.search_text_edit.selectAll()
def generate_slide_data(self, service_item, item=None, xml_version=False, def generate_slide_data(self, service_item, *, item=None, **kwargs):
remote=False, context=ServiceItemContext.Service):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: To be updated :param service_item: To be updated
:param item: The custom database item to be used :param item: The custom database item to be used
:param xml_version: No used :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
:param remote: Is this triggered by the Preview Controller or Service Manager.
:param context: Why is this item required to be build (Default Service).
""" """
item_id = self._get_id_of_item_to_generate(item, self.remote_custom) item_id = self._get_id_of_item_to_generate(item, self.remote_custom)
service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanEdit)

View File

@ -542,16 +542,16 @@ class ImageMediaItem(MediaManagerItem):
image_items.sort(key=lambda item: get_natural_key(item.text(0))) image_items.sort(key=lambda item: get_natural_key(item.text(0)))
target_group.addChildren(image_items) target_group.addChildren(image_items)
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
context=ServiceItemContext.Service): **kwargs):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from remote
:param context: Why is it being generated :param context: Why is it being generated
:param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
""" """
background = QtGui.QColor(Settings().value(self.settings_section + '/background color')) background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
if item: if item:

View File

@ -29,7 +29,7 @@ from openlp.core.state import State
from openlp.core.common.applocation import AppLocation from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, get_natural_key, translate from openlp.core.common.i18n import UiStrings, get_natural_key, translate
from openlp.core.common.mixins import RegistryProperties from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.path import Path, create_paths, path_to_str from openlp.core.common.path import create_paths, path_to_str
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings from openlp.core.common.settings import Settings
from openlp.core.lib import MediaType, ServiceItemContext, check_item_selected from openlp.core.lib import MediaType, ServiceItemContext, check_item_selected
@ -166,16 +166,16 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
# self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed) # self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed)
pass pass
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
context=ServiceItemContext.Service): **kwargs):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from remote
:param context: Why is it being generated :param context: Why is it being generated
:param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
""" """
if item is None: if item is None:
item = self.list_view.currentItem() item = self.list_view.currentItem()
@ -229,8 +229,8 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
Initialize media item. Initialize media item.
""" """
self.list_view.clear() self.list_view.clear()
self.service_path = str(AppLocation.get_section_data_path(self.settings_section) / 'thumbnails') self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
create_paths(Path(self.service_path)) create_paths(self.service_path)
self.load_list([path_to_str(file) for file in Settings().value(self.settings_section + '/media files')]) self.load_list([path_to_str(file) for file in Settings().value(self.settings_section + '/media files')])
self.rebuild_players() self.rebuild_players()
@ -264,7 +264,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
:param media: The media :param media: The media
:param target_group: :param target_group:
""" """
media.sort(key=lambda file_name: get_natural_key(os.path.split(str(file_name))[1])) media.sort(key=lambda file_path: get_natural_key(file_path.name))
for track in media: for track in media:
track_info = QtCore.QFileInfo(track) track_info = QtCore.QFileInfo(track)
item_name = None item_name = None

View File

@ -260,16 +260,16 @@ class PresentationMediaItem(MediaManagerItem):
doc.presentation_deleted() doc.presentation_deleted()
doc.close_presentation() doc.close_presentation()
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
context=ServiceItemContext.Service, file_path=None): file_path=None, **kwargs):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote :param remote: Triggered from remote
:param context: Why is it being generated :param context: Why is it being generated
:param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
""" """
if item: if item:
items = [item] items = [item]

View File

@ -337,14 +337,8 @@ class MessageListener(object):
# Create a copy of the original item, and then clear the original item so it can be filled with images # Create a copy of the original item, and then clear the original item so it can be filled with images
item_cpy = copy.copy(item) item_cpy = copy.copy(item)
item.__init__(None) item.__init__(None)
if is_live: context = ServiceItemContext.Live if is_live else ServiceItemContext.Preview
# TODO: To Path object self.media_item.generate_slide_data(item, item=item_cpy, context=context, file_path=file_path)
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live,
str(file_path))
else:
# TODO: To Path object
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview,
str(file_path))
# Some of the original serviceitem attributes is needed in the new serviceitem # Some of the original serviceitem attributes is needed in the new serviceitem
item.footer = item_cpy.footer item.footer = item_cpy.footer
item.from_service = item_cpy.from_service item.from_service = item_cpy.from_service

View File

@ -329,8 +329,13 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
importer = self.plugin.import_songs( importer = self.plugin.import_songs(
source_format, source_format,
file_paths=self.get_list_of_paths(self.format_widgets[source_format]['file_list_widget'])) file_paths=self.get_list_of_paths(self.format_widgets[source_format]['file_list_widget']))
importer.do_import() try:
self.progress_label.setText(WizardStrings.FinishedImport) importer.do_import()
self.progress_label.setText(WizardStrings.FinishedImport)
except OSError as e:
log.exception('Importing songs failed')
self.progress_label.setText(translate('SongsPlugin.ImportWizardForm',
'Your Song import failed. {error}').format(error=e))
def on_error_copy_to_button_clicked(self): def on_error_copy_to_button_clicked(self):
""" """

View File

@ -67,7 +67,7 @@ class CCLIFileImport(SongImport):
details = {'confidence': 1, 'encoding': 'utf-8'} details = {'confidence': 1, 'encoding': 'utf-8'}
except UnicodeDecodeError: except UnicodeDecodeError:
details = chardet.detect(detect_content) details = chardet.detect(detect_content)
in_file = codecs.open(str(file_path), 'r', details['encoding']) in_file = codecs.open(file_path, 'r', details['encoding'])
if not in_file.read(1) == '\ufeff': if not in_file.read(1) == '\ufeff':
# not UTF or no BOM was found # not UTF or no BOM was found
in_file.seek(0) in_file.seek(0)
@ -251,10 +251,10 @@ class CCLIFileImport(SongImport):
line_number = 0 line_number = 0
check_first_verse_line = False check_first_verse_line = False
verse_text = '' verse_text = ''
verse_type = VerseType.tags[VerseType.Verse]
song_author = '' song_author = ''
verse_start = False verse_start = False
for line in text_list: for line in text_list:
verse_type = 'v'
clean_line = line.strip() clean_line = line.strip()
if not clean_line: if not clean_line:
if line_number == 0: if line_number == 0:
@ -263,6 +263,7 @@ class CCLIFileImport(SongImport):
if verse_text: if verse_text:
self.add_verse(verse_text, verse_type) self.add_verse(verse_text, verse_type)
verse_text = '' verse_text = ''
verse_type = VerseType.tags[VerseType.Verse]
verse_start = False verse_start = False
else: else:
# line_number=0, song title # line_number=0, song title
@ -279,7 +280,7 @@ class CCLIFileImport(SongImport):
elif not verse_start: elif not verse_start:
# We have the verse descriptor # We have the verse descriptor
verse_desc_parts = clean_line.split(' ') verse_desc_parts = clean_line.split(' ')
if len(verse_desc_parts) == 2: if len(verse_desc_parts):
if verse_desc_parts[0].startswith('Ver'): if verse_desc_parts[0].startswith('Ver'):
verse_type = VerseType.tags[VerseType.Verse] verse_type = VerseType.tags[VerseType.Verse]
elif verse_desc_parts[0].startswith('Ch'): elif verse_desc_parts[0].startswith('Ch'):

View File

@ -557,16 +557,14 @@ class SongMediaItem(MediaManagerItem):
self.plugin.manager.save_object(new_song) self.plugin.manager.save_object(new_song)
self.on_song_list_load() self.on_song_list_load()
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, def generate_slide_data(self, service_item, *, item=None, context=ServiceItemContext.Service, **kwargs):
context=ServiceItemContext.Service):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on :param service_item: The service item to be built on
:param item: The Song item to be used :param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated :param context: Why is it being generated
:param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
""" """
log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item, log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item,
remote=self.remote_song)) remote=self.remote_song))

View File

@ -85,7 +85,7 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
self.main_window.error_message( self.main_window.error_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'), translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
translate('SongUsagePlugin.SongUsageDetailForm', 'You have not set a valid output location for your' translate('SongUsagePlugin.SongUsageDetailForm', 'You have not set a valid output location for your'
' song usage report. \nPlease select an existing path on your computer.') ' song usage report.\nPlease select an existing path on your computer.')
) )
return return
create_paths(path) create_paths(path)
@ -112,7 +112,7 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
self.main_window.information_message( self.main_window.information_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'), translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
translate('SongUsagePlugin.SongUsageDetailForm', translate('SongUsagePlugin.SongUsageDetailForm',
'Report \n{name} \nhas been successfully created. ').format(name=report_file_name) 'Report\n{name}\nhas been successfully created.').format(name=report_file_name)
) )
except OSError as ose: except OSError as ose:
log.exception('Failed to write out song usage records') log.exception('Failed to write out song usage records')

View File

@ -24,6 +24,7 @@
The entrypoint for OpenLP The entrypoint for OpenLP
""" """
import faulthandler import faulthandler
import logging
import multiprocessing import multiprocessing
import sys import sys
@ -34,14 +35,19 @@ from openlp.core.common import is_macosx, is_win
from openlp.core.common.applocation import AppLocation from openlp.core.common.applocation import AppLocation
from openlp.core.common.path import create_paths from openlp.core.common.path import create_paths
log = logging.getLogger(__name__)
def set_up_fault_handling(): def set_up_fault_handling():
""" """
Set up the Python fault handler Set up the Python fault handler
""" """
# Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
create_paths(AppLocation.get_directory(AppLocation.CacheDir)) try:
faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb')) create_paths(AppLocation.get_directory(AppLocation.CacheDir))
faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
except OSError:
log.exception('An exception occurred when enabling the fault handler')
def start(): def start():

View File

@ -63,8 +63,8 @@ class TestRemoteDeploy(TestCase):
deploy_zipfile(root_path, 'site.zip') deploy_zipfile(root_path, 'site.zip')
# THEN: the zip file should have been extracted to the right location # THEN: the zip file should have been extracted to the right location
MockZipFile.assert_called_once_with(root_path_str + os.sep + 'site.zip') MockZipFile.assert_called_once_with(Path('/tmp/remotes/site.zip'))
mocked_zipfile.extractall.assert_called_once_with(root_path_str) mocked_zipfile.extractall.assert_called_once_with(Path('/tmp/remotes'))
@patch('openlp.core.api.deploy.Registry') @patch('openlp.core.api.deploy.Registry')
@patch('openlp.core.api.deploy.get_web_page') @patch('openlp.core.api.deploy.get_web_page')

View File

@ -309,9 +309,9 @@ class TestInit(TestCase, TestMixin):
""" """
# GIVEN: A mocked UniversalDetector instance with done attribute set to True after first iteration # GIVEN: A mocked UniversalDetector instance with done attribute set to True after first iteration
with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
patch.object(Path, 'open', return_value=BytesIO(b"data" * 260)) as mocked_open: patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open:
encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
mocked_universal_detector_inst = MagicMock(result=encoding_result) mocked_universal_detector_inst = MagicMock(**{'close.return_value': encoding_result})
type(mocked_universal_detector_inst).done = PropertyMock(side_effect=[False, True]) type(mocked_universal_detector_inst).done = PropertyMock(side_effect=[False, True])
mocked_universal_detector.return_value = mocked_universal_detector_inst mocked_universal_detector.return_value = mocked_universal_detector_inst
@ -320,7 +320,7 @@ class TestInit(TestCase, TestMixin):
# THEN: The feed method of UniversalDetector should only br called once before returning a result # THEN: The feed method of UniversalDetector should only br called once before returning a result
mocked_open.assert_called_once_with('rb') mocked_open.assert_called_once_with('rb')
assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256)] assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256)]
mocked_universal_detector_inst.close.assert_called_once_with() mocked_universal_detector_inst.close.assert_called_once_with()
assert result == encoding_result assert result == encoding_result
@ -331,10 +331,10 @@ class TestInit(TestCase, TestMixin):
# GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test
# data (enough to run the iterator twice) # data (enough to run the iterator twice)
with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
patch.object(Path, 'open', return_value=BytesIO(b"data" * 260)) as mocked_open: patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open:
encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector, mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector,
**{'done': False, 'result': encoding_result}) **{'done': False, 'close.return_value': encoding_result})
mocked_universal_detector.return_value = mocked_universal_detector_inst mocked_universal_detector.return_value = mocked_universal_detector_inst
# WHEN: Calling get_file_encoding # WHEN: Calling get_file_encoding
@ -342,7 +342,7 @@ class TestInit(TestCase, TestMixin):
# THEN: The feed method of UniversalDetector should have been called twice before returning a result # THEN: The feed method of UniversalDetector should have been called twice before returning a result
mocked_open.assert_called_once_with('rb') mocked_open.assert_called_once_with('rb')
assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256), call(b"data" * 4)] assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256), call(b'data' * 4)]
mocked_universal_detector_inst.close.assert_called_once_with() mocked_universal_detector_inst.close.assert_called_once_with()
assert result == encoding_result assert result == encoding_result
@ -352,13 +352,19 @@ class TestInit(TestCase, TestMixin):
""" """
# GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test
# data (enough to run the iterator twice) # data (enough to run the iterator twice)
with patch('openlp.core.common.UniversalDetector'), \ with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
patch('builtins.open', side_effect=OSError), \ patch('builtins.open', side_effect=OSError), \
patch('openlp.core.common.log') as mocked_log: patch('openlp.core.common.log') as mocked_log:
encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector,
**{'done': False, 'close.return_value': encoding_result})
mocked_universal_detector.return_value = mocked_universal_detector_inst
# WHEN: Calling get_file_encoding # WHEN: Calling get_file_encoding
result = get_file_encoding(Path('file name')) result = get_file_encoding(Path('file name'))
# THEN: log.exception should be called and get_file_encoding should return None # THEN: log.exception should be called and get_file_encoding should return None
mocked_log.exception.assert_called_once_with('Error detecting file encoding') mocked_log.exception.assert_called_once_with('Error detecting file encoding')
assert result is None mocked_universal_detector_inst.feed.assert_not_called()
mocked_universal_detector_inst.close.assert_called_once_with()
assert result == encoding_result

View File

@ -179,9 +179,8 @@ class TestShutil(TestCase):
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type # WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type
path.rmtree() path.rmtree()
# THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object. # THEN: :func:`shutil.rmtree` should have been called with the the Path object.
mocked_shutil_rmtree.assert_called_once_with( mocked_shutil_rmtree.assert_called_once_with(Path('test', 'path'), False, None)
os.path.join('test', 'path'), False, None)
def test_rmtree_optional_params(self): def test_rmtree_optional_params(self):
""" """
@ -198,8 +197,7 @@ class TestShutil(TestCase):
# THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the # THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the
# values being modified # values being modified
mocked_shutil_rmtree.assert_called_once_with( mocked_shutil_rmtree.assert_called_once_with(path, True, mocked_on_error)
os.path.join('test', 'path'), True, mocked_on_error)
def test_which_no_command(self): def test_which_no_command(self):
""" """

View File

@ -141,7 +141,7 @@ class TestServiceItem(TestCase, TestMixin):
""" """
# GIVEN: A new service item and a mocked add icon function # GIVEN: A new service item and a mocked add icon function
image_name = 'image_1.jpg' image_name = 'image_1.jpg'
test_file = os.path.join(str(TEST_PATH), image_name) test_file = TEST_PATH / image_name
frame_array = {'path': test_file, 'title': image_name} frame_array = {'path': test_file, 'title': image_name}
service_item = ServiceItem(None) service_item = ServiceItem(None)
@ -154,13 +154,13 @@ class TestServiceItem(TestCase, TestMixin):
mocked_get_section_data_path: mocked_get_section_data_path:
mocked_exists.return_value = True mocked_exists.return_value = True
mocked_get_section_data_path.return_value = Path('/path/') mocked_get_section_data_path.return_value = Path('/path/')
service_item.set_from_service(line, str(TEST_PATH)) service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid' assert service_item.is_valid is True, 'The new service item should be valid'
assert test_file == service_item.get_rendered_frame(0), 'The first frame should match the path to the image' assert test_file == service_item.get_rendered_frame(0), 'The first frame should match the path to the image'
assert frame_array == service_item.get_frames()[0], 'The return should match frame array1' assert frame_array == service_item.get_frames()[0], 'The return should match frame array1'
assert test_file == str(service_item.get_frame_path(0)), \ assert test_file == service_item.get_frame_path(0), \
'The frame path should match the full path to the image' 'The frame path should match the full path to the image'
assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name' assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name'
assert image_name == service_item.get_display_title(), 'The display title should match the first image name' assert image_name == service_item.get_display_title(), 'The display title should match the first image name'
@ -328,7 +328,7 @@ class TestServiceItem(TestCase, TestMixin):
# WHEN: We add a custom from a saved service # WHEN: We add a custom from a saved service
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj') line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
service_item.set_from_service(line, '/test/') service_item.set_from_service(line, Path('/test/'))
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid' assert service_item.is_valid is True, 'The new service item should be valid'

View File

@ -66,9 +66,9 @@ class TestThemeManager(TestCase):
theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default') theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default')
# THEN: The zipfile should be created at the given path # THEN: The zipfile should be created at the given path
mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w') mocked_zipfile_init.assert_called_with(Path('some', 'path', 'Default.otz'), 'w')
mocked_zipfile_write.assert_called_with(str(RESOURCE_PATH / 'themes' / 'Default' / 'Default.xml'), mocked_zipfile_write.assert_called_with(RESOURCE_PATH / 'themes' / 'Default' / 'Default.xml',
os.path.join('Default', 'Default.xml')) Path('Default', 'Default.xml'))
def test_initial_theme_manager(self): def test_initial_theme_manager(self):
""" """

View File

@ -55,7 +55,8 @@ class TestManager(TestCase):
instance = BibleManager(MagicMock()) instance = BibleManager(MagicMock())
# We need to keep a reference to the mock for close_all as it gets set to None later on! # We need to keep a reference to the mock for close_all as it gets set to None later on!
mocked_close_all = MagicMock() mocked_close_all = MagicMock()
mocked_bible = MagicMock(file='KJV.sqlite', path='bibles', **{'session.close_all': mocked_close_all}) mocked_bible = MagicMock(file_path='KJV.sqlite', path=Path('bibles'),
**{'session.close_all': mocked_close_all})
instance.db_cache = {'KJV': mocked_bible} instance.db_cache = {'KJV': mocked_bible}
# WHEN: Calling delete_bible with 'KJV' # WHEN: Calling delete_bible with 'KJV'
@ -66,4 +67,4 @@ class TestManager(TestCase):
assert result is True assert result is True
mocked_close_all.assert_called_once_with() mocked_close_all.assert_called_once_with()
assert mocked_bible.session is None assert mocked_bible.session is None
mocked_delete_file.assert_called_once_with(Path('bibles', 'KJV.sqlite')) mocked_delete_file.assert_called_once_with(Path('bibles'), 'KJV.sqlite')

View File

@ -236,8 +236,8 @@ class TestSongMaintenanceForm(TestCase, TestMixin):
assert MockedQListWidgetItem.call_args_list == expected_widget_item_calls, MockedQListWidgetItem.call_args_list assert MockedQListWidgetItem.call_args_list == expected_widget_item_calls, MockedQListWidgetItem.call_args_list
mocked_author_item1.setData.assert_called_once_with(QtCore.Qt.UserRole, 2) mocked_author_item1.setData.assert_called_once_with(QtCore.Qt.UserRole, 2)
mocked_author_item2.setData.assert_called_once_with(QtCore.Qt.UserRole, 1) mocked_author_item2.setData.assert_called_once_with(QtCore.Qt.UserRole, 1)
mocked_authors_list_widget.addItem.call_args_list == [ mocked_authors_list_widget.addItem.assert_has_calls([
call(mocked_author_item1), call(mocked_author_item2)] call(mocked_author_item1), call(mocked_author_item2)])
@patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem') @patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem')
@patch('openlp.plugins.songs.forms.songmaintenanceform.Topic') @patch('openlp.plugins.songs.forms.songmaintenanceform.Topic')

View File

@ -70,7 +70,7 @@ class FakeIP4InterfaceEntry(QObject):
""" """
Return a QFlags enum with IsUp and IsRunning Return a QFlags enum with IsUp and IsRunning
""" """
return (QNetworkInterface.IsUp | QNetworkInterface.IsRunning) return QNetworkInterface.IsUp | QNetworkInterface.IsRunning
def name(self): def name(self):
return self.my_name return self.my_name

View File

@ -605,9 +605,9 @@ class TestPJLinkCommands(TestCase):
# THEN: Power should be set to ON # THEN: Power should be set to ON
assert pjlink.power == S_STANDBY, 'Power should not have changed' assert pjlink.power == S_STANDBY, 'Power should not have changed'
assert mock_UpdateIcons.emit.called is False, 'projectorUpdateIcons() should not have been called' mock_UpdateIcons.emit.assert_not_called()
mock_change_status.called is False, 'change_status() should not have been called' mock_change_status.assert_not_called()
mock_send_command.called is False, 'send_command() should not have been called' mock_send_command.assert_not_called()
mock_log.warning.assert_has_calls(log_warn_calls) mock_log.warning.assert_has_calls(log_warn_calls)
def test_projector_process_powr_off(self): def test_projector_process_powr_off(self):
@ -627,9 +627,9 @@ class TestPJLinkCommands(TestCase):
# THEN: Power should be set to ON # THEN: Power should be set to ON
assert pjlink.power == S_STANDBY, 'Power should have changed to S_STANDBY' assert pjlink.power == S_STANDBY, 'Power should have changed to S_STANDBY'
assert mock_UpdateIcons.emit.called is True, 'projectorUpdateIcons should have been called' mock_UpdateIcons.emit.assert_called_with()
mock_change_status.called is True, 'change_status should have been called' mock_change_status.assert_called_with(313)
mock_send_command.called is False, 'send_command should not have been called' mock_send_command.assert_not_called()
def test_projector_process_rfil_save(self): def test_projector_process_rfil_save(self):
""" """

View File

@ -83,8 +83,8 @@ class ProjectorSourceFormTest(TestCase, TestMixin):
Delete all C++ objects at end so we don't segfault. Delete all C++ objects at end so we don't segfault.
""" """
self.projectordb.session.close() self.projectordb.session.close()
del(self.projectordb) del self.projectordb
del(self.projector) del self.projector
retries = 0 retries = 0
while retries < 5: while retries < 5:
try: try: