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()
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
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',
sha256=sha256):
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')

View File

@ -21,18 +21,13 @@
###############################################################################
import logging
import os
from openlp.core.api.http.endpoint import Endpoint
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__)
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}')

View File

@ -23,7 +23,7 @@ import logging
import json
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

View File

@ -26,17 +26,24 @@ with OpenLP. It uses JSON to communicate with the remotes.
"""
import logging
import time
from PyQt5 import QtCore
from PyQt5 import QtCore, QtWidgets
from waitress import serve
from openlp.core.api.http import register_endpoint
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.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.service import service_endpoint, api_service_endpoint
from openlp.core.api.endpoint.remote import remote_endpoint
log = logging.getLogger(__name__)
@ -59,6 +66,7 @@ class HttpWorker(QtCore.QObject):
"""
address = Settings().value('api/ip address')
port = Settings().value('api/port')
Registry().execute('get_website_version')
serve(application, host=address, port=port)
def stop(self):
@ -79,11 +87,15 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
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):
"""
Register the poll return service and start the servers.
"""
self.initialise()
self.poller = Poller()
Registry().register('poller', self.poller)
application.initialise()
@ -95,3 +107,79 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
register_endpoint(main_endpoint)
register_endpoint(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.update_site_layout.addRow(self.master_version_label, self.master_version_value)
self.left_layout.addWidget(self.update_site_group_box)
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
self.android_app_group_box.setObjectName('android_app_group_box')
self.right_layout.addWidget(self.android_app_group_box)
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
self.android_qr_layout.setObjectName('android_qr_layout')
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.android_qr_code_label.setObjectName('android_qr_code_label')
self.android_qr_layout.addWidget(self.android_qr_code_label)
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
self.android_qr_description_label.setObjectName('android_qr_description_label')
self.android_qr_description_label.setOpenExternalLinks(True)
self.android_qr_description_label.setWordWrap(True)
self.android_qr_layout.addWidget(self.android_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.app_group_box = QtWidgets.QGroupBox(self.right_column)
self.app_group_box.setObjectName('app_group_box')
self.right_layout.addWidget(self.app_group_box)
self.app_qr_layout = QtWidgets.QVBoxLayout(self.app_group_box)
self.app_qr_layout.setObjectName('app_qr_layout')
self.app_qr_code_label = QtWidgets.QLabel(self.app_group_box)
self.app_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/app_qr.svg'))
self.app_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.app_qr_code_label.setObjectName('app_qr_code_label')
self.app_qr_layout.addWidget(self.app_qr_code_label)
self.app_qr_description_label = QtWidgets.QLabel(self.app_group_box)
self.app_qr_description_label.setObjectName('app_qr_description_label')
self.app_qr_description_label.setOpenExternalLinks(True)
self.app_qr_description_label.setWordWrap(True)
self.app_qr_layout.addWidget(self.app_qr_description_label)
self.left_layout.addStretch()
self.right_layout.addStretch()
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.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.android_qr_description_label.setText(
self.app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Remote App'))
self.app_qr_description_label.setText(
translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
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'))
'Scan the QR code or click <a href="{qr}">download</a> to download an app for your mobile device'
).format(qr='https://openlp.org/#mobile-app-downloads'))
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
self.aa = UiStrings()
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))
http_url_temp = http_url + 'stage'
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'
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',
'media/players': 'system,webkit',
'media/override player': QtCore.Qt.Unchecked,
'remotes/download version': '0.0',
'players/background color': '#000000',
'servicemanager/last directory': None,
'servicemanager/last file': None,

View File

@ -426,10 +426,10 @@ class ServiceItem(RegistryProperties):
self.background_audio = []
for filename in header['background_audio']:
# Give them real file paths.
filepath = filename
filepath = str(filename)
if path:
# 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.theme_overwritten = header.get('theme_overwritten', False)
if self.service_item_type == ServiceItemType.Text:

View File

@ -684,7 +684,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
if not isinstance(file_names, list):
file_names = [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):
"""

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)
if 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))
service_item['header']['background_audio'][i] = new_file
# 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:
zip_file.write(write_from, write_from)
for audio_from, audio_to in audio_files:
audio_from = str(audio_from)
audio_to = str(audio_to)
if audio_from.startswith('audio'):
# 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.

View File

@ -868,7 +868,7 @@ class SlideController(DisplayController, RegistryProperties):
self.track_menu.clear()
for counter in range(len(self.service_item.background_audio)):
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.triggered.connect(self.on_track_triggered)
self.display.audio_player.repeat = \

View File

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

View File

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

View File

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

View File

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

View File

@ -76,7 +76,7 @@ def presentations_service(request):
:param request: The http request object.
"""
service(request, 'presentations', log)
return service(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/search')
@ -109,6 +109,6 @@ def presentations_service_api(request):
:param request: The http request object.
"""
try:
search(request, 'presentations', log)
return search(request, 'presentations', log)
except NotFound:
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.
"""
service(request, 'songs', log)
return service(request, 'songs', log)
@api_songs_endpoint.route('songs/search')
@ -95,6 +95,6 @@ def songs_service_api(request):
:param request: The http request object.
"""
try:
search(request, 'songs', log)
return service(request, 'songs', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -23,27 +23,25 @@
The :mod:`~openlp.plugins.songs.forms.editsongform` module contains the form
used to edit songs.
"""
import logging
import re
import os
import shutil
import re
from PyQt5 import QtCore, QtWidgets
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.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.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.editverseform import EditVerseForm
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__)
@ -545,9 +543,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
songbook_entry.entry)
self.audio_list_widget.clear()
for media in self.song.media_files:
media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1])
media_file.setData(QtCore.Qt.UserRole, media.file_name)
self.audio_list_widget.addItem(media_file)
item = QtWidgets.QListWidgetItem(media.file_path.name)
item.setData(QtCore.Qt.UserRole, media.file_path)
self.audio_list_widget.addItem(item)
self.title_edit.setFocus()
# Hide or show the preview button.
self.preview_button.setVisible(preview)
@ -927,12 +925,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
Loads file(s) from the filesystem.
"""
filters = '{text} (*)'.format(text=UiStrings().AllFiles)
file_paths, selected_filter = FileDialog.getOpenFileNames(
self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters)
file_paths, filter_used = FileDialog.getOpenFileNames(
parent=self, caption=translate('SongsPlugin.EditSongForm', 'Open File(s)'), filter=filters)
for file_path in file_paths:
filename = path_to_str(file_path)
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
item.setData(QtCore.Qt.UserRole, filename)
item = QtWidgets.QListWidgetItem(file_path.name)
item.setData(QtCore.Qt.UserRole, file_path)
self.audio_list_widget.addItem(item)
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.
"""
if self.media_form.exec():
for filename in self.media_form.get_selected_files():
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
item.setData(QtCore.Qt.UserRole, filename)
for file_path in self.media_form.get_selected_files():
item = QtWidgets.QListWidgetItem(file_path.name)
item.setData(QtCore.Qt.UserRole, file_path)
self.audio_list_widget.addItem(item)
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.
clean_song(self.manager, self.song)
self.manager.save_object(self.song)
audio_files = [a.file_name for a in self.song.media_files]
log.debug(audio_files)
save_path = os.path.join(str(AppLocation.get_section_data_path(self.media_item.plugin.name)), 'audio',
str(self.song.id))
check_directory_exists(Path(save_path))
audio_paths = [a.file_path for a in self.song.media_files]
log.debug(audio_paths)
save_path = AppLocation.get_section_data_path(self.media_item.plugin.name) / 'audio' / str(self.song.id)
check_directory_exists(save_path)
self.song.media_files = []
files = []
file_paths = []
for row in range(self.audio_list_widget.count()):
item = self.audio_list_widget.item(row)
filename = item.data(QtCore.Qt.UserRole)
if not filename.startswith(save_path):
old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
shutil.copyfile(old_file, filename)
files.append(filename)
file_path = item.data(QtCore.Qt.UserRole)
if save_path not in file_path.parents:
old_file_path, file_path = file_path, save_path / file_path.name
copyfile(old_file_path, file_path)
file_paths.append(file_path)
media_file = MediaFile()
media_file.file_name = filename
media_file.file_path = file_path
media_file.type = 'audio'
media_file.weight = row
self.song.media_files.append(media_file)
for audio in audio_files:
if audio not in files:
for audio_path in audio_paths:
if audio_path not in file_paths:
try:
os.remove(audio)
audio_path.unlink()
except:
log.exception('Could not remove file: {audio}'.format(audio=audio))
if not files:
log.exception('Could not remove file: {audio}'.format(audio=audio_path))
if not file_paths:
try:
os.rmdir(save_path)
save_path.rmdir()
except OSError:
log.exception('Could not remove directory: {path}'.format(path=save_path))
clean_song(self.manager, self.song)

