merge trunk

This commit is contained in:
Tomas Groth 2017-10-04 22:01:29 +02:00
commit ea0dd6f7aa
87 changed files with 1240 additions and 979 deletions

View File

@ -63,7 +63,7 @@ def download_and_check(callback=None):
sha256, version = download_sha256() sha256, version = download_sha256()
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip') file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
callback.setRange(0, file_size) callback.setRange(0, file_size)
if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'), if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip',
AppLocation.get_section_data_path('remotes') / 'site.zip', AppLocation.get_section_data_path('remotes') / 'site.zip',
sha256=sha256): sha256=sha256):
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip') deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')

View File

@ -21,18 +21,13 @@
############################################################################### ###############################################################################
import logging import logging
import os
from openlp.core.api.http.endpoint import Endpoint from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
from openlp.core.common import AppLocation
static_dir = os.path.join(str(AppLocation.get_section_data_path('remotes')))
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
remote_endpoint = Endpoint('remote', template_dir=static_dir, static_dir=static_dir) remote_endpoint = Endpoint('remote', template_dir='remotes', static_dir='remotes')
@remote_endpoint.route('{view}') @remote_endpoint.route('{view}')

View File

@ -23,7 +23,7 @@ import logging
import json import json
from openlp.core.api.http.endpoint import Endpoint from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http import register_endpoint, requires_auth from openlp.core.api.http import requires_auth
from openlp.core.common import Registry from openlp.core.common import Registry

View File

@ -26,17 +26,24 @@ with OpenLP. It uses JSON to communicate with the remotes.
""" """
import logging import logging
import time
from PyQt5 import QtCore from PyQt5 import QtCore, QtWidgets
from waitress import serve from waitress import serve
from openlp.core.api.http import register_endpoint from openlp.core.api.http import register_endpoint
from openlp.core.api.http import application from openlp.core.api.http import application
from openlp.core.common import RegistryMixin, RegistryProperties, OpenLPMixin, Settings, Registry from openlp.core.common import AppLocation, RegistryMixin, RegistryProperties, OpenLPMixin, \
Settings, Registry, UiStrings, check_directory_exists
from openlp.core.lib import translate
from openlp.core.api.deploy import download_and_check, download_sha256
from openlp.core.api.poll import Poller from openlp.core.api.poll import Poller
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
from openlp.core.api.endpoint.remote import remote_endpoint
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -59,6 +66,7 @@ class HttpWorker(QtCore.QObject):
""" """
address = Settings().value('api/ip address') address = Settings().value('api/ip address')
port = Settings().value('api/port') port = Settings().value('api/port')
Registry().execute('get_website_version')
serve(application, host=address, port=port) serve(application, host=address, port=port)
def stop(self): def stop(self):
@ -79,11 +87,15 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
self.worker.moveToThread(self.thread) self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run) self.thread.started.connect(self.worker.run)
self.thread.start() self.thread.start()
Registry().register_function('download_website', self.first_time)
Registry().register_function('get_website_version', self.website_version)
Registry().set_flag('website_version', '0.0')
def bootstrap_post_set_up(self): def bootstrap_post_set_up(self):
""" """
Register the poll return service and start the servers. Register the poll return service and start the servers.
""" """
self.initialise()
self.poller = Poller() self.poller = Poller()
Registry().register('poller', self.poller) Registry().register('poller', self.poller)
application.initialise() application.initialise()
@ -95,3 +107,79 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
register_endpoint(main_endpoint) register_endpoint(main_endpoint)
register_endpoint(service_endpoint) register_endpoint(service_endpoint)
register_endpoint(api_service_endpoint) register_endpoint(api_service_endpoint)
register_endpoint(remote_endpoint)
@staticmethod
def initialise():
"""
Create the internal file structure if it does not exist
:return:
"""
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'assets')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'images')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static' / 'index')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'templates')
def first_time(self):
"""
Import web site code if active
"""
self.application.process_events()
progress = DownloadProgressDialog(self)
progress.forceShow()
self.application.process_events()
time.sleep(1)
download_and_check(progress)
self.application.process_events()
time.sleep(1)
progress.close()
self.application.process_events()
Settings().setValue('remotes/download version', self.version)
def website_version(self):
"""
Download and save the website version and sha256
:return: None
"""
sha256, self.version = download_sha256()
Registry().set_flag('website_sha256', sha256)
Registry().set_flag('website_version', self.version)
class DownloadProgressDialog(QtWidgets.QProgressDialog):
"""
Local class to handle download display based and supporting httputils:get_web_page
"""
def __init__(self, parent):
super(DownloadProgressDialog, self).__init__(parent.main_window)
self.parent = parent
self.setWindowModality(QtCore.Qt.WindowModal)
self.setWindowTitle(translate('RemotePlugin', 'Importing Website'))
self.setLabelText(UiStrings().StartingImport)
self.setCancelButton(None)
self.setRange(0, 1)
self.setMinimumDuration(0)
self.was_cancelled = False
self.previous_size = 0
def _download_progress(self, count, block_size):
"""
Calculate and display the download progress.
"""
increment = (count * block_size) - self.previous_size
self._increment_progress_bar(None, increment)
self.previous_size = count * block_size
def _increment_progress_bar(self, status_text, increment=1):
"""
Update the wizard progress page.
:param status_text: Current status information to display.
:param increment: The value to increment the progress bar by.
"""
if status_text:
self.setText(status_text)
if increment > 0:
self.setValue(self.value() + increment)
self.parent.application.process_events()

View File

@ -129,36 +129,21 @@ class ApiTab(SettingsTab):
self.master_version_value.setObjectName('master_version_value') self.master_version_value.setObjectName('master_version_value')
self.update_site_layout.addRow(self.master_version_label, self.master_version_value) self.update_site_layout.addRow(self.master_version_label, self.master_version_value)
self.left_layout.addWidget(self.update_site_group_box) self.left_layout.addWidget(self.update_site_group_box)
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column) self.app_group_box = QtWidgets.QGroupBox(self.right_column)
self.android_app_group_box.setObjectName('android_app_group_box') self.app_group_box.setObjectName('app_group_box')
self.right_layout.addWidget(self.android_app_group_box) self.right_layout.addWidget(self.app_group_box)
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box) self.app_qr_layout = QtWidgets.QVBoxLayout(self.app_group_box)
self.android_qr_layout.setObjectName('android_qr_layout') self.app_qr_layout.setObjectName('app_qr_layout')
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box) self.app_qr_code_label = QtWidgets.QLabel(self.app_group_box)
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png')) self.app_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/app_qr.svg'))
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter) self.app_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.android_qr_code_label.setObjectName('android_qr_code_label') self.app_qr_code_label.setObjectName('app_qr_code_label')
self.android_qr_layout.addWidget(self.android_qr_code_label) self.app_qr_layout.addWidget(self.app_qr_code_label)
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box) self.app_qr_description_label = QtWidgets.QLabel(self.app_group_box)
self.android_qr_description_label.setObjectName('android_qr_description_label') self.app_qr_description_label.setObjectName('app_qr_description_label')
self.android_qr_description_label.setOpenExternalLinks(True) self.app_qr_description_label.setOpenExternalLinks(True)
self.android_qr_description_label.setWordWrap(True) self.app_qr_description_label.setWordWrap(True)
self.android_qr_layout.addWidget(self.android_qr_description_label) self.app_qr_layout.addWidget(self.app_qr_description_label)
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
self.ios_app_group_box.setObjectName('ios_app_group_box')
self.right_layout.addWidget(self.ios_app_group_box)
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
self.ios_qr_layout.setObjectName('ios_qr_layout')
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
self.ios_qr_description_label.setOpenExternalLinks(True)
self.ios_qr_description_label.setWordWrap(True)
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
self.left_layout.addStretch() self.left_layout.addStretch()
self.right_layout.addStretch() self.right_layout.addStretch()
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed) self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
@ -195,16 +180,11 @@ class ApiTab(SettingsTab):
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format')) self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab', self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.')) 'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App')) self.app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Remote App'))
self.android_qr_description_label.setText( self.app_qr_description_label.setText(
translate('RemotePlugin.RemoteTab', translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google ' 'Scan the QR code or click <a href="{qr}">download</a> to download an app for your mobile device'
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2')) ).format(qr='https://openlp.org/#mobile-app-downloads'))
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
self.ios_qr_description_label.setText(
translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication')) self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
self.aa = UiStrings() self.aa = UiStrings()
self.update_site_group_box.setTitle(UiStrings().WebDownloadText) self.update_site_group_box.setTitle(UiStrings().WebDownloadText)
@ -222,6 +202,8 @@ class ApiTab(SettingsTab):
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url)) self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
http_url_temp = http_url + 'stage' http_url_temp = http_url + 'stage'
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
http_url_temp = http_url + 'chords'
self.chords_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
http_url_temp = http_url + 'main' http_url_temp = http_url + 'main'
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))

View File

@ -178,6 +178,7 @@ class Settings(QtCore.QSettings):
'images/background color': '#000000', 'images/background color': '#000000',
'media/players': 'system,webkit', 'media/players': 'system,webkit',
'media/override player': QtCore.Qt.Unchecked, 'media/override player': QtCore.Qt.Unchecked,
'remotes/download version': '0.0',
'players/background color': '#000000', 'players/background color': '#000000',
'servicemanager/last directory': None, 'servicemanager/last directory': None,
'servicemanager/last file': None, 'servicemanager/last file': None,

View File

@ -426,10 +426,10 @@ class ServiceItem(RegistryProperties):
self.background_audio = [] self.background_audio = []
for filename in header['background_audio']: for filename in header['background_audio']:
# Give them real file paths. # Give them real file paths.
filepath = filename filepath = str(filename)
if path: if path:
# 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
filepath = os.path.join(path, ntpath.basename(filename)) filepath = os.path.join(path, ntpath.basename(str(filename)))
self.background_audio.append(filepath) self.background_audio.append(filepath)
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:

View File

@ -684,7 +684,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
if not isinstance(file_names, list): if not isinstance(file_names, list):
file_names = [file_names] file_names = [file_names]
for file_name in file_names: for file_name in file_names:
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file_name))) self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(str(file_name))))
def next(self): def next(self):
""" """

View File

@ -561,7 +561,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
service_item = item['service_item'].get_service_repr(self._save_lite) service_item = item['service_item'].get_service_repr(self._save_lite)
if service_item['header']['background_audio']: if service_item['header']['background_audio']:
for i, file_name in enumerate(service_item['header']['background_audio']): for i, file_name in enumerate(service_item['header']['background_audio']):
new_file = os.path.join('audio', item['service_item'].unique_identifier, file_name) new_file = os.path.join('audio', item['service_item'].unique_identifier, str(file_name))
audio_files.append((file_name, new_file)) audio_files.append((file_name, new_file))
service_item['header']['background_audio'][i] = new_file service_item['header']['background_audio'][i] = new_file
# Add the service item to the service. # Add the service item to the service.
@ -586,6 +586,8 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
for write_from in write_list: for write_from in write_list:
zip_file.write(write_from, write_from) zip_file.write(write_from, write_from)
for audio_from, audio_to in audio_files: for audio_from, audio_to in audio_files:
audio_from = str(audio_from)
audio_to = str(audio_to)
if audio_from.startswith('audio'): if audio_from.startswith('audio'):
# When items are saved, they get new unique_identifier. Let's copy the file to the new location. # When items are saved, they get new unique_identifier. Let's copy the file to the new location.
# Unused files can be ignored, OpenLP automatically cleans up the service manager dir on exit. # Unused files can be ignored, OpenLP automatically cleans up the service manager dir on exit.

View File

@ -868,7 +868,7 @@ class SlideController(DisplayController, RegistryProperties):
self.track_menu.clear() self.track_menu.clear()
for counter in range(len(self.service_item.background_audio)): for counter in range(len(self.service_item.background_audio)):
action = self.track_menu.addAction( action = self.track_menu.addAction(
os.path.basename(self.service_item.background_audio[counter])) os.path.basename(str(self.service_item.background_audio[counter])))
action.setData(counter) action.setData(counter)
action.triggered.connect(self.on_track_triggered) action.triggered.connect(self.on_track_triggered)
self.display.audio_player.repeat = \ self.display.audio_player.repeat = \

View File

@ -62,7 +62,7 @@ def bibles_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'bibles', log) return service(request, 'bibles', log)
@api_bibles_endpoint.route('bibles/search') @api_bibles_endpoint.route('bibles/search')
@ -95,6 +95,6 @@ def bibles_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'bibles', log) return search(request, 'bibles', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -62,7 +62,7 @@ def custom_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'custom', log) return service(request, 'custom', log)
@api_custom_endpoint.route('custom/search') @api_custom_endpoint.route('custom/search')
@ -95,6 +95,6 @@ def custom_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'custom', log) return search(request, 'custom', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -75,7 +75,7 @@ def images_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'images', log) return service(request, 'images', log)
@api_images_endpoint.route('images/search') @api_images_endpoint.route('images/search')
@ -108,6 +108,6 @@ def images_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'images', log) return search(request, 'images', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -62,7 +62,7 @@ def media_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'media', log) return service(request, 'media', log)
@api_media_endpoint.route('media/search') @api_media_endpoint.route('media/search')
@ -95,6 +95,6 @@ def media_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'media', log) return search(request, 'media', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -76,7 +76,7 @@ def presentations_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'presentations', log) return service(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/search') @api_presentations_endpoint.route('presentations/search')
@ -109,6 +109,6 @@ def presentations_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'presentations', log) return search(request, 'presentations', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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 #
###############################################################################

View File

@ -1,155 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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 #
###############################################################################
import logging
import os
import time
from PyQt5 import QtCore, QtWidgets
from openlp.core.api.http import register_endpoint
from openlp.core.common import AppLocation, Registry, Settings, OpenLPMixin, UiStrings, check_directory_exists
from openlp.core.lib import Plugin, StringContent, translate, build_icon
from openlp.plugins.remotes.endpoint import remote_endpoint
from openlp.plugins.remotes.deploy import download_and_check, download_sha256
log = logging.getLogger(__name__)
__default_settings__ = {
'remotes/download version': '0000_00_00'
}
class RemotesPlugin(Plugin, OpenLPMixin):
log.info('Remotes Plugin loaded')
def __init__(self):
"""
remotes constructor
"""
super(RemotesPlugin, self).__init__('remotes', __default_settings__, {})
self.icon_path = ':/plugins/plugin_remote.png'
self.icon = build_icon(self.icon_path)
self.weight = -1
register_endpoint(remote_endpoint)
Registry().register_function('download_website', self.first_time)
Registry().register_function('get_website_version', self.website_version)
Registry().set_flag('website_version', '0001_01_01')
def initialise(self):
"""
Create the internal file structure if it does not exist
:return:
"""
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'assets')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'images')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static', 'index')
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'templates')
@staticmethod
def about():
"""
Information about this plugin
"""
about_text = translate(
'RemotePlugin',
'<strong>Web Interface</strong>'
'<br />The web interface plugin provides the ability to develop web based interfaces using OpenLP web '
'services.\nPredefined interfaces can be download as well as custom developed interfaces.')
return about_text
def set_plugin_text_strings(self):
"""
Called to define all translatable texts of the plugin
"""
# Name PluginList
self.text_strings[StringContent.Name] = {
'singular': translate('RemotePlugin', 'Web Interface', 'name singular'),
'plural': translate('RemotePlugin', 'Web Interface', 'name plural')
}
# Name for MediaDockManager, SettingsManager
self.text_strings[StringContent.VisibleName] = {
'title': translate('RemotePlugin', 'Web Remote', 'container title')
}
def first_time(self):
"""
Import web site code if active
"""
self.application.process_events()
progress = Progress(self)
progress.forceShow()
self.application.process_events()
time.sleep(1)
download_and_check(progress)
self.application.process_events()
time.sleep(1)
progress.close()
self.application.process_events()
Settings().setValue('remotes/download version', self.version)
def website_version(self):
"""
Download and save the website version and sha256
:return: None
"""
sha256, self.version = download_sha256()
Registry().set_flag('website_sha256', sha256)
Registry().set_flag('website_version', self.version)
class Progress(QtWidgets.QProgressDialog):
"""
Local class to handle download display based and supporting httputils:get_web_page
"""
def __init__(self, parent):
super(Progress, self).__init__(parent.main_window)
self.parent = parent
self.setWindowModality(QtCore.Qt.WindowModal)
self.setWindowTitle(translate('RemotePlugin', 'Importing Website'))
self.setLabelText(UiStrings().StartingImport)
self.setCancelButton(None)
self.setRange(0, 1)
self.setMinimumDuration(0)
self.was_cancelled = False
self.previous_size = 0
def _download_progress(self, count, block_size):
"""
Calculate and display the download progress.
"""
increment = (count * block_size) - self.previous_size
self._increment_progress_bar(None, increment)
self.previous_size = count * block_size
def _increment_progress_bar(self, status_text, increment=1):
"""
Update the wizard progress page.
:param status_text: Current status information to display.
:param increment: The value to increment the progress bar by.
"""
if status_text:
self.setText(status_text)
if increment > 0:
self.setValue(self.value() + increment)
self.parent.application.process_events()

View File

