forked from openlp/openlp
merge trunk
This commit is contained in:
commit
ea0dd6f7aa
@ -63,7 +63,7 @@ def download_and_check(callback=None):
|
|||||||
sha256, version = download_sha256()
|
sha256, version = download_sha256()
|
||||||
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
|
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
|
||||||
callback.setRange(0, file_size)
|
callback.setRange(0, file_size)
|
||||||
if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
|
if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip',
|
||||||
AppLocation.get_section_data_path('remotes') / 'site.zip',
|
AppLocation.get_section_data_path('remotes') / 'site.zip',
|
||||||
sha256=sha256):
|
sha256=sha256):
|
||||||
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
|
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
|
@ -21,18 +21,13 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from openlp.core.api.http.endpoint import Endpoint
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
|
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
|
||||||
from openlp.core.common import AppLocation
|
|
||||||
|
|
||||||
|
|
||||||
static_dir = os.path.join(str(AppLocation.get_section_data_path('remotes')))
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
remote_endpoint = Endpoint('remote', template_dir=static_dir, static_dir=static_dir)
|
remote_endpoint = Endpoint('remote', template_dir='remotes', static_dir='remotes')
|
||||||
|
|
||||||
|
|
||||||
@remote_endpoint.route('{view}')
|
@remote_endpoint.route('{view}')
|
@ -23,7 +23,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from openlp.core.api.http.endpoint import Endpoint
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
from openlp.core.api.http import register_endpoint, requires_auth
|
from openlp.core.api.http import requires_auth
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,17 +26,24 @@ with OpenLP. It uses JSON to communicate with the remotes.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
from openlp.core.api.http import register_endpoint
|
from openlp.core.api.http import register_endpoint
|
||||||
from openlp.core.api.http import application
|
from openlp.core.api.http import application
|
||||||
from openlp.core.common import RegistryMixin, RegistryProperties, OpenLPMixin, Settings, Registry
|
from openlp.core.common import AppLocation, RegistryMixin, RegistryProperties, OpenLPMixin, \
|
||||||
|
Settings, Registry, UiStrings, check_directory_exists
|
||||||
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
|
from openlp.core.api.deploy import download_and_check, download_sha256
|
||||||
from openlp.core.api.poll import Poller
|
from openlp.core.api.poll import Poller
|
||||||
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
|
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
|
||||||
from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
|
from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
|
||||||
from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
|
from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
|
||||||
|
from openlp.core.api.endpoint.remote import remote_endpoint
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -59,6 +66,7 @@ class HttpWorker(QtCore.QObject):
|
|||||||
"""
|
"""
|
||||||
address = Settings().value('api/ip address')
|
address = Settings().value('api/ip address')
|
||||||
port = Settings().value('api/port')
|
port = Settings().value('api/port')
|
||||||
|
Registry().execute('get_website_version')
|
||||||
serve(application, host=address, port=port)
|
serve(application, host=address, port=port)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@ -79,11 +87,15 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
|
|||||||
self.worker.moveToThread(self.thread)
|
self.worker.moveToThread(self.thread)
|
||||||
self.thread.started.connect(self.worker.run)
|
self.thread.started.connect(self.worker.run)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
Registry().register_function('download_website', self.first_time)
|
||||||
|
Registry().register_function('get_website_version', self.website_version)
|
||||||
|
Registry().set_flag('website_version', '0.0')
|
||||||
|
|
||||||
def bootstrap_post_set_up(self):
|
def bootstrap_post_set_up(self):
|
||||||
"""
|
"""
|
||||||
Register the poll return service and start the servers.
|
Register the poll return service and start the servers.
|
||||||
"""
|
"""
|
||||||
|
self.initialise()
|
||||||
self.poller = Poller()
|
self.poller = Poller()
|
||||||
Registry().register('poller', self.poller)
|
Registry().register('poller', self.poller)
|
||||||
application.initialise()
|
application.initialise()
|
||||||
@ -95,3 +107,79 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
|
|||||||
register_endpoint(main_endpoint)
|
register_endpoint(main_endpoint)
|
||||||
register_endpoint(service_endpoint)
|
register_endpoint(service_endpoint)
|
||||||
register_endpoint(api_service_endpoint)
|
register_endpoint(api_service_endpoint)
|
||||||
|
register_endpoint(remote_endpoint)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def initialise():
|
||||||
|
"""
|
||||||
|
Create the internal file structure if it does not exist
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'assets')
|
||||||
|
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'images')
|
||||||
|
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static')
|
||||||
|
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static' / 'index')
|
||||||
|
check_directory_exists(AppLocation.get_section_data_path('remotes') / 'templates')
|
||||||
|
|
||||||
|
def first_time(self):
|
||||||
|
"""
|
||||||
|
Import web site code if active
|
||||||
|
"""
|
||||||
|
self.application.process_events()
|
||||||
|
progress = DownloadProgressDialog(self)
|
||||||
|
progress.forceShow()
|
||||||
|
self.application.process_events()
|
||||||
|
time.sleep(1)
|
||||||
|
download_and_check(progress)
|
||||||
|
self.application.process_events()
|
||||||
|
time.sleep(1)
|
||||||
|
progress.close()
|
||||||
|
self.application.process_events()
|
||||||
|
Settings().setValue('remotes/download version', self.version)
|
||||||
|
|
||||||
|
def website_version(self):
|
||||||
|
"""
|
||||||
|
Download and save the website version and sha256
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
sha256, self.version = download_sha256()
|
||||||
|
Registry().set_flag('website_sha256', sha256)
|
||||||
|
Registry().set_flag('website_version', self.version)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadProgressDialog(QtWidgets.QProgressDialog):
|
||||||
|
"""
|
||||||
|
Local class to handle download display based and supporting httputils:get_web_page
|
||||||
|
"""
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(DownloadProgressDialog, self).__init__(parent.main_window)
|
||||||
|
self.parent = parent
|
||||||
|
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||||
|
self.setWindowTitle(translate('RemotePlugin', 'Importing Website'))
|
||||||
|
self.setLabelText(UiStrings().StartingImport)
|
||||||
|
self.setCancelButton(None)
|
||||||
|
self.setRange(0, 1)
|
||||||
|
self.setMinimumDuration(0)
|
||||||
|
self.was_cancelled = False
|
||||||
|
self.previous_size = 0
|
||||||
|
|
||||||
|
def _download_progress(self, count, block_size):
|
||||||
|
"""
|
||||||
|
Calculate and display the download progress.
|
||||||
|
"""
|
||||||
|
increment = (count * block_size) - self.previous_size
|
||||||
|
self._increment_progress_bar(None, increment)
|
||||||
|
self.previous_size = count * block_size
|
||||||
|
|
||||||
|
def _increment_progress_bar(self, status_text, increment=1):
|
||||||
|
"""
|
||||||
|
Update the wizard progress page.
|
||||||
|
|
||||||
|
:param status_text: Current status information to display.
|
||||||
|
:param increment: The value to increment the progress bar by.
|
||||||
|
"""
|
||||||
|
if status_text:
|
||||||
|
self.setText(status_text)
|
||||||
|
if increment > 0:
|
||||||
|
self.setValue(self.value() + increment)
|
||||||
|
self.parent.application.process_events()
|
||||||
|
@ -129,36 +129,21 @@ class ApiTab(SettingsTab):
|
|||||||
self.master_version_value.setObjectName('master_version_value')
|
self.master_version_value.setObjectName('master_version_value')
|
||||||
self.update_site_layout.addRow(self.master_version_label, self.master_version_value)
|
self.update_site_layout.addRow(self.master_version_label, self.master_version_value)
|
||||||
self.left_layout.addWidget(self.update_site_group_box)
|
self.left_layout.addWidget(self.update_site_group_box)
|
||||||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
self.app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
self.app_group_box.setObjectName('app_group_box')
|
||||||
self.right_layout.addWidget(self.android_app_group_box)
|
self.right_layout.addWidget(self.app_group_box)
|
||||||
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
|
self.app_qr_layout = QtWidgets.QVBoxLayout(self.app_group_box)
|
||||||
self.android_qr_layout.setObjectName('android_qr_layout')
|
self.app_qr_layout.setObjectName('app_qr_layout')
|
||||||
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
|
self.app_qr_code_label = QtWidgets.QLabel(self.app_group_box)
|
||||||
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
|
self.app_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/app_qr.svg'))
|
||||||
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
self.app_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.android_qr_code_label.setObjectName('android_qr_code_label')
|
self.app_qr_code_label.setObjectName('app_qr_code_label')
|
||||||
self.android_qr_layout.addWidget(self.android_qr_code_label)
|
self.app_qr_layout.addWidget(self.app_qr_code_label)
|
||||||
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
|
self.app_qr_description_label = QtWidgets.QLabel(self.app_group_box)
|
||||||
self.android_qr_description_label.setObjectName('android_qr_description_label')
|
self.app_qr_description_label.setObjectName('app_qr_description_label')
|
||||||
self.android_qr_description_label.setOpenExternalLinks(True)
|
self.app_qr_description_label.setOpenExternalLinks(True)
|
||||||
self.android_qr_description_label.setWordWrap(True)
|
self.app_qr_description_label.setWordWrap(True)
|
||||||
self.android_qr_layout.addWidget(self.android_qr_description_label)
|
self.app_qr_layout.addWidget(self.app_qr_description_label)
|
||||||
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
|
||||||
self.ios_app_group_box.setObjectName('ios_app_group_box')
|
|
||||||
self.right_layout.addWidget(self.ios_app_group_box)
|
|
||||||
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
|
|
||||||
self.ios_qr_layout.setObjectName('ios_qr_layout')
|
|
||||||
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
|
|
||||||
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
|
|
||||||
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
|
|
||||||
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
|
|
||||||
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
|
|
||||||
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
|
|
||||||
self.ios_qr_description_label.setOpenExternalLinks(True)
|
|
||||||
self.ios_qr_description_label.setWordWrap(True)
|
|
||||||
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
|
|
||||||
self.left_layout.addStretch()
|
self.left_layout.addStretch()
|
||||||
self.right_layout.addStretch()
|
self.right_layout.addStretch()
|
||||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||||
@ -195,16 +180,11 @@ class ApiTab(SettingsTab):
|
|||||||
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
||||||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||||
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
|
self.app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Remote App'))
|
||||||
self.android_qr_description_label.setText(
|
self.app_qr_description_label.setText(
|
||||||
translate('RemotePlugin.RemoteTab',
|
translate('RemotePlugin.RemoteTab',
|
||||||
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
|
'Scan the QR code or click <a href="{qr}">download</a> to download an app for your mobile device'
|
||||||
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
|
).format(qr='https://openlp.org/#mobile-app-downloads'))
|
||||||
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
|
|
||||||
self.ios_qr_description_label.setText(
|
|
||||||
translate('RemotePlugin.RemoteTab',
|
|
||||||
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
|
||||||
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
|
||||||
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
||||||
self.aa = UiStrings()
|
self.aa = UiStrings()
|
||||||
self.update_site_group_box.setTitle(UiStrings().WebDownloadText)
|
self.update_site_group_box.setTitle(UiStrings().WebDownloadText)
|
||||||
@ -222,6 +202,8 @@ class ApiTab(SettingsTab):
|
|||||||
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
||||||
http_url_temp = http_url + 'stage'
|
http_url_temp = http_url + 'stage'
|
||||||
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||||
|
http_url_temp = http_url + 'chords'
|
||||||
|
self.chords_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||||
http_url_temp = http_url + 'main'
|
http_url_temp = http_url + 'main'
|
||||||
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||||
|
|
||||||
|
@ -178,6 +178,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'images/background color': '#000000',
|
'images/background color': '#000000',
|
||||||
'media/players': 'system,webkit',
|
'media/players': 'system,webkit',
|
||||||
'media/override player': QtCore.Qt.Unchecked,
|
'media/override player': QtCore.Qt.Unchecked,
|
||||||
|
'remotes/download version': '0.0',
|
||||||
'players/background color': '#000000',
|
'players/background color': '#000000',
|
||||||
'servicemanager/last directory': None,
|
'servicemanager/last directory': None,
|
||||||
'servicemanager/last file': None,
|
'servicemanager/last file': None,
|
||||||
|
@ -426,10 +426,10 @@ class ServiceItem(RegistryProperties):
|
|||||||
self.background_audio = []
|
self.background_audio = []
|
||||||
for filename in header['background_audio']:
|
for filename in header['background_audio']:
|
||||||
# Give them real file paths.
|
# Give them real file paths.
|
||||||
filepath = filename
|
filepath = str(filename)
|
||||||
if path:
|
if path:
|
||||||
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename
|
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename
|
||||||
filepath = os.path.join(path, ntpath.basename(filename))
|
filepath = os.path.join(path, ntpath.basename(str(filename)))
|
||||||
self.background_audio.append(filepath)
|
self.background_audio.append(filepath)
|
||||||
self.theme_overwritten = header.get('theme_overwritten', False)
|
self.theme_overwritten = header.get('theme_overwritten', False)
|
||||||
if self.service_item_type == ServiceItemType.Text:
|
if self.service_item_type == ServiceItemType.Text:
|
||||||
|
@ -684,7 +684,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
|||||||
if not isinstance(file_names, list):
|
if not isinstance(file_names, list):
|
||||||
file_names = [file_names]
|
file_names = [file_names]
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file_name)))
|
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(str(file_name))))
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
"""
|
"""
|
||||||
|
@ -561,7 +561,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
service_item = item['service_item'].get_service_repr(self._save_lite)
|
service_item = item['service_item'].get_service_repr(self._save_lite)
|
||||||
if service_item['header']['background_audio']:
|
if service_item['header']['background_audio']:
|
||||||
for i, file_name in enumerate(service_item['header']['background_audio']):
|
for i, file_name in enumerate(service_item['header']['background_audio']):
|
||||||
new_file = os.path.join('audio', item['service_item'].unique_identifier, file_name)
|
new_file = os.path.join('audio', item['service_item'].unique_identifier, str(file_name))
|
||||||
audio_files.append((file_name, new_file))
|
audio_files.append((file_name, new_file))
|
||||||
service_item['header']['background_audio'][i] = new_file
|
service_item['header']['background_audio'][i] = new_file
|
||||||
# Add the service item to the service.
|
# Add the service item to the service.
|
||||||
@ -586,6 +586,8 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
for write_from in write_list:
|
for write_from in write_list:
|
||||||
zip_file.write(write_from, write_from)
|
zip_file.write(write_from, write_from)
|
||||||
for audio_from, audio_to in audio_files:
|
for audio_from, audio_to in audio_files:
|
||||||
|
audio_from = str(audio_from)
|
||||||
|
audio_to = str(audio_to)
|
||||||
if audio_from.startswith('audio'):
|
if audio_from.startswith('audio'):
|
||||||
# When items are saved, they get new unique_identifier. Let's copy the file to the new location.
|
# When items are saved, they get new unique_identifier. Let's copy the file to the new location.
|
||||||
# Unused files can be ignored, OpenLP automatically cleans up the service manager dir on exit.
|
# Unused files can be ignored, OpenLP automatically cleans up the service manager dir on exit.
|
||||||
|
@ -868,7 +868,7 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.track_menu.clear()
|
self.track_menu.clear()
|
||||||
for counter in range(len(self.service_item.background_audio)):
|
for counter in range(len(self.service_item.background_audio)):
|
||||||
action = self.track_menu.addAction(
|
action = self.track_menu.addAction(
|
||||||
os.path.basename(self.service_item.background_audio[counter]))
|
os.path.basename(str(self.service_item.background_audio[counter])))
|
||||||
action.setData(counter)
|
action.setData(counter)
|
||||||
action.triggered.connect(self.on_track_triggered)
|
action.triggered.connect(self.on_track_triggered)
|
||||||
self.display.audio_player.repeat = \
|
self.display.audio_player.repeat = \
|
||||||
|
@ -62,7 +62,7 @@ def bibles_service(request):
|
|||||||
|
|
||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
service(request, 'bibles', log)
|
return service(request, 'bibles', log)
|
||||||
|
|
||||||
|
|
||||||
@api_bibles_endpoint.route('bibles/search')
|
@api_bibles_endpoint.route('bibles/search')
|
||||||
@ -95,6 +95,6 @@ def bibles_service_api(request):
|
|||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
search(request, 'bibles', log)
|
return search(request, 'bibles', log)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
return {'results': {'items': []}}
|
return {'results': {'items': []}}
|
||||||
|
@ -62,7 +62,7 @@ def custom_service(request):
|
|||||||
|
|
||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
service(request, 'custom', log)
|
return service(request, 'custom', log)
|
||||||
|
|
||||||
|
|
||||||
@api_custom_endpoint.route('custom/search')
|
@api_custom_endpoint.route('custom/search')
|
||||||
@ -95,6 +95,6 @@ def custom_service_api(request):
|
|||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
search(request, 'custom', log)
|
return search(request, 'custom', log)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
return {'results': {'items': []}}
|
return {'results': {'items': []}}
|
||||||
|
@ -75,7 +75,7 @@ def images_service(request):
|
|||||||
|
|
||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
service(request, 'images', log)
|
return service(request, 'images', log)
|
||||||
|
|
||||||
|
|
||||||
@api_images_endpoint.route('images/search')
|
@api_images_endpoint.route('images/search')
|
||||||
@ -108,6 +108,6 @@ def images_service_api(request):
|
|||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
search(request, 'images', log)
|
return search(request, 'images', log)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
return {'results': {'items': []}}
|
return {'results': {'items': []}}
|
||||||
|
@ -62,7 +62,7 @@ def media_service(request):
|
|||||||
|
|
||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
service(request, 'media', log)
|
return service(request, 'media', log)
|
||||||
|
|
||||||
|
|
||||||
@api_media_endpoint.route('media/search')
|
@api_media_endpoint.route('media/search')
|
||||||
@ -95,6 +95,6 @@ def media_service_api(request):
|
|||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
search(request, 'media', log)
|
return search(request, 'media', log)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
return {'results': {'items': []}}
|
return {'results': {'items': []}}
|
||||||
|
@ -76,7 +76,7 @@ def presentations_service(request):
|
|||||||
|
|
||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
service(request, 'presentations', log)
|
return service(request, 'presentations', log)
|
||||||
|
|
||||||
|
|
||||||
@api_presentations_endpoint.route('presentations/search')
|
@api_presentations_endpoint.route('presentations/search')
|
||||||
@ -109,6 +109,6 @@ def presentations_service_api(request):
|
|||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
search(request, 'presentations', log)
|
return search(request, 'presentations', log)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
return {'results': {'items': []}}
|
return {'results': {'items': []}}
|
||||||
|
@ -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 #
|
|
||||||
###############################################################################
|
|
@ -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()
|
|
@ -62,7 +62,7 @@ def songs_service(request):
|
|||||||
|
|
||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
service(request, 'songs', log)
|
return service(request, 'songs', log)
|
||||||
|
|
||||||
|
|
||||||
@api_songs_endpoint.route('songs/search')
|
@api_songs_endpoint.route('songs/search')
|
||||||
@ -95,6 +95,6 @@ def songs_service_api(request):
|
|||||||
:param request: The http request object.
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
search(request, 'songs', log)
|
return service(request, 'songs', log)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
return {'results': {'items': []}}
|
return {'results': {'items': []}}
|
||||||
|
@ -23,27 +23,25 @@
|
|||||||
The :mod:`~openlp.plugins.songs.forms.editsongform` module contains the form
|
The :mod:`~openlp.plugins.songs.forms.editsongform` module contains the form
|
||||||
used to edit songs.
|
used to edit songs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import re
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
|
||||||
from openlp.core.common.path import Path, path_to_str
|
from openlp.core.common.languagemanager import get_natural_key
|
||||||
|
from openlp.core.common.path import copyfile
|
||||||
from openlp.core.lib import PluginStatus, MediaType, create_separated_list
|
from openlp.core.lib import PluginStatus, MediaType, create_separated_list
|
||||||
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
||||||
from openlp.core.ui.lib.filedialog import FileDialog
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.common.languagemanager import get_natural_key
|
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
|
||||||
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
|
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
|
||||||
from openlp.plugins.songs.lib.openlyricsxml import SongXML
|
|
||||||
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
||||||
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
||||||
from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm
|
from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm
|
||||||
|
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||||
|
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
|
||||||
|
from openlp.plugins.songs.lib.openlyricsxml import SongXML
|
||||||
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -545,9 +543,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
songbook_entry.entry)
|
songbook_entry.entry)
|
||||||
self.audio_list_widget.clear()
|
self.audio_list_widget.clear()
|
||||||
for media in self.song.media_files:
|
for media in self.song.media_files:
|
||||||
media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1])
|
item = QtWidgets.QListWidgetItem(media.file_path.name)
|
||||||
media_file.setData(QtCore.Qt.UserRole, media.file_name)
|
item.setData(QtCore.Qt.UserRole, media.file_path)
|
||||||
self.audio_list_widget.addItem(media_file)
|
self.audio_list_widget.addItem(item)
|
||||||
self.title_edit.setFocus()
|
self.title_edit.setFocus()
|
||||||
# Hide or show the preview button.
|
# Hide or show the preview button.
|
||||||
self.preview_button.setVisible(preview)
|
self.preview_button.setVisible(preview)
|
||||||
@ -927,12 +925,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
Loads file(s) from the filesystem.
|
Loads file(s) from the filesystem.
|
||||||
"""
|
"""
|
||||||
filters = '{text} (*)'.format(text=UiStrings().AllFiles)
|
filters = '{text} (*)'.format(text=UiStrings().AllFiles)
|
||||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
file_paths, filter_used = FileDialog.getOpenFileNames(
|
||||||
self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters)
|
parent=self, caption=translate('SongsPlugin.EditSongForm', 'Open File(s)'), filter=filters)
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
filename = path_to_str(file_path)
|
item = QtWidgets.QListWidgetItem(file_path.name)
|
||||||
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
|
item.setData(QtCore.Qt.UserRole, file_path)
|
||||||
item.setData(QtCore.Qt.UserRole, filename)
|
|
||||||
self.audio_list_widget.addItem(item)
|
self.audio_list_widget.addItem(item)
|
||||||
|
|
||||||
def on_audio_add_from_media_button_clicked(self):
|
def on_audio_add_from_media_button_clicked(self):
|
||||||
@ -940,9 +937,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
Loads file(s) from the media plugin.
|
Loads file(s) from the media plugin.
|
||||||
"""
|
"""
|
||||||
if self.media_form.exec():
|
if self.media_form.exec():
|
||||||
for filename in self.media_form.get_selected_files():
|
for file_path in self.media_form.get_selected_files():
|
||||||
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
|
item = QtWidgets.QListWidgetItem(file_path.name)
|
||||||
item.setData(QtCore.Qt.UserRole, filename)
|
item.setData(QtCore.Qt.UserRole, file_path)
|
||||||
self.audio_list_widget.addItem(item)
|
self.audio_list_widget.addItem(item)
|
||||||
|
|
||||||
def on_audio_remove_button_clicked(self):
|
def on_audio_remove_button_clicked(self):
|
||||||
@ -1066,34 +1063,33 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
# Save the song here because we need a valid id for the audio files.
|
# Save the song here because we need a valid id for the audio files.
|
||||||
clean_song(self.manager, self.song)
|
clean_song(self.manager, self.song)
|
||||||
self.manager.save_object(self.song)
|
self.manager.save_object(self.song)
|
||||||
audio_files = [a.file_name for a in self.song.media_files]
|
audio_paths = [a.file_path for a in self.song.media_files]
|
||||||
log.debug(audio_files)
|
log.debug(audio_paths)
|
||||||
save_path = os.path.join(str(AppLocation.get_section_data_path(self.media_item.plugin.name)), 'audio',
|
save_path = AppLocation.get_section_data_path(self.media_item.plugin.name) / 'audio' / str(self.song.id)
|
||||||
str(self.song.id))
|
check_directory_exists(save_path)
|
||||||
check_directory_exists(Path(save_path))
|
|
||||||
self.song.media_files = []
|
self.song.media_files = []
|
||||||
files = []
|
file_paths = []
|
||||||
for row in range(self.audio_list_widget.count()):
|
for row in range(self.audio_list_widget.count()):
|
||||||
item = self.audio_list_widget.item(row)
|
item = self.audio_list_widget.item(row)
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
file_path = item.data(QtCore.Qt.UserRole)
|
||||||
if not filename.startswith(save_path):
|
if save_path not in file_path.parents:
|
||||||
old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
|
old_file_path, file_path = file_path, save_path / file_path.name
|
||||||
shutil.copyfile(old_file, filename)
|
copyfile(old_file_path, file_path)
|
||||||
files.append(filename)
|
file_paths.append(file_path)
|
||||||
media_file = MediaFile()
|
media_file = MediaFile()
|
||||||
media_file.file_name = filename
|
media_file.file_path = file_path
|
||||||
media_file.type = 'audio'
|
media_file.type = 'audio'
|
||||||
media_file.weight = row
|
media_file.weight = row
|
||||||
self.song.media_files.append(media_file)
|
self.song.media_files.append(media_file)
|
||||||
for audio in audio_files:
|
for audio_path in audio_paths:
|
||||||
if audio not in files:
|
if audio_path not in file_paths:
|
||||||
try:
|
try:
|
||||||
os.remove(audio)
|
audio_path.unlink()
|
||||||
except:
|
except:
|
||||||
log.exception('Could not remove file: {audio}'.format(audio=audio))
|
log.exception('Could not remove file: {audio}'.format(audio=audio_path))
|
||||||
if not files:
|
if not file_paths:
|
||||||
try:
|
try:
|
||||||
os.rmdir(save_path)
|
save_path.rmdir()
|
||||||
except OSError:
|
except OSError:
|
||||||
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
||||||
clean_song(self.manager, self.song)
|
clean_song(self.manager, self.song)
|
||||||
|
@ -41,12 +41,19 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
|
|||||||
QtCore.Qt.WindowCloseButtonHint)
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def populate_files(self, files):
|
def populate_files(self, file_paths):
|
||||||
|
"""
|
||||||
|
:param list[openlp.core.common.path.Path] file_paths:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
self.file_list_widget.clear()
|
self.file_list_widget.clear()
|
||||||
for file in files:
|
for file_path in file_paths:
|
||||||
item = QtWidgets.QListWidgetItem(os.path.split(file)[1])
|
item = QtWidgets.QListWidgetItem(file_path.name)
|
||||||
item.setData(QtCore.Qt.UserRole, file)
|
item.setData(QtCore.Qt.UserRole, file_path)
|
||||||
self.file_list_widget.addItem(item)
|
self.file_list_widget.addItem(item)
|
||||||
|
|
||||||
def get_selected_files(self):
|
def get_selected_files(self):
|
||||||
|
"""
|
||||||
|
:rtype: list[openlp.core.common.path.Path]
|
||||||
|
"""
|
||||||
return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()]
|
return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()]
|
||||||
|
@ -27,9 +27,10 @@ import logging
|
|||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, UiStrings, translate
|
from openlp.core.common import Registry, Settings, UiStrings, translate
|
||||||
from openlp.core.lib import create_separated_list, build_icon
|
from openlp.core.lib import create_separated_list
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
|
from openlp.core.ui.lib import PathEdit, PathType
|
||||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||||
from openlp.plugins.songs.lib.db import Song
|
from openlp.plugins.songs.lib.db import Song
|
||||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||||
@ -76,7 +77,6 @@ class SongExportForm(OpenLPWizard):
|
|||||||
self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
|
self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
|
||||||
self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
|
self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
|
||||||
self.check_button.clicked.connect(self.on_check_button_clicked)
|
self.check_button.clicked.connect(self.on_check_button_clicked)
|
||||||
self.directory_button.clicked.connect(self.on_directory_button_clicked)
|
|
||||||
|
|
||||||
def add_custom_pages(self):
|
def add_custom_pages(self):
|
||||||
"""
|
"""
|
||||||
@ -120,21 +120,15 @@ class SongExportForm(OpenLPWizard):
|
|||||||
self.grid_layout.setObjectName('range_layout')
|
self.grid_layout.setObjectName('range_layout')
|
||||||
self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page)
|
self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page)
|
||||||
self.selected_list_widget.setObjectName('selected_list_widget')
|
self.selected_list_widget.setObjectName('selected_list_widget')
|
||||||
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)
|
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 2)
|
||||||
# FIXME: self.horizontal_layout is already defined above?!?!? Replace with Path Eidt!
|
self.output_directory_path_edit = PathEdit(
|
||||||
self.horizontal_layout = QtWidgets.QHBoxLayout()
|
self.export_song_page, PathType.Directories,
|
||||||
self.horizontal_layout.setObjectName('horizontal_layout')
|
dialog_caption=translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), show_revert=False)
|
||||||
|
self.output_directory_path_edit.path = Settings().value('songs/last directory export')
|
||||||
self.directory_label = QtWidgets.QLabel(self.export_song_page)
|
self.directory_label = QtWidgets.QLabel(self.export_song_page)
|
||||||
self.directory_label.setObjectName('directory_label')
|
self.directory_label.setObjectName('directory_label')
|
||||||
self.horizontal_layout.addWidget(self.directory_label)
|
self.grid_layout.addWidget(self.directory_label, 0, 0)
|
||||||
self.directory_line_edit = QtWidgets.QLineEdit(self.export_song_page)
|
self.grid_layout.addWidget(self.output_directory_path_edit, 0, 1)
|
||||||
self.directory_line_edit.setObjectName('directory_line_edit')
|
|
||||||
self.horizontal_layout.addWidget(self.directory_line_edit)
|
|
||||||
self.directory_button = QtWidgets.QToolButton(self.export_song_page)
|
|
||||||
self.directory_button.setIcon(build_icon(':/exports/export_load.png'))
|
|
||||||
self.directory_button.setObjectName('directory_button')
|
|
||||||
self.horizontal_layout.addWidget(self.directory_button)
|
|
||||||
self.grid_layout.addLayout(self.horizontal_layout, 0, 0, 1, 1)
|
|
||||||
self.export_song_layout.addLayout(self.grid_layout)
|
self.export_song_layout.addLayout(self.grid_layout)
|
||||||
self.addPage(self.export_song_page)
|
self.addPage(self.export_song_page)
|
||||||
|
|
||||||
@ -188,11 +182,12 @@ class SongExportForm(OpenLPWizard):
|
|||||||
self.selected_list_widget.addItem(song)
|
self.selected_list_widget.addItem(song)
|
||||||
return True
|
return True
|
||||||
elif self.currentPage() == self.export_song_page:
|
elif self.currentPage() == self.export_song_page:
|
||||||
if not self.directory_line_edit.text():
|
if not self.output_directory_path_edit.path:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
|
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
|
||||||
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
|
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
|
||||||
return False
|
return False
|
||||||
|
Settings().setValue('songs/last directory export', self.output_directory_path_edit.path)
|
||||||
return True
|
return True
|
||||||
elif self.currentPage() == self.progress_page:
|
elif self.currentPage() == self.progress_page:
|
||||||
self.available_list_widget.clear()
|
self.available_list_widget.clear()
|
||||||
@ -211,8 +206,6 @@ class SongExportForm(OpenLPWizard):
|
|||||||
self.finish_button.setVisible(False)
|
self.finish_button.setVisible(False)
|
||||||
self.cancel_button.setVisible(True)
|
self.cancel_button.setVisible(True)
|
||||||
self.available_list_widget.clear()
|
self.available_list_widget.clear()
|
||||||
self.selected_list_widget.clear()
|
|
||||||
self.directory_line_edit.clear()
|
|
||||||
self.search_line_edit.clear()
|
self.search_line_edit.clear()
|
||||||
# Load the list of songs.
|
# Load the list of songs.
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
@ -247,7 +240,7 @@ class SongExportForm(OpenLPWizard):
|
|||||||
song.data(QtCore.Qt.UserRole)
|
song.data(QtCore.Qt.UserRole)
|
||||||
for song in find_list_widget_items(self.selected_list_widget)
|
for song in find_list_widget_items(self.selected_list_widget)
|
||||||
]
|
]
|
||||||
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
|
exporter = OpenLyricsExport(self, songs, self.output_directory_path_edit.path)
|
||||||
try:
|
try:
|
||||||
if exporter.do_export():
|
if exporter.do_export():
|
||||||
self.progress_label.setText(
|
self.progress_label.setText(
|
||||||
@ -291,15 +284,6 @@ class SongExportForm(OpenLPWizard):
|
|||||||
if not item.isHidden():
|
if not item.isHidden():
|
||||||
item.setCheckState(QtCore.Qt.Checked)
|
item.setCheckState(QtCore.Qt.Checked)
|
||||||
|
|
||||||
def on_directory_button_clicked(self):
|
|
||||||
"""
|
|
||||||
Called when the *directory_button* was clicked. Opens a dialog and writes
|
|
||||||
the path to *directory_line_edit*.
|
|
||||||
"""
|
|
||||||
self.get_folder(
|
|
||||||
translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'),
|
|
||||||
self.directory_line_edit, 'last directory export')
|
|
||||||
|
|
||||||
|
|
||||||
def find_list_widget_items(list_widget, text=''):
|
def find_list_widget_items(list_widget, text=''):
|
||||||
"""
|
"""
|
||||||
|
@ -22,15 +22,13 @@
|
|||||||
"""
|
"""
|
||||||
The song import functions for OpenLP.
|
The song import functions for OpenLP.
|
||||||
"""
|
"""
|
||||||
import codecs
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
|
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
|
||||||
from openlp.core.common.path import path_to_str, str_to_path
|
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
|
from openlp.core.ui.lib import PathEdit, PathType
|
||||||
from openlp.core.ui.lib.filedialog import FileDialog
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||||
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
|
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
|
||||||
@ -93,9 +91,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked)
|
self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked)
|
||||||
self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked)
|
self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked)
|
||||||
else:
|
else:
|
||||||
self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked)
|
self.format_widgets[song_format]['path_edit'].pathChanged.connect(self.on_path_edit_path_changed)
|
||||||
self.format_widgets[song_format]['file_path_edit'].textChanged.\
|
|
||||||
connect(self.on_filepath_edit_text_changed)
|
|
||||||
|
|
||||||
def add_custom_pages(self):
|
def add_custom_pages(self):
|
||||||
"""
|
"""
|
||||||
@ -155,7 +151,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
self.format_widgets[format_list]['removeButton'].setText(
|
self.format_widgets[format_list]['removeButton'].setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
||||||
else:
|
else:
|
||||||
self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse)
|
|
||||||
f_label = 'Filename:'
|
f_label = 'Filename:'
|
||||||
if select_mode == SongFormatSelect.SingleFolder:
|
if select_mode == SongFormatSelect.SingleFolder:
|
||||||
f_label = 'Folder:'
|
f_label = 'Folder:'
|
||||||
@ -172,16 +167,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
self.error_save_to_button.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File'))
|
self.error_save_to_button.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File'))
|
||||||
# Align all QFormLayouts towards each other.
|
# Align all QFormLayouts towards each other.
|
||||||
formats = [f for f in SongFormat.get_format_list() if 'filepathLabel' in self.format_widgets[f]]
|
formats = [f for f in SongFormat.get_format_list() if 'filepathLabel' in self.format_widgets[f]]
|
||||||
labels = [self.format_widgets[f]['filepathLabel'] for f in formats]
|
labels = [self.format_widgets[f]['filepathLabel'] for f in formats] + [self.format_label]
|
||||||
# Get max width of all labels
|
# Get max width of all labels
|
||||||
max_label_width = max(self.format_label.minimumSizeHint().width(),
|
max_label_width = max(labels, key=lambda label: label.minimumSizeHint().width()).minimumSizeHint().width()
|
||||||
max([label.minimumSizeHint().width() for label in labels]))
|
for label in labels:
|
||||||
self.format_spacer.changeSize(max_label_width, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
label.setFixedWidth(max_label_width)
|
||||||
spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats]
|
|
||||||
for index, spacer in enumerate(spacers):
|
|
||||||
spacer.changeSize(
|
|
||||||
max_label_width - labels[index].minimumSizeHint().width(), 0,
|
|
||||||
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
|
||||||
# Align descriptionLabels with rest of layout
|
# Align descriptionLabels with rest of layout
|
||||||
for format_list in SongFormat.get_format_list():
|
for format_list in SongFormat.get_format_list():
|
||||||
if SongFormat.get(format_list, 'descriptionText') is not None:
|
if SongFormat.get(format_list, 'descriptionText') is not None:
|
||||||
@ -209,13 +199,13 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
Settings().setValue('songs/last import type', this_format)
|
Settings().setValue('songs/last import type', this_format)
|
||||||
select_mode, class_, error_msg = SongFormat.get(this_format, 'selectMode', 'class', 'invalidSourceMsg')
|
select_mode, class_, error_msg = SongFormat.get(this_format, 'selectMode', 'class', 'invalidSourceMsg')
|
||||||
if select_mode == SongFormatSelect.MultipleFiles:
|
if select_mode == SongFormatSelect.MultipleFiles:
|
||||||
import_source = self.get_list_of_files(self.format_widgets[this_format]['file_list_widget'])
|
import_source = self.get_list_of_paths(self.format_widgets[this_format]['file_list_widget'])
|
||||||
error_title = UiStrings().IFSp
|
error_title = UiStrings().IFSp
|
||||||
focus_button = self.format_widgets[this_format]['addButton']
|
focus_button = self.format_widgets[this_format]['addButton']
|
||||||
else:
|
else:
|
||||||
import_source = self.format_widgets[this_format]['file_path_edit'].text()
|
import_source = self.format_widgets[this_format]['path_edit'].path
|
||||||
error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs)
|
error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs)
|
||||||
focus_button = self.format_widgets[this_format]['browseButton']
|
focus_button = self.format_widgets[this_format]['path_edit']
|
||||||
if not class_.is_valid_source(import_source):
|
if not class_.is_valid_source(import_source):
|
||||||
critical_error_message_box(error_title, error_msg)
|
critical_error_message_box(error_title, error_msg)
|
||||||
focus_button.setFocus()
|
focus_button.setFocus()
|
||||||
@ -238,20 +228,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
if filters:
|
if filters:
|
||||||
filters += ';;'
|
filters += ';;'
|
||||||
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
|
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
|
||||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
file_paths, filter_used = FileDialog.getOpenFileNames(
|
||||||
self, title, Settings().value(self.plugin.settings_section + '/last directory import'), filters)
|
self, title,
|
||||||
|
Settings().value(self.plugin.settings_section + '/last directory import'), filters)
|
||||||
|
for file_path in file_paths:
|
||||||
|
list_item = QtWidgets.QListWidgetItem(str(file_path))
|
||||||
|
list_item.setData(QtCore.Qt.UserRole, file_path)
|
||||||
|
listbox.addItem(list_item)
|
||||||
if file_paths:
|
if file_paths:
|
||||||
file_names = [path_to_str(file_path) for file_path in file_paths]
|
|
||||||
listbox.addItems(file_names)
|
|
||||||
Settings().setValue(self.plugin.settings_section + '/last directory import', file_paths[0].parent)
|
Settings().setValue(self.plugin.settings_section + '/last directory import', file_paths[0].parent)
|
||||||
|
|
||||||
def get_list_of_files(self, list_box):
|
def get_list_of_paths(self, list_box):
|
||||||
"""
|
"""
|
||||||
Return a list of file from the list_box
|
Return a list of file from the list_box
|
||||||
|
|
||||||
:param list_box: The source list box
|
:param list_box: The source list box
|
||||||
"""
|
"""
|
||||||
return [list_box.item(i).text() for i in range(list_box.count())]
|
return [list_box.item(i).data(QtCore.Qt.UserRole) for i in range(list_box.count())]
|
||||||
|
|
||||||
def remove_selected_items(self, list_box):
|
def remove_selected_items(self, list_box):
|
||||||
"""
|
"""
|
||||||
@ -263,20 +256,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
item = list_box.takeItem(list_box.row(item))
|
item = list_box.takeItem(list_box.row(item))
|
||||||
del item
|
del item
|
||||||
|
|
||||||
def on_browse_button_clicked(self):
|
|
||||||
"""
|
|
||||||
Browse for files or a directory.
|
|
||||||
"""
|
|
||||||
this_format = self.current_format
|
|
||||||
select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter')
|
|
||||||
file_path_edit = self.format_widgets[this_format]['file_path_edit']
|
|
||||||
if select_mode == SongFormatSelect.SingleFile:
|
|
||||||
self.get_file_name(WizardStrings.OpenTypeFile.format(file_type=format_name),
|
|
||||||
file_path_edit, 'last directory import', ext_filter)
|
|
||||||
elif select_mode == SongFormatSelect.SingleFolder:
|
|
||||||
self.get_folder(
|
|
||||||
WizardStrings.OpenTypeFolder.format(folder_name=format_name), file_path_edit, 'last directory import')
|
|
||||||
|
|
||||||
def on_add_button_clicked(self):
|
def on_add_button_clicked(self):
|
||||||
"""
|
"""
|
||||||
Add a file or directory.
|
Add a file or directory.
|
||||||
@ -296,7 +275,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget'])
|
self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget'])
|
||||||
self.source_page.completeChanged.emit()
|
self.source_page.completeChanged.emit()
|
||||||
|
|
||||||
def on_filepath_edit_text_changed(self):
|
def on_path_edit_path_changed(self):
|
||||||
"""
|
"""
|
||||||
Called when the content of the Filename/Folder edit box changes.
|
Called when the content of the Filename/Folder edit box changes.
|
||||||
"""
|
"""
|
||||||
@ -317,8 +296,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
select_mode = SongFormat.get(format_list, 'selectMode')
|
select_mode = SongFormat.get(format_list, 'selectMode')
|
||||||
if select_mode == SongFormatSelect.MultipleFiles:
|
if select_mode == SongFormatSelect.MultipleFiles:
|
||||||
self.format_widgets[format_list]['file_list_widget'].clear()
|
self.format_widgets[format_list]['file_list_widget'].clear()
|
||||||
else:
|
|
||||||
self.format_widgets[format_list]['file_path_edit'].setText('')
|
|
||||||
self.error_report_text_edit.clear()
|
self.error_report_text_edit.clear()
|
||||||
self.error_report_text_edit.setHidden(True)
|
self.error_report_text_edit.setHidden(True)
|
||||||
self.error_copy_to_button.setHidden(True)
|
self.error_copy_to_button.setHidden(True)
|
||||||
@ -341,14 +318,14 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
select_mode = SongFormat.get(source_format, 'selectMode')
|
select_mode = SongFormat.get(source_format, 'selectMode')
|
||||||
if select_mode == SongFormatSelect.SingleFile:
|
if select_mode == SongFormatSelect.SingleFile:
|
||||||
importer = self.plugin.import_songs(source_format,
|
importer = self.plugin.import_songs(source_format,
|
||||||
filename=self.format_widgets[source_format]['file_path_edit'].text())
|
file_path=self.format_widgets[source_format]['path_edit'].path)
|
||||||
elif select_mode == SongFormatSelect.SingleFolder:
|
elif select_mode == SongFormatSelect.SingleFolder:
|
||||||
importer = self.plugin.import_songs(source_format,
|
importer = self.plugin.import_songs(source_format,
|
||||||
folder=self.format_widgets[source_format]['file_path_edit'].text())
|
folder_path=self.format_widgets[source_format]['path_edit'].path)
|
||||||
else:
|
else:
|
||||||
importer = self.plugin.import_songs(
|
importer = self.plugin.import_songs(
|
||||||
source_format,
|
source_format,
|
||||||
filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget']))
|
file_paths=self.get_list_of_paths(self.format_widgets[source_format]['file_list_widget']))
|
||||||
importer.do_import()
|
importer.do_import()
|
||||||
self.progress_label.setText(WizardStrings.FinishedImport)
|
self.progress_label.setText(WizardStrings.FinishedImport)
|
||||||
|
|
||||||
@ -366,18 +343,17 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
file_path, filter_used = FileDialog.getSaveFileName(
|
file_path, filter_used = FileDialog.getSaveFileName(
|
||||||
self, Settings().value(self.plugin.settings_section + '/last directory import'))
|
self, Settings().value(self.plugin.settings_section + '/last directory import'))
|
||||||
if not file_path:
|
if file_path is None:
|
||||||
return
|
return
|
||||||
with file_path.open('w', encoding='utf-8') as report_file:
|
file_path.write_text(self.error_report_text_edit.toPlainText(), encoding='utf-8')
|
||||||
report_file.write(self.error_report_text_edit.toPlainText())
|
|
||||||
|
|
||||||
def add_file_select_item(self):
|
def add_file_select_item(self):
|
||||||
"""
|
"""
|
||||||
Add a file selection page.
|
Add a file selection page.
|
||||||
"""
|
"""
|
||||||
this_format = self.current_format
|
this_format = self.current_format
|
||||||
prefix, can_disable, description_text, select_mode = \
|
format_name, prefix, can_disable, description_text, select_mode, filters = \
|
||||||
SongFormat.get(this_format, 'prefix', 'canDisable', 'descriptionText', 'selectMode')
|
SongFormat.get(this_format, 'name', 'prefix', 'canDisable', 'descriptionText', 'selectMode', 'filter')
|
||||||
page = QtWidgets.QWidget()
|
page = QtWidgets.QWidget()
|
||||||
page.setObjectName(prefix + 'Page')
|
page.setObjectName(prefix + 'Page')
|
||||||
if can_disable:
|
if can_disable:
|
||||||
@ -403,26 +379,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
|||||||
if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
|
if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
|
||||||
file_path_layout = QtWidgets.QHBoxLayout()
|
file_path_layout = QtWidgets.QHBoxLayout()
|
||||||
file_path_layout.setObjectName(prefix + '_file_path_layout')
|
file_path_layout.setObjectName(prefix + '_file_path_layout')
|
||||||
file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0)
|
|
||||||
file_path_label = QtWidgets.QLabel(import_widget)
|
file_path_label = QtWidgets.QLabel(import_widget)
|
||||||
file_path_label.setObjectName(prefix + 'FilepathLabel')
|
|
||||||
file_path_layout.addWidget(file_path_label)
|
file_path_layout.addWidget(file_path_label)
|
||||||
file_path_spacer = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
if select_mode == SongFormatSelect.SingleFile:
|
||||||
file_path_layout.addSpacerItem(file_path_spacer)
|
path_type = PathType.Files
|
||||||
file_path_edit = QtWidgets.QLineEdit(import_widget)
|
dialog_caption = WizardStrings.OpenTypeFile.format(file_type=format_name)
|
||||||
file_path_edit.setObjectName(prefix + '_file_path_edit')
|
else:
|
||||||
file_path_layout.addWidget(file_path_edit)
|
path_type = PathType.Directories
|
||||||
browse_button = QtWidgets.QToolButton(import_widget)
|
dialog_caption = WizardStrings.OpenTypeFolder.format(folder_name=format_name)
|
||||||
browse_button.setIcon(self.open_icon)
|
path_edit = PathEdit(
|
||||||
browse_button.setObjectName(prefix + 'BrowseButton')
|
parent=import_widget, path_type=path_type, dialog_caption=dialog_caption, show_revert=False)
|
||||||
file_path_layout.addWidget(browse_button)
|
path_edit.filters = path_edit.filters + filters
|
||||||
|
path_edit.path = Settings().value(self.plugin.settings_section + '/last directory import')
|
||||||
|
file_path_layout.addWidget(path_edit)
|
||||||
import_layout.addLayout(file_path_layout)
|
import_layout.addLayout(file_path_layout)
|
||||||
import_layout.addSpacerItem(self.stack_spacer)
|
import_layout.addSpacerItem(self.stack_spacer)
|
||||||
self.format_widgets[this_format]['filepathLabel'] = file_path_label
|
self.format_widgets[this_format]['filepathLabel'] = file_path_label
|
||||||
self.format_widgets[this_format]['filepathSpacer'] = file_path_spacer
|
self.format_widgets[this_format]['path_edit'] = path_edit
|
||||||
self.format_widgets[this_format]['file_path_layout'] = file_path_layout
|
|
||||||
self.format_widgets[this_format]['file_path_edit'] = file_path_edit
|
|
||||||
self.format_widgets[this_format]['browseButton'] = browse_button
|
|
||||||
elif select_mode == SongFormatSelect.MultipleFiles:
|
elif select_mode == SongFormatSelect.MultipleFiles:
|
||||||
file_list_widget = QtWidgets.QListWidget(import_widget)
|
file_list_widget = QtWidgets.QListWidget(import_widget)
|
||||||
file_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
file_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
@ -496,6 +469,8 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
|
|||||||
* or if SingleFolder mode, the specified folder exists
|
* or if SingleFolder mode, the specified folder exists
|
||||||
|
|
||||||
When this method returns True, the wizard's Next button is enabled.
|
When this method returns True, the wizard's Next button is enabled.
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
wizard = self.wizard()
|
wizard = self.wizard()
|
||||||
this_format = wizard.current_format
|
this_format = wizard.current_format
|
||||||
@ -505,10 +480,10 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
|
|||||||
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
|
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
file_path = str(wizard.format_widgets[this_format]['file_path_edit'].text())
|
file_path = wizard.format_widgets[this_format]['path_edit'].path
|
||||||
if file_path:
|
if file_path:
|
||||||
if select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path):
|
if select_mode == SongFormatSelect.SingleFile and file_path.is_file():
|
||||||
return True
|
return True
|
||||||
elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(file_path):
|
elif select_mode == SongFormatSelect.SingleFolder and file_path.is_dir():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -534,13 +534,13 @@ def delete_song(song_id, song_plugin):
|
|||||||
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
||||||
for media_file in media_files:
|
for media_file in media_files:
|
||||||
try:
|
try:
|
||||||
os.remove(media_file.file_name)
|
media_file.file_path.unlink()
|
||||||
except OSError:
|
except OSError:
|
||||||
log.exception('Could not remove file: {name}'.format(name=media_file.file_name))
|
log.exception('Could not remove file: {name}'.format(name=media_file.file_path))
|
||||||
try:
|
try:
|
||||||
save_path = os.path.join(str(AppLocation.get_section_data_path(song_plugin.name)), 'audio', str(song_id))
|
save_path = AppLocation.get_section_data_path(song_plugin.name) / 'audio' / str(song_id)
|
||||||
if os.path.exists(save_path):
|
if save_path.exists():
|
||||||
os.rmdir(save_path)
|
save_path.rmdir()
|
||||||
except OSError:
|
except OSError:
|
||||||
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
||||||
song_plugin.manager.delete_object(Song, song_id)
|
song_plugin.manager.delete_object(Song, song_id)
|
||||||
|
@ -23,14 +23,15 @@
|
|||||||
The :mod:`db` module provides the database and schema that is the backend for
|
The :mod:`db` module provides the database and schema that is the backend for
|
||||||
the Songs plugin
|
the Songs plugin
|
||||||
"""
|
"""
|
||||||
|
from contextlib import suppress
|
||||||
from sqlalchemy import Column, ForeignKey, Table, types
|
from sqlalchemy import Column, ForeignKey, Table, types
|
||||||
from sqlalchemy.orm import mapper, relation, reconstructor
|
from sqlalchemy.orm import mapper, relation, reconstructor
|
||||||
from sqlalchemy.sql.expression import func, text
|
from sqlalchemy.sql.expression import func, text
|
||||||
|
|
||||||
from openlp.core.lib.db import BaseModel, init_db
|
from openlp.core.common.applocation import AppLocation
|
||||||
from openlp.core.common.languagemanager import get_natural_key
|
from openlp.core.common.languagemanager import get_natural_key
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
|
from openlp.core.lib.db import BaseModel, PathType, init_db
|
||||||
|
|
||||||
|
|
||||||
class Author(BaseModel):
|
class Author(BaseModel):
|
||||||
@ -238,7 +239,7 @@ def init_schema(url):
|
|||||||
|
|
||||||
**media_files Table**
|
**media_files Table**
|
||||||
* id
|
* id
|
||||||
* file_name
|
* _file_path
|
||||||
* type
|
* type
|
||||||
|
|
||||||
**song_books Table**
|
**song_books Table**
|
||||||
@ -305,7 +306,7 @@ def init_schema(url):
|
|||||||
'media_files', metadata,
|
'media_files', metadata,
|
||||||
Column('id', types.Integer(), primary_key=True),
|
Column('id', types.Integer(), primary_key=True),
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
|
||||||
Column('file_name', types.Unicode(255), nullable=False),
|
Column('file_path', PathType, nullable=False),
|
||||||
Column('type', types.Unicode(64), nullable=False, default='audio'),
|
Column('type', types.Unicode(64), nullable=False, default='audio'),
|
||||||
Column('weight', types.Integer(), default=0)
|
Column('weight', types.Integer(), default=0)
|
||||||
)
|
)
|
||||||
|
@ -19,11 +19,9 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import chardet
|
import chardet
|
||||||
import codecs
|
import codecs
|
||||||
|
import logging
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
@ -48,7 +46,7 @@ class CCLIFileImport(SongImport):
|
|||||||
:param manager: The song manager for the running OpenLP installation.
|
:param manager: The song manager for the running OpenLP installation.
|
||||||
:param kwargs: The files to be imported.
|
:param kwargs: The files to be imported.
|
||||||
"""
|
"""
|
||||||
super(CCLIFileImport, self).__init__(manager, **kwargs)
|
super().__init__(manager, **kwargs)
|
||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
@ -56,37 +54,35 @@ class CCLIFileImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
log.debug('Starting CCLI File Import')
|
log.debug('Starting CCLI File Import')
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for filename in self.import_source:
|
for file_path in self.import_source:
|
||||||
filename = str(filename)
|
log.debug('Importing CCLI File: {name}'.format(name=file_path))
|
||||||
log.debug('Importing CCLI File: {name}'.format(name=filename))
|
if file_path.is_file():
|
||||||
if os.path.isfile(filename):
|
with file_path.open('rb') as detect_file:
|
||||||
detect_file = open(filename, 'rb')
|
detect_content = detect_file.read(2048)
|
||||||
detect_content = detect_file.read(2048)
|
try:
|
||||||
try:
|
str(detect_content, 'utf-8')
|
||||||
str(detect_content, 'utf-8')
|
details = {'confidence': 1, 'encoding': 'utf-8'}
|
||||||
details = {'confidence': 1, 'encoding': 'utf-8'}
|
except UnicodeDecodeError:
|
||||||
except UnicodeDecodeError:
|
details = chardet.detect(detect_content)
|
||||||
details = chardet.detect(detect_content)
|
in_file = codecs.open(str(file_path), 'r', details['encoding'])
|
||||||
detect_file.close()
|
if not in_file.read(1) == '\ufeff':
|
||||||
infile = codecs.open(filename, 'r', details['encoding'])
|
|
||||||
if not infile.read(1) == '\ufeff':
|
|
||||||
# not UTF or no BOM was found
|
# not UTF or no BOM was found
|
||||||
infile.seek(0)
|
in_file.seek(0)
|
||||||
lines = infile.readlines()
|
lines = in_file.readlines()
|
||||||
infile.close()
|
in_file.close()
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = file_path.suffix.lower()
|
||||||
if ext.lower() == '.usr' or ext.lower() == '.bin':
|
if ext == '.usr' or ext == '.bin':
|
||||||
log.info('SongSelect USR format file found: {name}'.format(name=filename))
|
log.info('SongSelect USR format file found: {name}'.format(name=file_path))
|
||||||
if not self.do_import_usr_file(lines):
|
if not self.do_import_usr_file(lines):
|
||||||
self.log_error(filename)
|
self.log_error(file_path)
|
||||||
elif ext.lower() == '.txt':
|
elif ext == '.txt':
|
||||||
log.info('SongSelect TEXT format file found: {name}'.format(name=filename))
|
log.info('SongSelect TEXT format file found: {name}'.format(name=file_path))
|
||||||
if not self.do_import_txt_file(lines):
|
if not self.do_import_txt_file(lines):
|
||||||
self.log_error(filename)
|
self.log_error(file_path)
|
||||||
else:
|
else:
|
||||||
self.log_error(filename, translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid '
|
self.log_error(file_path, translate('SongsPlugin.CCLIFileImport',
|
||||||
'extension.'))
|
'The file does not have a valid extension.'))
|
||||||
log.info('Extension {name} is not valid'.format(name=filename))
|
log.info('Extension {name} is not valid'.format(name=file_path))
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -47,12 +47,11 @@ class ChordProImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for filename in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
song_file = open(filename, 'rt')
|
with file_path.open('rt') as song_file:
|
||||||
self.do_import_file(song_file)
|
self.do_import_file(song_file)
|
||||||
song_file.close()
|
|
||||||
|
|
||||||
def do_import_file(self, song_file):
|
def do_import_file(self, song_file):
|
||||||
"""
|
"""
|
||||||
|
@ -78,27 +78,29 @@ class DreamBeamImport(SongImport):
|
|||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
Receive a single file or a list of files to import.
|
Receive a single file_path or a list of files to import.
|
||||||
"""
|
"""
|
||||||
if isinstance(self.import_source, list):
|
if isinstance(self.import_source, list):
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for file in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
try:
|
try:
|
||||||
parsed_file = etree.parse(open(file, 'r'), parser)
|
with file_path.open('r') as xml_file:
|
||||||
|
parsed_file = etree.parse(xml_file, parser)
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
log.exception('XML syntax error in file {name}'.format(name=file))
|
log.exception('XML syntax error in file_path {name}'.format(name=file_path))
|
||||||
self.log_error(file, SongStrings.XMLSyntaxError)
|
self.log_error(file_path, SongStrings.XMLSyntaxError)
|
||||||
continue
|
continue
|
||||||
xml = etree.tostring(parsed_file).decode()
|
xml = etree.tostring(parsed_file).decode()
|
||||||
song_xml = objectify.fromstring(xml)
|
song_xml = objectify.fromstring(xml)
|
||||||
if song_xml.tag != 'DreamSong':
|
if song_xml.tag != 'DreamSong':
|
||||||
self.log_error(
|
self.log_error(
|
||||||
file,
|
file_path,
|
||||||
translate('SongsPlugin.DreamBeamImport', 'Invalid DreamBeam song file. Missing DreamSong tag.'))
|
translate('SongsPlugin.DreamBeamImport',
|
||||||
|
'Invalid DreamBeam song file_path. Missing DreamSong tag.'))
|
||||||
continue
|
continue
|
||||||
if hasattr(song_xml, 'Version'):
|
if hasattr(song_xml, 'Version'):
|
||||||
self.version = float(song_xml.Version.text)
|
self.version = float(song_xml.Version.text)
|
||||||
@ -144,4 +146,4 @@ class DreamBeamImport(SongImport):
|
|||||||
else:
|
else:
|
||||||
self.parse_author(author_copyright)
|
self.parse_author(author_copyright)
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(file)
|
self.log_error(file_path)
|
||||||
|
@ -47,7 +47,7 @@ class EasySlidesImport(SongImport):
|
|||||||
def do_import(self):
|
def do_import(self):
|
||||||
log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
|
log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
|
||||||
parser = etree.XMLParser(remove_blank_text=True, recover=True)
|
parser = etree.XMLParser(remove_blank_text=True, recover=True)
|
||||||
parsed_file = etree.parse(self.import_source, parser)
|
parsed_file = etree.parse(str(self.import_source), parser)
|
||||||
xml = etree.tostring(parsed_file).decode()
|
xml = etree.tostring(parsed_file).decode()
|
||||||
song_xml = objectify.fromstring(xml)
|
song_xml = objectify.fromstring(xml)
|
||||||
self.import_wizard.progress_bar.setMaximum(len(song_xml.Item))
|
self.import_wizard.progress_bar.setMaximum(len(song_xml.Item))
|
||||||
|
@ -22,14 +22,15 @@
|
|||||||
"""
|
"""
|
||||||
The :mod:`easyworship` module provides the functionality for importing EasyWorship song databases into OpenLP.
|
The :mod:`easyworship` module provides the functionality for importing EasyWorship song databases into OpenLP.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
import re
|
|
||||||
import zlib
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import zlib
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
||||||
@ -76,9 +77,11 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
Determines the type of file to import and calls the appropiate method
|
Determines the type of file to import and calls the appropiate method
|
||||||
"""
|
"""
|
||||||
if self.import_source.lower().endswith('ews'):
|
self.import_source = Path(self.import_source)
|
||||||
|
ext = self.import_source.suffix.lower()
|
||||||
|
if ext == '.ews':
|
||||||
self.import_ews()
|
self.import_ews()
|
||||||
elif self.import_source.endswith('DB'):
|
elif ext == '.db':
|
||||||
self.import_db()
|
self.import_db()
|
||||||
else:
|
else:
|
||||||
self.import_sqlite_db()
|
self.import_sqlite_db()
|
||||||
@ -91,11 +94,11 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format
|
or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format
|
||||||
"""
|
"""
|
||||||
# Open ews file if it exists
|
# Open ews file if it exists
|
||||||
if not os.path.isfile(self.import_source):
|
if not self.import_source.is_file():
|
||||||
log.debug('Given ews file does not exists.')
|
log.debug('Given ews file does not exists.')
|
||||||
return
|
return
|
||||||
# Make sure there is room for at least a header and one entry
|
# Make sure there is room for at least a header and one entry
|
||||||
if os.path.getsize(self.import_source) < 892:
|
if self.import_source.stat().st_size < 892:
|
||||||
log.debug('Given ews file is to small to contain valid data.')
|
log.debug('Given ews file is to small to contain valid data.')
|
||||||
return
|
return
|
||||||
# Take a stab at how text is encoded
|
# Take a stab at how text is encoded
|
||||||
@ -104,7 +107,7 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
if not self.encoding:
|
if not self.encoding:
|
||||||
log.debug('No encoding set.')
|
log.debug('No encoding set.')
|
||||||
return
|
return
|
||||||
self.ews_file = open(self.import_source, 'rb')
|
self.ews_file = self.import_source.open('rb')
|
||||||
# EWS header, version '1.6'/' 3'/' 5':
|
# EWS header, version '1.6'/' 3'/' 5':
|
||||||
# Offset Field Data type Length Details
|
# Offset Field Data type Length Details
|
||||||
# --------------------------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------------------------
|
||||||
@ -199,23 +202,22 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
Import the songs from the database
|
Import the songs from the database
|
||||||
"""
|
"""
|
||||||
# Open the DB and MB files if they exist
|
# Open the DB and MB files if they exist
|
||||||
import_source_mb = self.import_source.replace('.DB', '.MB')
|
import_source_mb = self.import_source.with_suffix('.MB')
|
||||||
if not os.path.isfile(self.import_source):
|
if not self.import_source.is_file():
|
||||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
'This file does not exist.'))
|
'This file does not exist.'))
|
||||||
return
|
return
|
||||||
if not os.path.isfile(import_source_mb):
|
if not import_source_mb.is_file():
|
||||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
'Could not find the "Songs.MB" file. It must be in the same '
|
'Could not find the "Songs.MB" file. It must be in the same '
|
||||||
'folder as the "Songs.DB" file.'))
|
'folder as the "Songs.DB" file.'))
|
||||||
return
|
return
|
||||||
db_size = os.path.getsize(self.import_source)
|
if self.import_source.stat().st_size < 0x800:
|
||||||
if db_size < 0x800:
|
|
||||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
'This file is not a valid EasyWorship database.'))
|
'This file is not a valid EasyWorship database.'))
|
||||||
return
|
return
|
||||||
db_file = open(self.import_source, 'rb')
|
db_file = self.import_source.open('rb')
|
||||||
self.memo_file = open(import_source_mb, 'rb')
|
self.memo_file = import_source_mb.open('rb')
|
||||||
# Don't accept files that are clearly not paradox files
|
# Don't accept files that are clearly not paradox files
|
||||||
record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35))
|
record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35))
|
||||||
if header_size != 0x800 or block_size < 1 or block_size > 4:
|
if header_size != 0x800 or block_size < 1 or block_size > 4:
|
||||||
@ -340,52 +342,34 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
db_file.close()
|
db_file.close()
|
||||||
self.memo_file.close()
|
self.memo_file.close()
|
||||||
|
|
||||||
def _find_file(self, base_path, path_list):
|
|
||||||
"""
|
|
||||||
Find the specified file, with the option of the file being at any level in the specified directory structure.
|
|
||||||
|
|
||||||
:param base_path: the location search in
|
|
||||||
:param path_list: the targeted file, preceded by directories that may be their parents relative to the base_path
|
|
||||||
:return: path for targeted file
|
|
||||||
"""
|
|
||||||
target_file = ''
|
|
||||||
while len(path_list) > 0:
|
|
||||||
target_file = os.path.join(path_list[-1], target_file)
|
|
||||||
path_list = path_list[:len(path_list) - 1]
|
|
||||||
full_path = os.path.join(base_path, target_file)
|
|
||||||
full_path = full_path[:len(full_path) - 1]
|
|
||||||
if os.path.isfile(full_path):
|
|
||||||
return full_path
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def import_sqlite_db(self):
|
def import_sqlite_db(self):
|
||||||
"""
|
"""
|
||||||
Import the songs from an EasyWorship 6 SQLite database
|
Import the songs from an EasyWorship 6 SQLite database
|
||||||
"""
|
"""
|
||||||
songs_db_path = self._find_file(self.import_source, ["Databases", "Data", "Songs.db"])
|
songs_db_path = next(self.import_source.rglob('Songs.db'), None)
|
||||||
song_words_db_path = self._find_file(self.import_source, ["Databases", "Data", "SongWords.db"])
|
song_words_db_path = next(self.import_source.rglob('SongWords.db'), None)
|
||||||
invalid_dir_msg = 'This does not appear to be a valid Easy Worship 6 database directory.'
|
invalid_dir_msg = translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
'This does not appear to be a valid Easy Worship 6 database directory.')
|
||||||
|
invalid_db_msg = translate('SongsPlugin.EasyWorshipSongImport', 'This is not a valid Easy Worship 6 database.')
|
||||||
# check to see if needed files are there
|
# check to see if needed files are there
|
||||||
if not os.path.isfile(songs_db_path):
|
if not (songs_db_path and songs_db_path.is_file()):
|
||||||
self.log_error(songs_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg))
|
self.log_error(self.import_source, invalid_dir_msg)
|
||||||
return
|
return
|
||||||
if not os.path.isfile(song_words_db_path):
|
if not (song_words_db_path and song_words_db_path.is_file()):
|
||||||
self.log_error(song_words_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg))
|
self.log_error(self.import_source, invalid_dir_msg)
|
||||||
return
|
return
|
||||||
# get database handles
|
# get database handles
|
||||||
songs_conn = sqlite3.connect(songs_db_path)
|
songs_conn = sqlite3.connect(str(songs_db_path))
|
||||||
words_conn = sqlite3.connect(song_words_db_path)
|
words_conn = sqlite3.connect(str(song_words_db_path))
|
||||||
if songs_conn is None or words_conn is None:
|
if songs_conn is None or words_conn is None:
|
||||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
self.log_error(self.import_source, invalid_db_msg)
|
||||||
'This is not a valid Easy Worship 6 database.'))
|
|
||||||
songs_conn.close()
|
songs_conn.close()
|
||||||
words_conn.close()
|
words_conn.close()
|
||||||
return
|
return
|
||||||
songs_db = songs_conn.cursor()
|
songs_db = songs_conn.cursor()
|
||||||
words_db = words_conn.cursor()
|
words_db = words_conn.cursor()
|
||||||
if songs_conn is None or words_conn is None:
|
if songs_conn is None or words_conn is None:
|
||||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
self.log_error(self.import_source, invalid_db_msg)
|
||||||
'This is not a valid Easy Worship 6 database.'))
|
|
||||||
songs_conn.close()
|
songs_conn.close()
|
||||||
words_conn.close()
|
words_conn.close()
|
||||||
return
|
return
|
||||||
|
@ -82,10 +82,8 @@ The XML of `Foilpresenter <http://foilpresenter.de/>`_ songs is of the format::
|
|||||||
</kategorien>
|
</kategorien>
|
||||||
</foilpresenterfolie>
|
</foilpresenterfolie>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import os
|
|
||||||
|
|
||||||
from lxml import etree, objectify
|
from lxml import etree, objectify
|
||||||
|
|
||||||
@ -121,10 +119,9 @@ class FoilPresenterImport(SongImport):
|
|||||||
for file_path in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.import_wizard.increment_progress_bar(
|
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
|
||||||
try:
|
try:
|
||||||
parsed_file = etree.parse(file_path, parser)
|
parsed_file = etree.parse(str(file_path), parser)
|
||||||
xml = etree.tostring(parsed_file).decode()
|
xml = etree.tostring(parsed_file).decode()
|
||||||
self.foil_presenter.xml_to_song(xml)
|
self.foil_presenter.xml_to_song(xml)
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
|
@ -50,12 +50,11 @@ class LyrixImport(SongImport):
|
|||||||
if not isinstance(self.import_source, list):
|
if not isinstance(self.import_source, list):
|
||||||
return
|
return
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for filename in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
song_file = open(filename, 'rt', encoding='cp1251')
|
with file_path.open('rt', encoding='cp1251') as song_file:
|
||||||
self.do_import_file(song_file)
|
self.do_import_file(song_file)
|
||||||
song_file.close()
|
|
||||||
|
|
||||||
def do_import_file(self, file):
|
def do_import_file(self, file):
|
||||||
"""
|
"""
|
||||||
|
@ -266,7 +266,7 @@ class OpenLPSongImport(SongImport):
|
|||||||
if has_media_files and song.media_files:
|
if has_media_files and song.media_files:
|
||||||
for media_file in song.media_files:
|
for media_file in song.media_files:
|
||||||
existing_media_file = self.manager.get_object_filtered(
|
existing_media_file = self.manager.get_object_filtered(
|
||||||
MediaFile, MediaFile.file_name == media_file.file_name)
|
MediaFile, MediaFile.file_path == media_file.file_path)
|
||||||
if existing_media_file:
|
if existing_media_file:
|
||||||
new_song.media_files.append(existing_media_file)
|
new_song.media_files.append(existing_media_file)
|
||||||
else:
|
else:
|
||||||
|
@ -23,9 +23,7 @@
|
|||||||
The :mod:`openlyrics` module provides the functionality for importing
|
The :mod:`openlyrics` module provides the functionality for importing
|
||||||
songs which are saved as OpenLyrics files.
|
songs which are saved as OpenLyrics files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -58,12 +56,11 @@ class OpenLyricsImport(SongImport):
|
|||||||
for file_path in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.import_wizard.increment_progress_bar(
|
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
|
||||||
try:
|
try:
|
||||||
# Pass a file object, because lxml does not cope with some
|
# Pass a file object, because lxml does not cope with some
|
||||||
# special characters in the path (see lp:757673 and lp:744337).
|
# special characters in the path (see lp:757673 and lp:744337).
|
||||||
parsed_file = etree.parse(open(file_path, 'rb'), parser)
|
parsed_file = etree.parse(file_path.open('rb'), parser)
|
||||||
xml = etree.tostring(parsed_file).decode()
|
xml = etree.tostring(parsed_file).decode()
|
||||||
self.open_lyrics.xml_to_song(xml)
|
self.open_lyrics.xml_to_song(xml)
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
@ -70,12 +69,11 @@ class OpenOfficeImport(SongImport):
|
|||||||
log.error(exc)
|
log.error(exc)
|
||||||
return
|
return
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for filename in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
filename = str(filename)
|
if file_path.is_file():
|
||||||
if os.path.isfile(filename):
|
self.open_ooo_file(file_path)
|
||||||
self.open_ooo_file(filename)
|
|
||||||
if self.document:
|
if self.document:
|
||||||
self.process_ooo_document()
|
self.process_ooo_document()
|
||||||
self.close_ooo_file()
|
self.close_ooo_file()
|
||||||
@ -144,12 +142,7 @@ class OpenOfficeImport(SongImport):
|
|||||||
Open the passed file in OpenOffice.org Impress
|
Open the passed file in OpenOffice.org Impress
|
||||||
"""
|
"""
|
||||||
self.file_path = file_path
|
self.file_path = file_path
|
||||||
if is_win():
|
url = file_path.as_uri()
|
||||||
url = file_path.replace('\\', '/')
|
|
||||||
url = url.replace(':', '|').replace(' ', '%20')
|
|
||||||
url = 'file:///' + url
|
|
||||||
else:
|
|
||||||
url = uno.systemPathToFileUrl(file_path)
|
|
||||||
properties = []
|
properties = []
|
||||||
properties.append(self.create_property('Hidden', True))
|
properties.append(self.create_property('Hidden', True))
|
||||||
properties = tuple(properties)
|
properties = tuple(properties)
|
||||||
@ -159,7 +152,7 @@ class OpenOfficeImport(SongImport):
|
|||||||
self.document.supportsService("com.sun.star.text.TextDocument"):
|
self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||||
self.close_ooo_file()
|
self.close_ooo_file()
|
||||||
else:
|
else:
|
||||||
self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0)
|
self.import_wizard.increment_progress_bar('Processing file {file_path}'.format(file_path=file_path), 0)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.exception("open_ooo_file failed: {url}".format(url=url))
|
log.exception("open_ooo_file failed: {url}".format(url=url))
|
||||||
return
|
return
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -116,12 +115,11 @@ class OpenSongImport(SongImport):
|
|||||||
if not isinstance(self.import_source, list):
|
if not isinstance(self.import_source, list):
|
||||||
return
|
return
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for filename in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
song_file = open(filename, 'rb')
|
with file_path.open('rb') as song_file:
|
||||||
self.do_import_file(song_file)
|
self.do_import_file(song_file)
|
||||||
song_file.close()
|
|
||||||
|
|
||||||
def do_import_file(self, file):
|
def do_import_file(self, file):
|
||||||
"""
|
"""
|
||||||
|
@ -231,16 +231,15 @@ class OPSProImport(SongImport):
|
|||||||
xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50)
|
xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50)
|
||||||
# Access97 XOR of the source
|
# Access97 XOR of the source
|
||||||
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
|
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
|
||||||
mdb = open(self.import_source, 'rb')
|
with self.import_source.open('rb') as mdb_file:
|
||||||
mdb.seek(0x14)
|
mdb_file.seek(0x14)
|
||||||
version = struct.unpack('B', mdb.read(1))[0]
|
version = struct.unpack('B', mdb_file.read(1))[0]
|
||||||
# Get encrypted logo
|
# Get encrypted logo
|
||||||
mdb.seek(0x62)
|
mdb_file.seek(0x62)
|
||||||
EncrypFlag = struct.unpack('B', mdb.read(1))[0]
|
EncrypFlag = struct.unpack('B', mdb_file.read(1))[0]
|
||||||
# Get encrypted password
|
# Get encrypted password
|
||||||
mdb.seek(0x42)
|
mdb_file.seek(0x42)
|
||||||
encrypted_password = mdb.read(26)
|
encrypted_password = mdb_file.read(26)
|
||||||
mdb.close()
|
|
||||||
# "Decrypt" the password based on the version
|
# "Decrypt" the password based on the version
|
||||||
decrypted_password = ''
|
decrypted_password = ''
|
||||||
if version < 0x01:
|
if version < 0x01:
|
||||||
|
@ -23,8 +23,6 @@
|
|||||||
The :mod:`powerpraiseimport` module provides the functionality for importing
|
The :mod:`powerpraiseimport` module provides the functionality for importing
|
||||||
Powerpraise song files into the current database.
|
Powerpraise song files into the current database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
from lxml import objectify
|
from lxml import objectify
|
||||||
|
|
||||||
from openlp.core.ui.lib.wizard import WizardStrings
|
from openlp.core.ui.lib.wizard import WizardStrings
|
||||||
@ -41,10 +39,10 @@ class PowerPraiseImport(SongImport):
|
|||||||
for file_path in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.import_wizard.increment_progress_bar(
|
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
with file_path.open('rb') as xml_file:
|
||||||
root = objectify.parse(open(file_path, 'rb')).getroot()
|
root = objectify.parse(xml_file).getroot()
|
||||||
self.process_song(root)
|
self.process_song(root)
|
||||||
|
|
||||||
def process_song(self, root):
|
def process_song(self, root):
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
|
@ -72,10 +72,14 @@ class PowerSongImport(SongImport):
|
|||||||
Checks if source is a PowerSong 1.0 folder:
|
Checks if source is a PowerSong 1.0 folder:
|
||||||
* is a directory
|
* is a directory
|
||||||
* contains at least one \*.song file
|
* contains at least one \*.song file
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path import_source: Should be a Path object that fulfills the above criteria
|
||||||
|
:return: If the source is valid
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if os.path.isdir(import_source):
|
if import_source.is_dir():
|
||||||
for file in os.listdir(import_source):
|
for file_path in import_source.iterdir():
|
||||||
if fnmatch.fnmatch(file, '*.song'):
|
if file_path.suffix == '.song':
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -23,13 +23,11 @@
|
|||||||
The :mod:`presentationmanager` module provides the functionality for importing
|
The :mod:`presentationmanager` module provides the functionality for importing
|
||||||
Presentationmanager song files into the current database.
|
Presentationmanager song files into the current database.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import chardet
|
|
||||||
from lxml import objectify, etree
|
from lxml import objectify, etree
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import get_file_encoding, translate
|
||||||
from openlp.core.ui.lib.wizard import WizardStrings
|
from openlp.core.ui.lib.wizard import WizardStrings
|
||||||
from .songimport import SongImport
|
from .songimport import SongImport
|
||||||
|
|
||||||
@ -44,17 +42,14 @@ class PresentationManagerImport(SongImport):
|
|||||||
for file_path in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.import_wizard.increment_progress_bar(
|
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
|
||||||
try:
|
try:
|
||||||
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
|
tree = etree.parse(str(file_path), parser=etree.XMLParser(recover=True))
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
# Try to detect encoding and use it
|
# Try to detect encoding and use it
|
||||||
file = open(file_path, mode='rb')
|
encoding = get_file_encoding(file_path)['encoding']
|
||||||
encoding = chardet.detect(file.read())['encoding']
|
|
||||||
file.close()
|
|
||||||
# Open file with detected encoding and remove encoding declaration
|
# Open file with detected encoding and remove encoding declaration
|
||||||
text = open(file_path, mode='r', encoding=encoding).read()
|
text = file_path.read_text(encoding=encoding)
|
||||||
text = re.sub('.+\?>\n', '', text)
|
text = re.sub('.+\?>\n', '', text)
|
||||||
try:
|
try:
|
||||||
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
|
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
|
||||||
@ -80,6 +75,11 @@ class PresentationManagerImport(SongImport):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
def process_song(self, root, file_path):
|
def process_song(self, root, file_path):
|
||||||
|
"""
|
||||||
|
:param root:
|
||||||
|
:param openlp.core.common.path.Path file_path: Path to the file to process
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
attrs = None
|
attrs = None
|
||||||
if hasattr(root, 'attributes'):
|
if hasattr(root, 'attributes'):
|
||||||
@ -123,4 +123,4 @@ class PresentationManagerImport(SongImport):
|
|||||||
|
|
||||||
self.verse_order_list = verse_order_list
|
self.verse_order_list = verse_order_list
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(os.path.basename(file_path))
|
self.log_error(file_path.name)
|
||||||
|
@ -23,8 +23,6 @@
|
|||||||
The :mod:`propresenter` module provides the functionality for importing
|
The :mod:`propresenter` module provides the functionality for importing
|
||||||
ProPresenter song files into the current installation database.
|
ProPresenter song files into the current installation database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
from lxml import objectify
|
from lxml import objectify
|
||||||
@ -47,11 +45,17 @@ class ProPresenterImport(SongImport):
|
|||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.import_wizard.increment_progress_bar(
|
self.import_wizard.increment_progress_bar(
|
||||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
WizardStrings.ImportingType.format(source=file_path.name))
|
||||||
root = objectify.parse(open(file_path, 'rb')).getroot()
|
with file_path.open('rb') as xml_file:
|
||||||
self.process_song(root, file_path)
|
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()
|
self.set_defaults()
|
||||||
|
|
||||||
# Extract ProPresenter versionNumber
|
# Extract ProPresenter versionNumber
|
||||||
@ -64,9 +68,7 @@ class ProPresenterImport(SongImport):
|
|||||||
# Title
|
# Title
|
||||||
self.title = root.get('CCLISongTitle')
|
self.title = root.get('CCLISongTitle')
|
||||||
if not self.title or self.title == '':
|
if not self.title or self.title == '':
|
||||||
self.title = os.path.basename(filename)
|
self.title = file_path.stem
|
||||||
if self.title[-5:-1] == '.pro':
|
|
||||||
self.title = self.title[:-5]
|
|
||||||
# Notes
|
# Notes
|
||||||
self.comments = root.get('notes')
|
self.comments = root.get('notes')
|
||||||
# Author
|
# Author
|
||||||
|
@ -112,7 +112,7 @@ class SongBeamerImport(SongImport):
|
|||||||
if not isinstance(self.import_source, list):
|
if not isinstance(self.import_source, list):
|
||||||
return
|
return
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for import_file in self.import_source:
|
for file_path in self.import_source:
|
||||||
# TODO: check that it is a valid SongBeamer file
|
# TODO: check that it is a valid SongBeamer file
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
@ -120,20 +120,19 @@ class SongBeamerImport(SongImport):
|
|||||||
self.current_verse = ''
|
self.current_verse = ''
|
||||||
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
||||||
self.chord_table = None
|
self.chord_table = None
|
||||||
file_name = os.path.split(import_file)[1]
|
if file_path.is_file():
|
||||||
if os.path.isfile(import_file):
|
|
||||||
# Detect the encoding
|
# Detect the encoding
|
||||||
self.input_file_encoding = get_file_encoding(Path(import_file))['encoding']
|
self.input_file_encoding = get_file_encoding(file_path)['encoding']
|
||||||
# The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode.
|
# The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode.
|
||||||
# So if it doesn't start with 'u' we default to cp1252. See:
|
# So if it doesn't start with 'u' we default to cp1252. See:
|
||||||
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
|
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
|
||||||
if not self.input_file_encoding.lower().startswith('u'):
|
if not self.input_file_encoding.lower().startswith('u'):
|
||||||
self.input_file_encoding = 'cp1252'
|
self.input_file_encoding = 'cp1252'
|
||||||
infile = open(import_file, 'rt', encoding=self.input_file_encoding)
|
with file_path.open(encoding=self.input_file_encoding) as song_file:
|
||||||
song_data = infile.readlines()
|
song_data = song_file.readlines()
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
self.title = file_name.split('.sng')[0]
|
self.title = file_path.stem
|
||||||
read_verses = False
|
read_verses = False
|
||||||
# The first verse separator doesn't count, but the others does, so line count starts at -1
|
# The first verse separator doesn't count, but the others does, so line count starts at -1
|
||||||
line_number = -1
|
line_number = -1
|
||||||
@ -185,7 +184,7 @@ class SongBeamerImport(SongImport):
|
|||||||
# inserted by songbeamer, but are manually added headings. So restart the loop, and
|
# inserted by songbeamer, but are manually added headings. So restart the loop, and
|
||||||
# count tags as lines.
|
# count tags as lines.
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.title = file_name.split('.sng')[0]
|
self.title = file_path.stem
|
||||||
verse_tags_mode = VerseTagMode.ContainsNoTagsRestart
|
verse_tags_mode = VerseTagMode.ContainsNoTagsRestart
|
||||||
read_verses = False
|
read_verses = False
|
||||||
# The first verseseparator doesn't count, but the others does, so linecount starts at -1
|
# The first verseseparator doesn't count, but the others does, so linecount starts at -1
|
||||||
@ -207,7 +206,7 @@ class SongBeamerImport(SongImport):
|
|||||||
self.replace_html_tags()
|
self.replace_html_tags()
|
||||||
self.add_verse(self.current_verse, self.current_verse_type)
|
self.add_verse(self.current_verse, self.current_verse_type)
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(import_file)
|
self.log_error(file_path)
|
||||||
|
|
||||||
def insert_chords(self, line_number, line):
|
def insert_chords(self, line_number, line):
|
||||||
"""
|
"""
|
||||||
@ -414,14 +413,15 @@ class SongBeamerImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
# The path is relative to SongBeamers Song folder
|
# The path is relative to SongBeamers Song folder
|
||||||
if is_win():
|
if is_win():
|
||||||
user_doc_folder = os.path.expandvars('$DOCUMENTS')
|
user_doc_path = Path(os.path.expandvars('$DOCUMENTS'))
|
||||||
elif is_macosx():
|
elif is_macosx():
|
||||||
user_doc_folder = os.path.join(os.path.expanduser('~'), 'Documents')
|
user_doc_path = Path.home() / 'Documents'
|
||||||
else:
|
else:
|
||||||
# SongBeamer only runs on mac and win...
|
# SongBeamer only runs on mac and win...
|
||||||
return
|
return
|
||||||
audio_file_path = os.path.normpath(os.path.join(user_doc_folder, 'SongBeamer', 'Songs', audio_file_path))
|
audio_file_path = user_doc_path / 'SongBeamer' / 'Songs' / audio_file_path
|
||||||
if os.path.isfile(audio_file_path):
|
if audio_file_path.is_file():
|
||||||
self.add_media_file(audio_file_path)
|
self.add_media_file(audio_file_path)
|
||||||
else:
|
else:
|
||||||
log.debug('Could not import mediafile "%s" since it does not exists!' % audio_file_path)
|
log.debug('Could not import mediafile "{audio_file_path}" since it does not exists!'
|
||||||
|
.format(audio_file_path=audio_file_path))
|
||||||
|
@ -22,13 +22,11 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, AppLocation, check_directory_exists, translate
|
from openlp.core.common import Registry, AppLocation, check_directory_exists, translate
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import copyfile
|
||||||
from openlp.core.ui.lib.wizard import WizardStrings
|
from openlp.core.ui.lib.wizard import WizardStrings
|
||||||
from openlp.plugins.songs.lib import clean_song, VerseType
|
from openlp.plugins.songs.lib import clean_song, VerseType
|
||||||
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
|
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
|
||||||
@ -62,14 +60,14 @@ class SongImport(QtCore.QObject):
|
|||||||
"""
|
"""
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
QtCore.QObject.__init__(self)
|
QtCore.QObject.__init__(self)
|
||||||
if 'filename' in kwargs:
|
if 'file_path' in kwargs:
|
||||||
self.import_source = kwargs['filename']
|
self.import_source = kwargs['file_path']
|
||||||
elif 'filenames' in kwargs:
|
elif 'file_paths' in kwargs:
|
||||||
self.import_source = kwargs['filenames']
|
self.import_source = kwargs['file_paths']
|
||||||
elif 'folder' in kwargs:
|
elif 'folder_path' in kwargs:
|
||||||
self.import_source = kwargs['folder']
|
self.import_source = kwargs['folder_path']
|
||||||
else:
|
else:
|
||||||
raise KeyError('Keyword arguments "filename[s]" or "folder" not supplied.')
|
raise KeyError('Keyword arguments "file_path[s]" or "folder_path" not supplied.')
|
||||||
log.debug(self.import_source)
|
log.debug(self.import_source)
|
||||||
self.import_wizard = None
|
self.import_wizard = None
|
||||||
self.song = None
|
self.song = None
|
||||||
@ -270,13 +268,13 @@ class SongImport(QtCore.QObject):
|
|||||||
return
|
return
|
||||||
self.authors.append((author, type))
|
self.authors.append((author, type))
|
||||||
|
|
||||||
def add_media_file(self, filename, weight=0):
|
def add_media_file(self, file_path, weight=0):
|
||||||
"""
|
"""
|
||||||
Add a media file to the list
|
Add a media file to the list
|
||||||
"""
|
"""
|
||||||
if filename in [x[0] for x in self.media_files]:
|
if file_path in [x[0] for x in self.media_files]:
|
||||||
return
|
return
|
||||||
self.media_files.append((filename, weight))
|
self.media_files.append((file_path, weight))
|
||||||
|
|
||||||
def add_verse(self, verse_text, verse_def='v', lang=None):
|
def add_verse(self, verse_text, verse_def='v', lang=None):
|
||||||
"""
|
"""
|
||||||
@ -403,29 +401,30 @@ class SongImport(QtCore.QObject):
|
|||||||
self.manager.save_object(song)
|
self.manager.save_object(song)
|
||||||
# Now loop through the media files, copy them to the correct location,
|
# Now loop through the media files, copy them to the correct location,
|
||||||
# and save the song again.
|
# and save the song again.
|
||||||
for filename, weight in self.media_files:
|
for file_path, weight in self.media_files:
|
||||||
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_name == filename)
|
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_path == file_path)
|
||||||
if not media_file:
|
if not media_file:
|
||||||
if os.path.dirname(filename):
|
if file_path.parent:
|
||||||
filename = self.copy_media_file(song.id, filename)
|
file_path = self.copy_media_file(song.id, file_path)
|
||||||
song.media_files.append(MediaFile.populate(file_name=filename, weight=weight))
|
song.media_files.append(MediaFile.populate(file_path=file_path, weight=weight))
|
||||||
self.manager.save_object(song)
|
self.manager.save_object(song)
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def copy_media_file(self, song_id, filename):
|
def copy_media_file(self, song_id, file_path):
|
||||||
"""
|
"""
|
||||||
This method copies the media file to the correct location and returns
|
This method copies the media file to the correct location and returns
|
||||||
the new file location.
|
the new file location.
|
||||||
|
|
||||||
:param song_id:
|
:param song_id:
|
||||||
:param filename: The file to copy.
|
:param openlp.core.common.path.Path file_path: The file to copy.
|
||||||
|
:return: The new location of the file
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, 'save_path'):
|
if not hasattr(self, 'save_path'):
|
||||||
self.save_path = os.path.join(str(AppLocation.get_section_data_path(self.import_wizard.plugin.name)),
|
self.save_path = AppLocation.get_section_data_path(self.import_wizard.plugin.name) / 'audio' / str(song_id)
|
||||||
'audio', str(song_id))
|
check_directory_exists(self.save_path)
|
||||||
check_directory_exists(Path(self.save_path))
|
if self.save_path not in file_path.parents:
|
||||||
if not filename.startswith(self.save_path):
|
old_path, file_path = file_path, self.save_path / file_path.name
|
||||||
old_file, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
|
copyfile(old_path, file_path)
|
||||||
shutil.copyfile(old_file, filename)
|
return file_path
|
||||||
return filename
|
|
||||||
|
@ -25,6 +25,7 @@ songs into the OpenLP database.
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.lib import strip_rtf
|
from openlp.plugins.songs.lib import strip_rtf
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
|
|
||||||
@ -72,7 +73,8 @@ class SongProImport(SongImport):
|
|||||||
Receive a single file or a list of files to import.
|
Receive a single file or a list of files to import.
|
||||||
"""
|
"""
|
||||||
self.encoding = None
|
self.encoding = None
|
||||||
with open(self.import_source, 'rt', errors='ignore') as songs_file:
|
self.import_source = Path(self.import_source)
|
||||||
|
with self.import_source.open('rt', errors='ignore') as songs_file:
|
||||||
self.import_wizard.progress_bar.setMaximum(0)
|
self.import_wizard.progress_bar.setMaximum(0)
|
||||||
tag = ''
|
tag = ''
|
||||||
text = ''
|
text = ''
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
The :mod:`songshowplus` module provides the functionality for importing SongShow Plus songs into the OpenLP
|
The :mod:`songshowplus` module provides the functionality for importing SongShow Plus songs into the OpenLP
|
||||||
database.
|
database.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
@ -93,97 +92,95 @@ class SongShowPlusImport(SongImport):
|
|||||||
if not isinstance(self.import_source, list):
|
if not isinstance(self.import_source, list):
|
||||||
return
|
return
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for file in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.ssp_verse_order_list = []
|
self.ssp_verse_order_list = []
|
||||||
self.other_count = 0
|
self.other_count = 0
|
||||||
self.other_list = {}
|
self.other_list = {}
|
||||||
file_name = os.path.split(file)[1]
|
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name), 0)
|
||||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_name), 0)
|
with file_path.open('rb') as song_file:
|
||||||
song_data = open(file, 'rb')
|
while True:
|
||||||
while True:
|
block_key, = struct.unpack("I", song_file.read(4))
|
||||||
block_key, = struct.unpack("I", song_data.read(4))
|
log.debug('block_key: %d' % block_key)
|
||||||
log.debug('block_key: %d' % block_key)
|
# The file ends with 4 NULL's
|
||||||
# The file ends with 4 NULL's
|
if block_key == 0:
|
||||||
if block_key == 0:
|
break
|
||||||
break
|
next_block_starts, = struct.unpack("I", song_file.read(4))
|
||||||
next_block_starts, = struct.unpack("I", song_data.read(4))
|
next_block_starts += song_file.tell()
|
||||||
next_block_starts += song_data.tell()
|
if block_key in (VERSE, CHORUS, BRIDGE):
|
||||||
if block_key in (VERSE, CHORUS, BRIDGE):
|
null, verse_no, = struct.unpack("BB", song_file.read(2))
|
||||||
null, verse_no, = struct.unpack("BB", song_data.read(2))
|
elif block_key == CUSTOM_VERSE:
|
||||||
elif block_key == CUSTOM_VERSE:
|
null, verse_name_length, = struct.unpack("BB", song_file.read(2))
|
||||||
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
|
verse_name = self.decode(song_file.read(verse_name_length))
|
||||||
verse_name = self.decode(song_data.read(verse_name_length))
|
length_descriptor_size, = struct.unpack("B", song_file.read(1))
|
||||||
length_descriptor_size, = struct.unpack("B", song_data.read(1))
|
log.debug('length_descriptor_size: %d' % length_descriptor_size)
|
||||||
log.debug('length_descriptor_size: %d' % length_descriptor_size)
|
# In the case of song_numbers the number is in the data from the
|
||||||
# In the case of song_numbers the number is in the data from the
|
# current position to the next block starts
|
||||||
# current position to the next block starts
|
if block_key == SONG_NUMBER:
|
||||||
if block_key == SONG_NUMBER:
|
sn_bytes = song_file.read(length_descriptor_size - 1)
|
||||||
sn_bytes = song_data.read(length_descriptor_size - 1)
|
self.song_number = int.from_bytes(sn_bytes, byteorder='little')
|
||||||
self.song_number = int.from_bytes(sn_bytes, byteorder='little')
|
continue
|
||||||
continue
|
# Detect if/how long the length descriptor is
|
||||||
# Detect if/how long the length descriptor is
|
if length_descriptor_size == 12 or length_descriptor_size == 20:
|
||||||
if length_descriptor_size == 12 or length_descriptor_size == 20:
|
length_descriptor, = struct.unpack("I", song_file.read(4))
|
||||||
length_descriptor, = struct.unpack("I", song_data.read(4))
|
elif length_descriptor_size == 2:
|
||||||
elif length_descriptor_size == 2:
|
length_descriptor = 1
|
||||||
length_descriptor = 1
|
elif length_descriptor_size == 9:
|
||||||
elif length_descriptor_size == 9:
|
length_descriptor = 0
|
||||||
length_descriptor = 0
|
|
||||||
else:
|
|
||||||
length_descriptor, = struct.unpack("B", song_data.read(1))
|
|
||||||
log.debug('length_descriptor: %d' % length_descriptor)
|
|
||||||
data = song_data.read(length_descriptor)
|
|
||||||
log.debug(data)
|
|
||||||
if block_key == TITLE:
|
|
||||||
self.title = self.decode(data)
|
|
||||||
elif block_key == AUTHOR:
|
|
||||||
authors = self.decode(data).split(" / ")
|
|
||||||
for author in authors:
|
|
||||||
if author.find(",") != -1:
|
|
||||||
author_parts = author.split(", ")
|
|
||||||
author = author_parts[1] + " " + author_parts[0]
|
|
||||||
self.parse_author(author)
|
|
||||||
elif block_key == COPYRIGHT:
|
|
||||||
self.add_copyright(self.decode(data))
|
|
||||||
elif block_key == CCLI_NO:
|
|
||||||
# Try to get the CCLI number even if the field contains additional text
|
|
||||||
match = re.search(r'\d+', self.decode(data))
|
|
||||||
if match:
|
|
||||||
self.ccli_number = int(match.group())
|
|
||||||
else:
|
else:
|
||||||
log.warning("Can't parse CCLI Number from string: {text}".format(text=self.decode(data)))
|
length_descriptor, = struct.unpack("B", song_file.read(1))
|
||||||
elif block_key == VERSE:
|
log.debug('length_descriptor: %d' % length_descriptor)
|
||||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Verse],
|
data = song_file.read(length_descriptor)
|
||||||
number=verse_no))
|
log.debug(data)
|
||||||
elif block_key == CHORUS:
|
if block_key == TITLE:
|
||||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Chorus],
|
self.title = self.decode(data)
|
||||||
number=verse_no))
|
elif block_key == AUTHOR:
|
||||||
elif block_key == BRIDGE:
|
authors = self.decode(data).split(" / ")
|
||||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Bridge],
|
for author in authors:
|
||||||
number=verse_no))
|
if author.find(",") != -1:
|
||||||
elif block_key == TOPIC:
|
author_parts = author.split(", ")
|
||||||
self.topics.append(self.decode(data))
|
author = author_parts[1] + " " + author_parts[0]
|
||||||
elif block_key == COMMENTS:
|
self.parse_author(author)
|
||||||
self.comments = self.decode(data)
|
elif block_key == COPYRIGHT:
|
||||||
elif block_key == VERSE_ORDER:
|
self.add_copyright(self.decode(data))
|
||||||
verse_tag = self.to_openlp_verse_tag(self.decode(data), True)
|
elif block_key == CCLI_NO:
|
||||||
if verse_tag:
|
# Try to get the CCLI number even if the field contains additional text
|
||||||
if not isinstance(verse_tag, str):
|
match = re.search(r'\d+', self.decode(data))
|
||||||
verse_tag = self.decode(verse_tag)
|
if match:
|
||||||
self.ssp_verse_order_list.append(verse_tag)
|
self.ccli_number = int(match.group())
|
||||||
elif block_key == SONG_BOOK:
|
else:
|
||||||
self.song_book_name = self.decode(data)
|
log.warning("Can't parse CCLI Number from string: {text}".format(text=self.decode(data)))
|
||||||
elif block_key == CUSTOM_VERSE:
|
elif block_key == VERSE:
|
||||||
verse_tag = self.to_openlp_verse_tag(verse_name)
|
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Verse],
|
||||||
self.add_verse(self.decode(data), verse_tag)
|
number=verse_no))
|
||||||
else:
|
elif block_key == CHORUS:
|
||||||
log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data))
|
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Chorus],
|
||||||
song_data.seek(next_block_starts)
|
number=verse_no))
|
||||||
self.verse_order_list = self.ssp_verse_order_list
|
elif block_key == BRIDGE:
|
||||||
song_data.close()
|
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Bridge],
|
||||||
if not self.finish():
|
number=verse_no))
|
||||||
self.log_error(file)
|
elif block_key == TOPIC:
|
||||||
|
self.topics.append(self.decode(data))
|
||||||
|
elif block_key == COMMENTS:
|
||||||
|
self.comments = self.decode(data)
|
||||||
|
elif block_key == VERSE_ORDER:
|
||||||
|
verse_tag = self.to_openlp_verse_tag(self.decode(data), True)
|
||||||
|
if verse_tag:
|
||||||
|
if not isinstance(verse_tag, str):
|
||||||
|
verse_tag = self.decode(verse_tag)
|
||||||
|
self.ssp_verse_order_list.append(verse_tag)
|
||||||
|
elif block_key == SONG_BOOK:
|
||||||
|
self.song_book_name = self.decode(data)
|
||||||
|
elif block_key == CUSTOM_VERSE:
|
||||||
|
verse_tag = self.to_openlp_verse_tag(verse_name)
|
||||||
|
self.add_verse(self.decode(data), verse_tag)
|
||||||
|
else:
|
||||||
|
log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data))
|
||||||
|
song_file.seek(next_block_starts)
|
||||||
|
self.verse_order_list = self.ssp_verse_order_list
|
||||||
|
if not self.finish():
|
||||||
|
self.log_error(file_path)
|
||||||
|
|
||||||
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
|
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
|
||||||
"""
|
"""
|
||||||
|
@ -19,11 +19,8 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
|
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
|
||||||
from openlp.plugins.songs.lib import strip_rtf
|
from openlp.plugins.songs.lib import strip_rtf
|
||||||
@ -60,12 +57,11 @@ class SundayPlusImport(SongImport):
|
|||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for filename in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
song_file = open(filename, 'rb')
|
with file_path.open('rb') as song_file:
|
||||||
self.do_import_file(song_file)
|
self.do_import_file(song_file)
|
||||||
song_file.close()
|
|
||||||
|
|
||||||
def do_import_file(self, file):
|
def do_import_file(self, file):
|
||||||
"""
|
"""
|
||||||
|
@ -22,13 +22,12 @@
|
|||||||
"""
|
"""
|
||||||
The :mod:`lyrix` module provides the functionality for importing songs which are
|
The :mod:`lyrix` module provides the functionality for importing songs which are
|
||||||
exproted from Lyrix."""
|
exproted from Lyrix."""
|
||||||
|
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
import os
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from openlp.core.common import translate, Settings
|
from openlp.core.common import translate, Settings
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
from openlp.plugins.songs.lib.db import AuthorType
|
from openlp.plugins.songs.lib.db import AuthorType
|
||||||
|
|
||||||
@ -50,11 +49,10 @@ class VideoPsalmImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
Process the VideoPsalm file - pass in a file-like object, not a file path.
|
Process the VideoPsalm file - pass in a file-like object, not a file path.
|
||||||
"""
|
"""
|
||||||
|
self.import_source = Path(self.import_source)
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
# Open SongBook file
|
|
||||||
song_file = open(self.import_source, 'rt', encoding='utf-8-sig')
|
|
||||||
try:
|
try:
|
||||||
file_content = song_file.read()
|
file_content = self.import_source.read_text(encoding='utf-8-sig')
|
||||||
processed_content = ''
|
processed_content = ''
|
||||||
inside_quotes = False
|
inside_quotes = False
|
||||||
# The VideoPsalm format is not valid json, it uses illegal line breaks and unquoted keys, this must be fixed
|
# The VideoPsalm format is not valid json, it uses illegal line breaks and unquoted keys, this must be fixed
|
||||||
@ -89,7 +87,7 @@ class VideoPsalmImport(SongImport):
|
|||||||
songs = songbook['Songs']
|
songs = songbook['Songs']
|
||||||
self.import_wizard.progress_bar.setMaximum(len(songs))
|
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||||
songbook_name = songbook['Text']
|
songbook_name = songbook['Text']
|
||||||
media_folder = os.path.normpath(os.path.join(os.path.dirname(song_file.name), '..', 'Audio'))
|
media_path = Path('..', 'Audio')
|
||||||
for song in songs:
|
for song in songs:
|
||||||
self.song_book_name = songbook_name
|
self.song_book_name = songbook_name
|
||||||
if 'Text' in song:
|
if 'Text' in song:
|
||||||
@ -114,7 +112,7 @@ class VideoPsalmImport(SongImport):
|
|||||||
if 'Theme' in song:
|
if 'Theme' in song:
|
||||||
self.topics = song['Theme'].splitlines()
|
self.topics = song['Theme'].splitlines()
|
||||||
if 'AudioFile' in song:
|
if 'AudioFile' in song:
|
||||||
self.add_media_file(os.path.join(media_folder, song['AudioFile']))
|
self.add_media_file(media_path / song['AudioFile'])
|
||||||
if 'Memo1' in song:
|
if 'Memo1' in song:
|
||||||
self.add_comment(song['Memo1'])
|
self.add_comment(song['Memo1'])
|
||||||
if 'Memo2' in song:
|
if 'Memo2' in song:
|
||||||
@ -132,4 +130,5 @@ class VideoPsalmImport(SongImport):
|
|||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error('Could not import {title}'.format(title=self.title))
|
self.log_error('Could not import {title}'.format(title=self.title))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_error(song_file.name, translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
|
self.log_error(self.import_source.name,
|
||||||
|
translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
|
||||||
|
@ -25,6 +25,7 @@ Worship songs into the OpenLP database.
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
@ -100,62 +101,60 @@ class WordsOfWorshipImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
if isinstance(self.import_source, list):
|
if isinstance(self.import_source, list):
|
||||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||||
for source in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
song_data = open(source, 'rb')
|
with file_path.open('rb') as song_data:
|
||||||
if song_data.read(19).decode() != 'WoW File\nSong Words':
|
if song_data.read(19).decode() != 'WoW File\nSong Words':
|
||||||
self.log_error(source,
|
self.log_error(file_path,
|
||||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||||
'Invalid Words of Worship song file. Missing "{text}" '
|
'Invalid Words of Worship song file. Missing "{text}" '
|
||||||
'header.').format(text='WoW File\\nSong Words'))
|
'header.').format(text='WoW File\\nSong Words'))
|
||||||
continue
|
continue
|
||||||
# Seek to byte which stores number of blocks in the song
|
# Seek to byte which stores number of blocks in the song
|
||||||
song_data.seek(56)
|
song_data.seek(56)
|
||||||
no_of_blocks = ord(song_data.read(1))
|
no_of_blocks = ord(song_data.read(1))
|
||||||
song_data.seek(66)
|
song_data.seek(66)
|
||||||
if song_data.read(16).decode() != 'CSongDoc::CBlock':
|
if song_data.read(16).decode() != 'CSongDoc::CBlock':
|
||||||
self.log_error(source,
|
self.log_error(file_path,
|
||||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||||
'Invalid Words of Worship song file. Missing "{text}" '
|
'Invalid Words of Worship song file. Missing "{text}" '
|
||||||
'string.').format(text='CSongDoc::CBlock'))
|
'string.').format(text='CSongDoc::CBlock'))
|
||||||
continue
|
continue
|
||||||
# Seek to the beginning of the first block
|
# Seek to the beginning of the first block
|
||||||
song_data.seek(82)
|
song_data.seek(82)
|
||||||
for block in range(no_of_blocks):
|
for block in range(no_of_blocks):
|
||||||
skip_char_at_end = True
|
skip_char_at_end = True
|
||||||
self.lines_to_read = ord(song_data.read(4)[:1])
|
self.lines_to_read = ord(song_data.read(4)[:1])
|
||||||
block_text = ''
|
block_text = ''
|
||||||
while self.lines_to_read:
|
while self.lines_to_read:
|
||||||
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||||
if skip_char_at_end:
|
if skip_char_at_end:
|
||||||
skip_char = ord(song_data.read(1))
|
skip_char = ord(song_data.read(1))
|
||||||
# Check if we really should skip a char. In some wsg files we shouldn't
|
# Check if we really should skip a char. In some wsg files we shouldn't
|
||||||
if skip_char != 0:
|
if skip_char != 0:
|
||||||
song_data.seek(-1, os.SEEK_CUR)
|
song_data.seek(-1, os.SEEK_CUR)
|
||||||
skip_char_at_end = False
|
skip_char_at_end = False
|
||||||
if block_text:
|
if block_text:
|
||||||
block_text += '\n'
|
block_text += '\n'
|
||||||
block_text += self.line_text
|
block_text += self.line_text
|
||||||
self.lines_to_read -= 1
|
self.lines_to_read -= 1
|
||||||
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
||||||
# Blocks are separated by 2 bytes, skip them, but not if
|
# Blocks are separated by 2 bytes, skip them, but not if
|
||||||
# this is the last block!
|
# this is the last block!
|
||||||
if block + 1 < no_of_blocks:
|
if block + 1 < no_of_blocks:
|
||||||
song_data.seek(2, os.SEEK_CUR)
|
song_data.seek(2, os.SEEK_CUR)
|
||||||
self.add_verse(block_text, block_type)
|
self.add_verse(block_text, block_type)
|
||||||
# Now to extract the author
|
# Now to extract the author
|
||||||
author_length = ord(song_data.read(1))
|
author_length = ord(song_data.read(1))
|
||||||
if author_length:
|
if author_length:
|
||||||
self.parse_author(str(song_data.read(author_length), 'cp1252'))
|
self.parse_author(str(song_data.read(author_length), 'cp1252'))
|
||||||
# Finally the copyright
|
# Finally the copyright
|
||||||
copyright_length = ord(song_data.read(1))
|
copyright_length = ord(song_data.read(1))
|
||||||
if copyright_length:
|
if copyright_length:
|
||||||
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
|
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
|
||||||
file_name = os.path.split(source)[1]
|
# Get the song title
|
||||||
# Get the song title
|
self.title = file_path.stem
|
||||||
self.title = file_name.rpartition('.')[0]
|
if not self.finish():
|
||||||
song_data.close()
|
self.log_error(file_path)
|
||||||
if not self.finish():
|
|
||||||
self.log_error(source)
|
|
||||||
|
@ -28,7 +28,7 @@ import csv
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import get_file_encoding, translate
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
|
|
||||||
@ -81,19 +81,16 @@ class WorshipAssistantImport(SongImport):
|
|||||||
Receive a CSV file to import.
|
Receive a CSV file to import.
|
||||||
"""
|
"""
|
||||||
# Get encoding
|
# Get encoding
|
||||||
detect_file = open(self.import_source, 'rb')
|
encoding = get_file_encoding(self.import_source)['encoding']
|
||||||
detect_content = detect_file.read()
|
with self.import_source.open('r', encoding=encoding) as songs_file:
|
||||||
details = chardet.detect(detect_content)
|
songs_reader = csv.DictReader(songs_file, escapechar='\\')
|
||||||
detect_file.close()
|
try:
|
||||||
songs_file = open(self.import_source, 'r', encoding=details['encoding'])
|
records = list(songs_reader)
|
||||||
songs_reader = csv.DictReader(songs_file, escapechar='\\')
|
except csv.Error as e:
|
||||||
try:
|
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Error reading CSV file.'),
|
||||||
records = list(songs_reader)
|
translate('SongsPlugin.WorshipAssistantImport',
|
||||||
except csv.Error as e:
|
'Line {number:d}: {error}').format(number=songs_reader.line_num, error=e))
|
||||||
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Error reading CSV file.'),
|
return
|
||||||
translate('SongsPlugin.WorshipAssistantImport',
|
|
||||||
'Line {number:d}: {error}').format(number=songs_reader.line_num, error=e))
|
|
||||||
return
|
|
||||||
num_records = len(records)
|
num_records = len(records)
|
||||||
log.info('{count} records found in CSV file'.format(count=num_records))
|
log.info('{count} records found in CSV file'.format(count=num_records))
|
||||||
self.import_wizard.progress_bar.setMaximum(num_records)
|
self.import_wizard.progress_bar.setMaximum(num_records)
|
||||||
@ -185,4 +182,3 @@ class WorshipAssistantImport(SongImport):
|
|||||||
self.log_error(translate('SongsPlugin.WorshipAssistantImport',
|
self.log_error(translate('SongsPlugin.WorshipAssistantImport',
|
||||||
'Record {count:d}').format(count=index) +
|
'Record {count:d}').format(count=index) +
|
||||||
(': "' + self.title + '"' if self.title else ''))
|
(': "' + self.title + '"' if self.title else ''))
|
||||||
songs_file.close()
|
|
||||||
|
@ -76,7 +76,7 @@ class ZionWorxImport(SongImport):
|
|||||||
Receive a CSV file (from a ZionWorx database dump) to import.
|
Receive a CSV file (from a ZionWorx database dump) to import.
|
||||||
"""
|
"""
|
||||||
# Encoding should always be ISO-8859-1
|
# Encoding should always be ISO-8859-1
|
||||||
with open(self.import_source, 'rt', encoding='ISO-8859-1') as songs_file:
|
with self.import_source.open('rt', encoding='ISO-8859-1') as songs_file:
|
||||||
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
||||||
'DefaultStyle']
|
'DefaultStyle']
|
||||||
songs_reader = csv.DictReader(songs_file, field_names)
|
songs_reader = csv.DictReader(songs_file, field_names)
|
||||||
|
@ -22,25 +22,24 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from sqlalchemy.sql import and_, or_
|
from sqlalchemy.sql import and_, or_
|
||||||
|
|
||||||
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.languagemanager import get_natural_key
|
||||||
|
from openlp.core.common.path import copyfile
|
||||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
|
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
|
||||||
check_item_selected, create_separated_list
|
check_item_selected, create_separated_list
|
||||||
from openlp.core.lib.ui import create_widget_action
|
from openlp.core.lib.ui import create_widget_action
|
||||||
from openlp.core.common.languagemanager import get_natural_key
|
|
||||||
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
||||||
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
|
||||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
|
||||||
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
||||||
|
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||||
|
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
||||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
|
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
|
||||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
||||||
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -88,11 +87,11 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
def _update_background_audio(self, song, item):
|
def _update_background_audio(self, song, item):
|
||||||
song.media_files = []
|
song.media_files = []
|
||||||
for i, bga in enumerate(item.background_audio):
|
for i, bga in enumerate(item.background_audio):
|
||||||
dest_file = os.path.join(
|
dest_path =\
|
||||||
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(song.id), os.path.split(bga)[1])
|
AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga)[1]
|
||||||
check_directory_exists(Path(os.path.split(dest_file)[0]))
|
check_directory_exists(dest_path.parent)
|
||||||
shutil.copyfile(os.path.join(str(AppLocation.get_section_data_path('servicemanager')), bga), dest_file)
|
copyfile(AppLocation.get_section_data_path('servicemanager') / bga, dest_path)
|
||||||
song.media_files.append(MediaFile.populate(weight=i, file_name=dest_file))
|
song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path))
|
||||||
self.plugin.manager.save_object(song, True)
|
self.plugin.manager.save_object(song, True)
|
||||||
|
|
||||||
def add_end_header_bar(self):
|
def add_end_header_bar(self):
|
||||||
@ -534,14 +533,13 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
'copy', 'For song cloning'))
|
'copy', 'For song cloning'))
|
||||||
# Copy audio files from the old to the new song
|
# Copy audio files from the old to the new song
|
||||||
if len(old_song.media_files) > 0:
|
if len(old_song.media_files) > 0:
|
||||||
save_path = os.path.join(
|
save_path = AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(new_song.id)
|
||||||
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(new_song.id))
|
check_directory_exists(save_path)
|
||||||
check_directory_exists(Path(save_path))
|
|
||||||
for media_file in old_song.media_files:
|
for media_file in old_song.media_files:
|
||||||
new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name))
|
new_media_file_path = save_path / media_file.file_path.name
|
||||||
shutil.copyfile(media_file.file_name, new_media_file_name)
|
copyfile(media_file.file_path, new_media_file_path)
|
||||||
new_media_file = MediaFile()
|
new_media_file = MediaFile()
|
||||||
new_media_file.file_name = new_media_file_name
|
new_media_file.file_path = new_media_file_path
|
||||||
new_media_file.type = media_file.type
|
new_media_file.type = media_file.type
|
||||||
new_media_file.weight = media_file.weight
|
new_media_file.weight = media_file.weight
|
||||||
new_song.media_files.append(new_media_file)
|
new_song.media_files.append(new_media_file)
|
||||||
@ -613,7 +611,7 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
# Add the audio file to the service item.
|
# Add the audio file to the service item.
|
||||||
if song.media_files:
|
if song.media_files:
|
||||||
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
|
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
|
||||||
service_item.background_audio = [m.file_name for m in song.media_files]
|
service_item.background_audio = [m.file_path for m in song.media_files]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def generate_footer(self, item, song):
|
def generate_footer(self, item, song):
|
||||||
|
@ -24,12 +24,10 @@ The :mod:`openlyricsexport` module provides the functionality for exporting song
|
|||||||
format.
|
format.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties, check_directory_exists, translate, clean_filename
|
from openlp.core.common import RegistryProperties, check_directory_exists, translate, clean_filename
|
||||||
from openlp.core.common.path import Path
|
|
||||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
|
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -42,13 +40,16 @@ class OpenLyricsExport(RegistryProperties):
|
|||||||
def __init__(self, parent, songs, save_path):
|
def __init__(self, parent, songs, save_path):
|
||||||
"""
|
"""
|
||||||
Initialise the export.
|
Initialise the export.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path save_path: The directory to save the exported songs in
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
log.debug('initialise OpenLyricsExport')
|
log.debug('initialise OpenLyricsExport')
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.manager = parent.plugin.manager
|
self.manager = parent.plugin.manager
|
||||||
self.songs = songs
|
self.songs = songs
|
||||||
self.save_path = save_path
|
self.save_path = save_path
|
||||||
check_directory_exists(Path(self.save_path))
|
check_directory_exists(self.save_path)
|
||||||
|
|
||||||
def do_export(self):
|
def do_export(self):
|
||||||
"""
|
"""
|
||||||
@ -69,15 +70,15 @@ class OpenLyricsExport(RegistryProperties):
|
|||||||
author=', '.join([author.display_name for author in song.authors]))
|
author=', '.join([author.display_name for author in song.authors]))
|
||||||
filename = clean_filename(filename)
|
filename = clean_filename(filename)
|
||||||
# Ensure the filename isn't too long for some filesystems
|
# Ensure the filename isn't too long for some filesystems
|
||||||
filename_with_ext = '{name}.xml'.format(name=filename[0:250 - len(self.save_path)])
|
path_length = len(str(self.save_path))
|
||||||
|
filename_with_ext = '{name}.xml'.format(name=filename[0:250 - path_length])
|
||||||
# Make sure we're not overwriting an existing file
|
# Make sure we're not overwriting an existing file
|
||||||
conflicts = 0
|
conflicts = 0
|
||||||
while os.path.exists(os.path.join(self.save_path, filename_with_ext)):
|
while (self.save_path / filename_with_ext).exists():
|
||||||
conflicts += 1
|
conflicts += 1
|
||||||
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - len(self.save_path)],
|
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - path_length], extra=conflicts)
|
||||||
extra=conflicts)
|
|
||||||
# Pass a file object, because lxml does not cope with some special
|
# Pass a file object, because lxml does not cope with some special
|
||||||
# characters in the path (see lp:757673 and lp:744337).
|
# characters in the path (see lp:757673 and lp:744337).
|
||||||
tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8',
|
with (self.save_path / filename_with_ext).open('wb') as out_file:
|
||||||
xml_declaration=True, pretty_print=True)
|
tree.write(out_file, encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||||
return True
|
return True
|
||||||
|
@ -23,16 +23,20 @@
|
|||||||
The :mod:`upgrade` module provides a way for the database and schema that is the
|
The :mod:`upgrade` module provides a way for the database and schema that is the
|
||||||
backend for the Songs plugin
|
backend for the Songs plugin
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, ForeignKey, types
|
from sqlalchemy import Table, Column, ForeignKey, types
|
||||||
from sqlalchemy.sql.expression import func, false, null, text
|
from sqlalchemy.sql.expression import func, false, null, text
|
||||||
|
|
||||||
|
from openlp.core.common import AppLocation
|
||||||
from openlp.core.common.db import drop_columns
|
from openlp.core.common.db import drop_columns
|
||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.common.json import OpenLPJsonEncoder
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.core.lib.db import PathType, get_upgrade_op
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__version__ = 6
|
__version__ = 7
|
||||||
|
|
||||||
|
|
||||||
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
|
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
|
||||||
@ -162,3 +166,28 @@ def upgrade_6(session, metadata):
|
|||||||
op.drop_column('songs', 'song_number')
|
op.drop_column('songs', 'song_number')
|
||||||
# Finally, clean up our mess in people's databases
|
# Finally, clean up our mess in people's databases
|
||||||
op.execute('DELETE FROM songs_songbooks WHERE songbook_id = 0')
|
op.execute('DELETE FROM songs_songbooks WHERE songbook_id = 0')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_7(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 7 upgrade - Move file path from old db to JSON encoded path to new db. Upgrade added in 2.5 dev
|
||||||
|
"""
|
||||||
|
log.debug('Starting upgrade_7 for file_path to JSON')
|
||||||
|
old_table = Table('media_files', metadata, autoload=True)
|
||||||
|
if 'file_path' not in [col.name for col in old_table.c.values()]:
|
||||||
|
op = get_upgrade_op(session)
|
||||||
|
op.add_column('media_files', Column('file_path', PathType()))
|
||||||
|
conn = op.get_bind()
|
||||||
|
results = conn.execute('SELECT * FROM media_files')
|
||||||
|
data_path = AppLocation.get_data_path()
|
||||||
|
for row in results.fetchall():
|
||||||
|
file_path_json = json.dumps(Path(row.file_name), cls=OpenLPJsonEncoder, base_path=data_path)
|
||||||
|
sql = 'UPDATE media_files SET file_path = \'{file_path_json}\' WHERE id = {id}'.format(
|
||||||
|
file_path_json=file_path_json, id=row.id)
|
||||||
|
conn.execute(sql)
|
||||||
|
# Drop old columns
|
||||||
|
if metadata.bind.url.get_dialect().name == 'sqlite':
|
||||||
|
drop_columns(op, 'media_files', ['file_name', ])
|
||||||
|
else:
|
||||||
|
op.drop_constraint('media_files', 'foreignkey')
|
||||||
|
op.drop_column('media_files', 'filenames')
|
||||||
|
@ -31,7 +31,6 @@ from openlp.core.lib.ui import critical_error_message_box
|
|||||||
from openlp.core.ui.lib.filedialog import FileDialog
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.plugins.songs.lib.db import Song
|
from openlp.plugins.songs.lib.db import Song
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -58,9 +57,9 @@ def report_song_list():
|
|||||||
report_file_path.with_suffix('.csv')
|
report_file_path.with_suffix('.csv')
|
||||||
Registry().get('application').set_busy_cursor()
|
Registry().get('application').set_busy_cursor()
|
||||||
try:
|
try:
|
||||||
with report_file_path.open('wt') as file_handle:
|
with report_file_path.open('wt') as export_file:
|
||||||
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
|
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
|
||||||
writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
writer = csv.DictWriter(export_file, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
||||||
headers = dict((n, n) for n in fieldnames)
|
headers = dict((n, n) for n in fieldnames)
|
||||||
writer.writerow(headers)
|
writer.writerow(headers)
|
||||||
song_list = plugin.manager.get_all_objects(Song)
|
song_list = plugin.manager.get_all_objects(Song)
|
||||||
|
@ -37,7 +37,6 @@ from openlp.core.common.actions import ActionList
|
|||||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.core.lib.ui import create_action
|
from openlp.core.lib.ui import create_action
|
||||||
|
|
||||||
from openlp.plugins.songs import reporting
|
from openlp.plugins.songs import reporting
|
||||||
from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint
|
from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint
|
||||||
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
||||||
@ -50,7 +49,6 @@ from openlp.plugins.songs.lib.mediaitem import SongMediaItem
|
|||||||
from openlp.plugins.songs.lib.mediaitem import SongSearch
|
from openlp.plugins.songs.lib.mediaitem import SongSearch
|
||||||
from openlp.plugins.songs.lib.songstab import SongsTab
|
from openlp.plugins.songs.lib.songstab import SongsTab
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'songs/db type': 'sqlite',
|
'songs/db type': 'sqlite',
|
||||||
@ -340,7 +338,7 @@ class SongsPlugin(Plugin):
|
|||||||
progress.forceShow()
|
progress.forceShow()
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
for db in song_dbs:
|
for db in song_dbs:
|
||||||
importer = OpenLPSongImport(self.manager, filename=db)
|
importer = OpenLPSongImport(self.manager, file_path=db)
|
||||||
importer.do_import(progress)
|
importer.do_import(progress)
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
progress.setValue(song_count)
|
progress.setValue(song_count)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 766 B |
446
resources/images/app_qr.svg
Normal file
446
resources/images/app_qr.svg
Normal 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 |
@ -183,7 +183,6 @@
|
|||||||
<file>projector_warmup.png</file>
|
<file>projector_warmup.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="remotes">
|
<qresource prefix="remotes">
|
||||||
<file>android_app_qr.png</file>
|
<file>app_qr.svg</file>
|
||||||
<file>ios_app_qr.png</file>
|
</qresource>
|
||||||
</qresource>
|
</RCC>
|
||||||
</RCC>
|
|
@ -22,14 +22,12 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.plugins.remotes.deploy import deploy_zipfile
|
from openlp.core.api.deploy import deploy_zipfile
|
||||||
|
|
||||||
|
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
|
||||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
|
|
||||||
|
|
||||||
|
|
||||||
class TestRemoteDeploy(TestCase):
|
class TestRemoteDeploy(TestCase):
|
||||||
@ -54,6 +52,7 @@ class TestRemoteDeploy(TestCase):
|
|||||||
Remote Deploy tests - test the dummy zip file is processed correctly
|
Remote Deploy tests - test the dummy zip file is processed correctly
|
||||||
"""
|
"""
|
||||||
# GIVEN: A new downloaded zip file
|
# GIVEN: A new downloaded zip file
|
||||||
|
aa = TEST_PATH
|
||||||
zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
|
zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
|
||||||
app_root = os.path.join(self.app_root, 'site.zip')
|
app_root = os.path.join(self.app_root, 'site.zip')
|
||||||
shutil.copyfile(zip_file, app_root)
|
shutil.copyfile(zip_file, app_root)
|
@ -79,5 +79,6 @@ class TestImageDBUpgrade(TestCase, TestMixin):
|
|||||||
2: Path('/', 'test', 'dir', 'image2.jpg'),
|
2: Path('/', 'test', 'dir', 'image2.jpg'),
|
||||||
3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
|
3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
|
||||||
|
|
||||||
|
self.assertEqual(len(upgraded_results), 3)
|
||||||
for result in upgraded_results:
|
for result in upgraded_results:
|
||||||
self.assertEqual(expected_result_data[result.id], result.file_path)
|
self.assertEqual(expected_result_data[result.id], result.file_path)
|
||||||
|
@ -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 #
|
|
||||||
###############################################################################
|
|
@ -24,6 +24,8 @@ This module contains tests for the OpenSong song importer.
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
@ -48,5 +50,5 @@ class TestChordProFileImport(SongImportTestHelper):
|
|||||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||||
mocked_settings.return_value = mocked_returned_settings
|
mocked_settings.return_value = mocked_returned_settings
|
||||||
# Do the test import
|
# Do the test import
|
||||||
self.file_import([os.path.join(TEST_PATH, 'swing-low.chordpro')],
|
self.file_import([Path(TEST_PATH, 'swing-low.chordpro')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'swing-low.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'swing-low.json')))
|
||||||
|
@ -21,9 +21,10 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the EasySlides song importer.
|
This module contains tests for the EasySlides song importer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
@ -41,7 +42,7 @@ class TestEasySlidesFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading an EasySlides file works correctly on various files
|
Test that loading an EasySlides file works correctly on various files
|
||||||
"""
|
"""
|
||||||
self.file_import(os.path.join(TEST_PATH, 'amazing-grace.xml'),
|
self.file_import(Path(TEST_PATH, 'amazing-grace.xml'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
self.file_import(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.xml'),
|
self.file_import(Path(TEST_PATH, 'Export_2017-01-12_BB.xml'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.json')))
|
||||||
|
@ -97,7 +97,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
|
|||||||
_title_assignment_list = []
|
_title_assignment_list = []
|
||||||
|
|
||||||
def __init__(self, manager):
|
def __init__(self, manager):
|
||||||
EasyWorshipSongImport.__init__(self, manager, filenames=[])
|
EasyWorshipSongImport.__init__(self, manager, file_paths=[])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
@ -180,7 +180,7 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -192,7 +192,7 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
importer.field_descriptions = TEST_FIELD_DESCS
|
importer.field_descriptions = TEST_FIELD_DESCS
|
||||||
|
|
||||||
# WHEN: Called with a field name that exists
|
# WHEN: Called with a field name that exists
|
||||||
@ -210,7 +210,7 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
importer.field_descriptions = TEST_FIELD_DESCS
|
importer.field_descriptions = TEST_FIELD_DESCS
|
||||||
|
|
||||||
# WHEN: Called with a field name that does not exist
|
# WHEN: Called with a field name that does not exist
|
||||||
@ -229,7 +229,7 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
|
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# WHEN: db_set_record_struct is called with a list of field descriptions
|
# WHEN: db_set_record_struct is called with a list of field descriptions
|
||||||
return_value = importer.db_set_record_struct(TEST_FIELD_DESCS)
|
return_value = importer.db_set_record_struct(TEST_FIELD_DESCS)
|
||||||
@ -246,7 +246,7 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
|
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
importer.encoding = TEST_DATA_ENCODING
|
importer.encoding = TEST_DATA_ENCODING
|
||||||
importer.fields = TEST_FIELDS
|
importer.fields = TEST_FIELDS
|
||||||
importer.field_descriptions = TEST_FIELD_DESCS
|
importer.field_descriptions = TEST_FIELD_DESCS
|
||||||
@ -270,7 +270,7 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_memo_file = MagicMock()
|
mocked_memo_file = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
importer.memo_file = mocked_memo_file
|
importer.memo_file = mocked_memo_file
|
||||||
importer.encoding = TEST_DATA_ENCODING
|
importer.encoding = TEST_DATA_ENCODING
|
||||||
|
|
||||||
@ -294,44 +294,25 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
else:
|
else:
|
||||||
mocked_memo_file.seek.assert_any_call(call[0], call[1])
|
mocked_memo_file.seek.assert_any_call(call[0], call[1])
|
||||||
|
|
||||||
def test_do_import_source(self):
|
|
||||||
"""
|
|
||||||
Test the :mod:`do_import` module opens the correct files
|
|
||||||
"""
|
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
|
|
||||||
mocked_manager = MagicMock()
|
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
|
||||||
mocked_os_path.isfile.side_effect = [True, False]
|
|
||||||
|
|
||||||
# WHEN: Supplied with an import source
|
|
||||||
importer.import_source = 'Songs.DB'
|
|
||||||
|
|
||||||
# THEN: do_import should return None having called os.path.isfile
|
|
||||||
self.assertIsNone(importer.do_import(), 'do_import should return None')
|
|
||||||
mocked_os_path.isfile.assert_any_call('Songs.DB')
|
|
||||||
mocked_os_path.isfile.assert_any_call('Songs.MB')
|
|
||||||
|
|
||||||
def test_do_import_source_invalid(self):
|
def test_do_import_source_invalid(self):
|
||||||
"""
|
"""
|
||||||
Test the :mod:`do_import` module produces an error when Songs.MB not found.
|
Test the :mod:`do_import` module produces an error when Songs.MB not found.
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', side_effect=[True, False]):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
importer.log_error = MagicMock()
|
with patch.object(importer, 'log_error') as mocked_log_error:
|
||||||
mocked_os_path.isfile.side_effect = [True, False]
|
|
||||||
|
|
||||||
# WHEN: do_import is supplied with an import source (Songs.MB missing)
|
# WHEN: do_import is supplied with an import source (Songs.MB missing)
|
||||||
importer.import_source = 'Songs.DB'
|
importer.import_source = 'Songs.DB'
|
||||||
importer.do_import()
|
importer.do_import()
|
||||||
|
|
||||||
# THEN: do_import should have logged an error that the Songs.MB file could not be found.
|
# THEN: do_import should have logged an error that the Songs.MB file could not be found.
|
||||||
importer.log_error.assert_any_call(importer.import_source, 'Could not find the "Songs.MB" file. It must be '
|
mocked_log_error.assert_any_call(importer.import_source,
|
||||||
'in the same folder as the "Songs.DB" file.')
|
'Could not find the "Songs.MB" file. It must be in the same folder as '
|
||||||
|
'the "Songs.DB" file.')
|
||||||
|
|
||||||
def test_do_import_database_validity(self):
|
def test_do_import_database_validity(self):
|
||||||
"""
|
"""
|
||||||
@ -339,18 +320,19 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
|
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
|
||||||
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat') as mocked_stat:
|
||||||
|
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
mocked_os_path.isfile.return_value = True
|
|
||||||
importer.import_source = 'Songs.DB'
|
importer.import_source = 'Songs.DB'
|
||||||
|
|
||||||
# WHEN: DB file size is less than 0x800
|
# WHEN: DB file size is less than 0x800
|
||||||
mocked_os_path.getsize.return_value = 0x7FF
|
mocked_stat.return_value.st_size = 0x7FF
|
||||||
|
|
||||||
# THEN: do_import should return None having called os.path.isfile
|
# THEN: do_import should return None having called Path.stat()
|
||||||
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
|
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
|
||||||
mocked_os_path.getsize.assert_any_call('Songs.DB')
|
mocked_stat.assert_called_once_with()
|
||||||
|
|
||||||
def test_do_import_memo_validty(self):
|
def test_do_import_memo_validty(self):
|
||||||
"""
|
"""
|
||||||
@ -358,13 +340,12 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path, \
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
|
||||||
patch('builtins.open') as mocked_open, \
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \
|
||||||
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.open') as mocked_open, \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
|
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
mocked_os_path.isfile.return_value = True
|
|
||||||
mocked_os_path.getsize.return_value = 0x800
|
|
||||||
importer.import_source = 'Songs.DB'
|
importer.import_source = 'Songs.DB'
|
||||||
|
|
||||||
# WHEN: Unpacking first 35 bytes of Memo file
|
# WHEN: Unpacking first 35 bytes of Memo file
|
||||||
@ -385,14 +366,14 @@ class TestEasyWorshipSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path, \
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
|
||||||
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \
|
||||||
|
patch('openlp.plugins.songs.lib.importers.easyworship.Path.open'), \
|
||||||
patch('builtins.open'), patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct, \
|
patch('builtins.open'), patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct, \
|
||||||
patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \
|
patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \
|
||||||
mocked_retrieve_windows_encoding:
|
mocked_retrieve_windows_encoding:
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||||
mocked_os_path.isfile.return_value = True
|
|
||||||
mocked_os_path.getsize.return_value = 0x800
|
|
||||||
importer.import_source = 'Songs.DB'
|
importer.import_source = 'Songs.DB'
|
||||||
|
|
||||||
# WHEN: Unpacking the code page
|
# WHEN: Unpacking the code page
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
This module contains tests for the LyriX song importer.
|
This module contains tests for the LyriX song importer.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from unittest.mock import patch
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
@ -41,9 +42,9 @@ class TestLyrixFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading an LyriX file works correctly on various files
|
Test that loading an LyriX file works correctly on various files
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'A06.TXT')],
|
self.file_import([Path(TEST_PATH, 'A06.TXT')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'A002.TXT')],
|
self.file_import([Path(TEST_PATH, 'A002.TXT')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace2.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace2.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'AO05.TXT')],
|
self.file_import([Path(TEST_PATH, 'AO05.TXT')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'in die regterhand.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'in die regterhand.json')))
|
||||||
|
@ -51,7 +51,7 @@ class TestMediaShoutImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A MediaShoutImport class
|
# GIVEN: A MediaShoutImport class
|
||||||
# WHEN: It is created
|
# WHEN: It is created
|
||||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||||
|
|
||||||
# THEN: It should not be None
|
# THEN: It should not be None
|
||||||
self.assertIsNotNone(importer)
|
self.assertIsNotNone(importer)
|
||||||
@ -62,7 +62,7 @@ class TestMediaShoutImport(TestCase):
|
|||||||
Test that do_import exits early when unable to connect to the database
|
Test that do_import exits early when unable to connect to the database
|
||||||
"""
|
"""
|
||||||
# GIVEN: A MediaShoutImport instance
|
# GIVEN: A MediaShoutImport instance
|
||||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||||
mocked_pyodbc.connect.side_effect = Exception('Unable to connect')
|
mocked_pyodbc.connect.side_effect = Exception('Unable to connect')
|
||||||
|
|
||||||
# WHEN: do_import is called
|
# WHEN: do_import is called
|
||||||
@ -89,7 +89,7 @@ class TestMediaShoutImport(TestCase):
|
|||||||
group = GroupRecord('Hymns')
|
group = GroupRecord('Hymns')
|
||||||
|
|
||||||
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
|
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
|
||||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||||
mocked_cursor = MagicMock()
|
mocked_cursor = MagicMock()
|
||||||
mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]]
|
mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]]
|
||||||
mocked_cursor.tables.fetchone.return_value = True
|
mocked_cursor.tables.fetchone.return_value = True
|
||||||
@ -124,7 +124,7 @@ class TestMediaShoutImport(TestCase):
|
|||||||
song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '')
|
song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '')
|
||||||
|
|
||||||
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
|
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
|
||||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||||
mocked_cursor = MagicMock()
|
mocked_cursor = MagicMock()
|
||||||
mocked_cursor.fetchall.return_value = [song]
|
mocked_cursor.fetchall.return_value = [song]
|
||||||
mocked_connection = MagicMock()
|
mocked_connection = MagicMock()
|
||||||
@ -158,7 +158,7 @@ class TestMediaShoutImport(TestCase):
|
|||||||
play_order = PlayOrderRecord(0, 1, 1)
|
play_order = PlayOrderRecord(0, 1, 1)
|
||||||
theme = ThemeRecord('Grace')
|
theme = ThemeRecord('Grace')
|
||||||
group = GroupRecord('Hymns')
|
group = GroupRecord('Hymns')
|
||||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||||
|
|
||||||
# WHEN: A song is processed
|
# WHEN: A song is processed
|
||||||
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
|
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
|
||||||
@ -200,7 +200,7 @@ class TestMediaShoutImport(TestCase):
|
|||||||
play_order = PlayOrderRecord(0, 1, 1)
|
play_order = PlayOrderRecord(0, 1, 1)
|
||||||
theme = ThemeRecord('Grace')
|
theme = ThemeRecord('Grace')
|
||||||
group = GroupRecord('Hymns')
|
group = GroupRecord('Hymns')
|
||||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||||
|
|
||||||
# WHEN: A song is processed
|
# WHEN: A song is processed
|
||||||
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
|
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
|
||||||
|
@ -48,7 +48,7 @@ class TestOpenLPImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = OpenLPSongImport(mocked_manager, filenames=[])
|
importer = OpenLPSongImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -61,7 +61,7 @@ class TestOpenLPImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_import_wizard = MagicMock()
|
mocked_import_wizard = MagicMock()
|
||||||
importer = OpenLPSongImport(mocked_manager, filenames=[])
|
importer = OpenLPSongImport(mocked_manager, file_paths=[])
|
||||||
importer.import_wizard = mocked_import_wizard
|
importer.import_wizard = mocked_import_wizard
|
||||||
importer.stop_import_flag = True
|
importer.stop_import_flag = True
|
||||||
|
|
||||||
|
@ -22,14 +22,14 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the OpenLyrics song importer.
|
This module contains tests for the OpenLyrics song importer.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.common.path import Path, rmtree
|
||||||
|
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
@ -43,13 +43,13 @@ class TestOpenLyricsExport(TestCase, TestMixin):
|
|||||||
Create the registry
|
Create the registry
|
||||||
"""
|
"""
|
||||||
Registry.create()
|
Registry.create()
|
||||||
self.temp_folder = mkdtemp()
|
self.temp_folder = Path(mkdtemp())
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""
|
"""
|
||||||
Cleanup
|
Cleanup
|
||||||
"""
|
"""
|
||||||
shutil.rmtree(self.temp_folder)
|
rmtree(self.temp_folder)
|
||||||
|
|
||||||
def test_export_same_filename(self):
|
def test_export_same_filename(self):
|
||||||
"""
|
"""
|
||||||
@ -73,7 +73,9 @@ class TestOpenLyricsExport(TestCase, TestMixin):
|
|||||||
ol_export.do_export()
|
ol_export.do_export()
|
||||||
|
|
||||||
# THEN: The exporter should have created 2 files
|
# THEN: The exporter should have created 2 files
|
||||||
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
|
self.assertTrue((self.temp_folder /
|
||||||
'%s (%s).xml' % (song.title, author.display_name))))
|
'{title} ({display_name}).xml'.format(
|
||||||
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
|
title=song.title, display_name=author.display_name)).exists())
|
||||||
'%s (%s)-1.xml' % (song.title, author.display_name))))
|
self.assertTrue((self.temp_folder /
|
||||||
|
'{title} ({display_name})-1.xml'.format(
|
||||||
|
title=song.title, display_name=author.display_name)).exists())
|
||||||
|
@ -29,10 +29,11 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
from lxml import etree, objectify
|
from lxml import etree, objectify
|
||||||
|
|
||||||
|
from openlp.core.common import Registry, Settings
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.lib.importers.openlyrics import OpenLyricsImport
|
from openlp.plugins.songs.lib.importers.openlyrics import OpenLyricsImport
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
|
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
|
||||||
from openlp.core.common import Registry, Settings
|
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ class TestOpenLyricsImport(TestCase, TestMixin):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = OpenLyricsImport(mocked_manager, filenames=[])
|
importer = OpenLyricsImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer should be an instance of SongImport
|
# THEN: The importer should be an instance of SongImport
|
||||||
self.assertIsInstance(importer, SongImport)
|
self.assertIsInstance(importer, SongImport)
|
||||||
@ -122,13 +123,13 @@ class TestOpenLyricsImport(TestCase, TestMixin):
|
|||||||
for song_file in SONG_TEST_DATA:
|
for song_file in SONG_TEST_DATA:
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_import_wizard = MagicMock()
|
mocked_import_wizard = MagicMock()
|
||||||
importer = OpenLyricsImport(mocked_manager, filenames=[])
|
importer = OpenLyricsImport(mocked_manager, file_paths=[])
|
||||||
importer.import_wizard = mocked_import_wizard
|
importer.import_wizard = mocked_import_wizard
|
||||||
importer.open_lyrics = MagicMock()
|
importer.open_lyrics = MagicMock()
|
||||||
importer.open_lyrics.xml_to_song = MagicMock()
|
importer.open_lyrics.xml_to_song = MagicMock()
|
||||||
|
|
||||||
# WHEN: Importing each file
|
# WHEN: Importing each file
|
||||||
importer.import_source = [os.path.join(TEST_PATH, song_file)]
|
importer.import_source = [Path(TEST_PATH, song_file)]
|
||||||
importer.do_import()
|
importer.do_import()
|
||||||
|
|
||||||
# THEN: The xml_to_song() method should have been called
|
# THEN: The xml_to_song() method should have been called
|
||||||
|
@ -54,7 +54,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = OpenOfficeImport(mocked_manager, filenames=[])
|
importer = OpenOfficeImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -66,7 +66,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a document that raises an exception
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a document that raises an exception
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = OpenOfficeImport(mocked_manager, filenames=[])
|
importer = OpenOfficeImport(mocked_manager, file_paths=[])
|
||||||
importer.document = MagicMock()
|
importer.document = MagicMock()
|
||||||
importer.document.close = MagicMock(side_effect=Exception())
|
importer.document.close = MagicMock(side_effect=Exception())
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ from unittest import TestCase
|
|||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
@ -52,15 +53,15 @@ class TestOpenSongFileImport(SongImportTestHelper):
|
|||||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||||
mocked_settings.return_value = mocked_returned_settings
|
mocked_settings.return_value = mocked_returned_settings
|
||||||
# Do the test import
|
# Do the test import
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer')],
|
self.file_import([Path(TEST_PATH, 'Beautiful Garden Of Prayer')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'One, Two, Three, Four, Five')],
|
self.file_import([Path(TEST_PATH, 'One, Two, Three, Four, Five')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace2')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace2')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace with bad CCLI')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace with bad CCLI')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace without CCLI.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace without CCLI.json')))
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ class TestOpenSongImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
importer = OpenSongImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -96,7 +97,7 @@ class TestOpenSongImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_import_wizard = MagicMock()
|
mocked_import_wizard = MagicMock()
|
||||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
importer = OpenSongImport(mocked_manager, file_paths=[])
|
||||||
importer.import_wizard = mocked_import_wizard
|
importer.import_wizard = mocked_import_wizard
|
||||||
importer.stop_import_flag = True
|
importer.stop_import_flag = True
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ class TestOpenSongImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_import_wizard = MagicMock()
|
mocked_import_wizard = MagicMock()
|
||||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
importer = OpenSongImport(mocked_manager, file_paths=[])
|
||||||
importer.import_wizard = mocked_import_wizard
|
importer.import_wizard = mocked_import_wizard
|
||||||
importer.stop_import_flag = True
|
importer.stop_import_flag = True
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ class TestOpsProSongImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = OPSProImport(mocked_manager, filenames=[])
|
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -98,7 +98,7 @@ class TestOpsProSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = OPSProImport(mocked_manager, filenames=[])
|
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||||
importer.finish = MagicMock()
|
importer.finish = MagicMock()
|
||||||
song, lyrics = _build_data('you are so faithfull.txt', False)
|
song, lyrics = _build_data('you are so faithfull.txt', False)
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ class TestOpsProSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = OPSProImport(mocked_manager, filenames=[])
|
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||||
importer.finish = MagicMock()
|
importer.finish = MagicMock()
|
||||||
song, lyrics = _build_data('amazing grace.txt', False)
|
song, lyrics = _build_data('amazing grace.txt', False)
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ class TestOpsProSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = OPSProImport(mocked_manager, filenames=[])
|
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||||
importer.finish = MagicMock()
|
importer.finish = MagicMock()
|
||||||
song, lyrics = _build_data('amazing grace2.txt', True)
|
song, lyrics = _build_data('amazing grace2.txt', True)
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ class TestOpsProSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = OPSProImport(mocked_manager, filenames=[])
|
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||||
importer.finish = MagicMock()
|
importer.finish = MagicMock()
|
||||||
song, lyrics = _build_data('amazing grace3.txt', True)
|
song, lyrics = _build_data('amazing grace3.txt', True)
|
||||||
|
|
||||||
|
@ -26,8 +26,9 @@ ProPresenter song files into the current installation database.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
from openlp.core.common import Registry
|
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs'))
|
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs'))
|
||||||
@ -44,7 +45,7 @@ class TestPowerPraiseFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading a PowerPraise file works correctly
|
Test that loading a PowerPraise file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.ppl')],
|
self.file_import([Path(TEST_PATH, 'Naher, mein Gott zu Dir.ppl')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'You are so faithful.ppl')],
|
self.file_import([Path(TEST_PATH, 'You are so faithful.ppl')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json')))
|
||||||
|
@ -22,9 +22,10 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the PresentationManager song importer.
|
This module contains tests for the PresentationManager song importer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
@ -42,7 +43,7 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading a PresentationManager file works correctly
|
Test that loading a PresentationManager file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.sng')],
|
self.file_import([Path(TEST_PATH, 'Great Is Thy Faithfulness.sng')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.sng')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
|
@ -23,9 +23,10 @@
|
|||||||
The :mod:`propresenterimport` module provides the functionality for importing
|
The :mod:`propresenterimport` module provides the functionality for importing
|
||||||
ProPresenter song files into the current installation database.
|
ProPresenter song files into the current installation database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
@ -43,19 +44,19 @@ class TestProPresenterFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading a ProPresenter 4 file works correctly
|
Test that loading a ProPresenter 4 file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.pro4')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
|
|
||||||
def test_pro5_song_import(self):
|
def test_pro5_song_import(self):
|
||||||
"""
|
"""
|
||||||
Test that loading a ProPresenter 5 file works correctly
|
Test that loading a ProPresenter 5 file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro5')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.pro5')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
|
|
||||||
def test_pro6_song_import(self):
|
def test_pro6_song_import(self):
|
||||||
"""
|
"""
|
||||||
Test that loading a ProPresenter 6 file works correctly
|
Test that loading a ProPresenter 6 file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro6')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.pro6')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
|
@ -26,8 +26,9 @@ import os
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
@ -51,18 +52,18 @@ class TestSongBeamerFileImport(SongImportTestHelper):
|
|||||||
mocked_returned_settings = MagicMock()
|
mocked_returned_settings = MagicMock()
|
||||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||||
mocked_settings.return_value = mocked_returned_settings
|
mocked_settings.return_value = mocked_returned_settings
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.sng')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Lobsinget dem Herrn.sng')],
|
self.file_import([Path(TEST_PATH, 'Lobsinget dem Herrn.sng')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Lobsinget dem Herrn.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Lobsinget dem Herrn.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'When I Call On You.sng')],
|
self.file_import([Path(TEST_PATH, 'When I Call On You.sng')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'When I Call On You.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'When I Call On You.json')))
|
||||||
|
|
||||||
def test_cp1252_encoded_file(self):
|
def test_cp1252_encoded_file(self):
|
||||||
"""
|
"""
|
||||||
Test that a CP1252 encoded file get's decoded properly.
|
Test that a CP1252 encoded file get's decoded properly.
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'cp1252song.sng')],
|
self.file_import([Path(TEST_PATH, 'cp1252song.sng')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'cp1252song.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'cp1252song.json')))
|
||||||
|
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ class TestSongBeamerImport(TestCase):
|
|||||||
self.song_import_patcher = patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport')
|
self.song_import_patcher = patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport')
|
||||||
self.song_import_patcher.start()
|
self.song_import_patcher.start()
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
self.importer = SongBeamerImport(mocked_manager, filenames=[])
|
self.importer = SongBeamerImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""
|
"""
|
||||||
@ -95,7 +96,7 @@ class TestSongBeamerImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = SongBeamerImport(mocked_manager, filenames=[])
|
importer = SongBeamerImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
|
@ -23,9 +23,10 @@
|
|||||||
The :mod:`songproimport` module provides the functionality for importing
|
The :mod:`songproimport` module provides the functionality for importing
|
||||||
SongPro song files into the current installation database.
|
SongPro song files into the current installation database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
@ -43,5 +44,5 @@ class TestSongProFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading an SongPro file works correctly
|
Test that loading an SongPro file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import(os.path.join(TEST_PATH, 'amazing-grace.txt'),
|
self.file_import(Path(TEST_PATH, 'amazing-grace.txt'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
|
@ -31,6 +31,7 @@ from urllib.error import URLError
|
|||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
from openlp.core import Registry
|
from openlp.core import Registry
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
|
from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
|
||||||
from openlp.plugins.songs.lib import Song
|
from openlp.plugins.songs.lib import Song
|
||||||
from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGIN_PAGE, LOGOUT_URL, BASE_URL
|
from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGIN_PAGE, LOGOUT_URL, BASE_URL
|
||||||
@ -810,15 +811,15 @@ class TestSongSelectFileImport(SongImportTestHelper):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.importer_class_name = 'CCLIFileImport'
|
self.importer_class_name = 'CCLIFileImport'
|
||||||
self.importer_module_name = 'cclifile'
|
self.importer_module_name = 'cclifile'
|
||||||
super(TestSongSelectFileImport, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def test_song_import(self):
|
def test_song_import(self):
|
||||||
"""
|
"""
|
||||||
Test that loading an OpenSong file works correctly on various files
|
Test that loading an OpenSong file works correctly on various files
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'TestSong.bin')],
|
self.file_import([Path(TEST_PATH, 'TestSong.bin')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-bin.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-bin.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'TestSong.txt')],
|
self.file_import([Path(TEST_PATH, 'TestSong.txt')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-txt.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-txt.json')))
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import os
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.importers.songshowplus import SongShowPlusImport
|
from openlp.plugins.songs.lib.importers.songshowplus import SongShowPlusImport
|
||||||
|
|
||||||
@ -46,13 +47,13 @@ class TestSongShowPlusFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading a SongShow Plus file works correctly on various files
|
Test that loading a SongShow Plus file works correctly on various files
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sbsong')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.sbsong')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')],
|
self.file_import([Path(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong')],
|
self.file_import([Path(TEST_PATH, 'a mighty fortress is our god.sbsong')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'cleanse-me.sbsong')],
|
self.file_import([Path(TEST_PATH, 'cleanse-me.sbsong')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json')))
|
||||||
|
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ class TestSongShowPlusImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -82,7 +83,7 @@ class TestSongShowPlusImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_import_wizard = MagicMock()
|
mocked_import_wizard = MagicMock()
|
||||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||||
importer.import_wizard = mocked_import_wizard
|
importer.import_wizard = mocked_import_wizard
|
||||||
importer.stop_import_flag = True
|
importer.stop_import_flag = True
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ class TestSongShowPlusImport(TestCase):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_import_wizard = MagicMock()
|
mocked_import_wizard = MagicMock()
|
||||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||||
importer.import_wizard = mocked_import_wizard
|
importer.import_wizard = mocked_import_wizard
|
||||||
importer.stop_import_flag = True
|
importer.stop_import_flag = True
|
||||||
|
|
||||||
@ -123,7 +124,7 @@ class TestSongShowPlusImport(TestCase):
|
|||||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# WHEN: Supplied with the following arguments replicating verses being added
|
# WHEN: Supplied with the following arguments replicating verses being added
|
||||||
test_values = [
|
test_values = [
|
||||||
@ -151,7 +152,7 @@ class TestSongShowPlusImport(TestCase):
|
|||||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# WHEN: Supplied with the following arguments replicating a verse order being added
|
# WHEN: Supplied with the following arguments replicating a verse order being added
|
||||||
test_values = [
|
test_values = [
|
||||||
|
@ -24,6 +24,8 @@ This module contains tests for the SundayPlus song importer.
|
|||||||
import os
|
import os
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
@ -44,5 +46,5 @@ class TestSundayPlusFileImport(SongImportTestHelper):
|
|||||||
with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
|
with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
|
||||||
mocked_retrieve_windows_encoding:
|
mocked_retrieve_windows_encoding:
|
||||||
mocked_retrieve_windows_encoding.return_value = 'cp1252'
|
mocked_retrieve_windows_encoding.return_value = 'cp1252'
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.ptf')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace.ptf')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||||
|
@ -21,9 +21,10 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the VideoPsalm song importer.
|
This module contains tests for the VideoPsalm song importer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ class TestVideoPsalmFileImport(SongImportTestHelper):
|
|||||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||||
mocked_settings.return_value = mocked_returned_settings
|
mocked_settings.return_value = mocked_returned_settings
|
||||||
# Do the test import
|
# Do the test import
|
||||||
self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'),
|
self.file_import(Path(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json')))
|
||||||
self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold2.json'),
|
self.file_import(Path(TEST_PATH, 'videopsalm-as-safe-a-stronghold2.json'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold2.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold2.json')))
|
||||||
|
@ -22,9 +22,10 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the Words of Worship song importer.
|
This module contains tests for the Words of Worship song importer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
|
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
|
||||||
|
|
||||||
@ -43,10 +44,10 @@ class TestWordsOfWorshipFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading a Words of Worship file works correctly
|
Test that loading a Words of Worship file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
|
self.file_import([Path(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'When morning gilds the skies.wsg')],
|
self.file_import([Path(TEST_PATH, 'When morning gilds the skies.wsg')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))
|
||||||
self.file_import([os.path.join(TEST_PATH, 'Holy Holy Holy Lord God Almighty.wow-song')],
|
self.file_import([Path(TEST_PATH, 'Holy Holy Holy Lord God Almighty.wow-song')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH,
|
self.load_external_result_data(os.path.join(TEST_PATH,
|
||||||
'Holy Holy Holy Lord God Almighty.json')))
|
'Holy Holy Holy Lord God Almighty.json')))
|
||||||
|
@ -23,9 +23,10 @@
|
|||||||
The :mod:`worshipassistantimport` module provides the functionality for importing
|
The :mod:`worshipassistantimport` module provides the functionality for importing
|
||||||
WorshipAssistant song files into the current installation database.
|
WorshipAssistant song files into the current installation database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
@ -43,9 +44,9 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading an Worship Assistant file works correctly
|
Test that loading an Worship Assistant file works correctly
|
||||||
"""
|
"""
|
||||||
self.file_import(os.path.join(TEST_PATH, 'du_herr.csv'),
|
self.file_import(Path(TEST_PATH, 'du_herr.csv'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
|
||||||
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free.csv'),
|
self.file_import(Path(TEST_PATH, 'would_you_be_free.csv'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||||
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
|
self.file_import(Path(TEST_PATH, 'would_you_be_free2.csv'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||||
|
@ -55,7 +55,7 @@ if CAN_RUN_TESTS:
|
|||||||
_title_assignment_list = []
|
_title_assignment_list = []
|
||||||
|
|
||||||
def __init__(self, manager):
|
def __init__(self, manager):
|
||||||
WorshipCenterProImport.__init__(self, manager, filenames=[])
|
WorshipCenterProImport.__init__(self, manager, file_paths=[])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
@ -153,7 +153,7 @@ class TestWorshipCenterProSongImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = WorshipCenterProImport(mocked_manager, filenames=[])
|
importer = WorshipCenterProImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer object should not be None
|
# THEN: The importer object should not be None
|
||||||
self.assertIsNotNone(importer, 'Import should not be none')
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
@ -170,7 +170,7 @@ class TestWorshipCenterProSongImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
mocked_log_error = MagicMock()
|
mocked_log_error = MagicMock()
|
||||||
mocked_translate.return_value = 'Translated Text'
|
mocked_translate.return_value = 'Translated Text'
|
||||||
importer = WorshipCenterProImport(mocked_manager, filenames=[])
|
importer = WorshipCenterProImport(mocked_manager, file_paths=[])
|
||||||
importer.log_error = mocked_log_error
|
importer.log_error = mocked_log_error
|
||||||
importer.import_source = 'import_source'
|
importer.import_source = 'import_source'
|
||||||
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
|
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
|
||||||
|
@ -26,9 +26,10 @@ import os
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport
|
from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
from openlp.core.common import Registry
|
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ class TestZionWorxImport(TestCase):
|
|||||||
mocked_manager = MagicMock()
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: An importer object is created
|
# WHEN: An importer object is created
|
||||||
importer = ZionWorxImport(mocked_manager, filenames=[])
|
importer = ZionWorxImport(mocked_manager, file_paths=[])
|
||||||
|
|
||||||
# THEN: The importer should be an instance of SongImport
|
# THEN: The importer should be an instance of SongImport
|
||||||
self.assertIsInstance(importer, SongImport)
|
self.assertIsInstance(importer, SongImport)
|
||||||
@ -72,5 +73,5 @@ class TestZionWorxFileImport(SongImportTestHelper):
|
|||||||
"""
|
"""
|
||||||
Test that loading an ZionWorx file works correctly on various files
|
Test that loading an ZionWorx file works correctly on various files
|
||||||
"""
|
"""
|
||||||
self.file_import(os.path.join(TEST_PATH, 'zionworx.csv'),
|
self.file_import(Path(TEST_PATH, 'zionworx.csv'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'zionworx.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'zionworx.json')))
|
||||||
|
@ -89,7 +89,7 @@ class SongImportTestHelper(TestCase):
|
|||||||
"""
|
"""
|
||||||
Import the given file and check that it has imported correctly
|
Import the given file and check that it has imported correctly
|
||||||
"""
|
"""
|
||||||
importer = self.importer_class(self.mocked_manager, filenames=[source_file_name])
|
importer = self.importer_class(self.mocked_manager, file_paths=[source_file_name])
|
||||||
importer.import_wizard = self.mocked_import_wizard
|
importer.import_wizard = self.mocked_import_wizard
|
||||||
importer.stop_import_flag = False
|
importer.stop_import_flag = False
|
||||||
importer.topics = []
|
importer.topics = []
|
||||||
|
@ -94,4 +94,3 @@ class TestPluginManager(TestCase, TestMixin):
|
|||||||
self.assertIn('custom', plugin_names, 'There should be a "custom" plugin')
|
self.assertIn('custom', plugin_names, 'There should be a "custom" plugin')
|
||||||
self.assertIn('songusage', plugin_names, 'There should be a "songusage" plugin')
|
self.assertIn('songusage', plugin_names, 'There should be a "songusage" plugin')
|
||||||
self.assertIn('alerts', plugin_names, 'There should be a "alerts" plugin')
|
self.assertIn('alerts', plugin_names, 'There should be a "alerts" plugin')
|
||||||
self.assertIn('remotes', plugin_names, 'There should be a "remotes" plugin')
|
|
||||||
|
Loading…
Reference in New Issue
Block a user