View File

@ -41,12 +41,19 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
QtCore.Qt.WindowCloseButtonHint)
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()
for file in files:
item = QtWidgets.QListWidgetItem(os.path.split(file)[1])
item.setData(QtCore.Qt.UserRole, file)
for file_path in file_paths:
item = QtWidgets.QListWidgetItem(file_path.name)
item.setData(QtCore.Qt.UserRole, file_path)
self.file_list_widget.addItem(item)
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()]

View File

@ -27,9 +27,10 @@ import logging
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, UiStrings, translate
from openlp.core.lib import create_separated_list, build_icon
from openlp.core.common import Registry, Settings, UiStrings, translate
from openlp.core.lib import create_separated_list
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.plugins.songs.lib.db import Song
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.uncheck_button.clicked.connect(self.on_uncheck_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):
"""
@ -120,21 +120,15 @@ class SongExportForm(OpenLPWizard):
self.grid_layout.setObjectName('range_layout')
self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page)
self.selected_list_widget.setObjectName('selected_list_widget')
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)
# FIXME: self.horizontal_layout is already defined above?!?!? Replace with Path Eidt!
self.horizontal_layout = QtWidgets.QHBoxLayout()
self.horizontal_layout.setObjectName('horizontal_layout')
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 2)
self.output_directory_path_edit = PathEdit(
self.export_song_page, PathType.Directories,
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.setObjectName('directory_label')
self.horizontal_layout.addWidget(self.directory_label)
self.directory_line_edit = QtWidgets.QLineEdit(self.export_song_page)
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.grid_layout.addWidget(self.directory_label, 0, 0)
self.grid_layout.addWidget(self.output_directory_path_edit, 0, 1)
self.export_song_layout.addLayout(self.grid_layout)
self.addPage(self.export_song_page)
@ -188,11 +182,12 @@ class SongExportForm(OpenLPWizard):
self.selected_list_widget.addItem(song)
return True
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(
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
return False
Settings().setValue('songs/last directory export', self.output_directory_path_edit.path)
return True
elif self.currentPage() == self.progress_page:
self.available_list_widget.clear()
@ -211,8 +206,6 @@ class SongExportForm(OpenLPWizard):
self.finish_button.setVisible(False)
self.cancel_button.setVisible(True)
self.available_list_widget.clear()
self.selected_list_widget.clear()
self.directory_line_edit.clear()
self.search_line_edit.clear()
# Load the list of songs.
self.application.set_busy_cursor()
@ -247,7 +240,7 @@ class SongExportForm(OpenLPWizard):
song.data(QtCore.Qt.UserRole)
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:
if exporter.do_export():
self.progress_label.setText(
@ -291,15 +284,6 @@ class SongExportForm(OpenLPWizard):
if not item.isHidden():
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=''):
"""

View File