@ -62,7 +62,7 @@ def songs_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'songs', log) return service(request, 'songs', log)
@api_songs_endpoint.route('songs/search') @api_songs_endpoint.route('songs/search')
@ -95,6 +95,6 @@ def songs_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'songs', log) return service(request, 'songs', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -23,27 +23,25 @@
The :mod:`~openlp.plugins.songs.forms.editsongform` module contains the form The :mod:`~openlp.plugins.songs.forms.editsongform` module contains the form
used to edit songs. used to edit songs.
""" """
import logging import logging
import re
import os import os
import shutil import re
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
from openlp.core.common.path import Path, path_to_str from openlp.core.common.languagemanager import get_natural_key
from openlp.core.common.path import copyfile
from openlp.core.lib import PluginStatus, MediaType, create_separated_list from openlp.core.lib import PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.ui.lib.filedialog import FileDialog from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import SongXML
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
from openlp.plugins.songs.forms.editverseform import EditVerseForm from openlp.plugins.songs.forms.editverseform import EditVerseForm
from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm
from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
from openlp.plugins.songs.lib.openlyricsxml import SongXML
from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -545,9 +543,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
songbook_entry.entry) songbook_entry.entry)
self.audio_list_widget.clear() self.audio_list_widget.clear()
for media in self.song.media_files: for media in self.song.media_files:
media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1]) item = QtWidgets.QListWidgetItem(media.file_path.name)
media_file.setData(QtCore.Qt.UserRole, media.file_name) item.setData(QtCore.Qt.UserRole, media.file_path)
self.audio_list_widget.addItem(media_file) self.audio_list_widget.addItem(item)
self.title_edit.setFocus() self.title_edit.setFocus()
# Hide or show the preview button. # Hide or show the preview button.
self.preview_button.setVisible(preview) self.preview_button.setVisible(preview)
@ -927,12 +925,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
Loads file(s) from the filesystem. Loads file(s) from the filesystem.
""" """
filters = '{text} (*)'.format(text=UiStrings().AllFiles) filters = '{text} (*)'.format(text=UiStrings().AllFiles)
file_paths, selected_filter = FileDialog.getOpenFileNames( file_paths, filter_used = FileDialog.getOpenFileNames(
self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters) parent=self, caption=translate('SongsPlugin.EditSongForm', 'Open File(s)'), filter=filters)
for file_path in file_paths: for file_path in file_paths:
filename = path_to_str(file_path) item = QtWidgets.QListWidgetItem(file_path.name)
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1]) item.setData(QtCore.Qt.UserRole, file_path)
item.setData(QtCore.Qt.UserRole, filename)
self.audio_list_widget.addItem(item) self.audio_list_widget.addItem(item)
def on_audio_add_from_media_button_clicked(self): def on_audio_add_from_media_button_clicked(self):
@ -940,9 +937,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
Loads file(s) from the media plugin. Loads file(s) from the media plugin.
""" """
if self.media_form.exec(): if self.media_form.exec():
for filename in self.media_form.get_selected_files(): for file_path in self.media_form.get_selected_files():
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1]) item = QtWidgets.QListWidgetItem(file_path.name)
item.setData(QtCore.Qt.UserRole, filename) item.setData(QtCore.Qt.UserRole, file_path)
self.audio_list_widget.addItem(item) self.audio_list_widget.addItem(item)
def on_audio_remove_button_clicked(self): def on_audio_remove_button_clicked(self):
@ -1066,34 +1063,33 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
# Save the song here because we need a valid id for the audio files. # Save the song here because we need a valid id for the audio files.
clean_song(self.manager, self.song) clean_song(self.manager, self.song)
self.manager.save_object(self.song) self.manager.save_object(self.song)
audio_files = [a.file_name for a in self.song.media_files] audio_paths = [a.file_path for a in self.song.media_files]
log.debug(audio_files) log.debug(audio_paths)
save_path = os.path.join(str(AppLocation.get_section_data_path(self.media_item.plugin.name)), 'audio', save_path = AppLocation.get_section_data_path(self.media_item.plugin.name) / 'audio' / str(self.song.id)
str(self.song.id)) check_directory_exists(save_path)
check_directory_exists(Path(save_path))
self.song.media_files = [] self.song.media_files = []
files = [] file_paths = []
for row in range(self.audio_list_widget.count()): for row in range(self.audio_list_widget.count()):
item = self.audio_list_widget.item(row) item = self.audio_list_widget.item(row)
filename = item.data(QtCore.Qt.UserRole) file_path = item.data(QtCore.Qt.UserRole)
if not filename.startswith(save_path): if save_path not in file_path.parents:
old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1]) old_file_path, file_path = file_path, save_path / file_path.name
shutil.copyfile(old_file, filename) copyfile(old_file_path, file_path)
files.append(filename) file_paths.append(file_path)
media_file = MediaFile() media_file = MediaFile()
media_file.file_name = filename media_file.file_path = file_path
media_file.type = 'audio' media_file.type = 'audio'
media_file.weight = row media_file.weight = row
self.song.media_files.append(media_file) self.song.media_files.append(media_file)
for audio in audio_files: for audio_path in audio_paths:
if audio not in files: if audio_path not in file_paths:
try: try:
os.remove(audio) audio_path.unlink()
except: except:
log.exception('Could not remove file: {audio}'.format(audio=audio)) log.exception('Could not remove file: {audio}'.format(audio=audio_path))
if not files: if not file_paths:
try: try:
os.rmdir(save_path) save_path.rmdir()
except OSError: except OSError:
log.exception('Could not remove directory: {path}'.format(path=save_path)) log.exception('Could not remove directory: {path}'.format(path=save_path))
clean_song(self.manager, self.song) clean_song(self.manager, self.song)

View File

@ -41,12 +41,19 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
QtCore.Qt.WindowCloseButtonHint) QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def populate_files(self, files): def populate_files(self, file_paths):
"""
:param list[openlp.core.common.path.Path] file_paths:
:return:
"""
self.file_list_widget.clear() self.file_list_widget.clear()
for file in files: for file_path in file_paths:
item = QtWidgets.QListWidgetItem(os.path.split(file)[1]) item = QtWidgets.QListWidgetItem(file_path.name)
item.setData(QtCore.Qt.UserRole, file) item.setData(QtCore.Qt.UserRole, file_path)
self.file_list_widget.addItem(item) self.file_list_widget.addItem(item)
def get_selected_files(self): def get_selected_files(self):
"""
:rtype: list[openlp.core.common.path.Path]
"""
return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()] return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()]

View File

@ -27,9 +27,10 @@ import logging
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, UiStrings, translate from openlp.core.common import Registry, Settings, UiStrings, translate
from openlp.core.lib import create_separated_list, build_icon from openlp.core.lib import create_separated_list
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
@ -76,7 +77,6 @@ class SongExportForm(OpenLPWizard):
self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed) self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked) self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
self.check_button.clicked.connect(self.on_check_button_clicked) self.check_button.clicked.connect(self.on_check_button_clicked)
self.directory_button.clicked.connect(self.on_directory_button_clicked)
def add_custom_pages(self): def add_custom_pages(self):
""" """
@ -120,21 +120,15 @@ class SongExportForm(OpenLPWizard):
self.grid_layout.setObjectName('range_layout') self.grid_layout.setObjectName('range_layout')
self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page) self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page)
self.selected_list_widget.setObjectName('selected_list_widget') self.selected_list_widget.setObjectName('selected_list_widget')
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1) self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 2)
# FIXME: self.horizontal_layout is already defined above?!?!? Replace with Path Eidt! self.output_directory_path_edit = PathEdit(
self.horizontal_layout = QtWidgets.QHBoxLayout() self.export_song_page, PathType.Directories,
self.horizontal_layout.setObjectName('horizontal_layout') dialog_caption=translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), show_revert=False)
self.output_directory_path_edit.path = Settings().value('songs/last directory export')
self.directory_label = QtWidgets.QLabel(self.export_song_page) self.directory_label = QtWidgets.QLabel(self.export_song_page)
self.directory_label.setObjectName('directory_label') self.directory_label.setObjectName('directory_label')
self.horizontal_layout.addWidget(self.directory_label) self.grid_layout.addWidget(self.directory_label, 0, 0)
self.directory_line_edit = QtWidgets.QLineEdit(self.export_song_page) self.grid_layout.addWidget(self.output_directory_path_edit, 0, 1)
self.directory_line_edit.setObjectName('directory_line_edit')
self.horizontal_layout.addWidget(self.directory_line_edit)
self.directory_button = QtWidgets.QToolButton(self.export_song_page)
self.directory_button.setIcon(build_icon(':/exports/export_load.png'))
self.directory_button.setObjectName('directory_button')
self.horizontal_layout.addWidget(self.directory_button)
self.grid_layout.addLayout(self.horizontal_layout, 0, 0, 1, 1)
self.export_song_layout.addLayout(self.grid_layout) self.export_song_layout.addLayout(self.grid_layout)
self.addPage(self.export_song_page) self.addPage(self.export_song_page)
@ -188,11 +182,12 @@ class SongExportForm(OpenLPWizard):
self.selected_list_widget.addItem(song) self.selected_list_widget.addItem(song)
return True return True
elif self.currentPage() == self.export_song_page: elif self.currentPage() == self.export_song_page:
if not self.directory_line_edit.text(): if not self.output_directory_path_edit.path:
critical_error_message_box( critical_error_message_box(
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'), translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.')) translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
return False return False
Settings().setValue('songs/last directory export', self.output_directory_path_edit.path)
return True return True
elif self.currentPage() == self.progress_page: elif self.currentPage() == self.progress_page:
self.available_list_widget.clear() self.available_list_widget.clear()
@ -211,8 +206,6 @@ class SongExportForm(OpenLPWizard):
self.finish_button.setVisible(False) self.finish_button.setVisible(False)
self.cancel_button.setVisible(True) self.cancel_button.setVisible(True)
self.available_list_widget.clear() self.available_list_widget.clear()
self.selected_list_widget.clear()
self.directory_line_edit.clear()
self.search_line_edit.clear() self.search_line_edit.clear()
# Load the list of songs. # Load the list of songs.
self.application.set_busy_cursor() self.application.set_busy_cursor()
@ -247,7 +240,7 @@ class SongExportForm(OpenLPWizard):
song.data(QtCore.Qt.UserRole) song.data(QtCore.Qt.UserRole)
for song in find_list_widget_items(self.selected_list_widget) for song in find_list_widget_items(self.selected_list_widget)
] ]
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text()) exporter = OpenLyricsExport(self, songs, self.output_directory_path_edit.path)
try: try:
if exporter.do_export(): if exporter.do_export():
self.progress_label.setText( self.progress_label.setText(
@ -291,15 +284,6 @@ class SongExportForm(OpenLPWizard):
if not item.isHidden(): if not item.isHidden():
item.setCheckState(QtCore.Qt.Checked) item.setCheckState(QtCore.Qt.Checked)
def on_directory_button_clicked(self):
"""
Called when the *directory_button* was clicked. Opens a dialog and writes
the path to *directory_line_edit*.
"""
self.get_folder(
translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'),
self.directory_line_edit, 'last directory export')
def find_list_widget_items(list_widget, text=''): def find_list_widget_items(list_widget, text=''):
""" """

View File

@ -22,15 +22,13 @@
""" """
The song import functions for OpenLP. The song import functions for OpenLP.
""" """
import codecs
import logging import logging
import os
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.ui.lib.filedialog import FileDialog from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
@ -93,9 +91,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked) self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked)
self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked) self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked)
else: else:
self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked) self.format_widgets[song_format]['path_edit'].pathChanged.connect(self.on_path_edit_path_changed)
self.format_widgets[song_format]['file_path_edit'].textChanged.\
connect(self.on_filepath_edit_text_changed)
def add_custom_pages(self): def add_custom_pages(self):
""" """
@ -155,7 +151,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
self.format_widgets[format_list]['removeButton'].setText( self.format_widgets[format_list]['removeButton'].setText(
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
else: else:
self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse)
f_label = 'Filename:' f_label = 'Filename:'
if select_mode == SongFormatSelect.SingleFolder: if select_mode == SongFormatSelect.SingleFolder:
f_label = 'Folder:' f_label = 'Folder:'
@ -172,16 +167,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
self.error_save_to_button.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File')) self.error_save_to_button.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File'))
# Align all QFormLayouts towards each other. # Align all QFormLayouts towards each other.
formats = [f for f in SongFormat.get_format_list() if 'filepathLabel' in self.format_widgets[f]] formats = [f for f in SongFormat.get_format_list() if 'filepathLabel' in self.format_widgets[f]]
labels = [self.format_widgets[f]['filepathLabel'] for f in formats] labels = [self.format_widgets[f]['filepathLabel'] for f in formats] + [self.format_label]
# Get max width of all labels # Get max width of all labels
max_label_width = max(self.format_label.minimumSizeHint().width(), max_label_width = max(labels, key=lambda label: label.minimumSizeHint().width()).minimumSizeHint().width()
max([label.minimumSizeHint().width() for label in labels])) for label in labels:
self.format_spacer.changeSize(max_label_width, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) label.setFixedWidth(max_label_width)
spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats]
for index, spacer in enumerate(spacers):
spacer.changeSize(
max_label_width - labels[index].minimumSizeHint().width(), 0,
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
# Align descriptionLabels with rest of layout # Align descriptionLabels with rest of layout
for format_list in SongFormat.get_format_list(): for format_list in SongFormat.get_format_list():
if SongFormat.get(format_list, 'descriptionText') is not None: if SongFormat.get(format_list, 'descriptionText') is not None:
@ -209,13 +199,13 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
Settings().setValue('songs/last import type', this_format) Settings().setValue('songs/last import type', this_format)
select_mode, class_, error_msg = SongFormat.get(this_format, 'selectMode', 'class', 'invalidSourceMsg') select_mode, class_, error_msg = SongFormat.get(this_format, 'selectMode', 'class', 'invalidSourceMsg')
if select_mode == SongFormatSelect.MultipleFiles: if select_mode == SongFormatSelect.MultipleFiles:
import_source = self.get_list_of_files(self.format_widgets[this_format]['file_list_widget']) import_source = self.get_list_of_paths(self.format_widgets[this_format]['file_list_widget'])
error_title = UiStrings().IFSp error_title = UiStrings().IFSp
focus_button = self.format_widgets[this_format]['addButton'] focus_button = self.format_widgets[this_format]['addButton']
else: else:
import_source = self.format_widgets[this_format]['file_path_edit'].text() import_source = self.format_widgets[this_format]['path_edit'].path
error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs) error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs)
focus_button = self.format_widgets[this_format]['browseButton'] focus_button = self.format_widgets[this_format]['path_edit']
if not class_.is_valid_source(import_source): if not class_.is_valid_source(import_source):
critical_error_message_box(error_title, error_msg) critical_error_message_box(error_title, error_msg)
focus_button.setFocus() focus_button.setFocus()
@ -238,20 +228,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
if filters: if filters:
filters += ';;' filters += ';;'
filters += '{text} (*)'.format(text=UiStrings().AllFiles) filters += '{text} (*)'.format(text=UiStrings().AllFiles)
file_paths, selected_filter = FileDialog.getOpenFileNames( file_paths, filter_used = FileDialog.getOpenFileNames(
self, title, Settings().value(self.plugin.settings_section + '/last directory import'), filters) self, title,
Settings().value(self.plugin.settings_section + '/last directory import'), filters)
for file_path in file_paths:
list_item = QtWidgets.QListWidgetItem(str(file_path))
list_item.setData(QtCore.Qt.UserRole, file_path)
listbox.addItem(list_item)
if file_paths: if file_paths:
file_names = [path_to_str(file_path) for file_path in file_paths]
listbox.addItems(file_names)
Settings().setValue(self.plugin.settings_section + '/last directory import', file_paths[0].parent) Settings().setValue(self.plugin.settings_section + '/last directory import', file_paths[0].parent)
def get_list_of_files(self, list_box): def get_list_of_paths(self, list_box):
""" """
Return a list of file from the list_box Return a list of file from the list_box
:param list_box: The source list box :param list_box: The source list box
""" """
return [list_box.item(i).text() for i in range(list_box.count())] return [list_box.item(i).data(QtCore.Qt.UserRole) for i in range(list_box.count())]
def remove_selected_items(self, list_box): def remove_selected_items(self, list_box):
""" """
@ -263,20 +256,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
item = list_box.takeItem(list_box.row(item)) item = list_box.takeItem(list_box.row(item))
del item del item
def on_browse_button_clicked(self):
"""
Browse for files or a directory.
"""
this_format = self.current_format
select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter')
file_path_edit = self.format_widgets[this_format]['file_path_edit']
if select_mode == SongFormatSelect.SingleFile:
self.get_file_name(WizardStrings.OpenTypeFile.format(file_type=format_name),
file_path_edit, 'last directory import', ext_filter)
elif select_mode == SongFormatSelect.SingleFolder:
self.get_folder(
WizardStrings.OpenTypeFolder.format(folder_name=format_name), file_path_edit, 'last directory import')
def on_add_button_clicked(self): def on_add_button_clicked(self):
""" """
Add a file or directory. Add a file or directory.
@ -296,7 +275,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget']) self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget'])
self.source_page.completeChanged.emit() self.source_page.completeChanged.emit()
def on_filepath_edit_text_changed(self): def on_path_edit_path_changed(self):
""" """
Called when the content of the Filename/Folder edit box changes. Called when the content of the Filename/Folder edit box changes.
""" """
@ -317,8 +296,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
select_mode = SongFormat.get(format_list, 'selectMode') select_mode = SongFormat.get(format_list, 'selectMode')
if select_mode == SongFormatSelect.MultipleFiles: if select_mode == SongFormatSelect.MultipleFiles:
self.format_widgets[format_list]['file_list_widget'].clear() self.format_widgets[format_list]['file_list_widget'].clear()
else:
self.format_widgets[format_list]['file_path_edit'].setText('')
self.error_report_text_edit.clear() self.error_report_text_edit.clear()
self.error_report_text_edit.setHidden(True) self.error_report_text_edit.setHidden(True)
self.error_copy_to_button.setHidden(True) self.error_copy_to_button.setHidden(True)
@ -341,14 +318,14 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
select_mode = SongFormat.get(source_format, 'selectMode') select_mode = SongFormat.get(source_format, 'selectMode')
if select_mode == SongFormatSelect.SingleFile: if select_mode == SongFormatSelect.SingleFile:
importer = self.plugin.import_songs(source_format, importer = self.plugin.import_songs(source_format,
filename=self.format_widgets[source_format]['file_path_edit'].text()) file_path=self.format_widgets[source_format]['path_edit'].path)
elif select_mode == SongFormatSelect.SingleFolder: elif select_mode == SongFormatSelect.SingleFolder:
importer = self.plugin.import_songs(source_format, importer = self.plugin.import_songs(source_format,
folder=self.format_widgets[source_format]['file_path_edit'].text()) folder_path=self.format_widgets[source_format]['path_edit'].path)
else: else:
importer = self.plugin.import_songs( importer = self.plugin.import_songs(
source_format, source_format,
filenames=self.get_list_of_files(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() importer.do_import()
self.progress_label.setText(WizardStrings.FinishedImport) self.progress_label.setText(WizardStrings.FinishedImport)
@ -366,18 +343,17 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
""" """
file_path, filter_used = FileDialog.getSaveFileName( file_path, filter_used = FileDialog.getSaveFileName(
self, Settings().value(self.plugin.settings_section + '/last directory import')) self, Settings().value(self.plugin.settings_section + '/last directory import'))
if not file_path: if file_path is None:
return return
with file_path.open('w', encoding='utf-8') as report_file: file_path.write_text(self.error_report_text_edit.toPlainText(), encoding='utf-8')
report_file.write(self.error_report_text_edit.toPlainText())
def add_file_select_item(self): def add_file_select_item(self):
""" """
Add a file selection page. Add a file selection page.
""" """
this_format = self.current_format this_format = self.current_format
prefix, can_disable, description_text, select_mode = \ format_name, prefix, can_disable, description_text, select_mode, filters = \
SongFormat.get(this_format, 'prefix', 'canDisable', 'descriptionText', 'selectMode') SongFormat.get(this_format, 'name', 'prefix', 'canDisable', 'descriptionText', 'selectMode', 'filter')
page = QtWidgets.QWidget() page = QtWidgets.QWidget()
page.setObjectName(prefix + 'Page') page.setObjectName(prefix + 'Page')
if can_disable: if can_disable:
@ -403,26 +379,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder: if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
file_path_layout = QtWidgets.QHBoxLayout() file_path_layout = QtWidgets.QHBoxLayout()
file_path_layout.setObjectName(prefix + '_file_path_layout') file_path_layout.setObjectName(prefix + '_file_path_layout')
file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0)
file_path_label = QtWidgets.QLabel(import_widget) file_path_label = QtWidgets.QLabel(import_widget)
file_path_label.setObjectName(prefix + 'FilepathLabel')
file_path_layout.addWidget(file_path_label) file_path_layout.addWidget(file_path_label)
file_path_spacer = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) if select_mode == SongFormatSelect.SingleFile:
file_path_layout.addSpacerItem(file_path_spacer) path_type = PathType.Files
file_path_edit = QtWidgets.QLineEdit(import_widget) dialog_caption = WizardStrings.OpenTypeFile.format(file_type=format_name)
file_path_edit.setObjectName(prefix + '_file_path_edit') else:
file_path_layout.addWidget(file_path_edit) path_type = PathType.Directories
browse_button = QtWidgets.QToolButton(import_widget) dialog_caption = WizardStrings.OpenTypeFolder.format(folder_name=format_name)
browse_button.setIcon(self.open_icon) path_edit = PathEdit(
browse_button.setObjectName(prefix + 'BrowseButton') parent=import_widget, path_type=path_type, dialog_caption=dialog_caption, show_revert=False)
file_path_layout.addWidget(browse_button) path_edit.filters = path_edit.filters + filters
path_edit.path = Settings().value(self.plugin.settings_section + '/last directory import')
file_path_layout.addWidget(path_edit)
import_layout.addLayout(file_path_layout) import_layout.addLayout(file_path_layout)
import_layout.addSpacerItem(self.stack_spacer) import_layout.addSpacerItem(self.stack_spacer)
self.format_widgets[this_format]['filepathLabel'] = file_path_label self.format_widgets[this_format]['filepathLabel'] = file_path_label
self.format_widgets[this_format]['filepathSpacer'] = file_path_spacer self.format_widgets[this_format]['path_edit'] = path_edit
self.format_widgets[this_format]['file_path_layout'] = file_path_layout
self.format_widgets[this_format]['file_path_edit'] = file_path_edit
self.format_widgets[this_format]['browseButton'] = browse_button
elif select_mode == SongFormatSelect.MultipleFiles: elif select_mode == SongFormatSelect.MultipleFiles:
file_list_widget = QtWidgets.QListWidget(import_widget) file_list_widget = QtWidgets.QListWidget(import_widget)
file_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) file_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
@ -496,6 +469,8 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
* or if SingleFolder mode, the specified folder exists * or if SingleFolder mode, the specified folder exists
When this method returns True, the wizard's Next button is enabled. When this method returns True, the wizard's Next button is enabled.
:rtype: bool
""" """
wizard = self.wizard() wizard = self.wizard()
this_format = wizard.current_format this_format = wizard.current_format
@ -505,10 +480,10 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0: if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
return True return True
else: else:
file_path = str(wizard.format_widgets[this_format]['file_path_edit'].text()) file_path = wizard.format_widgets[this_format]['path_edit'].path
if file_path: if file_path:
if select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path): if select_mode == SongFormatSelect.SingleFile and file_path.is_file():
return True return True
elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(file_path): elif select_mode == SongFormatSelect.SingleFolder and file_path.is_dir():
return True return True
return False return False

