forked from openlp/openlp
head
This commit is contained in:
commit
9190019ce9
@ -107,7 +107,7 @@ class Renderer(object):
|
||||
del self._theme_dimensions[old_theme_name]
|
||||
if theme_name in self._theme_dimensions:
|
||||
del self._theme_dimensions[theme_name]
|
||||
if not only_delete:
|
||||
if not only_delete and theme_name:
|
||||
self._set_theme(theme_name)
|
||||
|
||||
def _set_theme(self, theme_name):
|
||||
|
@ -35,6 +35,7 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
import json
|
||||
from tempfile import mkstemp
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@ -458,7 +459,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
||||
path_file_name = unicode(self.file_name())
|
||||
path, file_name = os.path.split(path_file_name)
|
||||
base_name = os.path.splitext(file_name)[0]
|
||||
service_file_name = '%s.osd' % base_name
|
||||
service_file_name = '%s.osj' % base_name
|
||||
log.debug(u'ServiceManager.save_file - %s', path_file_name)
|
||||
Settings().setValue(self.main_window.service_manager_settings_section + u'/last directory', path)
|
||||
service = []
|
||||
@ -512,7 +513,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
||||
file_size = os.path.getsize(file_item)
|
||||
total_size += file_size
|
||||
log.debug(u'ServiceManager.save_file - ZIP contents size is %i bytes' % total_size)
|
||||
service_content = cPickle.dumps(service)
|
||||
service_content = json.dumps(service)
|
||||
# Usual Zip file cannot exceed 2GiB, file with Zip64 cannot be extracted using unzip in UNIX.
|
||||
allow_zip_64 = (total_size > 2147483648 + len(service_content))
|
||||
log.debug(u'ServiceManager.save_file - allowZip64 is %s' % allow_zip_64)
|
||||
@ -572,7 +573,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
||||
path_file_name = unicode(self.file_name())
|
||||
path, file_name = os.path.split(path_file_name)
|
||||
base_name = os.path.splitext(file_name)[0]
|
||||
service_file_name = '%s.osd' % base_name
|
||||
service_file_name = '%s.osj' % base_name
|
||||
log.debug(u'ServiceManager.save_file - %s', path_file_name)
|
||||
Settings().setValue(self.main_window.service_manager_settings_section + u'/last directory', path)
|
||||
service = []
|
||||
@ -585,7 +586,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
||||
#TODO: check for file item on save.
|
||||
service.append({u'serviceitem': service_item})
|
||||
self.main_window.increment_progress_bar()
|
||||
service_content = cPickle.dumps(service)
|
||||
service_content = json.dumps(service)
|
||||
zip_file = None
|
||||
success = True
|
||||
self.main_window.increment_progress_bar()
|
||||
@ -698,11 +699,14 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
||||
log.debug(u'Extract file: %s', osfile)
|
||||
zip_info.filename = osfile
|
||||
zip_file.extract(zip_info, self.servicePath)
|
||||
if osfile.endswith(u'osd'):
|
||||
if osfile.endswith(u'osj') or osfile.endswith(u'osd'):
|
||||
p_file = os.path.join(self.servicePath, osfile)
|
||||
if 'p_file' in locals():
|
||||
file_to = open(p_file, u'r')
|
||||
items = cPickle.load(file_to)
|
||||
if p_file.endswith(u'osj'):
|
||||
items = json.load(file_to)
|
||||
else:
|
||||
items = cPickle.load(file_to)
|
||||
file_to.close()
|
||||
self.new_file()
|
||||
self.set_file_name(file_name)
|
||||
|
@ -53,27 +53,25 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class AppLocation(object):
|
||||
"""
|
||||
The :class:`AppLocation` class is a static class which retrieves a
|
||||
directory based on the directory type.
|
||||
The :class:`AppLocation` class is a static class which retrieves a directory based on the directory type.
|
||||
"""
|
||||
AppDir = 1
|
||||
ConfigDir = 2
|
||||
DataDir = 3
|
||||
PluginsDir = 4
|
||||
VersionDir = 5
|
||||
CacheDir = 6
|
||||
LanguageDir = 7
|
||||
DataDir = 2
|
||||
PluginsDir = 3
|
||||
VersionDir = 4
|
||||
CacheDir = 5
|
||||
LanguageDir = 6
|
||||
|
||||
# Base path where data/config/cache dir is located
|
||||
BaseDir = None
|
||||
|
||||
@staticmethod
|
||||
def get_directory(dir_type=1):
|
||||
def get_directory(dir_type=AppDir):
|
||||
"""
|
||||
Return the appropriate directory according to the directory type.
|
||||
|
||||
``dir_type``
|
||||
The directory type you want, for instance the data directory.
|
||||
The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
|
||||
"""
|
||||
if dir_type == AppLocation.AppDir:
|
||||
return _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), os.path.split(openlp.__file__)[0])
|
||||
@ -161,16 +159,13 @@ def _get_os_dir_path(dir_type):
|
||||
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp')
|
||||
else:
|
||||
if dir_type == AppLocation.LanguageDir:
|
||||
prefixes = [u'/usr/local', u'/usr']
|
||||
for prefix in prefixes:
|
||||
for prefix in [u'/usr/local', u'/usr']:
|
||||
directory = os.path.join(prefix, u'share', u'openlp')
|
||||
if os.path.exists(directory):
|
||||
return directory
|
||||
return os.path.join(u'/usr', u'share', u'openlp')
|
||||
if XDG_BASE_AVAILABLE:
|
||||
if dir_type == AppLocation.ConfigDir:
|
||||
return os.path.join(unicode(BaseDirectory.xdg_config_home, encoding), u'openlp')
|
||||
elif dir_type == AppLocation.DataDir:
|
||||
if dir_type == AppLocation.DataDir:
|
||||
return os.path.join(unicode(BaseDirectory.xdg_data_home, encoding), u'openlp')
|
||||
elif dir_type == AppLocation.CacheDir:
|
||||
return os.path.join(unicode(BaseDirectory.xdg_cache_home, encoding), u'openlp')
|
||||
|
@ -74,7 +74,13 @@ if os.name == u'nt':
|
||||
HAS_MEDIASHOUT = True
|
||||
except ImportError:
|
||||
log.exception('Error importing %s', 'MediaShoutImport')
|
||||
|
||||
HAS_WORSHIPCENTERPRO = False
|
||||
if os.name == u'nt':
|
||||
try:
|
||||
from worshipcenterproimport import WorshipCenterProImport
|
||||
HAS_WORSHIPCENTERPRO = True
|
||||
except ImportError:
|
||||
log.exception('Error importing %s', 'WorshipCenterProImport')
|
||||
|
||||
class SongFormatSelect(object):
|
||||
"""
|
||||
@ -157,7 +163,8 @@ class SongFormat(object):
|
||||
SongsOfFellowship = 14
|
||||
SundayPlus = 15
|
||||
WordsOfWorship = 16
|
||||
ZionWorx = 17
|
||||
WorshipCenterPro = 17
|
||||
ZionWorx = 18
|
||||
|
||||
# Set optional attribute defaults
|
||||
__defaults__ = {
|
||||
@ -300,6 +307,16 @@ class SongFormat(object):
|
||||
u'filter': u'%s (*.wsg *.wow-song)' %
|
||||
translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
|
||||
},
|
||||
WorshipCenterPro: {
|
||||
u'name': u'WorshipCenter Pro',
|
||||
u'prefix': u'worshipCenterPro',
|
||||
u'canDisable': True,
|
||||
u'selectMode': SongFormatSelect.SingleFile,
|
||||
u'filter': u'%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'WorshipCenter Pro Song Files'),
|
||||
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The WorshipCenter Pro importer is only supported on Windows. It has been disabled due to a missing '
|
||||
'Python module. If you want to use this importer, you will need to install the "pyodbc" module.')
|
||||
},
|
||||
ZionWorx: {
|
||||
u'class': ZionWorxImport,
|
||||
u'name': u'ZionWorx',
|
||||
@ -336,6 +353,7 @@ class SongFormat(object):
|
||||
SongFormat.SongsOfFellowship,
|
||||
SongFormat.SundayPlus,
|
||||
SongFormat.WordsOfWorship,
|
||||
SongFormat.WorshipCenterPro,
|
||||
SongFormat.ZionWorx
|
||||
]
|
||||
|
||||
@ -385,5 +403,9 @@ if HAS_OOO:
|
||||
SongFormat.set(SongFormat.MediaShout, u'availability', HAS_MEDIASHOUT)
|
||||
if HAS_MEDIASHOUT:
|
||||
SongFormat.set(SongFormat.MediaShout, u'class', MediaShoutImport)
|
||||
SongFormat.set(SongFormat.WorshipCenterPro, u'availability', HAS_WORSHIPCENTERPRO)
|
||||
if HAS_WORSHIPCENTERPRO:
|
||||
SongFormat.set(SongFormat.WorshipCenterPro, u'class', WorshipCenterProImport)
|
||||
|
||||
|
||||
__all__ = [u'SongFormat', u'SongFormatSelect']
|
||||
|
85
openlp/plugins/songs/lib/worshipcenterproimport.py
Normal file
85
openlp/plugins/songs/lib/worshipcenterproimport.py
Normal file
@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2013 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`worshipcenterpro` module provides the functionality for importing
|
||||
a WorshipCenter Pro database into the OpenLP database.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import pyodbc
|
||||
|
||||
from openlp.core.lib import translate
|
||||
from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WorshipCenterProImport(SongImport):
|
||||
"""
|
||||
The :class:`WorshipCenterProImport` class provides the ability to import the
|
||||
WorshipCenter Pro Access Database
|
||||
"""
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
Initialise the WorshipCenter Pro importer.
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
|
||||
def doImport(self):
|
||||
"""
|
||||
Receive a single file to import.
|
||||
"""
|
||||
try:
|
||||
conn = pyodbc.connect(u'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s' % self.import_source)
|
||||
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError), e:
|
||||
log.warn(u'Unable to connect the WorshipCenter Pro database %s. %s', self.import_source, unicode(e))
|
||||
# Unfortunately no specific exception type
|
||||
self.logError(self.import_source,
|
||||
translate('SongsPlugin.WorshipCenterProImport', 'Unable to connect the WorshipCenter Pro database.'))
|
||||
return
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(u'SELECT ID, Field, Value FROM __SONGDATA')
|
||||
records = cursor.fetchall()
|
||||
songs = {}
|
||||
for record in records:
|
||||
id = record.ID
|
||||
if not songs.has_key(id):
|
||||
songs[id] = {}
|
||||
songs[id][record.Field] = record.Value
|
||||
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||
for song in songs:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
self.setDefaults()
|
||||
self.title = songs[song][u'TITLE']
|
||||
lyrics = songs[song][u'LYRICS'].strip(u'&crlf;&crlf;')
|
||||
for verse in lyrics.split(u'&crlf;&crlf;'):
|
||||
verse = verse.replace(u'&crlf;', u'\n')
|
||||
self.addVerse(verse)
|
||||
self.finish()
|
@ -62,6 +62,7 @@ WIN32_MODULES = [
|
||||
'win32com',
|
||||
'win32ui',
|
||||
'pywintypes',
|
||||
'pyodbc',
|
||||
]
|
||||
|
||||
MODULES = [
|
||||
|
261
tests/functional/openlp_core_lib/test_serviceitem_json.py
Normal file
261
tests/functional/openlp_core_lib/test_serviceitem_json.py
Normal file
@ -0,0 +1,261 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Package to test the openlp.core.lib package.
|
||||
"""
|
||||
import os
|
||||
import io
|
||||
import cPickle
|
||||
import json
|
||||
import tempfile
|
||||
from unittest import TestCase
|
||||
from mock import MagicMock, patch
|
||||
|
||||
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
|
||||
from lxml import objectify, etree
|
||||
|
||||
VERSE = u'The Lord said to {r}Noah{/r}: \n'\
|
||||
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
|
||||
'The Lord said to {g}Noah{/g}:\n'\
|
||||
'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n'\
|
||||
'Get those children out of the muddy, muddy \n'\
|
||||
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\
|
||||
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
|
||||
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
|
||||
|
||||
|
||||
class TestServiceItem(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the Registry
|
||||
"""
|
||||
Registry.create()
|
||||
mocked_renderer = MagicMock()
|
||||
mocked_renderer.format_slide.return_value = [VERSE]
|
||||
Registry().register(u'renderer', mocked_renderer)
|
||||
Registry().register(u'image_manager', MagicMock())
|
||||
|
||||
def serviceitem_basic_test(self):
|
||||
"""
|
||||
Test the Service Item - basic test
|
||||
"""
|
||||
# GIVEN: A new service item
|
||||
|
||||
# WHEN: A service item is created (without a plugin)
|
||||
service_item = ServiceItem(None)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
assert service_item.missing_frames() is True, u'There should not be any frames in the service item'
|
||||
|
||||
def serviceitem_load_custom_from_service_test(self):
|
||||
"""
|
||||
Test the Service Item - adding a custom slide from a saved service
|
||||
"""
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
service_item = ServiceItem(None)
|
||||
service_item.add_icon = MagicMock()
|
||||
|
||||
# WHEN: adding a custom from a saved Service
|
||||
line = self.convert_file_service_item(u'serviceitem_custom_1.osj')
|
||||
service_item.set_from_service(line)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
assert len(service_item._display_frames) == 0, u'The service item should have no display frames'
|
||||
assert len(service_item.capabilities) == 5, u'There should be 5 default custom item capabilities'
|
||||
service_item.render(True)
|
||||
assert service_item.get_display_title() == u'Test Custom', u'The title should be "Test Custom"'
|
||||
assert service_item.get_frames()[0][u'text'] == VERSE[:-1], \
|
||||
u'The returned text matches the input, except the last line feed'
|
||||
assert service_item.get_rendered_frame(1) == VERSE.split(u'\n', 1)[0], u'The first line has been returned'
|
||||
assert service_item.get_frame_title(0) == u'Slide 1', u'"Slide 1" has been returned as the title'
|
||||
assert service_item.get_frame_title(1) == u'Slide 2', u'"Slide 2" has been returned as the title'
|
||||
assert service_item.get_frame_title(2) == u'', u'Blank has been returned as the title of slide 3'
|
||||
|
||||
def serviceitem_load_image_from_service_test(self):
|
||||
"""
|
||||
Test the Service Item - adding an image from a saved service
|
||||
"""
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
image_name = u'image_1.jpg'
|
||||
test_file = os.path.join(TEST_PATH, image_name)
|
||||
frame_array = {u'path': test_file, u'title': image_name}
|
||||
|
||||
service_item = ServiceItem(None)
|
||||
service_item.add_icon = MagicMock()
|
||||
|
||||
# WHEN: adding an image from a saved Service and mocked exists
|
||||
line = self.convert_file_service_item(u'serviceitem_image_1.osj')
|
||||
with patch(u'openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
|
||||
mocked_exists.return_value = True
|
||||
service_item.set_from_service(line, TEST_PATH)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
assert service_item.get_rendered_frame(0) == test_file, u'The first frame should match the path to the image'
|
||||
assert service_item.get_frames()[0] == frame_array, u'The return should match frame array1'
|
||||
assert service_item.get_frame_path(0) == test_file, u'The frame path should match the full path to the image'
|
||||
assert service_item.get_frame_title(0) == image_name, u'The frame title should match the image name'
|
||||
assert service_item.get_display_title() == image_name, u'The display title should match the first image name'
|
||||
assert service_item.is_image() is True, u'This service item should be of an "image" type'
|
||||
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
|
||||
u'This service item should be able to be Maintained'
|
||||
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
|
||||
u'This service item should be able to be be Previewed'
|
||||
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
|
||||
u'This service item should be able to be run in a can be made to Loop'
|
||||
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
|
||||
u'This service item should be able to have new items added to it'
|
||||
|
||||
def serviceitem_load_image_from_local_service_test(self):
|
||||
"""
|
||||
Test the Service Item - adding an image from a saved local service
|
||||
"""
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
image_name1 = u'image_1.jpg'
|
||||
image_name2 = u'image_2.jpg'
|
||||
test_file1 = os.path.join(u'/home/openlp', image_name1)
|
||||
test_file2 = os.path.join(u'/home/openlp', image_name2)
|
||||
frame_array1 = {u'path': test_file1, u'title': image_name1}
|
||||
frame_array2 = {u'path': test_file2, u'title': image_name2}
|
||||
|
||||
service_item = ServiceItem(None)
|
||||
service_item.add_icon = MagicMock()
|
||||
|
||||
service_item2 = ServiceItem(None)
|
||||
service_item2.add_icon = MagicMock()
|
||||
|
||||
# WHEN: adding an image from a saved Service and mocked exists
|
||||
line = self.convert_file_service_item(u'serviceitem_image_2.osj')
|
||||
line2 = self.convert_file_service_item(u'serviceitem_image_2.osj', 1)
|
||||
|
||||
with patch(u'openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
|
||||
mocked_exists.return_value = True
|
||||
service_item2.set_from_service(line2)
|
||||
service_item.set_from_service(line)
|
||||
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
|
||||
# This test is copied from service_item.py, but is changed since to conform to
|
||||
# new layout of service item. The layout use in serviceitem_image_2.osd is actually invalid now.
|
||||
assert service_item.is_valid is True, u'The first service item should be valid'
|
||||
assert service_item2.is_valid is True, u'The second service item should be valid'
|
||||
assert service_item.get_rendered_frame(0) == test_file1, u'The first frame should match the path to the image'
|
||||
assert service_item2.get_rendered_frame(0) == test_file2, u'The Second frame should match the path to the image'
|
||||
assert service_item.get_frames()[0] == frame_array1, u'The return should match the frame array1'
|
||||
assert service_item2.get_frames()[0] == frame_array2, u'The return should match the frame array2'
|
||||
assert service_item.get_frame_path(0) == test_file1, u'The frame path should match the full path to the image'
|
||||
assert service_item2.get_frame_path(0) == test_file2, u'The frame path should match the full path to the image'
|
||||
assert service_item.get_frame_title(0) == image_name1, u'The 1st frame title should match the image name'
|
||||
assert service_item2.get_frame_title(0) == image_name2, u'The 2nd frame title should match the image name'
|
||||
assert service_item.title.lower() == service_item.name, \
|
||||
u'The plugin name should match the display title, as there are > 1 Images'
|
||||
assert service_item.is_image() is True, u'This service item should be of an "image" type'
|
||||
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
|
||||
u'This service item should be able to be Maintained'
|
||||
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
|
||||
u'This service item should be able to be be Previewed'
|
||||
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
|
||||
u'This service item should be able to be run in a can be made to Loop'
|
||||
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
|
||||
u'This service item should be able to have new items added to it'
|
||||
|
||||
def serviceitem_convert_osd2osj_test(self):
|
||||
"""
|
||||
Test the Service Item - load a osd to service_item, convert to json,
|
||||
load again to service_item and compare the old and new service_item.
|
||||
"""
|
||||
# GIVEN: A valid osd (python pickle format) service in file
|
||||
service_file = os.path.join(TEST_PATH, u'serviceitem_osd2osj.osd')
|
||||
osd_service_items = []
|
||||
try:
|
||||
open_file = open(service_file, u'r')
|
||||
osd_service_items = cPickle.load(open_file)
|
||||
except IOError:
|
||||
osd_service_items = []
|
||||
finally:
|
||||
open_file.close()
|
||||
|
||||
# WHEN: Dumping loaded osd service to json format, and save to file and reloading to service
|
||||
json_service_content = json.dumps(osd_service_items)
|
||||
open_file = None
|
||||
open_filename = u''
|
||||
try:
|
||||
(open_filep, open_filename) = tempfile.mkstemp()
|
||||
open_file = open(open_filename, u'w')
|
||||
open_file.write(json_service_content)
|
||||
open_file.close()
|
||||
open_file = open(open_filename, u'r')
|
||||
json_service_content = open_file.read()
|
||||
except IOError:
|
||||
json_service_content = u''
|
||||
finally:
|
||||
open_file.close()
|
||||
os.remove(open_filename)
|
||||
osj_service_items = json.loads(json_service_content)
|
||||
|
||||
# THEN: The service loaded from osj (json format) should be the same as the service loaded from the original osd (python pickle format)
|
||||
|
||||
# Loop over every item and compare the osj with osd version
|
||||
for osd_item, osj_item in zip(osd_service_items, osj_service_items):
|
||||
# Create service item objects
|
||||
service_item_osd = ServiceItem()
|
||||
service_item_osd.add_icon = MagicMock()
|
||||
|
||||
service_item_osj = ServiceItem()
|
||||
service_item_osj.add_icon = MagicMock()
|
||||
|
||||
with patch(u'openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
|
||||
mocked_exists.return_value = True
|
||||
service_item_osd.set_from_service(osd_item, TEST_PATH)
|
||||
service_item_osj.set_from_service(osj_item, TEST_PATH)
|
||||
|
||||
# Check that the exported/imported attributes are the same
|
||||
assert service_item_osj.is_valid is True, u'The osj service item should be valid'
|
||||
assert service_item_osd.is_valid is True, u'The osd service item should be valid'
|
||||
assert service_item_osj.name == service_item_osd.name , u'The osd and the osj attribute name should be the same!'
|
||||
assert service_item_osj.theme == service_item_osd.theme , u'The osd and the osj attribute theme should be the same!'
|
||||
assert service_item_osj.title == service_item_osd.title , u'The osd and the osj attribute title should be the same!'
|
||||
assert service_item_osj.icon == service_item_osd.icon , u'The osd and the osj attribute icon should be the same!'
|
||||
assert service_item_osj.raw_footer == service_item_osd.raw_footer , u'The osd and the osj attribute raw_footer should be the same!'
|
||||
assert service_item_osj.service_item_type == service_item_osd.service_item_type , u'The osd and the osj attribute service_item_type should be the same!'
|
||||
assert service_item_osj.audit == service_item_osd.audit , u'The osd and the osj attribute audit should be the same!'
|
||||
assert service_item_osj.notes == service_item_osd.notes , u'The osd and the osj attribute notes should be the same!'
|
||||
assert service_item_osj.from_plugin == service_item_osd.from_plugin , u'The osd and the osj attribute from_plugin should be the same!'
|
||||
assert service_item_osj.capabilities == service_item_osd.capabilities , u'The osd and the osj attribute capabilities should be the same!'
|
||||
assert service_item_osj.search_string == service_item_osd.search_string , u'The osd and the osj attribute search_string should be the same!'
|
||||
assert service_item_osj.data_string == service_item_osd.data_string , u'The osd and the osj attribute data_string should be the same!'
|
||||
# Notice that xml_version from osd needs to be utf-8 decoded, since unicode-characters
|
||||
# is written as byte-codes by pickle, while json can escape unicode-characters
|
||||
if service_item_osd.xml_version:
|
||||
assert service_item_osj.xml_version == service_item_osd.xml_version.decode(u'utf-8') , u'The osd and the osj attribute xml_version should be the same!'
|
||||
assert service_item_osj.auto_play_slides_once == service_item_osd.auto_play_slides_once , u'The osd and the osj attribute auto_play_slides_once should be the same!'
|
||||
assert service_item_osj.auto_play_slides_loop == service_item_osd.auto_play_slides_loop , u'The osd and the osj attribute auto_play_slides_loop should be the same!'
|
||||
assert service_item_osj.timed_slide_interval == service_item_osd.timed_slide_interval , u'The osd and the osj attribute timed_slide_interval should be the same!'
|
||||
assert service_item_osj.start_time == service_item_osd.start_time , u'The osd and the osj attribute start_time should be the same!'
|
||||
assert service_item_osj.end_time == service_item_osd.end_time , u'The osd and the osj attribute end_time should be the same!'
|
||||
assert service_item_osj.media_length == service_item_osd.media_length , u'The osd and the osj attribute media_length should be the same!'
|
||||
assert service_item_osj.background_audio == service_item_osd.background_audio , u'The osd and the osj attribute background_audio should be the same!'
|
||||
assert service_item_osj.theme_overwritten == service_item_osd.theme_overwritten , u'The osd and the osj attribute theme_overwritten should be the same!'
|
||||
assert service_item_osj.will_auto_start == service_item_osd.will_auto_start , u'The osd and the osj attribute will_auto_start should be the same!'
|
||||
assert service_item_osj.processor == service_item_osd.processor , u'The osd and the osj attribute processor should be the same!'
|
||||
|
||||
|
||||
|
||||
def convert_file_service_item(self, name, row=0):
|
||||
service_file = os.path.join(TEST_PATH, name)
|
||||
try:
|
||||
open_file = open(service_file, u'r')
|
||||
items = json.load(open_file)
|
||||
first_line = items[row]
|
||||
except IOError:
|
||||
first_line = u''
|
||||
finally:
|
||||
open_file.close()
|
||||
return first_line
|
||||
|
@ -7,7 +7,7 @@ from unittest import TestCase
|
||||
|
||||
from mock import patch, MagicMock
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import Registry, ServiceItem, Settings
|
||||
|
||||
@ -32,6 +32,7 @@ class TestMediaItem(TestCase):
|
||||
fd, self.ini_file = mkstemp(u'.ini')
|
||||
Settings().set_filename(self.ini_file)
|
||||
self.application = QtGui.QApplication.instance()
|
||||
QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
|
@ -0,0 +1,192 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
"""
|
||||
This module contains tests for the WorshipCenter Pro song importer.
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
from mock import patch, MagicMock
|
||||
import pyodbc
|
||||
|
||||
from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport
|
||||
|
||||
class TestRecord(object):
|
||||
"""
|
||||
Microsoft Access Driver is not available on non Microsoft Systems for this reason the :class:`TestRecord` is used
|
||||
to simulate a recordset that would be returned by pyobdc.
|
||||
"""
|
||||
def __init__(self, id, field, value):
|
||||
# The case of the following instance variables is important as it needs to be the same as the ones in use in the
|
||||
# WorshipCenter Pro database.
|
||||
self.ID = id
|
||||
self.Field = field
|
||||
self.Value = value
|
||||
|
||||
class WorshipCenterProImportLogger(WorshipCenterProImport):
|
||||
"""
|
||||
This class logs changes in the title instance variable
|
||||
"""
|
||||
_title_assignment_list = []
|
||||
|
||||
def __init__(self, manager):
|
||||
WorshipCenterProImport.__init__(self, manager)
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title_assignment_list[-1]
|
||||
|
||||
@title.setter
|
||||
def title(self, title):
|
||||
self._title_assignment_list.append(title)
|
||||
|
||||
|
||||
RECORDSET_TEST_DATA = [TestRecord(1, u'TITLE', u'Amazing Grace'),
|
||||
TestRecord(1, u'LYRICS',
|
||||
u'Amazing grace! How&crlf;sweet the sound&crlf;That saved a wretch like me!&crlf;'
|
||||
u'I once was lost,&crlf;but now am found;&crlf;Was blind, but now I see.&crlf;&crlf;'
|
||||
u'\'Twas grace that&crlf;taught my heart to fear,&crlf;And grace my fears relieved;&crlf;'
|
||||
u'How precious did&crlf;that grace appear&crlf;The hour I first believed.&crlf;&crlf;'
|
||||
u'Through many dangers,&crlf;toils and snares,&crlf;I have already come;&crlf;'
|
||||
u'\'Tis grace hath brought&crlf;me safe thus far,&crlf;'
|
||||
u'And grace will lead me home.&crlf;&crlf;The Lord has&crlf;promised good to me,&crlf;'
|
||||
u'His Word my hope secures;&crlf;He will my Shield&crlf;and Portion be,&crlf;'
|
||||
u'As long as life endures.&crlf;&crlf;Yea, when this flesh&crlf;and heart shall fail,&crlf;'
|
||||
u'And mortal life shall cease,&crlf;I shall possess,&crlf;within the veil,&crlf;'
|
||||
u'A life of joy and peace.&crlf;&crlf;The earth shall soon&crlf;dissolve like snow,&crlf;'
|
||||
u'The sun forbear to shine;&crlf;But God, Who called&crlf;me here below,&crlf;'
|
||||
u'Shall be forever mine.&crlf;&crlf;When we\'ve been there&crlf;ten thousand years,&crlf;'
|
||||
u'Bright shining as the sun,&crlf;We\'ve no less days to&crlf;sing God\'s praise&crlf;'
|
||||
u'Than when we\'d first begun.&crlf;&crlf;'),
|
||||
TestRecord(2, u'TITLE', u'Beautiful Garden Of Prayer, The'),
|
||||
TestRecord(2, u'LYRICS',
|
||||
u'There\'s a garden where&crlf;Jesus is waiting,&crlf;'
|
||||
u'There\'s a place that&crlf;is wondrously fair,&crlf;For it glows with the&crlf;'
|
||||
u'light of His presence.&crlf;\'Tis the beautiful&crlf;garden of prayer.&crlf;&crlf;'
|
||||
u'Oh, the beautiful garden,&crlf;the garden of prayer!&crlf;Oh, the beautiful&crlf;'
|
||||
u'garden of prayer!&crlf;There my Savior awaits,&crlf;and He opens the gates&crlf;'
|
||||
u'To the beautiful&crlf;garden of prayer.&crlf;&crlf;There\'s a garden where&crlf;'
|
||||
u'Jesus is waiting,&crlf;And I go with my&crlf;burden and care,&crlf;'
|
||||
u'Just to learn from His&crlf;lips words of comfort&crlf;In the beautiful&crlf;'
|
||||
u'garden of prayer.&crlf;&crlf;There\'s a garden where&crlf;Jesus is waiting,&crlf;'
|
||||
u'And He bids you to come,&crlf;meet Him there;&crlf;Just to bow and&crlf;'
|
||||
u'receive a new blessing&crlf;In the beautiful&crlf;garden of prayer.&crlf;&crlf;')]
|
||||
SONG_TEST_DATA = [{u'title': u'Amazing Grace',
|
||||
u'verses': [
|
||||
(u'Amazing grace! How\nsweet the sound\nThat saved a wretch like me!\nI once was lost,\n'
|
||||
u'but now am found;\nWas blind, but now I see.'),
|
||||
(u'\'Twas grace that\ntaught my heart to fear,\nAnd grace my fears relieved;\nHow precious did\n'
|
||||
u'that grace appear\nThe hour I first believed.'),
|
||||
(u'Through many dangers,\ntoils and snares,\nI have already come;\n\'Tis grace hath brought\n'
|
||||
u'me safe thus far,\nAnd grace will lead me home.'),
|
||||
(u'The Lord has\npromised good to me,\nHis Word my hope secures;\n'
|
||||
u'He will my Shield\nand Portion be,\nAs long as life endures.'),
|
||||
(u'Yea, when this flesh\nand heart shall fail,\nAnd mortal life shall cease,\nI shall possess,\n'
|
||||
u'within the veil,\nA life of joy and peace.'),
|
||||
(u'The earth shall soon\ndissolve like snow,\nThe sun forbear to shine;\nBut God, Who called\n'
|
||||
u'me here below,\nShall be forever mine.'),
|
||||
(u'When we\'ve been there\nten thousand years,\nBright shining as the sun,\n'
|
||||
u'We\'ve no less days to\nsing God\'s praise\nThan when we\'d first begun.')]},
|
||||
{u'title': u'Beautiful Garden Of Prayer, The',
|
||||
u'verses': [
|
||||
(u'There\'s a garden where\nJesus is waiting,\nThere\'s a place that\nis wondrously fair,\n'
|
||||
u'For it glows with the\nlight of His presence.\n\'Tis the beautiful\ngarden of prayer.'),
|
||||
(u'Oh, the beautiful garden,\nthe garden of prayer!\nOh, the beautiful\ngarden of prayer!\n'
|
||||
u'There my Savior awaits,\nand He opens the gates\nTo the beautiful\ngarden of prayer.'),
|
||||
(u'There\'s a garden where\nJesus is waiting,\nAnd I go with my\nburden and care,\n'
|
||||
u'Just to learn from His\nlips words of comfort\nIn the beautiful\ngarden of prayer.'),
|
||||
(u'There\'s a garden where\nJesus is waiting,\nAnd He bids you to come,\nmeet Him there;\n'
|
||||
u'Just to bow and\nreceive a new blessing\nIn the beautiful\ngarden of prayer.')]}]
|
||||
|
||||
class TestWorshipCenterProSongImport(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`worshipcenterproimport` module.
|
||||
"""
|
||||
def create_importer_test(self):
|
||||
"""
|
||||
Test creating an instance of the WorshipCenter Pro file importer
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch(u'openlp.plugins.songs.lib.worshipcenterproimport.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = WorshipCenterProImport(mocked_manager)
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, u'Import should not be none')
|
||||
|
||||
def pyodbc_exception_test(self):
|
||||
"""
|
||||
Test that exceptions raised by pyodbc are handled
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module, a mocked out translate method,
|
||||
# a mocked "manager" and a mocked out logError method.
|
||||
with patch(u'openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
|
||||
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.pyodbc.connect') as mocked_pyodbc_connect, \
|
||||
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
|
||||
mocked_manager = MagicMock()
|
||||
mocked_log_error = MagicMock()
|
||||
mocked_translate.return_value = u'Translated Text'
|
||||
importer = WorshipCenterProImport(mocked_manager)
|
||||
importer.logError = mocked_log_error
|
||||
importer.import_source = u'import_source'
|
||||
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
|
||||
mocked_pyodbc_connect.side_effect = pyodbc_errors
|
||||
|
||||
# WHEN: Calling the doImport method
|
||||
for effect in pyodbc_errors:
|
||||
return_value = importer.doImport()
|
||||
|
||||
# THEN: doImport should return None, and pyodbc, translate & logError are called with known calls
|
||||
self.assertIsNone(return_value, u'doImport should return None when pyodbc raises an exception.')
|
||||
mocked_pyodbc_connect.assert_called_with( u'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
|
||||
mocked_translate.assert_called_with('SongsPlugin.WorshipCenterProImport',
|
||||
'Unable to connect the WorshipCenter Pro database.')
|
||||
mocked_log_error.assert_called_with(u'import_source', u'Translated Text')
|
||||
|
||||
def song_import_test(self):
|
||||
"""
|
||||
Test that a simulated WorshipCenter Pro recordset is imported correctly
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module with a simulated recordset, a mocked out
|
||||
# translate method, a mocked "manager", addVerse method & mocked_finish method.
|
||||
with patch(u'openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
|
||||
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.pyodbc') as mocked_pyodbc, \
|
||||
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
mocked_add_verse = MagicMock()
|
||||
mocked_finish = MagicMock()
|
||||
mocked_pyodbc.connect().cursor().fetchall.return_value = RECORDSET_TEST_DATA
|
||||
mocked_translate.return_value = u'Translated Text'
|
||||
importer = WorshipCenterProImportLogger(mocked_manager)
|
||||
importer.import_source = u'import_source'
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.addVerse = mocked_add_verse
|
||||
importer.stop_import_flag = False
|
||||
importer.finish = mocked_finish
|
||||
|
||||
# WHEN: Calling the doImport method
|
||||
return_value = importer.doImport()
|
||||
|
||||
|
||||
# THEN: doImport should return None, and pyodbc, import_wizard, importer.title and addVerse are called with
|
||||
# known calls
|
||||
self.assertIsNone(return_value, u'doImport should return None when pyodbc raises an exception.')
|
||||
mocked_pyodbc.connect.assert_called_with(u'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
|
||||
mocked_pyodbc.connect().cursor.assert_any_call()
|
||||
mocked_pyodbc.connect().cursor().execute.assert_called_with(u'SELECT ID, Field, Value FROM __SONGDATA')
|
||||
mocked_pyodbc.connect().cursor().fetchall.assert_any_call()
|
||||
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(2)
|
||||
add_verse_call_count = 0
|
||||
for song_data in SONG_TEST_DATA:
|
||||
title_value = song_data[u'title']
|
||||
self.assertIn(title_value, importer._title_assignment_list,
|
||||
u'title should have been set to %s' % title_value)
|
||||
verse_calls = song_data[u'verses']
|
||||
add_verse_call_count += len(verse_calls)
|
||||
for call in verse_calls:
|
||||
mocked_add_verse.assert_any_call(call)
|
||||
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
|
||||
u'Incorrect number of calls made to addVerse')
|
1
tests/resources/serviceitem_custom_1.osj
Normal file
1
tests/resources/serviceitem_custom_1.osj
Normal file
@ -0,0 +1 @@
|
||||
[{"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Test Custom", "capabilities": [2, 1, 5, 13, 8], "theme": null, "background_audio": [], "icon": ":/plugins/plugin_custom.png", "type": 1, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "custom", "footer": ["Test Custom Credits"], "notes": "", "plugin": "custom", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": [{"verseTag": null, "raw_slide": "Slide 1", "title": "Slide 1"}, {"verseTag": null, "raw_slide": "Slide 2", "title": "Slide 2"}]}}]
|
1
tests/resources/serviceitem_image_1.osj
Normal file
1
tests/resources/serviceitem_image_1.osj
Normal file
@ -0,0 +1 @@
|
||||
[{"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Images", "capabilities": [3, 1, 5, 6], "theme": -1, "background_audio": [], "icon": ":/plugins/plugin_images.png", "type": 2, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "images", "footer": [], "notes": "", "plugin": "images", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": ["image_1.jpg"]}}]
|
1
tests/resources/serviceitem_image_2.osj
Normal file
1
tests/resources/serviceitem_image_2.osj
Normal file
@ -0,0 +1 @@
|
||||
[{"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Images", "capabilities": [3, 1, 5, 6], "theme": -1, "background_audio": [], "icon": ":/plugins/plugin_images.png", "type": 2, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "images", "footer": [], "notes": "", "plugin": "images", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": [{"path": "/home/openlp/image_1.jpg", "title": "image_1.jpg"}]}}, {"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Images", "capabilities": [3, 1, 5, 6], "theme": -1, "background_audio": [], "icon": ":/plugins/plugin_images.png", "type": 2, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "images", "footer": [], "notes": "", "plugin": "images", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": [{"path": "/home/openlp/image_2.jpg", "title": "image_2.jpg"}]}}]
|
1037
tests/resources/serviceitem_osd2osj.osd
Normal file
1037
tests/resources/serviceitem_osd2osj.osd
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user