@ -22,15 +22,13 @@
"""
The song import functions for OpenLP.
"""
import codecs
import logging
import os
from PyQt5 import QtCore, QtWidgets
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.ui.lib import PathEdit, PathType
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
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]['removeButton'].clicked.connect(self.on_remove_button_clicked)
else:
self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked)
self.format_widgets[song_format]['file_path_edit'].textChanged.\
connect(self.on_filepath_edit_text_changed)
self.format_widgets[song_format]['path_edit'].pathChanged.connect(self.on_path_edit_path_changed)
def add_custom_pages(self):
"""
@ -155,7 +151,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
self.format_widgets[format_list]['removeButton'].setText(
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
else:
self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse)
f_label = 'Filename:'
if select_mode == SongFormatSelect.SingleFolder:
f_label = 'Folder:'
@ -172,16 +167,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
self.error_save_to_button.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File'))
# Align all QFormLayouts towards each other.
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
max_label_width = max(self.format_label.minimumSizeHint().width(),
max([label.minimumSizeHint().width() for label in labels]))
self.format_spacer.changeSize(max_label_width, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
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)
max_label_width = max(labels, key=lambda label: label.minimumSizeHint().width()).minimumSizeHint().width()
for label in labels:
label.setFixedWidth(max_label_width)
# Align descriptionLabels with rest of layout
for format_list in SongFormat.get_format_list():
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)
select_mode, class_, error_msg = SongFormat.get(this_format, 'selectMode', 'class', 'invalidSourceMsg')
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
focus_button = self.format_widgets[this_format]['addButton']
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)
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):
critical_error_message_box(error_title, error_msg)
focus_button.setFocus()
@ -238,20 +228,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
if filters:
filters += ';;'
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
file_paths, selected_filter = FileDialog.getOpenFileNames(
self, title, Settings().value(self.plugin.settings_section + '/last directory import'), filters)
file_paths, filter_used = FileDialog.getOpenFileNames(
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:
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)
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
: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):
"""
@ -263,20 +256,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
item = list_box.takeItem(list_box.row(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):
"""
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.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.
"""
@ -317,8 +296,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
select_mode = SongFormat.get(format_list, 'selectMode')
if select_mode == SongFormatSelect.MultipleFiles:
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.setHidden(True)
self.error_copy_to_button.setHidden(True)
@ -341,14 +318,14 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
select_mode = SongFormat.get(source_format, 'selectMode')
if select_mode == SongFormatSelect.SingleFile:
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:
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:
importer = self.plugin.import_songs(
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()
self.progress_label.setText(WizardStrings.FinishedImport)
@ -366,18 +343,17 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
"""
file_path, filter_used = FileDialog.getSaveFileName(
self, Settings().value(self.plugin.settings_section + '/last directory import'))
if not file_path:
if file_path is None:
return
with file_path.open('w', encoding='utf-8') as report_file:
report_file.write(self.error_report_text_edit.toPlainText())
file_path.write_text(self.error_report_text_edit.toPlainText(), encoding='utf-8')
def add_file_select_item(self):
"""
Add a file selection page.
"""
this_format = self.current_format
prefix, can_disable, description_text, select_mode = \
SongFormat.get(this_format, 'prefix', 'canDisable', 'descriptionText', 'selectMode')
format_name, prefix, can_disable, description_text, select_mode, filters = \
SongFormat.get(this_format, 'name', 'prefix', 'canDisable', 'descriptionText', 'selectMode', 'filter')
page = QtWidgets.QWidget()
page.setObjectName(prefix + 'Page')
if can_disable:
@ -403,26 +379,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
file_path_layout = QtWidgets.QHBoxLayout()
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.setObjectName(prefix + 'FilepathLabel')
file_path_layout.addWidget(file_path_label)
file_path_spacer = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
file_path_layout.addSpacerItem(file_path_spacer)
file_path_edit = QtWidgets.QLineEdit(import_widget)
file_path_edit.setObjectName(prefix + '_file_path_edit')
file_path_layout.addWidget(file_path_edit)
browse_button = QtWidgets.QToolButton(import_widget)
browse_button.setIcon(self.open_icon)
browse_button.setObjectName(prefix + 'BrowseButton')
file_path_layout.addWidget(browse_button)
if select_mode == SongFormatSelect.SingleFile:
path_type = PathType.Files
dialog_caption = WizardStrings.OpenTypeFile.format(file_type=format_name)
else:
path_type = PathType.Directories
dialog_caption = WizardStrings.OpenTypeFolder.format(folder_name=format_name)
path_edit = PathEdit(
parent=import_widget, path_type=path_type, dialog_caption=dialog_caption, show_revert=False)
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.addSpacerItem(self.stack_spacer)
self.format_widgets[this_format]['filepathLabel'] = file_path_label
self.format_widgets[this_format]['filepathSpacer'] = file_path_spacer
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
self.format_widgets[this_format]['path_edit'] = path_edit
elif select_mode == SongFormatSelect.MultipleFiles:
file_list_widget = QtWidgets.QListWidget(import_widget)
file_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
@ -496,6 +469,8 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
* or if SingleFolder mode, the specified folder exists
When this method returns True, the wizard's Next button is enabled.
:rtype: bool
"""
wizard = self.wizard()
this_format = wizard.current_format
@ -505,10 +480,10 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
return True
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 select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path):
if select_mode == SongFormatSelect.SingleFile and file_path.is_file():
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 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)
for media_file in media_files:
try:
os.remove(media_file.file_name)
media_file.file_path.unlink()
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:
save_path = os.path.join(str(AppLocation.get_section_data_path(song_plugin.name)), 'audio', str(song_id))
if os.path.exists(save_path):
os.rmdir(save_path)
save_path = AppLocation.get_section_data_path(song_plugin.name) / 'audio' / str(song_id)
if save_path.exists():
save_path.rmdir()
except OSError:
log.exception('Could not remove directory: {path}'.format(path=save_path))
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 Songs plugin
"""
from contextlib import suppress
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation, reconstructor
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.lib import translate
from openlp.core.lib.db import BaseModel, PathType, init_db
class Author(BaseModel):
@ -238,7 +239,7 @@ def init_schema(url):
**media_files Table**
* id
* file_name
* _file_path
* type
**song_books Table**
@ -305,7 +306,7 @@ def init_schema(url):
'media_files', metadata,
Column('id', types.Integer(), primary_key=True),
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('weight', types.Integer(), default=0)
)

View File

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

View File

@ -47,12 +47,11 @@ class ChordProImport(SongImport):
"""
def do_import(self):
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:
return
song_file = open(filename, 'rt')
with file_path.open('rt') as song_file:
self.do_import_file(song_file)
song_file.close()
def do_import_file(self, song_file):
"""

View File

@ -78,27 +78,29 @@ class DreamBeamImport(SongImport):
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):
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:
return
self.set_defaults()
parser = etree.XMLParser(remove_blank_text=True)
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:
log.exception('XML syntax error in file {name}'.format(name=file))
self.log_error(file, SongStrings.XMLSyntaxError)
log.exception('XML syntax error in file_path {name}'.format(name=file_path))
self.log_error(file_path, SongStrings.XMLSyntaxError)
continue
xml = etree.tostring(parsed_file).decode()
song_xml = objectify.fromstring(xml)
if song_xml.tag != 'DreamSong':
self.log_error(
file,
translate('SongsPlugin.DreamBeamImport', 'Invalid DreamBeam song file. Missing DreamSong tag.'))
file_path,
translate('SongsPlugin.DreamBeamImport',
'Invalid DreamBeam song file_path. Missing DreamSong tag.'))
continue
if hasattr(song_xml, 'Version'):
self.version = float(song_xml.Version.text)
@ -144,4 +146,4 @@ class DreamBeamImport(SongImport):
else:
self.parse_author(author_copyright)
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):
log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
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()
song_xml = objectify.fromstring(xml)
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.
"""
import os
import struct
import re
import zlib
import logging
import os
import re
import struct
import zlib
import sqlite3
from openlp.core.common.path import Path
from openlp.core.lib import translate
from openlp.plugins.songs.lib import VerseType
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
"""
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()
elif self.import_source.endswith('DB'):
elif ext == '.db':
self.import_db()
else:
self.import_sqlite_db()
@ -91,11 +94,11 @@ class EasyWorshipSongImport(SongImport):
or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format
"""
# 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.')
return
# 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.')
return
# Take a stab at how text is encoded
@ -104,7 +107,7 @@ class EasyWorshipSongImport(SongImport):
if not self.encoding:
log.debug('No encoding set.')
return
self.ews_file = open(self.import_source, 'rb')
self.ews_file = self.import_source.open('rb')
# EWS header, version '1.6'/' 3'/' 5':
# Offset Field Data type Length Details
# --------------------------------------------------------------------------------------------------
@ -199,23 +202,22 @@ class EasyWorshipSongImport(SongImport):
Import the songs from the database
"""
# Open the DB and MB files if they exist
import_source_mb = self.import_source.replace('.DB', '.MB')
if not os.path.isfile(self.import_source):
import_source_mb = self.import_source.with_suffix('.MB')
if not self.import_source.is_file():
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'This file does not exist.'))
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',
'Could not find the "Songs.MB" file. It must be in the same '
'folder as the "Songs.DB" file.'))
return
db_size = os.path.getsize(self.import_source)
if db_size < 0x800:
if self.import_source.stat().st_size < 0x800:
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'This file is not a valid EasyWorship database.'))
return
db_file = open(self.import_source, 'rb')
self.memo_file = open(import_source_mb, 'rb')
db_file = self.import_source.open('rb')
self.memo_file = import_source_mb.open('rb')
# 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))
if header_size != 0x800 or block_size < 1 or block_size > 4:
@ -340,52 +342,34 @@ class EasyWorshipSongImport(SongImport):
db_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):
"""
Import the songs from an EasyWorship 6 SQLite database
"""
songs_db_path = self._find_file(self.import_source, ["Databases", "Data", "Songs.db"])
song_words_db_path = self._find_file(self.import_source, ["Databases", "Data", "SongWords.db"])
invalid_dir_msg = 'This does not appear to be a valid Easy Worship 6 database directory.'
songs_db_path = next(self.import_source.rglob('Songs.db'), None)
song_words_db_path = next(self.import_source.rglob('SongWords.db'), None)
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
if not os.path.isfile(songs_db_path):
self.log_error(songs_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg))
if not (songs_db_path and songs_db_path.is_file()):
self.log_error(self.import_source, invalid_dir_msg)
return
if not os.path.isfile(song_words_db_path):
self.log_error(song_words_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg))
if not (song_words_db_path and song_words_db_path.is_file()):
self.log_error(self.import_source, invalid_dir_msg)
return
# get database handles
songs_conn = sqlite3.connect(songs_db_path)
words_conn = sqlite3.connect(song_words_db_path)
songs_conn = sqlite3.connect(str(songs_db_path))
words_conn = sqlite3.connect(str(song_words_db_path))
if songs_conn is None or words_conn is None:
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'This is not a valid Easy Worship 6 database.'))
self.log_error(self.import_source, invalid_db_msg)
songs_conn.close()
words_conn.close()
return
songs_db = songs_conn.cursor()
words_db = words_conn.cursor()
if songs_conn is None or words_conn is None:
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
'This is not a valid Easy Worship 6 database.'))
self.log_error(self.import_source, invalid_db_msg)
songs_conn.close()
words_conn.close()
return

View File

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

View File

@ -50,12 +50,11 @@ class LyrixImport(SongImport):
if not isinstance(self.import_source, list):
return
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:
return
song_file = open(filename, 'rt', encoding='cp1251')
with file_path.open('rt', encoding='cp1251') as song_file:
self.do_import_file(song_file)
song_file.close()
def do_import_file(self, file):
"""