View File

@ -534,13 +534,13 @@ def delete_song(song_id, song_plugin):
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id) media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
for media_file in media_files: for media_file in media_files:
try: try:
os.remove(media_file.file_name) media_file.file_path.unlink()
except OSError: except OSError:
log.exception('Could not remove file: {name}'.format(name=media_file.file_name)) log.exception('Could not remove file: {name}'.format(name=media_file.file_path))
try: try:
save_path = os.path.join(str(AppLocation.get_section_data_path(song_plugin.name)), 'audio', str(song_id)) save_path = AppLocation.get_section_data_path(song_plugin.name) / 'audio' / str(song_id)
if os.path.exists(save_path): if save_path.exists():
os.rmdir(save_path) save_path.rmdir()
except OSError: except OSError:
log.exception('Could not remove directory: {path}'.format(path=save_path)) log.exception('Could not remove directory: {path}'.format(path=save_path))
song_plugin.manager.delete_object(Song, song_id) song_plugin.manager.delete_object(Song, song_id)

View File

@ -23,14 +23,15 @@
The :mod:`db` module provides the database and schema that is the backend for The :mod:`db` module provides the database and schema that is the backend for
the Songs plugin the Songs plugin
""" """
from contextlib import suppress
from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation, reconstructor from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func, text from sqlalchemy.sql.expression import func, text
from openlp.core.lib.db import BaseModel, init_db from openlp.core.common.applocation import AppLocation
from openlp.core.common.languagemanager import get_natural_key from openlp.core.common.languagemanager import get_natural_key
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.lib.db import BaseModel, PathType, init_db
class Author(BaseModel): class Author(BaseModel):
@ -238,7 +239,7 @@ def init_schema(url):
**media_files Table** **media_files Table**
* id * id
* file_name * _file_path
* type * type
**song_books Table** **song_books Table**
@ -305,7 +306,7 @@ def init_schema(url):
'media_files', metadata, 'media_files', metadata,
Column('id', types.Integer(), primary_key=True), Column('id', types.Integer(), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None), Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
Column('file_name', types.Unicode(255), nullable=False), Column('file_path', PathType, nullable=False),
Column('type', types.Unicode(64), nullable=False, default='audio'), Column('type', types.Unicode(64), nullable=False, default='audio'),
Column('weight', types.Integer(), default=0) Column('weight', types.Integer(), default=0)
) )

View File

@ -19,11 +19,9 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import logging
import os
import chardet import chardet
import codecs import codecs
import logging
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
@ -48,7 +46,7 @@ class CCLIFileImport(SongImport):
:param manager: The song manager for the running OpenLP installation. :param manager: The song manager for the running OpenLP installation.
:param kwargs: The files to be imported. :param kwargs: The files to be imported.
""" """
super(CCLIFileImport, self).__init__(manager, **kwargs) super().__init__(manager, **kwargs)
def do_import(self): def do_import(self):
""" """
@ -56,37 +54,35 @@ class CCLIFileImport(SongImport):
""" """
log.debug('Starting CCLI File Import') log.debug('Starting CCLI File Import')
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for file_path in self.import_source:
filename = str(filename) log.debug('Importing CCLI File: {name}'.format(name=file_path))
log.debug('Importing CCLI File: {name}'.format(name=filename)) if file_path.is_file():
if os.path.isfile(filename): with file_path.open('rb') as detect_file:
detect_file = open(filename, 'rb')
detect_content = detect_file.read(2048) detect_content = detect_file.read(2048)
try: try:
str(detect_content, 'utf-8') str(detect_content, 'utf-8')
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)
detect_file.close() in_file = codecs.open(str(file_path), 'r', details['encoding'])
infile = codecs.open(filename, 'r', details['encoding']) if not in_file.read(1) == '\ufeff':
if not infile.read(1) == '\ufeff':
# not UTF or no BOM was found # not UTF or no BOM was found
infile.seek(0) in_file.seek(0)
lines = infile.readlines() lines = in_file.readlines()
infile.close() in_file.close()
ext = os.path.splitext(filename)[1] ext = file_path.suffix.lower()
if ext.lower() == '.usr' or ext.lower() == '.bin': if ext == '.usr' or ext == '.bin':
log.info('SongSelect USR format file found: {name}'.format(name=filename)) log.info('SongSelect USR format file found: {name}'.format(name=file_path))
if not self.do_import_usr_file(lines): if not self.do_import_usr_file(lines):
self.log_error(filename) self.log_error(file_path)
elif ext.lower() == '.txt': elif ext == '.txt':
log.info('SongSelect TEXT format file found: {name}'.format(name=filename)) log.info('SongSelect TEXT format file found: {name}'.format(name=file_path))
if not self.do_import_txt_file(lines): if not self.do_import_txt_file(lines):
self.log_error(filename) self.log_error(file_path)
else: else:
self.log_error(filename, translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid ' self.log_error(file_path, translate('SongsPlugin.CCLIFileImport',
'extension.')) 'The file does not have a valid extension.'))
log.info('Extension {name} is not valid'.format(name=filename)) log.info('Extension {name} is not valid'.format(name=file_path))
if self.stop_import_flag: if self.stop_import_flag:
return return

View File

@ -47,12 +47,11 @@ class ChordProImport(SongImport):
""" """
def do_import(self): def do_import(self):
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
song_file = open(filename, 'rt') with file_path.open('rt') as song_file:
self.do_import_file(song_file) self.do_import_file(song_file)
song_file.close()
def do_import_file(self, song_file): def do_import_file(self, song_file):
""" """

View File

@ -78,27 +78,29 @@ class DreamBeamImport(SongImport):
def do_import(self): def do_import(self):
""" """
Receive a single file or a list of files to import. Receive a single file_path or a list of files to import.
""" """
if isinstance(self.import_source, list): if isinstance(self.import_source, list):
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for file in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.set_defaults() self.set_defaults()
parser = etree.XMLParser(remove_blank_text=True) parser = etree.XMLParser(remove_blank_text=True)
try: try:
parsed_file = etree.parse(open(file, 'r'), parser) with file_path.open('r') as xml_file:
parsed_file = etree.parse(xml_file, parser)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
log.exception('XML syntax error in file {name}'.format(name=file)) log.exception('XML syntax error in file_path {name}'.format(name=file_path))
self.log_error(file, SongStrings.XMLSyntaxError) self.log_error(file_path, SongStrings.XMLSyntaxError)
continue continue
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
song_xml = objectify.fromstring(xml) song_xml = objectify.fromstring(xml)
if song_xml.tag != 'DreamSong': if song_xml.tag != 'DreamSong':
self.log_error( self.log_error(
file, file_path,
translate('SongsPlugin.DreamBeamImport', 'Invalid DreamBeam song file. Missing DreamSong tag.')) translate('SongsPlugin.DreamBeamImport',
'Invalid DreamBeam song file_path. Missing DreamSong tag.'))
continue continue
if hasattr(song_xml, 'Version'): if hasattr(song_xml, 'Version'):
self.version = float(song_xml.Version.text) self.version = float(song_xml.Version.text)
@ -144,4 +146,4 @@ class DreamBeamImport(SongImport):
else: else:
self.parse_author(author_copyright) self.parse_author(author_copyright)
if not self.finish(): if not self.finish():
self.log_error(file) self.log_error(file_path)

View File

@ -47,7 +47,7 @@ class EasySlidesImport(SongImport):
def do_import(self): def do_import(self):
log.info('Importing EasySlides XML file {source}'.format(source=self.import_source)) log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
parser = etree.XMLParser(remove_blank_text=True, recover=True) parser = etree.XMLParser(remove_blank_text=True, recover=True)
parsed_file = etree.parse(self.import_source, parser) parsed_file = etree.parse(str(self.import_source), parser)
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
song_xml = objectify.fromstring(xml) song_xml = objectify.fromstring(xml)
self.import_wizard.progress_bar.setMaximum(len(song_xml.Item)) self.import_wizard.progress_bar.setMaximum(len(song_xml.Item))

View File

@ -22,14 +22,15 @@
""" """
The :mod:`easyworship` module provides the functionality for importing EasyWorship song databases into OpenLP. The :mod:`easyworship` module provides the functionality for importing EasyWorship song databases into OpenLP.
""" """
import os
import struct
import re
import zlib
import logging import logging
import os
import re
import struct
import zlib
import sqlite3 import sqlite3
from openlp.core.common.path import Path
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
@ -76,9 +77,11 @@ class EasyWorshipSongImport(SongImport):
""" """
Determines the type of file to import and calls the appropiate method Determines the type of file to import and calls the appropiate method
""" """
if self.import_source.lower().endswith('ews'): self.import_source = Path(self.import_source)
ext = self.import_source.suffix.lower()
if ext == '.ews':
self.import_ews() self.import_ews()
elif self.import_source.endswith('DB'): elif ext == '.db':
self.import_db() self.import_db()
else: else:
self.import_sqlite_db() self.import_sqlite_db()
@ -91,11 +94,11 @@ class EasyWorshipSongImport(SongImport):
or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format
""" """
# Open ews file if it exists # Open ews file if it exists
if not os.path.isfile(self.import_source): if not self.import_source.is_file():
log.debug('Given ews file does not exists.') log.debug('Given ews file does not exists.')
return return
# Make sure there is room for at least a header and one entry # Make sure there is room for at least a header and one entry
if os.path.getsize(self.import_source) < 892: if self.import_source.stat().st_size < 892:
log.debug('Given ews file is to small to contain valid data.') log.debug('Given ews file is to small to contain valid data.')
return return
# Take a stab at how text is encoded # Take a stab at how text is encoded
@ -104,7 +107,7 @@ class EasyWorshipSongImport(SongImport):
if not self.encoding: if not self.encoding:
log.debug('No encoding set.') log.debug('No encoding set.')
return return
self.ews_file = open(self.import_source, 'rb') self.ews_file = self.import_source.open('rb')
# EWS header, version '1.6'/' 3'/' 5': # EWS header, version '1.6'/' 3'/' 5':
# Offset Field Data type Length Details # Offset Field Data type Length Details
# -------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------
@ -199,23 +202,22 @@ class EasyWorshipSongImport(SongImport):
Import the songs from the database Import the songs from the database
""" """
# Open the DB and MB files if they exist # Open the DB and MB files if they exist
import_source_mb = self.import_source.replace('.DB', '.MB') import_source_mb = self.import_source.with_suffix('.MB')
if not os.path.isfile(self.import_source): if not self.import_source.is_file():
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport', self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'This file does not exist.')) 'This file does not exist.'))
return return
if not os.path.isfile(import_source_mb): if not import_source_mb.is_file():
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport', self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'Could not find the "Songs.MB" file. It must be in the same ' 'Could not find the "Songs.MB" file. It must be in the same '
'folder as the "Songs.DB" file.')) 'folder as the "Songs.DB" file.'))
return return
db_size = os.path.getsize(self.import_source) if self.import_source.stat().st_size < 0x800:
if db_size < 0x800:
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport', self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'This file is not a valid EasyWorship database.')) 'This file is not a valid EasyWorship database.'))
return return
db_file = open(self.import_source, 'rb') db_file = self.import_source.open('rb')
self.memo_file = open(import_source_mb, 'rb') self.memo_file = import_source_mb.open('rb')
# Don't accept files that are clearly not paradox files # Don't accept files that are clearly not paradox files
record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35)) record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35))
if header_size != 0x800 or block_size < 1 or block_size > 4: if header_size != 0x800 or block_size < 1 or block_size > 4:
@ -340,52 +342,34 @@ class EasyWorshipSongImport(SongImport):
db_file.close() db_file.close()
self.memo_file.close() self.memo_file.close()
def _find_file(self, base_path, path_list):
"""
Find the specified file, with the option of the file being at any level in the specified directory structure.
:param base_path: the location search in
:param path_list: the targeted file, preceded by directories that may be their parents relative to the base_path
:return: path for targeted file
"""
target_file = ''
while len(path_list) > 0:
target_file = os.path.join(path_list[-1], target_file)
path_list = path_list[:len(path_list) - 1]
full_path = os.path.join(base_path, target_file)
full_path = full_path[:len(full_path) - 1]
if os.path.isfile(full_path):
return full_path
return ''
def import_sqlite_db(self): def import_sqlite_db(self):
""" """
Import the songs from an EasyWorship 6 SQLite database Import the songs from an EasyWorship 6 SQLite database
""" """
songs_db_path = self._find_file(self.import_source, ["Databases", "Data", "Songs.db"]) songs_db_path = next(self.import_source.rglob('Songs.db'), None)
song_words_db_path = self._find_file(self.import_source, ["Databases", "Data", "SongWords.db"]) song_words_db_path = next(self.import_source.rglob('SongWords.db'), None)
invalid_dir_msg = 'This does not appear to be a valid Easy Worship 6 database directory.' invalid_dir_msg = translate('SongsPlugin.EasyWorshipSongImport',
'This does not appear to be a valid Easy Worship 6 database directory.')
invalid_db_msg = translate('SongsPlugin.EasyWorshipSongImport', 'This is not a valid Easy Worship 6 database.')
# check to see if needed files are there # check to see if needed files are there
if not os.path.isfile(songs_db_path): if not (songs_db_path and songs_db_path.is_file()):
self.log_error(songs_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg)) self.log_error(self.import_source, invalid_dir_msg)
return return
if not os.path.isfile(song_words_db_path): if not (song_words_db_path and song_words_db_path.is_file()):
self.log_error(song_words_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg)) self.log_error(self.import_source, invalid_dir_msg)
return return
# get database handles # get database handles
songs_conn = sqlite3.connect(songs_db_path) songs_conn = sqlite3.connect(str(songs_db_path))
words_conn = sqlite3.connect(song_words_db_path) words_conn = sqlite3.connect(str(song_words_db_path))
if songs_conn is None or words_conn is None: if songs_conn is None or words_conn is None:
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport', self.log_error(self.import_source, invalid_db_msg)
'This is not a valid Easy Worship 6 database.'))
songs_conn.close() songs_conn.close()
words_conn.close() words_conn.close()
return return
songs_db = songs_conn.cursor() songs_db = songs_conn.cursor()
words_db = words_conn.cursor() words_db = words_conn.cursor()
if songs_conn is None or words_conn is None: if songs_conn is None or words_conn is None:
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport', self.log_error(self.import_source, invalid_db_msg)
'This is not a valid Easy Worship 6 database.'))
songs_conn.close() songs_conn.close()
words_conn.close() words_conn.close()
return return

View File

@ -82,10 +82,8 @@ The XML of `Foilpresenter <http://foilpresenter.de/>`_ songs is of the format::
</kategorien> </kategorien>
</foilpresenterfolie> </foilpresenterfolie>
""" """
import logging import logging
import re import re
import os
from lxml import etree, objectify from lxml import etree, objectify
@ -121,10 +119,9 @@ class FoilPresenterImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar( self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
try: try:
parsed_file = etree.parse(file_path, parser) parsed_file = etree.parse(str(file_path), parser)
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
self.foil_presenter.xml_to_song(xml) self.foil_presenter.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:

View File

@ -50,12 +50,11 @@ class LyrixImport(SongImport):
if not isinstance(self.import_source, list): if not isinstance(self.import_source, list):
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
song_file = open(filename, 'rt', encoding='cp1251') with file_path.open('rt', encoding='cp1251') as song_file:
self.do_import_file(song_file) self.do_import_file(song_file)
song_file.close()
def do_import_file(self, file): def do_import_file(self, file):
""" """

View File

@ -266,7 +266,7 @@ class OpenLPSongImport(SongImport):
if has_media_files and song.media_files: if has_media_files and song.media_files:
for media_file in song.media_files: for media_file in song.media_files:
existing_media_file = self.manager.get_object_filtered( existing_media_file = self.manager.get_object_filtered(
MediaFile, MediaFile.file_name == media_file.file_name) MediaFile, MediaFile.file_path == media_file.file_path)
if existing_media_file: if existing_media_file:
new_song.media_files.append(existing_media_file) new_song.media_files.append(existing_media_file)
else: else:

View File

@ -23,9 +23,7 @@
The :mod:`openlyrics` module provides the functionality for importing The :mod:`openlyrics` module provides the functionality for importing
songs which are saved as OpenLyrics files. songs which are saved as OpenLyrics files.
""" """
import logging import logging
import os
from lxml import etree from lxml import etree
@ -58,12 +56,11 @@ class OpenLyricsImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar( self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
try: try:
# Pass a file object, because lxml does not cope with some # Pass a file object, because lxml does not cope with some
# special characters in the path (see lp:757673 and lp:744337). # special characters in the path (see lp:757673 and lp:744337).
parsed_file = etree.parse(open(file_path, 'rb'), parser) parsed_file = etree.parse(file_path.open('rb'), parser)
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
self.open_lyrics.xml_to_song(xml) self.open_lyrics.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:

View File

@ -20,7 +20,6 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import logging import logging
import os
import time import time
from PyQt5 import QtCore from PyQt5 import QtCore
@ -70,12 +69,11 @@ class OpenOfficeImport(SongImport):
log.error(exc) log.error(exc)
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
break break
filename = str(filename) if file_path.is_file():
if os.path.isfile(filename): self.open_ooo_file(file_path)
self.open_ooo_file(filename)
if self.document: if self.document:
self.process_ooo_document() self.process_ooo_document()
self.close_ooo_file() self.close_ooo_file()
@ -144,12 +142,7 @@ class OpenOfficeImport(SongImport):
Open the passed file in OpenOffice.org Impress Open the passed file in OpenOffice.org Impress
""" """
self.file_path = file_path self.file_path = file_path
if is_win(): url = file_path.as_uri()
url = file_path.replace('\\', '/')
url = url.replace(':', '|').replace(' ', '%20')
url = 'file:///' + url
else:
url = uno.systemPathToFileUrl(file_path)
properties = [] properties = []
properties.append(self.create_property('Hidden', True)) properties.append(self.create_property('Hidden', True))
properties = tuple(properties) properties = tuple(properties)
@ -159,7 +152,7 @@ class OpenOfficeImport(SongImport):
self.document.supportsService("com.sun.star.text.TextDocument"): self.document.supportsService("com.sun.star.text.TextDocument"):
self.close_ooo_file() self.close_ooo_file()
else: else:
self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0) self.import_wizard.increment_progress_bar('Processing file {file_path}'.format(file_path=file_path), 0)
except AttributeError: except AttributeError:
log.exception("open_ooo_file failed: {url}".format(url=url)) log.exception("open_ooo_file failed: {url}".format(url=url))
return return

View File

@ -19,7 +19,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import logging import logging
import re import re
@ -116,12 +115,11 @@ class OpenSongImport(SongImport):
if not isinstance(self.import_source, list): if not isinstance(self.import_source, list):
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
song_file = open(filename, 'rb') with file_path.open('rb') as song_file:
self.do_import_file(song_file) self.do_import_file(song_file)
song_file.close()
def do_import_file(self, file): def do_import_file(self, file):
""" """

View File

@ -231,16 +231,15 @@ class OPSProImport(SongImport):
xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50) xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50)
# Access97 XOR of the source # Access97 XOR of the source
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13) xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
mdb = open(self.import_source, 'rb') with self.import_source.open('rb') as mdb_file:
mdb.seek(0x14) mdb_file.seek(0x14)
version = struct.unpack('B', mdb.read(1))[0] version = struct.unpack('B', mdb_file.read(1))[0]
# Get encrypted logo # Get encrypted logo
mdb.seek(0x62) mdb_file.seek(0x62)
EncrypFlag = struct.unpack('B', mdb.read(1))[0] EncrypFlag = struct.unpack('B', mdb_file.read(1))[0]
# Get encrypted password # Get encrypted password
mdb.seek(0x42) mdb_file.seek(0x42)
encrypted_password = mdb.read(26) encrypted_password = mdb_file.read(26)
mdb.close()
# "Decrypt" the password based on the version # "Decrypt" the password based on the version
decrypted_password = '' decrypted_password = ''
if version < 0x01: if version < 0x01:

View File

@ -23,8 +23,6 @@
The :mod:`powerpraiseimport` module provides the functionality for importing The :mod:`powerpraiseimport` module provides the functionality for importing
Powerpraise song files into the current database. Powerpraise song files into the current database.
""" """
import os
from lxml import objectify from lxml import objectify
from openlp.core.ui.lib.wizard import WizardStrings from openlp.core.ui.lib.wizard import WizardStrings
@ -41,9 +39,9 @@ class PowerPraiseImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar( self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
WizardStrings.ImportingType.format(source=os.path.basename(file_path))) with file_path.open('rb') as xml_file:
root = objectify.parse(open(file_path, 'rb')).getroot() root = objectify.parse(xml_file).getroot()
self.process_song(root) self.process_song(root)
def process_song(self, root): def process_song(self, root):

View File

@ -72,10 +72,14 @@ class PowerSongImport(SongImport):
Checks if source is a PowerSong 1.0 folder: Checks if source is a PowerSong 1.0 folder:
* is a directory * is a directory
* contains at least one \*.song file * contains at least one \*.song file
:param openlp.core.common.path.Path import_source: Should be a Path object that fulfills the above criteria
:return: If the source is valid
:rtype: bool
""" """
if os.path.isdir(import_source): if import_source.is_dir():
for file in os.listdir(import_source): for file_path in import_source.iterdir():
if fnmatch.fnmatch(file, '*.song'): if file_path.suffix == '.song':
return True return True
return False return False

View File

@ -23,13 +23,11 @@
The :mod:`presentationmanager` module provides the functionality for importing The :mod:`presentationmanager` module provides the functionality for importing
Presentationmanager song files into the current database. Presentationmanager song files into the current database.
""" """
import os
import re import re
import chardet
from lxml import objectify, etree from lxml import objectify, etree
from openlp.core.common import translate from openlp.core.common import get_file_encoding, translate
from openlp.core.ui.lib.wizard import WizardStrings from openlp.core.ui.lib.wizard import WizardStrings
from .songimport import SongImport from .songimport import SongImport
@ -44,17 +42,14 @@ class PresentationManagerImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar( self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
try: try:
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True)) tree = etree.parse(str(file_path), parser=etree.XMLParser(recover=True))
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
# Try to detect encoding and use it # Try to detect encoding and use it
file = open(file_path, mode='rb') encoding = get_file_encoding(file_path)['encoding']
encoding = chardet.detect(file.read())['encoding']
file.close()
# Open file with detected encoding and remove encoding declaration # Open file with detected encoding and remove encoding declaration
text = open(file_path, mode='r', encoding=encoding).read() text = file_path.read_text(encoding=encoding)
text = re.sub('.+\?>\n', '', text) text = re.sub('.+\?>\n', '', text)
try: try:
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True)) tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
@ -80,6 +75,11 @@ class PresentationManagerImport(SongImport):
return '' return ''
def process_song(self, root, file_path): def process_song(self, root, file_path):
"""
:param root:
:param openlp.core.common.path.Path file_path: Path to the file to process
:rtype: None
"""
self.set_defaults() self.set_defaults()
attrs = None attrs = None
if hasattr(root, 'attributes'): if hasattr(root, 'attributes'):
@ -123,4 +123,4 @@ class PresentationManagerImport(SongImport):
self.verse_order_list = verse_order_list self.verse_order_list = verse_order_list
if not self.finish(): if not self.finish():
self.log_error(os.path.basename(file_path)) self.log_error(file_path.name)

View File

@ -23,8 +23,6 @@
The :mod:`propresenter` module provides the functionality for importing The :mod:`propresenter` module provides the functionality for importing
ProPresenter song files into the current installation database. ProPresenter song files into the current installation database.
""" """
import os
import base64 import base64
import logging import logging
from lxml import objectify from lxml import objectify
@ -47,11 +45,17 @@ class ProPresenterImport(SongImport):
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar( self.import_wizard.increment_progress_bar(
WizardStrings.ImportingType.format(source=os.path.basename(file_path))) WizardStrings.ImportingType.format(source=file_path.name))
root = objectify.parse(open(file_path, 'rb')).getroot() with file_path.open('rb') as xml_file:
root = objectify.parse(xml_file).getroot()
self.process_song(root, file_path) self.process_song(root, file_path)
def process_song(self, root, filename): def process_song(self, root, file_path):
"""
:param root:
:param openlp.core.common.path.Path file_path: Path to the file thats being imported
:rtype: None
"""
self.set_defaults() self.set_defaults()
# Extract ProPresenter versionNumber # Extract ProPresenter versionNumber
@ -64,9 +68,7 @@ class ProPresenterImport(SongImport):
# Title # Title
self.title = root.get('CCLISongTitle') self.title = root.get('CCLISongTitle')
if not self.title or self.title == '': if not self.title or self.title == '':
self.title = os.path.basename(filename) self.title = file_path.stem
if self.title[-5:-1] == '.pro':
self.title = self.title[:-5]
# Notes # Notes
self.comments = root.get('notes') self.comments = root.get('notes')
# Author # Author

View File

@ -112,7 +112,7 @@ class SongBeamerImport(SongImport):
if not isinstance(self.import_source, list): if not isinstance(self.import_source, list):
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for import_file in self.import_source: for file_path in self.import_source:
# TODO: check that it is a valid SongBeamer file # TODO: check that it is a valid SongBeamer file
if self.stop_import_flag: if self.stop_import_flag:
return return
@ -120,20 +120,19 @@ class SongBeamerImport(SongImport):
self.current_verse = '' self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse] self.current_verse_type = VerseType.tags[VerseType.Verse]
self.chord_table = None self.chord_table = None
file_name = os.path.split(import_file)[1] if file_path.is_file():
if os.path.isfile(import_file):
# Detect the encoding # Detect the encoding
self.input_file_encoding = get_file_encoding(Path(import_file))['encoding'] self.input_file_encoding = get_file_encoding(file_path)['encoding']
# The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode. # The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode.
# So if it doesn't start with 'u' we default to cp1252. See: # So if it doesn't start with 'u' we default to cp1252. See:
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2 # https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
if not self.input_file_encoding.lower().startswith('u'): if not self.input_file_encoding.lower().startswith('u'):
self.input_file_encoding = 'cp1252' self.input_file_encoding = 'cp1252'
infile = open(import_file, 'rt', encoding=self.input_file_encoding) with file_path.open(encoding=self.input_file_encoding) as song_file:
song_data = infile.readlines() song_data = song_file.readlines()
else: else:
continue continue
self.title = file_name.split('.sng')[0] self.title = file_path.stem
read_verses = False read_verses = False
# The first verse separator doesn't count, but the others does, so line count starts at -1 # The first verse separator doesn't count, but the others does, so line count starts at -1
line_number = -1 line_number = -1
@ -185,7 +184,7 @@ class SongBeamerImport(SongImport):
# inserted by songbeamer, but are manually added headings. So restart the loop, and # inserted by songbeamer, but are manually added headings. So restart the loop, and
# count tags as lines. # count tags as lines.
self.set_defaults() self.set_defaults()
self.title = file_name.split('.sng')[0] self.title = file_path.stem
verse_tags_mode = VerseTagMode.ContainsNoTagsRestart verse_tags_mode = VerseTagMode.ContainsNoTagsRestart
read_verses = False read_verses = False
# The first verseseparator doesn't count, but the others does, so linecount starts at -1 # The first verseseparator doesn't count, but the others does, so linecount starts at -1
@ -207,7 +206,7 @@ class SongBeamerImport(SongImport):
self.replace_html_tags() self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type) self.add_verse(self.current_verse, self.current_verse_type)
if not self.finish(): if not self.finish():
self.log_error(import_file) self.log_error(file_path)
def insert_chords(self, line_number, line): def insert_chords(self, line_number, line):
""" """
@ -414,14 +413,15 @@ class SongBeamerImport(SongImport):
""" """
# The path is relative to SongBeamers Song folder # The path is relative to SongBeamers Song folder
if is_win(): if is_win():
user_doc_folder = os.path.expandvars('$DOCUMENTS') user_doc_path = Path(os.path.expandvars('$DOCUMENTS'))
elif is_macosx(): elif is_macosx():
user_doc_folder = os.path.join(os.path.expanduser('~'), 'Documents') user_doc_path = Path.home() / 'Documents'
else: else:
# SongBeamer only runs on mac and win... # SongBeamer only runs on mac and win...
return return
audio_file_path = os.path.normpath(os.path.join(user_doc_folder, 'SongBeamer', 'Songs', audio_file_path)) audio_file_path = user_doc_path / 'SongBeamer' / 'Songs' / audio_file_path
if os.path.isfile(audio_file_path): if audio_file_path.is_file():
self.add_media_file(audio_file_path) self.add_media_file(audio_file_path)
else: else:
log.debug('Could not import mediafile "%s" since it does not exists!' % audio_file_path) log.debug('Could not import mediafile "{audio_file_path}" since it does not exists!'
.format(audio_file_path=audio_file_path))

View File

@ -22,13 +22,11 @@
import logging import logging
import re import re
import shutil
import os
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common import Registry, AppLocation, check_directory_exists, translate from openlp.core.common import Registry, AppLocation, check_directory_exists, translate
from openlp.core.common.path import Path from openlp.core.common.path import copyfile
from openlp.core.ui.lib.wizard import WizardStrings from openlp.core.ui.lib.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
@ -62,14 +60,14 @@ class SongImport(QtCore.QObject):
""" """
self.manager = manager self.manager = manager
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
if 'filename' in kwargs: if 'file_path' in kwargs:
self.import_source = kwargs['filename'] self.import_source = kwargs['file_path']
elif 'filenames' in kwargs: elif 'file_paths' in kwargs:
self.import_source = kwargs['filenames'] self.import_source = kwargs['file_paths']
elif 'folder' in kwargs: elif 'folder_path' in kwargs:
self.import_source = kwargs['folder'] self.import_source = kwargs['folder_path']
else: else:
raise KeyError('Keyword arguments "filename[s]" or "folder" not supplied.') raise KeyError('Keyword arguments "file_path[s]" or "folder_path" not supplied.')
log.debug(self.import_source) log.debug(self.import_source)
self.import_wizard = None self.import_wizard = None
self.song = None self.song = None
@ -270,13 +268,13 @@ class SongImport(QtCore.QObject):
return return
self.authors.append((author, type)) self.authors.append((author, type))
def add_media_file(self, filename, weight=0): def add_media_file(self, file_path, weight=0):
""" """
Add a media file to the list Add a media file to the list
""" """
if filename in [x[0] for x in self.media_files]: if file_path in [x[0] for x in self.media_files]:
return return
self.media_files.append((filename, weight)) self.media_files.append((file_path, weight))
def add_verse(self, verse_text, verse_def='v', lang=None): def add_verse(self, verse_text, verse_def='v', lang=None):
""" """
@ -403,29 +401,30 @@ class SongImport(QtCore.QObject):
self.manager.save_object(song) self.manager.save_object(song)
# Now loop through the media files, copy them to the correct location, # Now loop through the media files, copy them to the correct location,
# and save the song again. # and save the song again.
for filename, weight in self.media_files: for file_path, weight in self.media_files:
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_name == filename) media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_path == file_path)
if not media_file: if not media_file:
if os.path.dirname(filename): if file_path.parent:
filename = self.copy_media_file(song.id, filename) file_path = self.copy_media_file(song.id, file_path)
song.media_files.append(MediaFile.populate(file_name=filename, weight=weight)) song.media_files.append(MediaFile.populate(file_path=file_path, weight=weight))
self.manager.save_object(song) self.manager.save_object(song)
self.set_defaults() self.set_defaults()
return True return True
def copy_media_file(self, song_id, filename): def copy_media_file(self, song_id, file_path):
""" """
This method copies the media file to the correct location and returns This method copies the media file to the correct location and returns
the new file location. the new file location.
:param song_id: :param song_id:
:param filename: The file to copy. :param openlp.core.common.path.Path file_path: The file to copy.
:return: The new location of the file
:rtype: openlp.core.common.path.Path
""" """
if not hasattr(self, 'save_path'): if not hasattr(self, 'save_path'):
self.save_path = os.path.join(str(AppLocation.get_section_data_path(self.import_wizard.plugin.name)), self.save_path = AppLocation.get_section_data_path(self.import_wizard.plugin.name) / 'audio' / str(song_id)
'audio', str(song_id)) check_directory_exists(self.save_path)
check_directory_exists(Path(self.save_path)) if self.save_path not in file_path.parents:
if not filename.startswith(self.save_path): old_path, file_path = file_path, self.save_path / file_path.name
old_file, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1]) copyfile(old_path, file_path)
shutil.copyfile(old_file, filename) return file_path
return filename

View File

@ -25,6 +25,7 @@ songs into the OpenLP database.
""" """
import re import re
from openlp.core.common.path import Path
from openlp.plugins.songs.lib import strip_rtf from openlp.plugins.songs.lib import strip_rtf
from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.importers.songimport import SongImport
@ -72,7 +73,8 @@ class SongProImport(SongImport):
Receive a single file or a list of files to import. Receive a single file or a list of files to import.
""" """
self.encoding = None self.encoding = None
with open(self.import_source, 'rt', errors='ignore') as songs_file: self.import_source = Path(self.import_source)
with self.import_source.open('rt', errors='ignore') as songs_file:
self.import_wizard.progress_bar.setMaximum(0) self.import_wizard.progress_bar.setMaximum(0)
tag = '' tag = ''
text = '' text = ''

View File