View File

@ -266,7 +266,7 @@ class OpenLPSongImport(SongImport):
if has_media_files and song.media_files:
for media_file in song.media_files:
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:
new_song.media_files.append(existing_media_file)
else:

View File

@ -23,9 +23,7 @@
The :mod:`openlyrics` module provides the functionality for importing
songs which are saved as OpenLyrics files.
"""
import logging
import os
from lxml import etree
@ -58,12 +56,11 @@ class OpenLyricsImport(SongImport):
for file_path in self.import_source:
if self.stop_import_flag:
return
self.import_wizard.increment_progress_bar(
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
try:
# Pass a file object, because lxml does not cope with some
# 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()
self.open_lyrics.xml_to_song(xml)
except etree.XMLSyntaxError:

View File

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

View File

@ -19,7 +19,6 @@
# 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 re
@ -116,12 +115,11 @@ class OpenSongImport(SongImport):
if not isinstance(self.import_source, list):
return
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:
return
song_file = open(filename, 'rb')
with file_path.open('rb') as song_file:
self.do_import_file(song_file)
song_file.close()
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)
# Access97 XOR of the source
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
mdb = open(self.import_source, 'rb')
mdb.seek(0x14)
version = struct.unpack('B', mdb.read(1))[0]
with self.import_source.open('rb') as mdb_file:
mdb_file.seek(0x14)
version = struct.unpack('B', mdb_file.read(1))[0]
# Get encrypted logo
mdb.seek(0x62)
EncrypFlag = struct.unpack('B', mdb.read(1))[0]
mdb_file.seek(0x62)
EncrypFlag = struct.unpack('B', mdb_file.read(1))[0]
# Get encrypted password
mdb.seek(0x42)
encrypted_password = mdb.read(26)
mdb.close()
mdb_file.seek(0x42)
encrypted_password = mdb_file.read(26)
# "Decrypt" the password based on the version
decrypted_password = ''
if version < 0x01:

View File

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

View File

@ -72,10 +72,14 @@ class PowerSongImport(SongImport):
Checks if source is a PowerSong 1.0 folder:
* is a directory
* 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):
for file in os.listdir(import_source):
if fnmatch.fnmatch(file, '*.song'):
if import_source.is_dir():
for file_path in import_source.iterdir():
if file_path.suffix == '.song':
return True
return False

View File

@ -23,13 +23,11 @@
The :mod:`presentationmanager` module provides the functionality for importing
Presentationmanager song files into the current database.
"""
import os
import re
import chardet
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 .songimport import SongImport
@ -44,17 +42,14 @@ class PresentationManagerImport(SongImport):
for file_path in self.import_source:
if self.stop_import_flag:
return
self.import_wizard.increment_progress_bar(
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
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:
# Try to detect encoding and use it
file = open(file_path, mode='rb')
encoding = chardet.detect(file.read())['encoding']
file.close()
encoding = get_file_encoding(file_path)['encoding']
# 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)
try:
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
@ -80,6 +75,11 @@ class PresentationManagerImport(SongImport):
return ''
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()
attrs = None
if hasattr(root, 'attributes'):
@ -123,4 +123,4 @@ class PresentationManagerImport(SongImport):
self.verse_order_list = verse_order_list
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
ProPresenter song files into the current installation database.
"""
import os
import base64
import logging
from lxml import objectify
@ -47,11 +45,17 @@ class ProPresenterImport(SongImport):
if self.stop_import_flag:
return
self.import_wizard.increment_progress_bar(
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
root = objectify.parse(open(file_path, 'rb')).getroot()
WizardStrings.ImportingType.format(source=file_path.name))
with file_path.open('rb') as xml_file:
root = objectify.parse(xml_file).getroot()
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()
# Extract ProPresenter versionNumber
@ -64,9 +68,7 @@ class ProPresenterImport(SongImport):
# Title
self.title = root.get('CCLISongTitle')
if not self.title or self.title == '':
self.title = os.path.basename(filename)
if self.title[-5:-1] == '.pro':
self.title = self.title[:-5]
self.title = file_path.stem
# Notes
self.comments = root.get('notes')
# Author

View File

@ -112,7 +112,7 @@ class SongBeamerImport(SongImport):
if not isinstance(self.import_source, list):
return
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
if self.stop_import_flag:
return
@ -120,20 +120,19 @@ class SongBeamerImport(SongImport):
self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse]
self.chord_table = None
file_name = os.path.split(import_file)[1]
if os.path.isfile(import_file):
if file_path.is_file():
# 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.
# So if it doesn't start with 'u' we default to cp1252. See:
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
if not self.input_file_encoding.lower().startswith('u'):
self.input_file_encoding = 'cp1252'
infile = open(import_file, 'rt', encoding=self.input_file_encoding)
song_data = infile.readlines()
with file_path.open(encoding=self.input_file_encoding) as song_file:
song_data = song_file.readlines()
else:
continue
self.title = file_name.split('.sng')[0]
self.title = file_path.stem
read_verses = False
# The first verse separator doesn't count, but the others does, so line count starts at -1
line_number = -1
@ -185,7 +184,7 @@ class SongBeamerImport(SongImport):
# inserted by songbeamer, but are manually added headings. So restart the loop, and
# count tags as lines.
self.set_defaults()
self.title = file_name.split('.sng')[0]
self.title = file_path.stem
verse_tags_mode = VerseTagMode.ContainsNoTagsRestart
read_verses = False
# 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.add_verse(self.current_verse, self.current_verse_type)
if not self.finish():
self.log_error(import_file)
self.log_error(file_path)
def insert_chords(self, line_number, line):
"""
@ -414,14 +413,15 @@ class SongBeamerImport(SongImport):
"""
# The path is relative to SongBeamers Song folder
if is_win():
user_doc_folder = os.path.expandvars('$DOCUMENTS')
user_doc_path = Path(os.path.expandvars('$DOCUMENTS'))
elif is_macosx():
user_doc_folder = os.path.join(os.path.expanduser('~'), 'Documents')
user_doc_path = Path.home() / 'Documents'
else:
# SongBeamer only runs on mac and win...
return
audio_file_path = os.path.normpath(os.path.join(user_doc_folder, 'SongBeamer', 'Songs', audio_file_path))
if os.path.isfile(audio_file_path):
audio_file_path = user_doc_path / 'SongBeamer' / 'Songs' / audio_file_path
if audio_file_path.is_file():
self.add_media_file(audio_file_path)
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 re
import shutil
import os
from PyQt5 import QtCore
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.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
@ -62,14 +60,14 @@ class SongImport(QtCore.QObject):
"""
self.manager = manager
QtCore.QObject.__init__(self)
if 'filename' in kwargs:
self.import_source = kwargs['filename']
elif 'filenames' in kwargs:
self.import_source = kwargs['filenames']
elif 'folder' in kwargs:
self.import_source = kwargs['folder']
if 'file_path' in kwargs:
self.import_source = kwargs['file_path']
elif 'file_paths' in kwargs:
self.import_source = kwargs['file_paths']
elif 'folder_path' in kwargs:
self.import_source = kwargs['folder_path']
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)
self.import_wizard = None
self.song = None
@ -270,13 +268,13 @@ class SongImport(QtCore.QObject):
return
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
"""
if filename in [x[0] for x in self.media_files]:
if file_path in [x[0] for x in self.media_files]:
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):
"""
@ -403,29 +401,30 @@ class SongImport(QtCore.QObject):
self.manager.save_object(song)
# Now loop through the media files, copy them to the correct location,
# and save the song again.
for filename, weight in self.media_files:
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_name == filename)
for file_path, weight in self.media_files:
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_path == file_path)
if not media_file:
if os.path.dirname(filename):
filename = self.copy_media_file(song.id, filename)
song.media_files.append(MediaFile.populate(file_name=filename, weight=weight))
if file_path.parent:
file_path = self.copy_media_file(song.id, file_path)
song.media_files.append(MediaFile.populate(file_path=file_path, weight=weight))
self.manager.save_object(song)
self.set_defaults()
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
the new file location.
: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'):
self.save_path = os.path.join(str(AppLocation.get_section_data_path(self.import_wizard.plugin.name)),
'audio', str(song_id))
check_directory_exists(Path(self.save_path))
if not filename.startswith(self.save_path):
old_file, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
shutil.copyfile(old_file, filename)
return filename
self.save_path = AppLocation.get_section_data_path(self.import_wizard.plugin.name) / 'audio' / str(song_id)
check_directory_exists(self.save_path)
if self.save_path not in file_path.parents:
old_path, file_path = file_path, self.save_path / file_path.name
copyfile(old_path, file_path)
return file_path