@ -23,7 +23,6 @@
The :mod:`songshowplus` module provides the functionality for importing SongShow Plus songs into the OpenLP The :mod:`songshowplus` module provides the functionality for importing SongShow Plus songs into the OpenLP
database. database.
""" """
import os
import logging import logging
import re import re
import struct import struct
@ -93,47 +92,46 @@ class SongShowPlusImport(SongImport):
if not isinstance(self.import_source, list): if not isinstance(self.import_source, list):
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for file in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.ssp_verse_order_list = [] self.ssp_verse_order_list = []
self.other_count = 0 self.other_count = 0
self.other_list = {} self.other_list = {}
file_name = os.path.split(file)[1] self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name), 0)
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_name), 0) with file_path.open('rb') as song_file:
song_data = open(file, 'rb')
while True: while True:
block_key, = struct.unpack("I", song_data.read(4)) block_key, = struct.unpack("I", song_file.read(4))
log.debug('block_key: %d' % block_key) log.debug('block_key: %d' % block_key)
# The file ends with 4 NULL's # The file ends with 4 NULL's
if block_key == 0: if block_key == 0:
break break
next_block_starts, = struct.unpack("I", song_data.read(4)) next_block_starts, = struct.unpack("I", song_file.read(4))
next_block_starts += song_data.tell() next_block_starts += song_file.tell()
if block_key in (VERSE, CHORUS, BRIDGE): if block_key in (VERSE, CHORUS, BRIDGE):
null, verse_no, = struct.unpack("BB", song_data.read(2)) null, verse_no, = struct.unpack("BB", song_file.read(2))
elif block_key == CUSTOM_VERSE: elif block_key == CUSTOM_VERSE:
null, verse_name_length, = struct.unpack("BB", song_data.read(2)) null, verse_name_length, = struct.unpack("BB", song_file.read(2))
verse_name = self.decode(song_data.read(verse_name_length)) verse_name = self.decode(song_file.read(verse_name_length))
length_descriptor_size, = struct.unpack("B", song_data.read(1)) length_descriptor_size, = struct.unpack("B", song_file.read(1))
log.debug('length_descriptor_size: %d' % length_descriptor_size) log.debug('length_descriptor_size: %d' % length_descriptor_size)
# In the case of song_numbers the number is in the data from the # In the case of song_numbers the number is in the data from the
# current position to the next block starts # current position to the next block starts
if block_key == SONG_NUMBER: if block_key == SONG_NUMBER:
sn_bytes = song_data.read(length_descriptor_size - 1) sn_bytes = song_file.read(length_descriptor_size - 1)
self.song_number = int.from_bytes(sn_bytes, byteorder='little') self.song_number = int.from_bytes(sn_bytes, byteorder='little')
continue continue
# Detect if/how long the length descriptor is # Detect if/how long the length descriptor is
if length_descriptor_size == 12 or length_descriptor_size == 20: if length_descriptor_size == 12 or length_descriptor_size == 20:
length_descriptor, = struct.unpack("I", song_data.read(4)) length_descriptor, = struct.unpack("I", song_file.read(4))
elif length_descriptor_size == 2: elif length_descriptor_size == 2:
length_descriptor = 1 length_descriptor = 1
elif length_descriptor_size == 9: elif length_descriptor_size == 9:
length_descriptor = 0 length_descriptor = 0
else: else:
length_descriptor, = struct.unpack("B", song_data.read(1)) length_descriptor, = struct.unpack("B", song_file.read(1))
log.debug('length_descriptor: %d' % length_descriptor) log.debug('length_descriptor: %d' % length_descriptor)
data = song_data.read(length_descriptor) data = song_file.read(length_descriptor)
log.debug(data) log.debug(data)
if block_key == TITLE: if block_key == TITLE:
self.title = self.decode(data) self.title = self.decode(data)
@ -179,11 +177,10 @@ class SongShowPlusImport(SongImport):
self.add_verse(self.decode(data), verse_tag) self.add_verse(self.decode(data), verse_tag)
else: else:
log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data)) log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data))
song_data.seek(next_block_starts) song_file.seek(next_block_starts)
self.verse_order_list = self.ssp_verse_order_list self.verse_order_list = self.ssp_verse_order_list
song_data.close()
if not self.finish(): if not self.finish():
self.log_error(file) self.log_error(file_path)
def to_openlp_verse_tag(self, verse_name, ignore_unique=False): def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
""" """

View File

@ -19,11 +19,8 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import os import os
import re import re
import logging
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib import strip_rtf from openlp.plugins.songs.lib import strip_rtf
@ -60,12 +57,11 @@ class SundayPlusImport(SongImport):
def do_import(self): def do_import(self):
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
song_file = open(filename, 'rb') with file_path.open('rb') as song_file:
self.do_import_file(song_file) self.do_import_file(song_file)
song_file.close()
def do_import_file(self, file): def do_import_file(self, file):
""" """

View File

@ -22,13 +22,12 @@
""" """
The :mod:`lyrix` module provides the functionality for importing songs which are The :mod:`lyrix` module provides the functionality for importing songs which are
exproted from Lyrix.""" exproted from Lyrix."""
import logging
import json import json
import os import logging
import re import re
from openlp.core.common import translate, Settings from openlp.core.common import translate, Settings
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.plugins.songs.lib.db import AuthorType from openlp.plugins.songs.lib.db import AuthorType
@ -50,11 +49,10 @@ class VideoPsalmImport(SongImport):
""" """
Process the VideoPsalm file - pass in a file-like object, not a file path. Process the VideoPsalm file - pass in a file-like object, not a file path.
""" """
self.import_source = Path(self.import_source)
self.set_defaults() self.set_defaults()
# Open SongBook file
song_file = open(self.import_source, 'rt', encoding='utf-8-sig')
try: try:
file_content = song_file.read() file_content = self.import_source.read_text(encoding='utf-8-sig')
processed_content = '' processed_content = ''
inside_quotes = False inside_quotes = False
# The VideoPsalm format is not valid json, it uses illegal line breaks and unquoted keys, this must be fixed # The VideoPsalm format is not valid json, it uses illegal line breaks and unquoted keys, this must be fixed
@ -89,7 +87,7 @@ class VideoPsalmImport(SongImport):
songs = songbook['Songs'] songs = songbook['Songs']
self.import_wizard.progress_bar.setMaximum(len(songs)) self.import_wizard.progress_bar.setMaximum(len(songs))
songbook_name = songbook['Text'] songbook_name = songbook['Text']
media_folder = os.path.normpath(os.path.join(os.path.dirname(song_file.name), '..', 'Audio')) media_path = Path('..', 'Audio')
for song in songs: for song in songs:
self.song_book_name = songbook_name self.song_book_name = songbook_name
if 'Text' in song: if 'Text' in song:
@ -114,7 +112,7 @@ class VideoPsalmImport(SongImport):
if 'Theme' in song: if 'Theme' in song:
self.topics = song['Theme'].splitlines() self.topics = song['Theme'].splitlines()
if 'AudioFile' in song: if 'AudioFile' in song:
self.add_media_file(os.path.join(media_folder, song['AudioFile'])) self.add_media_file(media_path / song['AudioFile'])
if 'Memo1' in song: if 'Memo1' in song:
self.add_comment(song['Memo1']) self.add_comment(song['Memo1'])
if 'Memo2' in song: if 'Memo2' in song:
@ -132,4 +130,5 @@ class VideoPsalmImport(SongImport):
if not self.finish(): if not self.finish():
self.log_error('Could not import {title}'.format(title=self.title)) self.log_error('Could not import {title}'.format(title=self.title))
except Exception as e: except Exception as e:
self.log_error(song_file.name, translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e)) self.log_error(self.import_source.name,
translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))

View File

@ -25,6 +25,7 @@ Worship songs into the OpenLP database.
""" """
import os import os
import logging import logging
from openlp.core.common.path import Path
from openlp.core.common import translate from openlp.core.common import translate
from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.importers.songimport import SongImport
@ -100,13 +101,13 @@ class WordsOfWorshipImport(SongImport):
""" """
if isinstance(self.import_source, list): if isinstance(self.import_source, list):
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for source in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
self.set_defaults() self.set_defaults()
song_data = open(source, 'rb') with file_path.open('rb') as song_data:
if song_data.read(19).decode() != 'WoW File\nSong Words': if song_data.read(19).decode() != 'WoW File\nSong Words':
self.log_error(source, self.log_error(file_path,
translate('SongsPlugin.WordsofWorshipSongImport', translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "{text}" ' 'Invalid Words of Worship song file. Missing "{text}" '
'header.').format(text='WoW File\\nSong Words')) 'header.').format(text='WoW File\\nSong Words'))
@ -116,7 +117,7 @@ class WordsOfWorshipImport(SongImport):
no_of_blocks = ord(song_data.read(1)) no_of_blocks = ord(song_data.read(1))
song_data.seek(66) song_data.seek(66)
if song_data.read(16).decode() != 'CSongDoc::CBlock': if song_data.read(16).decode() != 'CSongDoc::CBlock':
self.log_error(source, self.log_error(file_path,
translate('SongsPlugin.WordsofWorshipSongImport', translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "{text}" ' 'Invalid Words of Worship song file. Missing "{text}" '
'string.').format(text='CSongDoc::CBlock')) 'string.').format(text='CSongDoc::CBlock'))
@ -153,9 +154,7 @@ class WordsOfWorshipImport(SongImport):
copyright_length = ord(song_data.read(1)) copyright_length = ord(song_data.read(1))
if copyright_length: if copyright_length:
self.add_copyright(str(song_data.read(copyright_length), 'cp1252')) self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
file_name = os.path.split(source)[1]
# Get the song title # Get the song title
self.title = file_name.rpartition('.')[0] self.title = file_path.stem
song_data.close()
if not self.finish(): if not self.finish():
self.log_error(source) self.log_error(file_path)

View File

@ -28,7 +28,7 @@ import csv
import logging import logging
import re import re
from openlp.core.common import translate from openlp.core.common import get_file_encoding, translate
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.importers.songimport import SongImport
@ -81,11 +81,8 @@ class WorshipAssistantImport(SongImport):
Receive a CSV file to import. Receive a CSV file to import.
""" """
# Get encoding # Get encoding
detect_file = open(self.import_source, 'rb') encoding = get_file_encoding(self.import_source)['encoding']
detect_content = detect_file.read() with self.import_source.open('r', encoding=encoding) as songs_file:
details = chardet.detect(detect_content)
detect_file.close()
songs_file = open(self.import_source, 'r', encoding=details['encoding'])
songs_reader = csv.DictReader(songs_file, escapechar='\\') songs_reader = csv.DictReader(songs_file, escapechar='\\')
try: try:
records = list(songs_reader) records = list(songs_reader)
@ -185,4 +182,3 @@ class WorshipAssistantImport(SongImport):
self.log_error(translate('SongsPlugin.WorshipAssistantImport', self.log_error(translate('SongsPlugin.WorshipAssistantImport',
'Record {count:d}').format(count=index) + 'Record {count:d}').format(count=index) +
(': "' + self.title + '"' if self.title else '')) (': "' + self.title + '"' if self.title else ''))
songs_file.close()

View File

@ -76,7 +76,7 @@ class ZionWorxImport(SongImport):
Receive a CSV file (from a ZionWorx database dump) to import. Receive a CSV file (from a ZionWorx database dump) to import.
""" """
# Encoding should always be ISO-8859-1 # Encoding should always be ISO-8859-1
with open(self.import_source, 'rt', encoding='ISO-8859-1') as songs_file: with self.import_source.open('rt', encoding='ISO-8859-1') as songs_file:
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords', field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
'DefaultStyle'] 'DefaultStyle']
songs_reader = csv.DictReader(songs_file, field_names) songs_reader = csv.DictReader(songs_file, field_names)

View File

@ -22,25 +22,24 @@
import logging import logging
import os import os
import shutil
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_, or_ from sqlalchemy.sql import and_, or_
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
from openlp.core.common.path import Path from openlp.core.common.languagemanager import get_natural_key
from openlp.core.common.path import copyfile
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \ from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
check_item_selected, create_separated_list check_item_selected, create_separated_list
from openlp.core.lib.ui import create_widget_action from openlp.core.lib.ui import create_widget_action
from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.forms.editsongform import EditSongForm from openlp.plugins.songs.forms.editsongform import EditSongForm
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songexportform import SongExportForm from openlp.plugins.songs.forms.songexportform import SongExportForm
from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -88,11 +87,11 @@ class SongMediaItem(MediaManagerItem):
def _update_background_audio(self, song, item): def _update_background_audio(self, song, item):
song.media_files = [] song.media_files = []
for i, bga in enumerate(item.background_audio): for i, bga in enumerate(item.background_audio):
dest_file = os.path.join( dest_path =\
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(song.id), os.path.split(bga)[1]) AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga)[1]
check_directory_exists(Path(os.path.split(dest_file)[0])) check_directory_exists(dest_path.parent)
shutil.copyfile(os.path.join(str(AppLocation.get_section_data_path('servicemanager')), bga), dest_file) copyfile(AppLocation.get_section_data_path('servicemanager') / bga, dest_path)
song.media_files.append(MediaFile.populate(weight=i, file_name=dest_file)) song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path))
self.plugin.manager.save_object(song, True) self.plugin.manager.save_object(song, True)
def add_end_header_bar(self): def add_end_header_bar(self):
@ -534,14 +533,13 @@ class SongMediaItem(MediaManagerItem):
'copy', 'For song cloning')) 'copy', 'For song cloning'))
# Copy audio files from the old to the new song # Copy audio files from the old to the new song
if len(old_song.media_files) > 0: if len(old_song.media_files) > 0:
save_path = os.path.join( save_path = AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(new_song.id)
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(new_song.id)) check_directory_exists(save_path)
check_directory_exists(Path(save_path))
for media_file in old_song.media_files: for media_file in old_song.media_files:
new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name)) new_media_file_path = save_path / media_file.file_path.name
shutil.copyfile(media_file.file_name, new_media_file_name) copyfile(media_file.file_path, new_media_file_path)
new_media_file = MediaFile() new_media_file = MediaFile()
new_media_file.file_name = new_media_file_name new_media_file.file_path = new_media_file_path
new_media_file.type = media_file.type new_media_file.type = media_file.type
new_media_file.weight = media_file.weight new_media_file.weight = media_file.weight
new_song.media_files.append(new_media_file) new_song.media_files.append(new_media_file)
@ -613,7 +611,7 @@ class SongMediaItem(MediaManagerItem):
# Add the audio file to the service item. # Add the audio file to the service item.
if song.media_files: if song.media_files:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio) service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
service_item.background_audio = [m.file_name for m in song.media_files] service_item.background_audio = [m.file_path for m in song.media_files]
return True return True
def generate_footer(self, item, song): def generate_footer(self, item, song):

View File

@ -24,12 +24,10 @@ The :mod:`openlyricsexport` module provides the functionality for exporting song
format. format.
""" """
import logging import logging
import os
from lxml import etree from lxml import etree
from openlp.core.common import RegistryProperties, check_directory_exists, translate, clean_filename from openlp.core.common import RegistryProperties, check_directory_exists, translate, clean_filename
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -42,13 +40,16 @@ class OpenLyricsExport(RegistryProperties):
def __init__(self, parent, songs, save_path): def __init__(self, parent, songs, save_path):
""" """
Initialise the export. Initialise the export.
:param openlp.core.common.path.Path save_path: The directory to save the exported songs in
:rtype: None
""" """
log.debug('initialise OpenLyricsExport') log.debug('initialise OpenLyricsExport')
self.parent = parent self.parent = parent
self.manager = parent.plugin.manager self.manager = parent.plugin.manager
self.songs = songs self.songs = songs
self.save_path = save_path self.save_path = save_path
check_directory_exists(Path(self.save_path)) check_directory_exists(self.save_path)
def do_export(self): def do_export(self):
""" """
@ -69,15 +70,15 @@ class OpenLyricsExport(RegistryProperties):
author=', '.join([author.display_name for author in song.authors])) author=', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename) filename = clean_filename(filename)
# Ensure the filename isn't too long for some filesystems # Ensure the filename isn't too long for some filesystems
filename_with_ext = '{name}.xml'.format(name=filename[0:250 - len(self.save_path)]) path_length = len(str(self.save_path))
filename_with_ext = '{name}.xml'.format(name=filename[0:250 - path_length])
# Make sure we're not overwriting an existing file # Make sure we're not overwriting an existing file
conflicts = 0 conflicts = 0
while os.path.exists(os.path.join(self.save_path, filename_with_ext)): while (self.save_path / filename_with_ext).exists():
conflicts += 1 conflicts += 1
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - len(self.save_path)], filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - path_length], extra=conflicts)
extra=conflicts)
# Pass a file object, because lxml does not cope with some special # Pass a file object, because lxml does not cope with some special
# characters in the path (see lp:757673 and lp:744337). # characters in the path (see lp:757673 and lp:744337).
tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8', with (self.save_path / filename_with_ext).open('wb') as out_file:
xml_declaration=True, pretty_print=True) tree.write(out_file, encoding='utf-8', xml_declaration=True, pretty_print=True)
return True return True

View File

@ -23,16 +23,20 @@
The :mod:`upgrade` module provides a way for the database and schema that is the The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the Songs plugin backend for the Songs plugin
""" """
import json
import logging import logging
from sqlalchemy import Table, Column, ForeignKey, types from sqlalchemy import Table, Column, ForeignKey, types
from sqlalchemy.sql.expression import func, false, null, text from sqlalchemy.sql.expression import func, false, null, text
from openlp.core.common import AppLocation
from openlp.core.common.db import drop_columns from openlp.core.common.db import drop_columns
from openlp.core.lib.db import get_upgrade_op from openlp.core.common.json import OpenLPJsonEncoder
from openlp.core.common.path import Path
from openlp.core.lib.db import PathType, get_upgrade_op
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__version__ = 6 __version__ = 7
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version # TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@ -162,3 +166,28 @@ def upgrade_6(session, metadata):
op.drop_column('songs', 'song_number') op.drop_column('songs', 'song_number')
# Finally, clean up our mess in people's databases # Finally, clean up our mess in people's databases
op.execute('DELETE FROM songs_songbooks WHERE songbook_id = 0') op.execute('DELETE FROM songs_songbooks WHERE songbook_id = 0')
def upgrade_7(session, metadata):
"""
Version 7 upgrade - Move file path from old db to JSON encoded path to new db. Upgrade added in 2.5 dev
"""
log.debug('Starting upgrade_7 for file_path to JSON')
old_table = Table('media_files', metadata, autoload=True)
if 'file_path' not in [col.name for col in old_table.c.values()]:
op = get_upgrade_op(session)
op.add_column('media_files', Column('file_path', PathType()))
conn = op.get_bind()
results = conn.execute('SELECT * FROM media_files')
data_path = AppLocation.get_data_path()
for row in results.fetchall():
file_path_json = json.dumps(Path(row.file_name), cls=OpenLPJsonEncoder, base_path=data_path)
sql = 'UPDATE media_files SET file_path = \'{file_path_json}\' WHERE id = {id}'.format(
file_path_json=file_path_json, id=row.id)
conn.execute(sql)
# Drop old columns
if metadata.bind.url.get_dialect().name == 'sqlite':
drop_columns(op, 'media_files', ['file_name', ])
else:
op.drop_constraint('media_files', 'foreignkey')
op.drop_column('media_files', 'filenames')

View File

@ -31,7 +31,6 @@ from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.filedialog import FileDialog from openlp.core.ui.lib.filedialog import FileDialog
from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.db import Song
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -58,9 +57,9 @@ def report_song_list():
report_file_path.with_suffix('.csv') report_file_path.with_suffix('.csv')
Registry().get('application').set_busy_cursor() Registry().get('application').set_busy_cursor()
try: try:
with report_file_path.open('wt') as file_handle: with report_file_path.open('wt') as export_file:
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic') fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL) writer = csv.DictWriter(export_file, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
headers = dict((n, n) for n in fieldnames) headers = dict((n, n) for n in fieldnames)
writer.writerow(headers) writer.writerow(headers)
song_list = plugin.manager.get_all_objects(Song) song_list = plugin.manager.get_all_objects(Song)

View File