View File

@ -25,6 +25,7 @@ songs into the OpenLP database.
"""
import re
from openlp.core.common.path import Path
from openlp.plugins.songs.lib import strip_rtf
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.
"""
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)
tag = ''
text = ''

View File

@ -23,7 +23,6 @@
The :mod:`songshowplus` module provides the functionality for importing SongShow Plus songs into the OpenLP
database.
"""
import os
import logging
import re
import struct
@ -93,47 +92,46 @@ class SongShowPlusImport(SongImport):
if not isinstance(self.import_source, list):
return
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:
return
self.ssp_verse_order_list = []
self.other_count = 0
self.other_list = {}
file_name = os.path.split(file)[1]
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_name), 0)
song_data = open(file, 'rb')
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name), 0)
with file_path.open('rb') as song_file:
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)
# The file ends with 4 NULL's
if block_key == 0:
break
next_block_starts, = struct.unpack("I", song_data.read(4))
next_block_starts += song_data.tell()
next_block_starts, = struct.unpack("I", song_file.read(4))
next_block_starts += song_file.tell()
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:
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
verse_name = self.decode(song_data.read(verse_name_length))
length_descriptor_size, = struct.unpack("B", song_data.read(1))
null, verse_name_length, = struct.unpack("BB", song_file.read(2))
verse_name = self.decode(song_file.read(verse_name_length))
length_descriptor_size, = struct.unpack("B", song_file.read(1))
log.debug('length_descriptor_size: %d' % length_descriptor_size)
# In the case of song_numbers the number is in the data from the
# current position to the next block starts
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')
continue
# Detect if/how long the length descriptor is
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:
length_descriptor = 1
elif length_descriptor_size == 9:
length_descriptor = 0
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)
data = song_data.read(length_descriptor)
data = song_file.read(length_descriptor)
log.debug(data)
if block_key == TITLE:
self.title = self.decode(data)
@ -179,11 +177,10 @@ class SongShowPlusImport(SongImport):
self.add_verse(self.decode(data), verse_tag)
else:
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
song_data.close()
if not self.finish():
self.log_error(file)
self.log_error(file_path)
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 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import os
import re
import logging
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib import strip_rtf
@ -60,12 +57,11 @@ class SundayPlusImport(SongImport):
def do_import(self):
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:
return
song_file = open(filename, 'rb')
with file_path.open('rb') as song_file:
self.do_import_file(song_file)
song_file.close()
def do_import_file(self, file):
"""

View File

@ -22,13 +22,12 @@
"""
The :mod:`lyrix` module provides the functionality for importing songs which are
exproted from Lyrix."""
import logging
import json
import os
import logging
import re
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.db import AuthorType
@ -50,11 +49,10 @@ class VideoPsalmImport(SongImport):
"""
Process the VideoPsalm file - pass in a file-like object, not a file path.
"""
self.import_source = Path(self.import_source)
self.set_defaults()
# Open SongBook file
song_file = open(self.import_source, 'rt', encoding='utf-8-sig')
try:
file_content = song_file.read()
file_content = self.import_source.read_text(encoding='utf-8-sig')
processed_content = ''
inside_quotes = False
# 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']
self.import_wizard.progress_bar.setMaximum(len(songs))
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:
self.song_book_name = songbook_name
if 'Text' in song:
@ -114,7 +112,7 @@ class VideoPsalmImport(SongImport):
if 'Theme' in song:
self.topics = song['Theme'].splitlines()
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:
self.add_comment(song['Memo1'])
if 'Memo2' in song:
@ -132,4 +130,5 @@ class VideoPsalmImport(SongImport):
if not self.finish():
self.log_error('Could not import {title}'.format(title=self.title))
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 logging
from openlp.core.common.path import Path
from openlp.core.common import translate
from openlp.plugins.songs.lib.importers.songimport import SongImport
@ -100,13 +101,13 @@ class WordsOfWorshipImport(SongImport):
"""
if isinstance(self.import_source, list):
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:
return
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':
self.log_error(source,
self.log_error(file_path,
translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "{text}" '
'header.').format(text='WoW File\\nSong Words'))
@ -116,7 +117,7 @@ class WordsOfWorshipImport(SongImport):
no_of_blocks = ord(song_data.read(1))
song_data.seek(66)
if song_data.read(16).decode() != 'CSongDoc::CBlock':
self.log_error(source,
self.log_error(file_path,
translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "{text}" '
'string.').format(text='CSongDoc::CBlock'))
@ -153,9 +154,7 @@ class WordsOfWorshipImport(SongImport):
copyright_length = ord(song_data.read(1))
if copyright_length:
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
file_name = os.path.split(source)[1]
# Get the song title
self.title = file_name.rpartition('.')[0]
song_data.close()
self.title = file_path.stem
if not self.finish():
self.log_error(source)
self.log_error(file_path)

View File

@ -28,7 +28,7 @@ import csv
import logging
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.importers.songimport import SongImport
@ -81,11 +81,8 @@ class WorshipAssistantImport(SongImport):
Receive a CSV file to import.
"""
# Get encoding
detect_file = open(self.import_source, 'rb')
detect_content = detect_file.read()
details = chardet.detect(detect_content)
detect_file.close()
songs_file = open(self.import_source, 'r', encoding=details['encoding'])
encoding = get_file_encoding(self.import_source)['encoding']
with self.import_source.open('r', encoding=encoding) as songs_file:
songs_reader = csv.DictReader(songs_file, escapechar='\\')
try:
records = list(songs_reader)
@ -185,4 +182,3 @@ class WorshipAssistantImport(SongImport):
self.log_error(translate('SongsPlugin.WorshipAssistantImport',
'Record {count:d}').format(count=index) +
(': "' + 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.
"""
# 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',
'DefaultStyle']
songs_reader = csv.DictReader(songs_file, field_names)

View File

@ -22,25 +22,24 @@
import logging
import os
import shutil
from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_, or_
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, \
check_item_selected, create_separated_list
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.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm
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.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.ui import SongStrings
log = logging.getLogger(__name__)
@ -88,11 +87,11 @@ class SongMediaItem(MediaManagerItem):
def _update_background_audio(self, song, item):
song.media_files = []
for i, bga in enumerate(item.background_audio):
dest_file = os.path.join(
str(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]))
shutil.copyfile(os.path.join(str(AppLocation.get_section_data_path('servicemanager')), bga), dest_file)
song.media_files.append(MediaFile.populate(weight=i, file_name=dest_file))
dest_path =\
AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga)[1]
check_directory_exists(dest_path.parent)
copyfile(AppLocation.get_section_data_path('servicemanager') / bga, dest_path)
song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path))
self.plugin.manager.save_object(song, True)
def add_end_header_bar(self):
@ -534,14 +533,13 @@ class SongMediaItem(MediaManagerItem):
'copy', 'For song cloning'))
# Copy audio files from the old to the new song
if len(old_song.media_files) > 0:
save_path = os.path.join(
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(new_song.id))
check_directory_exists(Path(save_path))
save_path = AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(new_song.id)
check_directory_exists(save_path)
for media_file in old_song.media_files:
new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name))
shutil.copyfile(media_file.file_name, new_media_file_name)
new_media_file_path = save_path / media_file.file_path.name
copyfile(media_file.file_path, new_media_file_path)
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.weight = media_file.weight
new_song.media_files.append(new_media_file)
@ -613,7 +611,7 @@ class SongMediaItem(MediaManagerItem):
# Add the audio file to the service item.
if song.media_files:
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
def generate_footer(self, item, song):

View File

@ -24,12 +24,10 @@ The :mod:`openlyricsexport` module provides the functionality for exporting song
format.
"""
import logging
import os
from lxml import etree
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
log = logging.getLogger(__name__)
@ -42,13 +40,16 @@ class OpenLyricsExport(RegistryProperties):
def __init__(self, parent, songs, save_path):
"""
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')
self.parent = parent
self.manager = parent.plugin.manager
self.songs = songs
self.save_path = save_path
check_directory_exists(Path(self.save_path))
check_directory_exists(self.save_path)
def do_export(self):
"""
@ -69,15 +70,15 @@ class OpenLyricsExport(RegistryProperties):
author=', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename)
# 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
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
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - len(self.save_path)],
extra=conflicts)
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - path_length], extra=conflicts)
# Pass a file object, because lxml does not cope with some special
# 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',
xml_declaration=True, pretty_print=True)
with (self.save_path / filename_with_ext).open('wb') as out_file:
tree.write(out_file, encoding='utf-8', xml_declaration=True, pretty_print=True)
return True

View File

@ -23,16 +23,20 @@
The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the Songs plugin
"""
import json
import logging
from sqlalchemy import Table, Column, ForeignKey, types
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.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__)
__version__ = 6
__version__ = 7
# 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')
# Finally, clean up our mess in people's databases
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.plugins.songs.lib.db import Song
log = logging.getLogger(__name__)
@ -58,9 +57,9 @@ def report_song_list():
report_file_path.with_suffix('.csv')
Registry().get('application').set_busy_cursor()
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')
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)
writer.writerow(headers)
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.db import Manager
from openlp.core.lib.ui import create_action
from openlp.plugins.songs import reporting
from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint
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.songstab import SongsTab
log = logging.getLogger(__name__)
__default_settings__ = {
'songs/db type': 'sqlite',
@ -340,7 +338,7 @@ class SongsPlugin(Plugin):
progress.forceShow()
self.application.process_events()
for db in song_dbs:
importer = OpenLPSongImport(self.manager, filename=db)
importer = OpenLPSongImport(self.manager, file_path=db)
importer.do_import(progress)
self.application.process_events()
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>
</qresource>
<qresource prefix="remotes">
<file>android_app_qr.png</file>
<file>ios_app_qr.png</file>
<file>app_qr.svg</file>
</qresource>
</RCC>

View File

@ -22,14 +22,12 @@
import os
import shutil
from tempfile import mkdtemp
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):
@ -54,6 +52,7 @@ class TestRemoteDeploy(TestCase):
Remote Deploy tests - test the dummy zip file is processed correctly
"""
# GIVEN: A new downloaded zip file
aa = TEST_PATH
zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
app_root = os.path.join(self.app_root, 'site.zip')
shutil.copyfile(zip_file, app_root)

View File

@ -79,5 +79,6 @@ class TestImageDBUpgrade(TestCase, TestMixin):
2: Path('/', 'test', 'dir', 'image2.jpg'),
3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
self.assertEqual(len(upgraded_results), 3)
for result in upgraded_results:
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
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
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_settings.return_value = mocked_returned_settings
# 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')))