@ -37,7 +37,6 @@ from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.core.lib.db import Manager from openlp.core.lib.db import Manager
from openlp.core.lib.ui import create_action from openlp.core.lib.ui import create_action
from openlp.plugins.songs import reporting from openlp.plugins.songs import reporting
from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
@ -50,7 +49,6 @@ from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from openlp.plugins.songs.lib.mediaitem import SongSearch from openlp.plugins.songs.lib.mediaitem import SongSearch
from openlp.plugins.songs.lib.songstab import SongsTab from openlp.plugins.songs.lib.songstab import SongsTab
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__default_settings__ = { __default_settings__ = {
'songs/db type': 'sqlite', 'songs/db type': 'sqlite',
@ -340,7 +338,7 @@ class SongsPlugin(Plugin):
progress.forceShow() progress.forceShow()
self.application.process_events() self.application.process_events()
for db in song_dbs: for db in song_dbs:
importer = OpenLPSongImport(self.manager, filename=db) importer = OpenLPSongImport(self.manager, file_path=db)
importer.do_import(progress) importer.do_import(progress)
self.application.process_events() self.application.process_events()
progress.setValue(song_count) progress.setValue(song_count)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

446
resources/images/app_qr.svg Normal file
View File

@ -0,0 +1,446 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" baseProfile="full" width="296" height="296" viewBox="0 0 296 296"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
<desc></desc>
<rect width="296" height="296" fill="#ffffff" cx="0" cy="0" />
<defs>
<rect id="p" width="8" height="8" />
</defs>
<g fill="#000000">
<use x="32" y="32" xlink:href="#p" />
<use x="32" y="40" xlink:href="#p" />
<use x="32" y="48" xlink:href="#p" />
<use x="32" y="56" xlink:href="#p" />
<use x="32" y="64" xlink:href="#p" />
<use x="32" y="72" xlink:href="#p" />
<use x="32" y="80" xlink:href="#p" />
<use x="32" y="96" xlink:href="#p" />
<use x="32" y="104" xlink:href="#p" />
<use x="32" y="120" xlink:href="#p" />
<use x="32" y="128" xlink:href="#p" />
<use x="32" y="168" xlink:href="#p" />
<use x="32" y="184" xlink:href="#p" />
<use x="32" y="192" xlink:href="#p" />
<use x="32" y="208" xlink:href="#p" />
<use x="32" y="216" xlink:href="#p" />
<use x="32" y="224" xlink:href="#p" />
<use x="32" y="232" xlink:href="#p" />
<use x="32" y="240" xlink:href="#p" />
<use x="32" y="248" xlink:href="#p" />
<use x="32" y="256" xlink:href="#p" />
<use x="40" y="32" xlink:href="#p" />
<use x="40" y="80" xlink:href="#p" />
<use x="40" y="96" xlink:href="#p" />
<use x="40" y="104" xlink:href="#p" />
<use x="40" y="120" xlink:href="#p" />
<use x="40" y="136" xlink:href="#p" />
<use x="40" y="152" xlink:href="#p" />
<use x="40" y="160" xlink:href="#p" />
<use x="40" y="176" xlink:href="#p" />
<use x="40" y="184" xlink:href="#p" />
<use x="40" y="208" xlink:href="#p" />
<use x="40" y="256" xlink:href="#p" />
<use x="48" y="32" xlink:href="#p" />
<use x="48" y="48" xlink:href="#p" />
<use x="48" y="56" xlink:href="#p" />
<use x="48" y="64" xlink:href="#p" />
<use x="48" y="80" xlink:href="#p" />
<use x="48" y="96" xlink:href="#p" />
<use x="48" y="104" xlink:href="#p" />
<use x="48" y="112" xlink:href="#p" />
<use x="48" y="120" xlink:href="#p" />
<use x="48" y="128" xlink:href="#p" />
<use x="48" y="136" xlink:href="#p" />
<use x="48" y="152" xlink:href="#p" />
<use x="48" y="160" xlink:href="#p" />
<use x="48" y="184" xlink:href="#p" />
<use x="48" y="192" xlink:href="#p" />
<use x="48" y="208" xlink:href="#p" />
<use x="48" y="224" xlink:href="#p" />
<use x="48" y="232" xlink:href="#p" />
<use x="48" y="240" xlink:href="#p" />
<use x="48" y="256" xlink:href="#p" />
<use x="56" y="32" xlink:href="#p" />
<use x="56" y="48" xlink:href="#p" />
<use x="56" y="56" xlink:href="#p" />
<use x="56" y="64" xlink:href="#p" />
<use x="56" y="80" xlink:href="#p" />
<use x="56" y="96" xlink:href="#p" />
<use x="56" y="112" xlink:href="#p" />
<use x="56" y="128" xlink:href="#p" />
<use x="56" y="136" xlink:href="#p" />
<use x="56" y="176" xlink:href="#p" />
<use x="56" y="192" xlink:href="#p" />
<use x="56" y="208" xlink:href="#p" />
<use x="56" y="224" xlink:href="#p" />
<use x="56" y="232" xlink:href="#p" />
<use x="56" y="240" xlink:href="#p" />
<use x="56" y="256" xlink:href="#p" />
<use x="64" y="32" xlink:href="#p" />
<use x="64" y="48" xlink:href="#p" />
<use x="64" y="56" xlink:href="#p" />
<use x="64" y="64" xlink:href="#p" />
<use x="64" y="80" xlink:href="#p" />
<use x="64" y="120" xlink:href="#p" />
<use x="64" y="128" xlink:href="#p" />
<use x="64" y="136" xlink:href="#p" />
<use x="64" y="152" xlink:href="#p" />
<use x="64" y="168" xlink:href="#p" />
<use x="64" y="176" xlink:href="#p" />
<use x="64" y="208" xlink:href="#p" />
<use x="64" y="224" xlink:href="#p" />
<use x="64" y="232" xlink:href="#p" />
<use x="64" y="240" xlink:href="#p" />
<use x="64" y="256" xlink:href="#p" />
<use x="72" y="32" xlink:href="#p" />
<use x="72" y="80" xlink:href="#p" />
<use x="72" y="120" xlink:href="#p" />
<use x="72" y="136" xlink:href="#p" />
<use x="72" y="160" xlink:href="#p" />
<use x="72" y="176" xlink:href="#p" />
<use x="72" y="208" xlink:href="#p" />
<use x="72" y="256" xlink:href="#p" />
<use x="80" y="32" xlink:href="#p" />
<use x="80" y="40" xlink:href="#p" />
<use x="80" y="48" xlink:href="#p" />
<use x="80" y="56" xlink:href="#p" />
<use x="80" y="64" xlink:href="#p" />
<use x="80" y="72" xlink:href="#p" />
<use x="80" y="80" xlink:href="#p" />
<use x="80" y="96" xlink:href="#p" />
<use x="80" y="112" xlink:href="#p" />
<use x="80" y="128" xlink:href="#p" />
<use x="80" y="144" xlink:href="#p" />
<use x="80" y="160" xlink:href="#p" />
<use x="80" y="176" xlink:href="#p" />
<use x="80" y="192" xlink:href="#p" />
<use x="80" y="208" xlink:href="#p" />
<use x="80" y="216" xlink:href="#p" />
<use x="80" y="224" xlink:href="#p" />
<use x="80" y="232" xlink:href="#p" />
<use x="80" y="240" xlink:href="#p" />
<use x="80" y="248" xlink:href="#p" />
<use x="80" y="256" xlink:href="#p" />
<use x="88" y="112" xlink:href="#p" />
<use x="88" y="120" xlink:href="#p" />
<use x="88" y="160" xlink:href="#p" />
<use x="88" y="168" xlink:href="#p" />
<use x="88" y="192" xlink:href="#p" />
<use x="96" y="32" xlink:href="#p" />
<use x="96" y="48" xlink:href="#p" />
<use x="96" y="56" xlink:href="#p" />
<use x="96" y="64" xlink:href="#p" />
<use x="96" y="80" xlink:href="#p" />
<use x="96" y="96" xlink:href="#p" />
<use x="96" y="112" xlink:href="#p" />
<use x="96" y="120" xlink:href="#p" />
<use x="96" y="136" xlink:href="#p" />
<use x="96" y="152" xlink:href="#p" />
<use x="96" y="168" xlink:href="#p" />
<use x="96" y="176" xlink:href="#p" />
<use x="96" y="200" xlink:href="#p" />
<use x="96" y="232" xlink:href="#p" />
<use x="96" y="240" xlink:href="#p" />
<use x="96" y="248" xlink:href="#p" />
<use x="96" y="256" xlink:href="#p" />
<use x="104" y="40" xlink:href="#p" />
<use x="104" y="48" xlink:href="#p" />
<use x="104" y="56" xlink:href="#p" />
<use x="104" y="112" xlink:href="#p" />
<use x="104" y="120" xlink:href="#p" />
<use x="104" y="128" xlink:href="#p" />
<use x="104" y="168" xlink:href="#p" />
<use x="104" y="176" xlink:href="#p" />
<use x="104" y="192" xlink:href="#p" />
<use x="104" y="200" xlink:href="#p" />
<use x="104" y="208" xlink:href="#p" />
<use x="104" y="216" xlink:href="#p" />
<use x="104" y="224" xlink:href="#p" />
<use x="104" y="232" xlink:href="#p" />
<use x="104" y="240" xlink:href="#p" />
<use x="104" y="248" xlink:href="#p" />
<use x="104" y="256" xlink:href="#p" />
<use x="112" y="32" xlink:href="#p" />
<use x="112" y="40" xlink:href="#p" />
<use x="112" y="48" xlink:href="#p" />
<use x="112" y="56" xlink:href="#p" />
<use x="112" y="72" xlink:href="#p" />
<use x="112" y="80" xlink:href="#p" />
<use x="112" y="88" xlink:href="#p" />
<use x="112" y="96" xlink:href="#p" />
<use x="112" y="104" xlink:href="#p" />
<use x="112" y="112" xlink:href="#p" />
<use x="112" y="120" xlink:href="#p" />
<use x="112" y="128" xlink:href="#p" />
<use x="112" y="200" xlink:href="#p" />
<use x="112" y="208" xlink:href="#p" />
<use x="112" y="216" xlink:href="#p" />
<use x="112" y="232" xlink:href="#p" />
<use x="112" y="240" xlink:href="#p" />
<use x="120" y="40" xlink:href="#p" />
<use x="120" y="64" xlink:href="#p" />
<use x="120" y="72" xlink:href="#p" />
<use x="120" y="96" xlink:href="#p" />
<use x="120" y="112" xlink:href="#p" />
<use x="120" y="136" xlink:href="#p" />
<use x="120" y="144" xlink:href="#p" />
<use x="120" y="152" xlink:href="#p" />
<use x="120" y="184" xlink:href="#p" />
<use x="120" y="216" xlink:href="#p" />
<use x="120" y="224" xlink:href="#p" />
<use x="120" y="232" xlink:href="#p" />
<use x="120" y="256" xlink:href="#p" />
<use x="128" y="32" xlink:href="#p" />
<use x="128" y="40" xlink:href="#p" />
<use x="128" y="56" xlink:href="#p" />
<use x="128" y="64" xlink:href="#p" />
<use x="128" y="80" xlink:href="#p" />
<use x="128" y="88" xlink:href="#p" />
<use x="128" y="96" xlink:href="#p" />
<use x="128" y="104" xlink:href="#p" />
<use x="128" y="112" xlink:href="#p" />
<use x="128" y="128" xlink:href="#p" />
<use x="128" y="136" xlink:href="#p" />
<use x="128" y="160" xlink:href="#p" />
<use x="128" y="216" xlink:href="#p" />
<use x="128" y="240" xlink:href="#p" />
<use x="128" y="248" xlink:href="#p" />
<use x="136" y="32" xlink:href="#p" />
<use x="136" y="40" xlink:href="#p" />
<use x="136" y="48" xlink:href="#p" />
<use x="136" y="56" xlink:href="#p" />
<use x="136" y="72" xlink:href="#p" />
<use x="136" y="120" xlink:href="#p" />
<use x="136" y="128" xlink:href="#p" />
<use x="136" y="176" xlink:href="#p" />
<use x="136" y="184" xlink:href="#p" />
<use x="136" y="192" xlink:href="#p" />
<use x="136" y="216" xlink:href="#p" />
<use x="136" y="232" xlink:href="#p" />
<use x="136" y="248" xlink:href="#p" />
<use x="144" y="48" xlink:href="#p" />
<use x="144" y="56" xlink:href="#p" />
<use x="144" y="64" xlink:href="#p" />
<use x="144" y="80" xlink:href="#p" />
<use x="144" y="96" xlink:href="#p" />
<use x="144" y="128" xlink:href="#p" />
<use x="144" y="152" xlink:href="#p" />
<use x="144" y="160" xlink:href="#p" />
<use x="144" y="176" xlink:href="#p" />
<use x="144" y="184" xlink:href="#p" />
<use x="144" y="192" xlink:href="#p" />
<use x="144" y="208" xlink:href="#p" />
<use x="144" y="216" xlink:href="#p" />
<use x="144" y="240" xlink:href="#p" />
<use x="152" y="64" xlink:href="#p" />
<use x="152" y="88" xlink:href="#p" />
<use x="152" y="96" xlink:href="#p" />
<use x="152" y="120" xlink:href="#p" />
<use x="152" y="128" xlink:href="#p" />
<use x="152" y="136" xlink:href="#p" />
<use x="152" y="160" xlink:href="#p" />
<use x="152" y="168" xlink:href="#p" />
<use x="152" y="176" xlink:href="#p" />
<use x="152" y="232" xlink:href="#p" />
<use x="152" y="248" xlink:href="#p" />
<use x="160" y="32" xlink:href="#p" />
<use x="160" y="64" xlink:href="#p" />
<use x="160" y="72" xlink:href="#p" />
<use x="160" y="80" xlink:href="#p" />
<use x="160" y="88" xlink:href="#p" />
<use x="160" y="96" xlink:href="#p" />
<use x="160" y="104" xlink:href="#p" />
<use x="160" y="112" xlink:href="#p" />
<use x="160" y="128" xlink:href="#p" />
<use x="160" y="136" xlink:href="#p" />
<use x="160" y="152" xlink:href="#p" />
<use x="160" y="184" xlink:href="#p" />
<use x="160" y="192" xlink:href="#p" />
<use x="160" y="200" xlink:href="#p" />
<use x="160" y="208" xlink:href="#p" />
<use x="160" y="240" xlink:href="#p" />
<use x="160" y="256" xlink:href="#p" />
<use x="168" y="56" xlink:href="#p" />
<use x="168" y="88" xlink:href="#p" />
<use x="168" y="96" xlink:href="#p" />
<use x="168" y="104" xlink:href="#p" />
<use x="168" y="112" xlink:href="#p" />
<use x="168" y="128" xlink:href="#p" />
<use x="168" y="136" xlink:href="#p" />
<use x="168" y="168" xlink:href="#p" />
<use x="168" y="184" xlink:href="#p" />
<use x="168" y="192" xlink:href="#p" />
<use x="168" y="208" xlink:href="#p" />
<use x="168" y="224" xlink:href="#p" />
<use x="168" y="232" xlink:href="#p" />
<use x="176" y="32" xlink:href="#p" />
<use x="176" y="80" xlink:href="#p" />
<use x="176" y="88" xlink:href="#p" />
<use x="176" y="96" xlink:href="#p" />
<use x="176" y="104" xlink:href="#p" />
<use x="176" y="112" xlink:href="#p" />
<use x="176" y="144" xlink:href="#p" />
<use x="176" y="184" xlink:href="#p" />
<use x="176" y="192" xlink:href="#p" />
<use x="176" y="200" xlink:href="#p" />
<use x="176" y="216" xlink:href="#p" />
<use x="176" y="232" xlink:href="#p" />
<use x="176" y="248" xlink:href="#p" />
<use x="176" y="256" xlink:href="#p" />
<use x="184" y="32" xlink:href="#p" />
<use x="184" y="56" xlink:href="#p" />
<use x="184" y="64" xlink:href="#p" />
<use x="184" y="72" xlink:href="#p" />
<use x="184" y="104" xlink:href="#p" />
<use x="184" y="136" xlink:href="#p" />
<use x="184" y="144" xlink:href="#p" />
<use x="184" y="152" xlink:href="#p" />
<use x="184" y="160" xlink:href="#p" />
<use x="184" y="184" xlink:href="#p" />
<use x="184" y="192" xlink:href="#p" />
<use x="184" y="208" xlink:href="#p" />
<use x="184" y="240" xlink:href="#p" />
<use x="184" y="248" xlink:href="#p" />
<use x="184" y="256" xlink:href="#p" />
<use x="192" y="32" xlink:href="#p" />
<use x="192" y="48" xlink:href="#p" />
<use x="192" y="72" xlink:href="#p" />
<use x="192" y="80" xlink:href="#p" />
<use x="192" y="104" xlink:href="#p" />
<use x="192" y="136" xlink:href="#p" />
<use x="192" y="144" xlink:href="#p" />
<use x="192" y="152" xlink:href="#p" />
<use x="192" y="160" xlink:href="#p" />
<use x="192" y="176" xlink:href="#p" />
<use x="192" y="192" xlink:href="#p" />
<use x="192" y="200" xlink:href="#p" />
<use x="192" y="208" xlink:href="#p" />
<use x="192" y="216" xlink:href="#p" />
<use x="192" y="224" xlink:href="#p" />
<use x="192" y="248" xlink:href="#p" />
<use x="192" y="256" xlink:href="#p" />
<use x="200" y="96" xlink:href="#p" />
<use x="200" y="104" xlink:href="#p" />
<use x="200" y="112" xlink:href="#p" />
<use x="200" y="136" xlink:href="#p" />
<use x="200" y="152" xlink:href="#p" />
<use x="200" y="192" xlink:href="#p" />
<use x="200" y="224" xlink:href="#p" />
<use x="200" y="232" xlink:href="#p" />
<use x="200" y="256" xlink:href="#p" />
<use x="208" y="32" xlink:href="#p" />
<use x="208" y="40" xlink:href="#p" />
<use x="208" y="48" xlink:href="#p" />
<use x="208" y="56" xlink:href="#p" />
<use x="208" y="64" xlink:href="#p" />
<use x="208" y="72" xlink:href="#p" />
<use x="208" y="80" xlink:href="#p" />
<use x="208" y="104" xlink:href="#p" />
<use x="208" y="112" xlink:href="#p" />
<use x="208" y="120" xlink:href="#p" />
<use x="208" y="136" xlink:href="#p" />
<use x="208" y="144" xlink:href="#p" />
<use x="208" y="176" xlink:href="#p" />
<use x="208" y="184" xlink:href="#p" />
<use x="208" y="192" xlink:href="#p" />
<use x="208" y="208" xlink:href="#p" />
<use x="208" y="224" xlink:href="#p" />
<use x="208" y="248" xlink:href="#p" />
<use x="208" y="256" xlink:href="#p" />
<use x="216" y="32" xlink:href="#p" />
<use x="216" y="80" xlink:href="#p" />
<use x="216" y="104" xlink:href="#p" />
<use x="216" y="112" xlink:href="#p" />
<use x="216" y="120" xlink:href="#p" />
<use x="216" y="144" xlink:href="#p" />
<use x="216" y="152" xlink:href="#p" />
<use x="216" y="160" xlink:href="#p" />
<use x="216" y="168" xlink:href="#p" />
<use x="216" y="184" xlink:href="#p" />
<use x="216" y="192" xlink:href="#p" />
<use x="216" y="224" xlink:href="#p" />
<use x="216" y="240" xlink:href="#p" />
<use x="216" y="248" xlink:href="#p" />
<use x="224" y="32" xlink:href="#p" />
<use x="224" y="48" xlink:href="#p" />
<use x="224" y="56" xlink:href="#p" />
<use x="224" y="64" xlink:href="#p" />
<use x="224" y="80" xlink:href="#p" />
<use x="224" y="96" xlink:href="#p" />
<use x="224" y="104" xlink:href="#p" />
<use x="224" y="112" xlink:href="#p" />
<use x="224" y="120" xlink:href="#p" />
<use x="224" y="152" xlink:href="#p" />
<use x="224" y="160" xlink:href="#p" />
<use x="224" y="176" xlink:href="#p" />
<use x="224" y="192" xlink:href="#p" />
<use x="224" y="200" xlink:href="#p" />
<use x="224" y="208" xlink:href="#p" />
<use x="224" y="216" xlink:href="#p" />
<use x="224" y="224" xlink:href="#p" />
<use x="224" y="232" xlink:href="#p" />
<use x="224" y="256" xlink:href="#p" />
<use x="232" y="32" xlink:href="#p" />
<use x="232" y="48" xlink:href="#p" />
<use x="232" y="56" xlink:href="#p" />
<use x="232" y="64" xlink:href="#p" />
<use x="232" y="80" xlink:href="#p" />
<use x="232" y="96" xlink:href="#p" />
<use x="232" y="128" xlink:href="#p" />
<use x="232" y="160" xlink:href="#p" />
<use x="232" y="168" xlink:href="#p" />
<use x="232" y="192" xlink:href="#p" />
<use x="232" y="200" xlink:href="#p" />
<use x="232" y="208" xlink:href="#p" />
<use x="232" y="216" xlink:href="#p" />
<use x="232" y="232" xlink:href="#p" />
<use x="232" y="248" xlink:href="#p" />
<use x="240" y="32" xlink:href="#p" />
<use x="240" y="48" xlink:href="#p" />
<use x="240" y="56" xlink:href="#p" />
<use x="240" y="64" xlink:href="#p" />
<use x="240" y="80" xlink:href="#p" />
<use x="240" y="96" xlink:href="#p" />
<use x="240" y="112" xlink:href="#p" />
<use x="240" y="128" xlink:href="#p" />
<use x="240" y="136" xlink:href="#p" />
<use x="240" y="144" xlink:href="#p" />
<use x="240" y="168" xlink:href="#p" />
<use x="240" y="176" xlink:href="#p" />
<use x="240" y="184" xlink:href="#p" />
<use x="240" y="192" xlink:href="#p" />
<use x="240" y="200" xlink:href="#p" />
<use x="240" y="224" xlink:href="#p" />
<use x="240" y="240" xlink:href="#p" />
<use x="248" y="32" xlink:href="#p" />
<use x="248" y="80" xlink:href="#p" />
<use x="248" y="112" xlink:href="#p" />
<use x="248" y="136" xlink:href="#p" />
<use x="248" y="144" xlink:href="#p" />
<use x="248" y="152" xlink:href="#p" />
<use x="248" y="160" xlink:href="#p" />
<use x="248" y="168" xlink:href="#p" />
<use x="248" y="200" xlink:href="#p" />
<use x="248" y="208" xlink:href="#p" />
<use x="248" y="224" xlink:href="#p" />
<use x="248" y="248" xlink:href="#p" />
<use x="248" y="256" xlink:href="#p" />
<use x="256" y="32" xlink:href="#p" />
<use x="256" y="40" xlink:href="#p" />
<use x="256" y="48" xlink:href="#p" />
<use x="256" y="56" xlink:href="#p" />
<use x="256" y="64" xlink:href="#p" />
<use x="256" y="72" xlink:href="#p" />
<use x="256" y="80" xlink:href="#p" />
<use x="256" y="96" xlink:href="#p" />
<use x="256" y="104" xlink:href="#p" />
<use x="256" y="120" xlink:href="#p" />
<use x="256" y="136" xlink:href="#p" />
<use x="256" y="144" xlink:href="#p" />
<use x="256" y="200" xlink:href="#p" />
<use x="256" y="216" xlink:href="#p" />
<use x="256" y="224" xlink:href="#p" />
<use x="256" y="232" xlink:href="#p" />
<use x="256" y="240" xlink:href="#p" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B

View File

@ -183,7 +183,6 @@
<file>projector_warmup.png</file> <file>projector_warmup.png</file>
</qresource> </qresource>
<qresource prefix="remotes"> <qresource prefix="remotes">
<file>android_app_qr.png</file> <file>app_qr.svg</file>
<file>ios_app_qr.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -22,14 +22,12 @@
import os import os
import shutil import shutil
from tempfile import mkdtemp from tempfile import mkdtemp
from unittest import TestCase from unittest import TestCase
from openlp.plugins.remotes.deploy import deploy_zipfile from openlp.core.api.deploy import deploy_zipfile
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
class TestRemoteDeploy(TestCase): class TestRemoteDeploy(TestCase):
@ -54,6 +52,7 @@ class TestRemoteDeploy(TestCase):
Remote Deploy tests - test the dummy zip file is processed correctly Remote Deploy tests - test the dummy zip file is processed correctly
""" """
# GIVEN: A new downloaded zip file # GIVEN: A new downloaded zip file
aa = TEST_PATH
zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip') zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
app_root = os.path.join(self.app_root, 'site.zip') app_root = os.path.join(self.app_root, 'site.zip')
shutil.copyfile(zip_file, app_root) shutil.copyfile(zip_file, app_root)

View File

@ -79,5 +79,6 @@ class TestImageDBUpgrade(TestCase, TestMixin):
2: Path('/', 'test', 'dir', 'image2.jpg'), 2: Path('/', 'test', 'dir', 'image2.jpg'),
3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')} 3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
self.assertEqual(len(upgraded_results), 3)
for result in upgraded_results: for result in upgraded_results:
self.assertEqual(expected_result_data[result.id], result.file_path) self.assertEqual(expected_result_data[result.id], result.file_path)

View File

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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 #
###############################################################################

View File

@ -24,6 +24,8 @@ This module contains tests for the OpenSong song importer.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
@ -48,5 +50,5 @@ class TestChordProFileImport(SongImportTestHelper):
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
mocked_settings.return_value = mocked_returned_settings mocked_settings.return_value = mocked_returned_settings
# Do the test import # Do the test import
self.file_import([os.path.join(TEST_PATH, 'swing-low.chordpro')], self.file_import([Path(TEST_PATH, 'swing-low.chordpro')],
self.load_external_result_data(os.path.join(TEST_PATH, 'swing-low.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'swing-low.json')))

View File

@ -21,9 +21,10 @@
""" """
This module contains tests for the EasySlides song importer. This module contains tests for the EasySlides song importer.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
@ -41,7 +42,7 @@ class TestEasySlidesFileImport(SongImportTestHelper):
""" """
Test that loading an EasySlides file works correctly on various files Test that loading an EasySlides file works correctly on various files
""" """
self.file_import(os.path.join(TEST_PATH, 'amazing-grace.xml'), self.file_import(Path(TEST_PATH, 'amazing-grace.xml'),
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.xml'), self.file_import(Path(TEST_PATH, 'Export_2017-01-12_BB.xml'),
self.load_external_result_data(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.json')))

View File

@ -97,7 +97,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
_title_assignment_list = [] _title_assignment_list = []
def __init__(self, manager): def __init__(self, manager):
EasyWorshipSongImport.__init__(self, manager, filenames=[]) EasyWorshipSongImport.__init__(self, manager, file_paths=[])
@property @property
def title(self): def title(self):
@ -180,7 +180,7 @@ class TestEasyWorshipSongImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -192,7 +192,7 @@ class TestEasyWorshipSongImport(TestCase):
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions. # GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.field_descriptions = TEST_FIELD_DESCS importer.field_descriptions = TEST_FIELD_DESCS
# WHEN: Called with a field name that exists # WHEN: Called with a field name that exists
@ -210,7 +210,7 @@ class TestEasyWorshipSongImport(TestCase):
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions # GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.field_descriptions = TEST_FIELD_DESCS importer.field_descriptions = TEST_FIELD_DESCS
# WHEN: Called with a field name that does not exist # WHEN: Called with a field name that does not exist
@ -229,7 +229,7 @@ class TestEasyWorshipSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct: patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
# WHEN: db_set_record_struct is called with a list of field descriptions # WHEN: db_set_record_struct is called with a list of field descriptions
return_value = importer.db_set_record_struct(TEST_FIELD_DESCS) return_value = importer.db_set_record_struct(TEST_FIELD_DESCS)
@ -246,7 +246,7 @@ class TestEasyWorshipSongImport(TestCase):
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results # GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.encoding = TEST_DATA_ENCODING importer.encoding = TEST_DATA_ENCODING
importer.fields = TEST_FIELDS importer.fields = TEST_FIELDS
importer.field_descriptions = TEST_FIELD_DESCS importer.field_descriptions = TEST_FIELD_DESCS
@ -270,7 +270,7 @@ class TestEasyWorshipSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_memo_file = MagicMock() mocked_memo_file = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.memo_file = mocked_memo_file importer.memo_file = mocked_memo_file
importer.encoding = TEST_DATA_ENCODING importer.encoding = TEST_DATA_ENCODING
@ -294,44 +294,25 @@ class TestEasyWorshipSongImport(TestCase):
else: else:
mocked_memo_file.seek.assert_any_call(call[0], call[1]) mocked_memo_file.seek.assert_any_call(call[0], call[1])
def test_do_import_source(self):
"""
Test the :mod:`do_import` module opens the correct files
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
mocked_os_path.isfile.side_effect = [True, False]
# WHEN: Supplied with an import source
importer.import_source = 'Songs.DB'
# THEN: do_import should return None having called os.path.isfile
self.assertIsNone(importer.do_import(), 'do_import should return None')
mocked_os_path.isfile.assert_any_call('Songs.DB')
mocked_os_path.isfile.assert_any_call('Songs.MB')
def test_do_import_source_invalid(self): def test_do_import_source_invalid(self):
""" """
Test the :mod:`do_import` module produces an error when Songs.MB not found. Test the :mod:`do_import` module produces an error when Songs.MB not found.
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" # GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path: patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', side_effect=[True, False]):
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.log_error = MagicMock() with patch.object(importer, 'log_error') as mocked_log_error:
mocked_os_path.isfile.side_effect = [True, False]
# WHEN: do_import is supplied with an import source (Songs.MB missing) # WHEN: do_import is supplied with an import source (Songs.MB missing)
importer.import_source = 'Songs.DB' importer.import_source = 'Songs.DB'
importer.do_import() importer.do_import()
# THEN: do_import should have logged an error that the Songs.MB file could not be found. # THEN: do_import should have logged an error that the Songs.MB file could not be found.
importer.log_error.assert_any_call(importer.import_source, 'Could not find the "Songs.MB" file. It must be ' mocked_log_error.assert_any_call(importer.import_source,
'in the same folder as the "Songs.DB" file.') 'Could not find the "Songs.MB" file. It must be in the same folder as '
'the "Songs.DB" file.')
def test_do_import_database_validity(self): def test_do_import_database_validity(self):
""" """
@ -339,18 +320,19 @@ class TestEasyWorshipSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager" # GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path: patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat') as mocked_stat:
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
mocked_os_path.isfile.return_value = True
importer.import_source = 'Songs.DB' importer.import_source = 'Songs.DB'
# WHEN: DB file size is less than 0x800 # WHEN: DB file size is less than 0x800
mocked_os_path.getsize.return_value = 0x7FF mocked_stat.return_value.st_size = 0x7FF
# THEN: do_import should return None having called os.path.isfile # THEN: do_import should return None having called Path.stat()
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
mocked_os_path.getsize.assert_any_call('Songs.DB') mocked_stat.assert_called_once_with()
def test_do_import_memo_validty(self): def test_do_import_memo_validty(self):
""" """
@ -358,13 +340,12 @@ class TestEasyWorshipSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" # GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path, \ patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
patch('builtins.open') as mocked_open, \ patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \
patch('openlp.plugins.songs.lib.importers.easyworship.Path.open') as mocked_open, \
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct: patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
mocked_os_path.isfile.return_value = True
mocked_os_path.getsize.return_value = 0x800
importer.import_source = 'Songs.DB' importer.import_source = 'Songs.DB'
# WHEN: Unpacking first 35 bytes of Memo file # WHEN: Unpacking first 35 bytes of Memo file
@ -385,14 +366,14 @@ class TestEasyWorshipSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" # GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path, \ patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \
patch('openlp.plugins.songs.lib.importers.easyworship.Path.open'), \
patch('builtins.open'), patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct, \ patch('builtins.open'), patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct, \
patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \ patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \
mocked_retrieve_windows_encoding: mocked_retrieve_windows_encoding:
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[]) importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
mocked_os_path.isfile.return_value = True
mocked_os_path.getsize.return_value = 0x800
importer.import_source = 'Songs.DB' importer.import_source = 'Songs.DB'
# WHEN: Unpacking the code page # WHEN: Unpacking the code page

View File

@ -22,7 +22,8 @@
This module contains tests for the LyriX song importer. This module contains tests for the LyriX song importer.
""" """
import os import os
from unittest.mock import patch
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
@ -41,9 +42,9 @@ class TestLyrixFileImport(SongImportTestHelper):
""" """
Test that loading an LyriX file works correctly on various files Test that loading an LyriX file works correctly on various files
""" """
self.file_import([os.path.join(TEST_PATH, 'A06.TXT')], self.file_import([Path(TEST_PATH, 'A06.TXT')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import([os.path.join(TEST_PATH, 'A002.TXT')], self.file_import([Path(TEST_PATH, 'A002.TXT')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace2.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace2.json')))
self.file_import([os.path.join(TEST_PATH, 'AO05.TXT')], self.file_import([Path(TEST_PATH, 'AO05.TXT')],
self.load_external_result_data(os.path.join(TEST_PATH, 'in die regterhand.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'in die regterhand.json')))

View File

@ -51,7 +51,7 @@ class TestMediaShoutImport(TestCase):
""" """
# GIVEN: A MediaShoutImport class # GIVEN: A MediaShoutImport class
# WHEN: It is created # WHEN: It is created
importer = MediaShoutImport(MagicMock(), filename='mediashout.db') importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
# THEN: It should not be None # THEN: It should not be None
self.assertIsNotNone(importer) self.assertIsNotNone(importer)
@ -62,7 +62,7 @@ class TestMediaShoutImport(TestCase):
Test that do_import exits early when unable to connect to the database Test that do_import exits early when unable to connect to the database
""" """
# GIVEN: A MediaShoutImport instance # GIVEN: A MediaShoutImport instance
importer = MediaShoutImport(MagicMock(), filename='mediashout.db') importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
mocked_pyodbc.connect.side_effect = Exception('Unable to connect') mocked_pyodbc.connect.side_effect = Exception('Unable to connect')
# WHEN: do_import is called # WHEN: do_import is called
@ -89,7 +89,7 @@ class TestMediaShoutImport(TestCase):
group = GroupRecord('Hymns') group = GroupRecord('Hymns')
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
importer = MediaShoutImport(MagicMock(), filename='mediashout.db') importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
mocked_cursor = MagicMock() mocked_cursor = MagicMock()
mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]] mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]]
mocked_cursor.tables.fetchone.return_value = True mocked_cursor.tables.fetchone.return_value = True
@ -124,7 +124,7 @@ class TestMediaShoutImport(TestCase):
song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '') song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '')
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
importer = MediaShoutImport(MagicMock(), filename='mediashout.db') importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
mocked_cursor = MagicMock() mocked_cursor = MagicMock()
mocked_cursor.fetchall.return_value = [song] mocked_cursor.fetchall.return_value = [song]
mocked_connection = MagicMock() mocked_connection = MagicMock()
@ -158,7 +158,7 @@ class TestMediaShoutImport(TestCase):
play_order = PlayOrderRecord(0, 1, 1) play_order = PlayOrderRecord(0, 1, 1)
theme = ThemeRecord('Grace') theme = ThemeRecord('Grace')
group = GroupRecord('Hymns') group = GroupRecord('Hymns')
importer = MediaShoutImport(MagicMock(), filename='mediashout.db') importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
# WHEN: A song is processed # WHEN: A song is processed
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \ with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
@ -200,7 +200,7 @@ class TestMediaShoutImport(TestCase):
play_order = PlayOrderRecord(0, 1, 1) play_order = PlayOrderRecord(0, 1, 1)
theme = ThemeRecord('Grace') theme = ThemeRecord('Grace')
group = GroupRecord('Hymns') group = GroupRecord('Hymns')
importer = MediaShoutImport(MagicMock(), filename='mediashout.db') importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
# WHEN: A song is processed # WHEN: A song is processed
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \ with patch.object(importer, 'set_defaults') as mocked_set_defaults, \

View File

@ -48,7 +48,7 @@ class TestOpenLPImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = OpenLPSongImport(mocked_manager, filenames=[]) importer = OpenLPSongImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -61,7 +61,7 @@ class TestOpenLPImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'): with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = OpenLPSongImport(mocked_manager, filenames=[]) importer = OpenLPSongImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True importer.stop_import_flag = True

View File

@ -22,14 +22,14 @@
""" """
This module contains tests for the OpenLyrics song importer. This module contains tests for the OpenLyrics song importer.
""" """
import os
import shutil import shutil
from tempfile import mkdtemp from tempfile import mkdtemp
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
from openlp.core.common import Registry from openlp.core.common import Registry
from openlp.core.common.path import Path, rmtree
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -43,13 +43,13 @@ class TestOpenLyricsExport(TestCase, TestMixin):
Create the registry Create the registry
""" """
Registry.create() Registry.create()
self.temp_folder = mkdtemp() self.temp_folder = Path(mkdtemp())
def tearDown(self): def tearDown(self):
""" """
Cleanup Cleanup
""" """
shutil.rmtree(self.temp_folder) rmtree(self.temp_folder)
def test_export_same_filename(self): def test_export_same_filename(self):
""" """
@ -73,7 +73,9 @@ class TestOpenLyricsExport(TestCase, TestMixin):
ol_export.do_export() ol_export.do_export()
# THEN: The exporter should have created 2 files # THEN: The exporter should have created 2 files
self.assertTrue(os.path.exists(os.path.join(self.temp_folder, self.assertTrue((self.temp_folder /
'%s (%s).xml' % (song.title, author.display_name)))) '{title} ({display_name}).xml'.format(
self.assertTrue(os.path.exists(os.path.join(self.temp_folder, title=song.title, display_name=author.display_name)).exists())
'%s (%s)-1.xml' % (song.title, author.display_name)))) self.assertTrue((self.temp_folder /
'{title} ({display_name})-1.xml'.format(
title=song.title, display_name=author.display_name)).exists())

View File

@ -29,10 +29,11 @@ from unittest.mock import MagicMock, patch
from lxml import etree, objectify from lxml import etree, objectify
from openlp.core.common import Registry, Settings
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.importers.openlyrics import OpenLyricsImport from openlp.plugins.songs.lib.importers.openlyrics import OpenLyricsImport
from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
from openlp.core.common import Registry, Settings
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -109,7 +110,7 @@ class TestOpenLyricsImport(TestCase, TestMixin):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = OpenLyricsImport(mocked_manager, filenames=[]) importer = OpenLyricsImport(mocked_manager, file_paths=[])
# THEN: The importer should be an instance of SongImport # THEN: The importer should be an instance of SongImport
self.assertIsInstance(importer, SongImport) self.assertIsInstance(importer, SongImport)
@ -122,13 +123,13 @@ class TestOpenLyricsImport(TestCase, TestMixin):
for song_file in SONG_TEST_DATA: for song_file in SONG_TEST_DATA:
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = OpenLyricsImport(mocked_manager, filenames=[]) importer = OpenLyricsImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard importer.import_wizard = mocked_import_wizard
importer.open_lyrics = MagicMock() importer.open_lyrics = MagicMock()
importer.open_lyrics.xml_to_song = MagicMock() importer.open_lyrics.xml_to_song = MagicMock()
# WHEN: Importing each file # WHEN: Importing each file
importer.import_source = [os.path.join(TEST_PATH, song_file)] importer.import_source = [Path(TEST_PATH, song_file)]
importer.do_import() importer.do_import()
# THEN: The xml_to_song() method should have been called # THEN: The xml_to_song() method should have been called

View File

@ -54,7 +54,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = OpenOfficeImport(mocked_manager, filenames=[]) importer = OpenOfficeImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -66,7 +66,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a document that raises an exception # GIVEN: A mocked out SongImport class, a mocked out "manager" and a document that raises an exception
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = OpenOfficeImport(mocked_manager, filenames=[]) importer = OpenOfficeImport(mocked_manager, file_paths=[])
importer.document = MagicMock() importer.document = MagicMock()
importer.document.close = MagicMock(side_effect=Exception()) importer.document.close = MagicMock(side_effect=Exception())

View File

@ -27,6 +27,7 @@ from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from openlp.core.common import Registry from openlp.core.common import Registry
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
@ -52,15 +53,15 @@ class TestOpenSongFileImport(SongImportTestHelper):
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
mocked_settings.return_value = mocked_returned_settings mocked_settings.return_value = mocked_returned_settings
# Do the test import # Do the test import
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace')], self.file_import([Path(TEST_PATH, 'Amazing Grace')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer')], self.file_import([Path(TEST_PATH, 'Beautiful Garden Of Prayer')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
self.file_import([os.path.join(TEST_PATH, 'One, Two, Three, Four, Five')], self.file_import([Path(TEST_PATH, 'One, Two, Three, Four, Five')],
self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace2')], self.file_import([Path(TEST_PATH, 'Amazing Grace2')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace with bad CCLI')], self.file_import([Path(TEST_PATH, 'Amazing Grace with bad CCLI')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace without CCLI.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace without CCLI.json')))
@ -83,7 +84,7 @@ class TestOpenSongImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = OpenSongImport(mocked_manager, filenames=[]) importer = OpenSongImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -96,7 +97,7 @@ class TestOpenSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'): with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = OpenSongImport(mocked_manager, filenames=[]) importer = OpenSongImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True importer.stop_import_flag = True
@ -117,7 +118,7 @@ class TestOpenSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'): with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = OpenSongImport(mocked_manager, filenames=[]) importer = OpenSongImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True importer.stop_import_flag = True

View File

@ -86,7 +86,7 @@ class TestOpsProSongImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = OPSProImport(mocked_manager, filenames=[]) importer = OPSProImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -98,7 +98,7 @@ class TestOpsProSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry # GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[]) importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock() importer.finish = MagicMock()
song, lyrics = _build_data('you are so faithfull.txt', False) song, lyrics = _build_data('you are so faithfull.txt', False)
@ -118,7 +118,7 @@ class TestOpsProSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry # GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[]) importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock() importer.finish = MagicMock()
song, lyrics = _build_data('amazing grace.txt', False) song, lyrics = _build_data('amazing grace.txt', False)
@ -138,7 +138,7 @@ class TestOpsProSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry # GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[]) importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock() importer.finish = MagicMock()
song, lyrics = _build_data('amazing grace2.txt', True) song, lyrics = _build_data('amazing grace2.txt', True)
@ -158,7 +158,7 @@ class TestOpsProSongImport(TestCase):
""" """
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry # GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[]) importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock() importer.finish = MagicMock()
song, lyrics = _build_data('amazing grace3.txt', True) song, lyrics = _build_data('amazing grace3.txt', True)

View File

@ -26,8 +26,9 @@ ProPresenter song files into the current installation database.
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
from openlp.core.common import Registry
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs')) os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs'))
@ -44,7 +45,7 @@ class TestPowerPraiseFileImport(SongImportTestHelper):
""" """
Test that loading a PowerPraise file works correctly Test that loading a PowerPraise file works correctly
""" """
self.file_import([os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.ppl')], self.file_import([Path(TEST_PATH, 'Naher, mein Gott zu Dir.ppl')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.json')))
self.file_import([os.path.join(TEST_PATH, 'You are so faithful.ppl')], self.file_import([Path(TEST_PATH, 'You are so faithful.ppl')],
self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json')))

View File

@ -22,9 +22,10 @@
""" """
This module contains tests for the PresentationManager song importer. This module contains tests for the PresentationManager song importer.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
@ -42,7 +43,7 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
""" """
Test that loading a PresentationManager file works correctly Test that loading a PresentationManager file works correctly
""" """
self.file_import([os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.sng')], self.file_import([Path(TEST_PATH, 'Great Is Thy Faithfulness.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')], self.file_import([Path(TEST_PATH, 'Amazing Grace.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

View File

@ -23,9 +23,10 @@
The :mod:`propresenterimport` module provides the functionality for importing The :mod:`propresenterimport` module provides the functionality for importing
ProPresenter song files into the current installation database. ProPresenter song files into the current installation database.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
@ -43,19 +44,19 @@ class TestProPresenterFileImport(SongImportTestHelper):
""" """
Test that loading a ProPresenter 4 file works correctly Test that loading a ProPresenter 4 file works correctly
""" """
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')], self.file_import([Path(TEST_PATH, 'Amazing Grace.pro4')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
def test_pro5_song_import(self): def test_pro5_song_import(self):
""" """
Test that loading a ProPresenter 5 file works correctly Test that loading a ProPresenter 5 file works correctly
""" """
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro5')], self.file_import([Path(TEST_PATH, 'Amazing Grace.pro5')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
def test_pro6_song_import(self): def test_pro6_song_import(self):
""" """
Test that loading a ProPresenter 6 file works correctly Test that loading a ProPresenter 6 file works correctly
""" """
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro6')], self.file_import([Path(TEST_PATH, 'Amazing Grace.pro6')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

View File

@ -26,8 +26,9 @@ import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
from openlp.core.common import Registry from openlp.core.common import Registry
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
@ -51,18 +52,18 @@ class TestSongBeamerFileImport(SongImportTestHelper):
mocked_returned_settings = MagicMock() mocked_returned_settings = MagicMock()
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
mocked_settings.return_value = mocked_returned_settings mocked_settings.return_value = mocked_returned_settings
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')], self.file_import([Path(TEST_PATH, 'Amazing Grace.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import([os.path.join(TEST_PATH, 'Lobsinget dem Herrn.sng')], self.file_import([Path(TEST_PATH, 'Lobsinget dem Herrn.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Lobsinget dem Herrn.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Lobsinget dem Herrn.json')))
self.file_import([os.path.join(TEST_PATH, 'When I Call On You.sng')], self.file_import([Path(TEST_PATH, 'When I Call On You.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'When I Call On You.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'When I Call On You.json')))
def test_cp1252_encoded_file(self): def test_cp1252_encoded_file(self):
""" """
Test that a CP1252 encoded file get's decoded properly. Test that a CP1252 encoded file get's decoded properly.
""" """
self.file_import([os.path.join(TEST_PATH, 'cp1252song.sng')], self.file_import([Path(TEST_PATH, 'cp1252song.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'cp1252song.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'cp1252song.json')))
@ -78,7 +79,7 @@ class TestSongBeamerImport(TestCase):
self.song_import_patcher = patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport') self.song_import_patcher = patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport')
self.song_import_patcher.start() self.song_import_patcher.start()
mocked_manager = MagicMock() mocked_manager = MagicMock()
self.importer = SongBeamerImport(mocked_manager, filenames=[]) self.importer = SongBeamerImport(mocked_manager, file_paths=[])
def tearDown(self): def tearDown(self):
""" """
@ -95,7 +96,7 @@ class TestSongBeamerImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = SongBeamerImport(mocked_manager, filenames=[]) importer = SongBeamerImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')

View File

@ -23,9 +23,10 @@
The :mod:`songproimport` module provides the functionality for importing The :mod:`songproimport` module provides the functionality for importing
SongPro song files into the current installation database. SongPro song files into the current installation database.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
@ -43,5 +44,5 @@ class TestSongProFileImport(SongImportTestHelper):
""" """
Test that loading an SongPro file works correctly Test that loading an SongPro file works correctly
""" """
self.file_import(os.path.join(TEST_PATH, 'amazing-grace.txt'), self.file_import(Path(TEST_PATH, 'amazing-grace.txt'),
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

View File

@ -31,6 +31,7 @@ from urllib.error import URLError
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from openlp.core import Registry from openlp.core import Registry
from openlp.core.common.path import Path
from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
from openlp.plugins.songs.lib import Song from openlp.plugins.songs.lib import Song
from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGIN_PAGE, LOGOUT_URL, BASE_URL from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGIN_PAGE, LOGOUT_URL, BASE_URL
@ -810,15 +811,15 @@ class TestSongSelectFileImport(SongImportTestHelper):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.importer_class_name = 'CCLIFileImport' self.importer_class_name = 'CCLIFileImport'
self.importer_module_name = 'cclifile' self.importer_module_name = 'cclifile'
super(TestSongSelectFileImport, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def test_song_import(self): def test_song_import(self):
""" """
Test that loading an OpenSong file works correctly on various files Test that loading an OpenSong file works correctly on various files
""" """
self.file_import([os.path.join(TEST_PATH, 'TestSong.bin')], self.file_import([Path(TEST_PATH, 'TestSong.bin')],
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-bin.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-bin.json')))
self.file_import([os.path.join(TEST_PATH, 'TestSong.txt')], self.file_import([Path(TEST_PATH, 'TestSong.txt')],
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-txt.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-txt.json')))

View File

@ -26,6 +26,7 @@ import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from openlp.core.common.path import Path
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.importers.songshowplus import SongShowPlusImport from openlp.plugins.songs.lib.importers.songshowplus import SongShowPlusImport
@ -46,13 +47,13 @@ class TestSongShowPlusFileImport(SongImportTestHelper):
""" """
Test that loading a SongShow Plus file works correctly on various files Test that loading a SongShow Plus file works correctly on various files
""" """
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sbsong')], self.file_import([Path(TEST_PATH, 'Amazing Grace.sbsong')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')], self.file_import([Path(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
self.file_import([os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong')], self.file_import([Path(TEST_PATH, 'a mighty fortress is our god.sbsong')],
self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json')))
self.file_import([os.path.join(TEST_PATH, 'cleanse-me.sbsong')], self.file_import([Path(TEST_PATH, 'cleanse-me.sbsong')],
self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json')))
@ -69,7 +70,7 @@ class TestSongShowPlusImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = SongShowPlusImport(mocked_manager, filenames=[]) importer = SongShowPlusImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -82,7 +83,7 @@ class TestSongShowPlusImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'): with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[]) importer = SongShowPlusImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True importer.stop_import_flag = True
@ -103,7 +104,7 @@ class TestSongShowPlusImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'): with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_import_wizard = MagicMock() mocked_import_wizard = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[]) importer = SongShowPlusImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True importer.stop_import_flag = True
@ -123,7 +124,7 @@ class TestSongShowPlusImport(TestCase):
# GIVEN: A mocked out SongImport class, and a mocked out "manager" # GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'): with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[]) importer = SongShowPlusImport(mocked_manager, file_paths=[])
# WHEN: Supplied with the following arguments replicating verses being added # WHEN: Supplied with the following arguments replicating verses being added
test_values = [ test_values = [
@ -151,7 +152,7 @@ class TestSongShowPlusImport(TestCase):
# GIVEN: A mocked out SongImport class, and a mocked out "manager" # GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'): with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock() mocked_manager = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[]) importer = SongShowPlusImport(mocked_manager, file_paths=[])
# WHEN: Supplied with the following arguments replicating a verse order being added # WHEN: Supplied with the following arguments replicating a verse order being added
test_values = [ test_values = [

View File

@ -24,6 +24,8 @@ This module contains tests for the SundayPlus song importer.
import os import os
from unittest.mock import patch from unittest.mock import patch
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
@ -44,5 +46,5 @@ class TestSundayPlusFileImport(SongImportTestHelper):
with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \ with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
mocked_retrieve_windows_encoding: mocked_retrieve_windows_encoding:
mocked_retrieve_windows_encoding.return_value = 'cp1252' mocked_retrieve_windows_encoding.return_value = 'cp1252'
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.ptf')], self.file_import([Path(TEST_PATH, 'Amazing Grace.ptf')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

View File

@ -21,9 +21,10 @@
""" """
This module contains tests for the VideoPsalm song importer. This module contains tests for the VideoPsalm song importer.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
@ -48,7 +49,7 @@ class TestVideoPsalmFileImport(SongImportTestHelper):
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
mocked_settings.return_value = mocked_returned_settings mocked_settings.return_value = mocked_returned_settings
# Do the test import # Do the test import
self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'), self.file_import(Path(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'),
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json')))
self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold2.json'), self.file_import(Path(TEST_PATH, 'videopsalm-as-safe-a-stronghold2.json'),
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold2.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold2.json')))

View File

@ -22,9 +22,10 @@
""" """
This module contains tests for the Words of Worship song importer. This module contains tests for the Words of Worship song importer.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
@ -43,10 +44,10 @@ class TestWordsOfWorshipFileImport(SongImportTestHelper):
""" """
Test that loading a Words of Worship file works correctly Test that loading a Words of Worship file works correctly
""" """
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')], self.file_import([Path(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
self.file_import([os.path.join(TEST_PATH, 'When morning gilds the skies.wsg')], self.file_import([Path(TEST_PATH, 'When morning gilds the skies.wsg')],
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))
self.file_import([os.path.join(TEST_PATH, 'Holy Holy Holy Lord God Almighty.wow-song')], self.file_import([Path(TEST_PATH, 'Holy Holy Holy Lord God Almighty.wow-song')],
self.load_external_result_data(os.path.join(TEST_PATH, self.load_external_result_data(os.path.join(TEST_PATH,
'Holy Holy Holy Lord God Almighty.json'))) 'Holy Holy Holy Lord God Almighty.json')))

View File

@ -23,9 +23,10 @@
The :mod:`worshipassistantimport` module provides the functionality for importing The :mod:`worshipassistantimport` module provides the functionality for importing
WorshipAssistant song files into the current installation database. WorshipAssistant song files into the current installation database.
""" """
import os import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath( TEST_PATH = os.path.abspath(
@ -43,9 +44,9 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
""" """
Test that loading an Worship Assistant file works correctly Test that loading an Worship Assistant file works correctly
""" """
self.file_import(os.path.join(TEST_PATH, 'du_herr.csv'), self.file_import(Path(TEST_PATH, 'du_herr.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free.csv'), self.file_import(Path(TEST_PATH, 'would_you_be_free.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'), self.file_import(Path(TEST_PATH, 'would_you_be_free2.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))

View File

@ -55,7 +55,7 @@ if CAN_RUN_TESTS:
_title_assignment_list = [] _title_assignment_list = []
def __init__(self, manager): def __init__(self, manager):
WorshipCenterProImport.__init__(self, manager, filenames=[]) WorshipCenterProImport.__init__(self, manager, file_paths=[])
@property @property
def title(self): def title(self):
@ -153,7 +153,7 @@ class TestWorshipCenterProSongImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = WorshipCenterProImport(mocked_manager, filenames=[]) importer = WorshipCenterProImport(mocked_manager, file_paths=[])
# THEN: The importer object should not be None # THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none') self.assertIsNotNone(importer, 'Import should not be none')
@ -170,7 +170,7 @@ class TestWorshipCenterProSongImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
mocked_log_error = MagicMock() mocked_log_error = MagicMock()
mocked_translate.return_value = 'Translated Text' mocked_translate.return_value = 'Translated Text'
importer = WorshipCenterProImport(mocked_manager, filenames=[]) importer = WorshipCenterProImport(mocked_manager, file_paths=[])
importer.log_error = mocked_log_error importer.log_error = mocked_log_error
importer.import_source = 'import_source' importer.import_source = 'import_source'
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError] pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]

View File

@ -26,9 +26,10 @@ import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.core.common import Registry
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport
from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.core.common import Registry
from tests.helpers.songfileimport import SongImportTestHelper from tests.helpers.songfileimport import SongImportTestHelper
@ -55,7 +56,7 @@ class TestZionWorxImport(TestCase):
mocked_manager = MagicMock() mocked_manager = MagicMock()
# WHEN: An importer object is created # WHEN: An importer object is created
importer = ZionWorxImport(mocked_manager, filenames=[]) importer = ZionWorxImport(mocked_manager, file_paths=[])
# THEN: The importer should be an instance of SongImport # THEN: The importer should be an instance of SongImport
self.assertIsInstance(importer, SongImport) self.assertIsInstance(importer, SongImport)
@ -72,5 +73,5 @@ class TestZionWorxFileImport(SongImportTestHelper):
""" """
Test that loading an ZionWorx file works correctly on various files Test that loading an ZionWorx file works correctly on various files
""" """
self.file_import(os.path.join(TEST_PATH, 'zionworx.csv'), self.file_import(Path(TEST_PATH, 'zionworx.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'zionworx.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'zionworx.json')))

View File

@ -89,7 +89,7 @@ class SongImportTestHelper(TestCase):
""" """
Import the given file and check that it has imported correctly Import the given file and check that it has imported correctly
""" """
importer = self.importer_class(self.mocked_manager, filenames=[source_file_name]) importer = self.importer_class(self.mocked_manager, file_paths=[source_file_name])
importer.import_wizard = self.mocked_import_wizard importer.import_wizard = self.mocked_import_wizard
importer.stop_import_flag = False importer.stop_import_flag = False
importer.topics = [] importer.topics = []

View File

@ -94,4 +94,3 @@ class TestPluginManager(TestCase, TestMixin):
self.assertIn('custom', plugin_names, 'There should be a "custom" plugin') self.assertIn('custom', plugin_names, 'There should be a "custom" plugin')
self.assertIn('songusage', plugin_names, 'There should be a "songusage" plugin') self.assertIn('songusage', plugin_names, 'There should be a "songusage" plugin')
self.assertIn('alerts', plugin_names, 'There should be a "alerts" plugin') self.assertIn('alerts', plugin_names, 'There should be a "alerts" plugin')
self.assertIn('remotes', plugin_names, 'There should be a "remotes" plugin')