View File

@ -21,9 +21,10 @@
"""
This module contains tests for the EasySlides song importer.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath(
@ -41,7 +42,7 @@ class TestEasySlidesFileImport(SongImportTestHelper):
"""
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.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')))

View File

@ -97,7 +97,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
_title_assignment_list = []
def __init__(self, manager):
EasyWorshipSongImport.__init__(self, manager, filenames=[])
EasyWorshipSongImport.__init__(self, manager, file_paths=[])
@property
def title(self):
@ -180,7 +180,7 @@ class TestEasyWorshipSongImport(TestCase):
mocked_manager = MagicMock()
# 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
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.
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.field_descriptions = TEST_FIELD_DESCS
# 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
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.field_descriptions = TEST_FIELD_DESCS
# 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'), \
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
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
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
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.encoding = TEST_DATA_ENCODING
importer.fields = TEST_FIELDS
importer.field_descriptions = TEST_FIELD_DESCS
@ -270,7 +270,7 @@ class TestEasyWorshipSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
mocked_manager = MagicMock()
mocked_memo_file = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.memo_file = mocked_memo_file
importer.encoding = TEST_DATA_ENCODING
@ -294,44 +294,25 @@ class TestEasyWorshipSongImport(TestCase):
else:
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):
"""
Test the :mod:`do_import` module produces an error when Songs.MB not found.
"""
# 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:
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', side_effect=[True, False]):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
importer.log_error = MagicMock()
mocked_os_path.isfile.side_effect = [True, False]
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
with patch.object(importer, 'log_error') as mocked_log_error:
# WHEN: do_import is supplied with an import source (Songs.MB missing)
importer.import_source = 'Songs.DB'
importer.do_import()
# 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 '
'in the same folder as the "Songs.DB" file.')
mocked_log_error.assert_any_call(importer.import_source,
'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):
"""
@ -339,18 +320,19 @@ class TestEasyWorshipSongImport(TestCase):
"""
# GIVEN: A mocked out SongImport class, os.path and 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:
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()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
mocked_os_path.isfile.return_value = True
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.import_source = 'Songs.DB'
# 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')
mocked_os_path.getsize.assert_any_call('Songs.DB')
mocked_stat.assert_called_once_with()
def test_do_import_memo_validty(self):
"""
@ -358,13 +340,12 @@ class TestEasyWorshipSongImport(TestCase):
"""
# 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, \
patch('builtins.open') as mocked_open, \
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') as mocked_open, \
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
mocked_os_path.isfile.return_value = True
mocked_os_path.getsize.return_value = 0x800
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.import_source = 'Songs.DB'
# 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"
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('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \
mocked_retrieve_windows_encoding:
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
mocked_os_path.isfile.return_value = True
mocked_os_path.getsize.return_value = 0x800
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
importer.import_source = 'Songs.DB'
# WHEN: Unpacking the code page

View File

@ -22,7 +22,8 @@
This module contains tests for the LyriX song importer.
"""
import os
from unittest.mock import patch
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
@ -41,9 +42,9 @@ class TestLyrixFileImport(SongImportTestHelper):
"""
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.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.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')))

View File

@ -51,7 +51,7 @@ class TestMediaShoutImport(TestCase):
"""
# GIVEN: A MediaShoutImport class
# WHEN: It is created
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
# THEN: It should not be None
self.assertIsNotNone(importer)
@ -62,7 +62,7 @@ class TestMediaShoutImport(TestCase):
Test that do_import exits early when unable to connect to the database
"""
# 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')
# WHEN: do_import is called
@ -89,7 +89,7 @@ class TestMediaShoutImport(TestCase):
group = GroupRecord('Hymns')
# 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.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]]
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', '')
# 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.fetchall.return_value = [song]
mocked_connection = MagicMock()
@ -158,7 +158,7 @@ class TestMediaShoutImport(TestCase):
play_order = PlayOrderRecord(0, 1, 1)
theme = ThemeRecord('Grace')
group = GroupRecord('Hymns')
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
# WHEN: A song is processed
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
@ -200,7 +200,7 @@ class TestMediaShoutImport(TestCase):
play_order = PlayOrderRecord(0, 1, 1)
theme = ThemeRecord('Grace')
group = GroupRecord('Hymns')
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
# WHEN: A song is processed
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \

View File

@ -48,7 +48,7 @@ class TestOpenLPImport(TestCase):
mocked_manager = MagicMock()
# 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
self.assertIsNotNone(importer, 'Import should not be none')
@ -61,7 +61,7 @@ class TestOpenLPImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OpenLPSongImport(mocked_manager, filenames=[])
importer = OpenLPSongImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True

View File

@ -22,14 +22,14 @@
"""
This module contains tests for the OpenLyrics song importer.
"""
import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
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
@ -43,13 +43,13 @@ class TestOpenLyricsExport(TestCase, TestMixin):
Create the registry
"""
Registry.create()
self.temp_folder = mkdtemp()
self.temp_folder = Path(mkdtemp())
def tearDown(self):
"""
Cleanup
"""
shutil.rmtree(self.temp_folder)
rmtree(self.temp_folder)
def test_export_same_filename(self):
"""
@ -73,7 +73,9 @@ class TestOpenLyricsExport(TestCase, TestMixin):
ol_export.do_export()
# THEN: The exporter should have created 2 files
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
'%s (%s).xml' % (song.title, author.display_name))))
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
'%s (%s)-1.xml' % (song.title, author.display_name))))
self.assertTrue((self.temp_folder /
'{title} ({display_name}).xml'.format(
title=song.title, display_name=author.display_name)).exists())
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 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.songimport import SongImport
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
from openlp.core.common import Registry, Settings
from tests.helpers.testmixin import TestMixin
@ -109,7 +110,7 @@ class TestOpenLyricsImport(TestCase, TestMixin):
mocked_manager = MagicMock()
# 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
self.assertIsInstance(importer, SongImport)
@ -122,13 +123,13 @@ class TestOpenLyricsImport(TestCase, TestMixin):
for song_file in SONG_TEST_DATA:
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OpenLyricsImport(mocked_manager, filenames=[])
importer = OpenLyricsImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard
importer.open_lyrics = MagicMock()
importer.open_lyrics.xml_to_song = MagicMock()
# 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()
# THEN: The xml_to_song() method should have been called

View File

@ -54,7 +54,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
mocked_manager = MagicMock()
# 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
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
mocked_manager = MagicMock()
importer = OpenOfficeImport(mocked_manager, filenames=[])
importer = OpenOfficeImport(mocked_manager, file_paths=[])
importer.document = MagicMock()
importer.document.close = MagicMock(side_effect=Exception())

View File

@ -27,6 +27,7 @@ from unittest import TestCase
from unittest.mock import patch, MagicMock
from openlp.core.common import Registry
from openlp.core.common.path import Path
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
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_settings.return_value = mocked_returned_settings
# 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.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.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.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.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')))
@ -83,7 +84,7 @@ class TestOpenSongImport(TestCase):
mocked_manager = MagicMock()
# 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
self.assertIsNotNone(importer, 'Import should not be none')
@ -96,7 +97,7 @@ class TestOpenSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OpenSongImport(mocked_manager, filenames=[])
importer = OpenSongImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
@ -117,7 +118,7 @@ class TestOpenSongImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OpenSongImport(mocked_manager, filenames=[])
importer = OpenSongImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True

View File

@ -86,7 +86,7 @@ class TestOpsProSongImport(TestCase):
mocked_manager = MagicMock()
# 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
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
mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[])
importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock()
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
mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[])
importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock()
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
mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[])
importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock()
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
mocked_manager = MagicMock()
importer = OPSProImport(mocked_manager, filenames=[])
importer = OPSProImport(mocked_manager, file_paths=[])
importer.finish = MagicMock()
song, lyrics = _build_data('amazing grace3.txt', True)

View File

@ -26,8 +26,9 @@ ProPresenter song files into the current installation database.
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
from openlp.core.common import Registry
TEST_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs'))
@ -44,7 +45,7 @@ class TestPowerPraiseFileImport(SongImportTestHelper):
"""
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.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')))

View File

@ -22,9 +22,10 @@
"""
This module contains tests for the PresentationManager song importer.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath(
@ -42,7 +43,7 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
"""
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.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')))

View File

@ -23,9 +23,10 @@
The :mod:`propresenterimport` module provides the functionality for importing
ProPresenter song files into the current installation database.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath(
@ -43,19 +44,19 @@ class TestProPresenterFileImport(SongImportTestHelper):
"""
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')))
def test_pro5_song_import(self):
"""
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')))
def test_pro6_song_import(self):
"""
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')))

View File

@ -26,8 +26,9 @@ import os
from unittest import TestCase
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.path import Path
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
from tests.helpers.songfileimport import SongImportTestHelper
@ -51,18 +52,18 @@ class TestSongBeamerFileImport(SongImportTestHelper):
mocked_returned_settings = MagicMock()
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
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.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.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')))
def test_cp1252_encoded_file(self):
"""
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')))
@ -78,7 +79,7 @@ class TestSongBeamerImport(TestCase):
self.song_import_patcher = patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport')
self.song_import_patcher.start()
mocked_manager = MagicMock()
self.importer = SongBeamerImport(mocked_manager, filenames=[])
self.importer = SongBeamerImport(mocked_manager, file_paths=[])
def tearDown(self):
"""
@ -95,7 +96,7 @@ class TestSongBeamerImport(TestCase):
mocked_manager = MagicMock()
# 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
self.assertIsNotNone(importer, 'Import should not be none')

View File

@ -23,9 +23,10 @@
The :mod:`songproimport` module provides the functionality for importing
SongPro song files into the current installation database.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath(
@ -43,5 +44,5 @@ class TestSongProFileImport(SongImportTestHelper):
"""
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')))

View File

@ -31,6 +31,7 @@ from urllib.error import URLError
from PyQt5 import QtWidgets
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.lib import Song
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):
self.importer_class_name = 'CCLIFileImport'
self.importer_module_name = 'cclifile'
super(TestSongSelectFileImport, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def test_song_import(self):
"""
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.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')))

View File

@ -26,6 +26,7 @@ import os
from unittest import TestCase
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.importers.songshowplus import SongShowPlusImport
@ -46,13 +47,13 @@ class TestSongShowPlusFileImport(SongImportTestHelper):
"""
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.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.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.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')))
@ -69,7 +70,7 @@ class TestSongShowPlusImport(TestCase):
mocked_manager = MagicMock()
# 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
self.assertIsNotNone(importer, 'Import should not be none')
@ -82,7 +83,7 @@ class TestSongShowPlusImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[])
importer = SongShowPlusImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
@ -103,7 +104,7 @@ class TestSongShowPlusImport(TestCase):
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[])
importer = SongShowPlusImport(mocked_manager, file_paths=[])
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
@ -123,7 +124,7 @@ class TestSongShowPlusImport(TestCase):
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
mocked_manager = MagicMock()
importer = SongShowPlusImport(mocked_manager, filenames=[])
importer = SongShowPlusImport(mocked_manager, file_paths=[])
# WHEN: Supplied with the following arguments replicating verses being added
test_values = [
@ -151,7 +152,7 @@ class TestSongShowPlusImport(TestCase):
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
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
test_values = [

View File

@ -24,6 +24,8 @@ This module contains tests for the SundayPlus song importer.
import os
from unittest.mock import patch
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath(
@ -44,5 +46,5 @@ class TestSundayPlusFileImport(SongImportTestHelper):
with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
mocked_retrieve_windows_encoding:
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')))

View File

@ -21,9 +21,10 @@
"""
This module contains tests for the VideoPsalm song importer.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
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_settings.return_value = mocked_returned_settings
# 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.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')))

View File

@ -22,9 +22,10 @@
"""
This module contains tests for the Words of Worship song importer.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
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
"""
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.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.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,
'Holy Holy Holy Lord God Almighty.json')))

View File

@ -23,9 +23,10 @@
The :mod:`worshipassistantimport` module provides the functionality for importing
WorshipAssistant song files into the current installation database.
"""
import os
from openlp.core.common.path import Path
from tests.helpers.songfileimport import SongImportTestHelper
TEST_PATH = os.path.abspath(
@ -43,9 +44,9 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
"""
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.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.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')))

View File

@ -55,7 +55,7 @@ if CAN_RUN_TESTS:
_title_assignment_list = []
def __init__(self, manager):
WorshipCenterProImport.__init__(self, manager, filenames=[])
WorshipCenterProImport.__init__(self, manager, file_paths=[])
@property
def title(self):
@ -153,7 +153,7 @@ class TestWorshipCenterProSongImport(TestCase):
mocked_manager = MagicMock()
# 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
self.assertIsNotNone(importer, 'Import should not be none')
@ -170,7 +170,7 @@ class TestWorshipCenterProSongImport(TestCase):
mocked_manager = MagicMock()
mocked_log_error = MagicMock()
mocked_translate.return_value = 'Translated Text'
importer = WorshipCenterProImport(mocked_manager, filenames=[])
importer = WorshipCenterProImport(mocked_manager, file_paths=[])
importer.log_error = mocked_log_error
importer.import_source = 'import_source'
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]

View File

@ -26,9 +26,10 @@ import os
from unittest import TestCase
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.songimport import SongImport
from openlp.core.common import Registry
from tests.helpers.songfileimport import SongImportTestHelper
@ -55,7 +56,7 @@ class TestZionWorxImport(TestCase):
mocked_manager = MagicMock()
# 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
self.assertIsInstance(importer, SongImport)
@ -72,5 +73,5 @@ class TestZionWorxFileImport(SongImportTestHelper):
"""
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')))

View File

@ -89,7 +89,7 @@ class SongImportTestHelper(TestCase):
"""
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.stop_import_flag = False
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('songusage', plugin_names, 'There should be a "songusage" plugin')
self.assertIn('alerts', plugin_names, 'There should be a "alerts" plugin')
self.assertIn('remotes', plugin_names, 'There should be a "remotes" plugin')