diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 5161246c0..b7dc12b88 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -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): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 9ef1269ba..4f5534602 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -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) diff --git a/openlp/core/utils/applocation.py b/openlp/core/utils/applocation.py index 2f1cb45ba..5ec0fc5a6 100644 --- a/openlp/core/utils/applocation.py +++ b/openlp/core/utils/applocation.py @@ -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') diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 76b3e0a70..3ce4cb143 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -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'] diff --git a/openlp/plugins/songs/lib/worshipcenterproimport.py b/openlp/plugins/songs/lib/worshipcenterproimport.py new file mode 100644 index 000000000..80237d38b --- /dev/null +++ b/openlp/plugins/songs/lib/worshipcenterproimport.py @@ -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() diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index e3849d449..bd7786b01 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -62,6 +62,7 @@ WIN32_MODULES = [ 'win32com', 'win32ui', 'pywintypes', + 'pyodbc', ] MODULES = [ diff --git a/tests/functional/openlp_core_lib/test_serviceitem_json.py b/tests/functional/openlp_core_lib/test_serviceitem_json.py new file mode 100644 index 000000000..3e0b74f2c --- /dev/null +++ b/tests/functional/openlp_core_lib/test_serviceitem_json.py @@ -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 + diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index 37f1c76e4..21616e959 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -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): """ diff --git a/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py b/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py new file mode 100644 index 000000000..655eff900 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py @@ -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') \ No newline at end of file diff --git a/tests/resources/serviceitem_custom_1.osj b/tests/resources/serviceitem_custom_1.osj new file mode 100644 index 000000000..da585ff18 --- /dev/null +++ b/tests/resources/serviceitem_custom_1.osj @@ -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"}]}}] \ No newline at end of file diff --git a/tests/resources/serviceitem_image_1.osj b/tests/resources/serviceitem_image_1.osj new file mode 100644 index 000000000..eca5e3287 --- /dev/null +++ b/tests/resources/serviceitem_image_1.osj @@ -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"]}}] diff --git a/tests/resources/serviceitem_image_2.osj b/tests/resources/serviceitem_image_2.osj new file mode 100644 index 000000000..3fb66800c --- /dev/null +++ b/tests/resources/serviceitem_image_2.osj @@ -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"}]}}] diff --git a/tests/resources/serviceitem_osd2osj.osd b/tests/resources/serviceitem_osd2osj.osd new file mode 100644 index 000000000..2a09588c4 --- /dev/null +++ b/tests/resources/serviceitem_osd2osj.osd @@ -0,0 +1,1037 @@ +(lp1 +(dp2 +Vserviceitem +p3 +(dp4 +Vheader +p5 +(dp6 +Vxml_version +p7 +S'\nAmazing GraceJohn NewtonAmazing Grace! how sweet the sound
That saved a wretch like me;
I once was lost, but now am found,
Was blind, but now I see.
\xe2\x80\x99Twas grace that taught my heart to fear,
And grace my fears relieved;
How precious did that grace appear,
The hour I first believed!
Through many dangers, toils and snares
I have already come;
\xe2\x80\x99Tis grace that brought me safe thus far,
And grace will lead me home.
The Lord has promised good to me,
His word my hope secures;
He will my shield and portion be
As long as life endures.
Yes, when this heart and flesh shall fail,
And mortal life shall cease,
I shall possess within the veil
A life of joy and peace.
When we\xe2\x80\x99ve been there a thousand years,
Bright shining as the sun,
We\xe2\x80\x99ve no less days to sing God\xe2\x80\x99s praise
Than when we first begun.
' +p8 +sVauto_play_slides_loop +p9 +I00 +sVauto_play_slides_once +p10 +I00 +sVwill_auto_start +p11 +I00 +sVtitle +p12 +VAmazing Grace +p13 +sVcapabilities +p14 +(lp15 +I2 +aI1 +aI5 +aI8 +aI9 +aI13 +asVtheme +p16 +NsVbackground_audio +p17 +(lp18 +sVicon +p19 +V:/plugins/plugin_songs.png +p20 +sVtype +p21 +I1 +sVstart_time +p22 +I0 +sVfrom_plugin +p23 +I00 +sVmedia_length +p24 +I0 +sVdata +p25 +(dp26 +Vauthors +p27 +VJohn Newton +p28 +sVtitle +p29 +Vamazing grace@ +p30 +ssVtimed_slide_interval +p31 +I0 +sVaudit +p32 +(lp33 +g13 +a(lp34 +g28 +aaV +aV +asVsearch +p35 +V +sVname +p36 +Vsongs +p37 +sVfooter +p38 +(lp39 +g13 +aVJohn Newton +p40 +aV +asVnotes +p41 +V +sVplugin +p42 +g37 +sVtheme_overwritten +p43 +I00 +sVend_time +p44 +I0 +sVprocessor +p45 +Nssg25 +(lp46 +(dp47 +VverseTag +p48 +VV1 +p49 +sVraw_slide +p50 +VAmazing Grace! how sweet the sound\u000aThat saved a wretch like me;\u000aI once was lost, but now am found,\u000aWas blind, but now I see. +p51 +sVtitle +p52 +VAmazing Grace! how sweet the s +p53 +sa(dp54 +g48 +VV2 +p55 +sg50 +V\u2019Twas grace that taught my heart to fear,\u000aAnd grace my fears relieved;\u000aHow precious did that grace appear,\u000aThe hour I first believed! +p56 +sg52 +V\u2019Twas grace that taught my hea +p57 +sa(dp58 +g48 +VV3 +p59 +sg50 +VThrough many dangers, toils and snares\u000aI have already come;\u000a\u2019Tis grace that brought me safe thus far,\u000aAnd grace will lead me home. +p60 +sg52 +VThrough many dangers, toils an +p61 +sa(dp62 +g48 +VV4 +p63 +sg50 +VThe Lord has promised good to me,\u000aHis word my hope secures;\u000aHe will my shield and portion be\u000aAs long as life endures. +p64 +sg52 +VThe Lord has promised good to +p65 +sa(dp66 +g48 +VV5 +p67 +sg50 +VYes, when this heart and flesh shall fail,\u000aAnd mortal life shall cease,\u000aI shall possess within the veil\u000aA life of joy and peace. +p68 +sg52 +VYes, when this heart and flesh +p69 +sa(dp70 +g48 +VV6 +p71 +sg50 +VWhen we\u2019ve been there a thousand years,\u000aBright shining as the sun,\u000aWe\u2019ve no less days to sing God\u2019s praise\u000aThan when we first begun. +p72 +sg52 +VWhen we\u2019ve been there a thousa +p73 +sassa(dp74 +g3 +(dp75 +g5 +(dp76 +g7 +S'\nAs With GladnessW. C. DixAs with gladness men of old
Did the guiding star behold;
As with joy they hailed its light,
Leading onward, beaming bright,
So, most gracious God, may we
Evermore be led by Thee.
As with joyful steps they sped,
Saviour, to Thy lowly bed,
There to bend the knee before
Thee whom heaven and earth adore,
So may we with willing feet
Ever seek Thy mercy-seat.
As they offered gifts most rare
At Thy cradle rude and bare,
So may we with holy joy,
Pure, and free from sin\xe2\x80\x99s alloy,
All our costliest treasures bring,
Christ, to Thee, our heavenly King.
Holy Jesus, every day
Keep us in the narrow way;
And, when earthly things are past,
Bring our ransomed souls at last
Where they need no star to guide,
Where no clouds Thy glory hide.
In the heavenly country bright
Need they no created light;
Thou its light, its joy, its crown,
Thou its sun, which goes not down.
There forever may we sing
Hallelujahs to our King.
' +p77 +sg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VAs With Gladness +p78 +sg14 +(lp79 +I2 +aI1 +aI5 +aI8 +aI9 +aI13 +asg16 +Nsg17 +(lp80 +sg19 +g20 +sg21 +I1 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +(dp81 +g27 +VW. C. Dix +p82 +sg29 +Vas with gladness@ +p83 +ssg31 +I0 +sg32 +(lp84 +g78 +a(lp85 +g82 +aaV +aV +asg35 +V +sg36 +g37 +sg38 +(lp86 +g78 +aVW. C. Dix +p87 +aV +asg41 +V +sg42 +g37 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp88 +(dp89 +g48 +VV1 +p90 +sg50 +VAs with gladness men of old\u000aDid the guiding star behold;\u000aAs with joy they hailed its light,\u000aLeading onward, beaming bright,\u000aSo, most gracious God, may we\u000aEvermore be led by Thee. +p91 +sg52 +VAs with gladness men of old +p92 +sa(dp93 +g48 +VV2 +p94 +sg50 +VAs with joyful steps they sped,\u000aSaviour, to Thy lowly bed,\u000aThere to bend the knee before\u000aThee whom heaven and earth adore,\u000aSo may we with willing feet\u000aEver seek Thy mercy-seat.\u000aAs they offered gifts most rare\u000aAt Thy cradle rude and bare,\u000aSo may we with holy joy,\u000aPure, and free from sin\u2019s alloy,\u000aAll our costliest treasures bring,\u000aChrist, to Thee, our heavenly King. +p95 +sg52 +VAs with joyful steps they sped +p96 +sa(dp97 +g48 +VV3 +p98 +sg50 +VHoly Jesus, every day\u000aKeep us in the narrow way;\u000aAnd, when earthly things are past,\u000aBring our ransomed souls at last\u000aWhere they need no star to guide,\u000aWhere no clouds Thy glory hide. +p99 +sg52 +VHoly Jesus, every day +p100 +sa(dp101 +g48 +VV4 +p102 +sg50 +VIn the heavenly country bright\u000aNeed they no created light;\u000aThou its light, its joy, its crown,\u000aThou its sun, which goes not down.\u000aThere forever may we sing\u000aHallelujahs to our King. +p103 +sg52 +VIn the heavenly country bright +p104 +sassa(dp105 +g3 +(dp106 +g5 +(dp107 +g7 +Nsg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VPsalms 23:1-6 (KJV) +p108 +sg14 +(lp109 +I7 +aI1 +aI5 +aI14 +asg16 +Nsg17 +(lp110 +sg19 +V:/plugins/plugin_bibles.png +p111 +sg21 +I1 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +V +sg31 +I0 +sg32 +V +sg35 +V +sg36 +Vbibles +p112 +sg38 +(lp113 +VPsalms 23:1-6 +p114 +aVKJV, Public Domain +p115 +asg41 +V +sg42 +g112 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp116 +(dp117 +g48 +Nsg50 +V{su}23:1{/su}  The LORD [is] my shepherd; I shall not want.\u000a {su}23:2{/su} He maketh me to lie down in green pastures: he leadeth me beside the still waters.\u000a {su}23:3{/su} He restoreth my soul: he leadeth me in the paths of righteousness for his name's sake.\u000a {su}23:4{/su} Yea, though I walk through the valley of the shadow of death, I will fear no evil: for thou [art] with me; thy rod and thy staff they comfort me.\u000a {su}23:5{/su} Thou preparest a table before me in the presence of mine enemies: thou anointest my head with oil; my cup runneth over.\u000a {su}23:6{/su} Surely goodness and mercy shall follow me all the days of my life: and I will dwell in the house of the LORD for ever.\u000a +p118 +sg52 +V{su}23:1{/su} \nBe Still and KnowAuthor UnknownBe still and know that I am God,
Be still and know that I am God,
Be still and know that I am God.
I am the Lord that healeth thee,
I am the Lord that healeth thee,
I am the Lord that healeth thee.
In Thee, O Lord, do I put my trust,
In Thee, O Lord, do I put my trust,
In Thee, O Lord, do I put my trust.
' +p123 +sg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VBe Still and Know +p124 +sg14 +(lp125 +I2 +aI1 +aI5 +aI8 +aI9 +aI13 +asg16 +Nsg17 +(lp126 +sg19 +g20 +sg21 +I1 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +(dp127 +g27 +VAuthor Unknown +p128 +sg29 +Vbe still and know@ +p129 +ssg31 +I0 +sg32 +(lp130 +g124 +a(lp131 +g128 +aaV +aV +asg35 +V +sg36 +g37 +sg38 +(lp132 +g124 +aVAuthor Unknown +p133 +aV +asg41 +V +sg42 +g37 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp134 +(dp135 +g48 +VV1 +p136 +sg50 +VBe still and know that I am God,\u000aBe still and know that I am God,\u000aBe still and know that I am God. +p137 +sg52 +VBe still and know that I am Go +p138 +sa(dp139 +g48 +VV2 +p140 +sg50 +VI am the Lord that healeth thee,\u000aI am the Lord that healeth thee,\u000aI am the Lord that healeth thee. +p141 +sg52 +VI am the Lord that healeth the +p142 +sa(dp143 +g48 +VV3 +p144 +sg50 +VIn Thee, O Lord, do I put my trust,\u000aIn Thee, O Lord, do I put my trust,\u000aIn Thee, O Lord, do I put my trust. +p145 +sg52 +VIn Thee, O Lord, do I put my t +p146 +sassa(dp147 +g3 +(dp148 +g5 +(dp149 +g7 +Nsg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VImages +p150 +sg14 +(lp151 +I3 +aI1 +aI5 +aI6 +asg16 +I-1 +sg17 +(lp152 +sg19 +V:/plugins/plugin_images.png +p153 +sg21 +I2 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +V +sg31 +I0 +sg32 +V +sg35 +V +sg36 +Vimages +p154 +sg38 +(lp155 +sg41 +V +sg42 +g154 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp156 +Vimagetest1.jpg +p157 +assa(dp158 +g3 +(dp159 +g5 +(dp160 +g7 +S'\nBlessed AssuranceFanny CrosbyBlessed assurance, Jesus is mine:
O what a foretaste of glory divine!
Heir of salvation, purchase of God;
Born of His Spirit, washed in His blood.
This is my story, this is my song,
Praising my Saviour all the day long.
This is my story, this is my song,
Praising my Saviour all the day long.
Perfect submission, perfect delight,
Visions of rapture burst on my sight;
Angels descending bring from above
Echoes of mercy, whispers of love.
Perfect submission, all is at rest,
I in my Saviour am happy and blessed;
Watching and waiting, looking above,
Filled with His goodness, lost in His love.
' +p161 +sg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VBlessed Assurance +p162 +sg14 +(lp163 +I2 +aI1 +aI5 +aI8 +aI9 +aI13 +asg16 +Nsg17 +(lp164 +sg19 +g20 +sg21 +I1 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +(dp165 +g27 +VFanny Crosby +p166 +sg29 +Vblessed assurance@ +p167 +ssg31 +I0 +sg32 +(lp168 +g162 +a(lp169 +g166 +aaV +aV +asg35 +V +sg36 +g37 +sg38 +(lp170 +g162 +aVFanny Crosby +p171 +aV +asg41 +V +sg42 +g37 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp172 +(dp173 +g48 +VV1 +p174 +sg50 +VBlessed assurance, Jesus is mine:\u000aO what a foretaste of glory divine!\u000aHeir of salvation, purchase of God;\u000aBorn of His Spirit, washed in His blood. +p175 +sg52 +VBlessed assurance, Jesus is mi +p176 +sa(dp177 +g48 +VV2 +p178 +sg50 +VThis is my story, this is my song,\u000aPraising my Saviour all the day long.\u000aThis is my story, this is my song,\u000aPraising my Saviour all the day long. +p179 +sg52 +VThis is my story, this is my s +p180 +sa(dp181 +g48 +VV3 +p182 +sg50 +VPerfect submission, perfect delight,\u000aVisions of rapture burst on my sight;\u000aAngels descending bring from above\u000aEchoes of mercy, whispers of love. +p183 +sg52 +VPerfect submission, perfect de +p184 +sa(dp185 +g48 +VV4 +p186 +sg50 +VPerfect submission, all is at rest,\u000aI in my Saviour am happy and blessed;\u000aWatching and waiting, looking above,\u000aFilled with His goodness, lost in His love. +p187 +sg52 +VPerfect submission, all is at +p188 +sassa(dp189 +g3 +(dp190 +g5 +(dp191 +g7 +S'\nCrown Him With Many CrownsMatthew BridgesGodfrey ThringCrown Him with many crowns,
The Lamb upon His throne;
Hark, how the heavenly anthem drowns
All music but its own!
Awake, my soul, and sing
Of Him who died for thee,
And hail Him as thy matchless King
Through all eternity.
Crown Him the Lord of life,
Who triumphed o\xe2\x80\x99er the grave
And rose victorious in the strife
For those He came to save:
His glories now we sing,
Who died and rose on high,
Who died eternal life to bring
And lives that death may die.
Crown Him the Lord of love;
Behold His hands and side,
Those wounds yet visible above
In beauty glorified:
No angel in the sky
Can fully bear that sight,
But downward bends His burning eye
At mysteries so bright.
Crown Him the Lord of peace,
Whose power a sceptre sways
From pole to pole, that wars may cease,
And all be prayer and praise:
His reign shall know no end,
And round His pierced feet
Fair flowers of paradise extend
Their fragrance ever sweet.
Crown Him the Lord of years,
The Potentate of time,
Creator of the rolling spheres,
Ineffably sublime!
All hail, Redeemer, hail!
For Thou hast died for me;
Thy praise shall never, never fail
Throughout eternity.
' +p192 +sg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VCrown Him With Many Crowns +p193 +sg14 +(lp194 +I2 +aI1 +aI5 +aI8 +aI9 +aI13 +asg16 +Nsg17 +(lp195 +sg19 +g20 +sg21 +I1 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +(dp196 +g27 +VMatthew Bridges, Godfrey Thring +p197 +sg29 +Vcrown him with many crowns@ +p198 +ssg31 +I0 +sg32 +(lp199 +g193 +a(lp200 +VMatthew Bridges +p201 +aVGodfrey Thring +p202 +aaV +aV +asg35 +V +sg36 +g37 +sg38 +(lp203 +g193 +aVMatthew Bridges og Godfrey Thring +p204 +aV +asg41 +V +sg42 +g37 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp205 +(dp206 +g48 +VV1 +p207 +sg50 +VCrown Him with many crowns,\u000aThe Lamb upon His throne;\u000aHark, how the heavenly anthem drowns\u000aAll music but its own!\u000aAwake, my soul, and sing\u000aOf Him who died for thee,\u000aAnd hail Him as thy matchless King\u000aThrough all eternity. +p208 +sg52 +VCrown Him with many crowns, +p209 +sa(dp210 +g48 +VV2 +p211 +sg50 +VCrown Him the Lord of life,\u000aWho triumphed o\u2019er the grave\u000aAnd rose victorious in the strife\u000aFor those He came to save:\u000aHis glories now we sing,\u000aWho died and rose on high,\u000aWho died eternal life to bring\u000aAnd lives that death may die. +p212 +sg52 +VCrown Him the Lord of life, +p213 +sa(dp214 +g48 +VV3 +p215 +sg50 +VCrown Him the Lord of love;\u000aBehold His hands and side,\u000aThose wounds yet visible above\u000aIn beauty glorified:\u000aNo angel in the sky\u000aCan fully bear that sight,\u000aBut downward bends His burning eye\u000aAt mysteries so bright. +p216 +sg52 +VCrown Him the Lord of love; +p217 +sa(dp218 +g48 +VV4 +p219 +sg50 +VCrown Him the Lord of peace,\u000aWhose power a sceptre sways\u000aFrom pole to pole, that wars may cease,\u000aAnd all be prayer and praise:\u000aHis reign shall know no end,\u000aAnd round His pierced feet\u000aFair flowers of paradise extend\u000aTheir fragrance ever sweet. +p220 +sg52 +VCrown Him the Lord of peace, +p221 +sa(dp222 +g48 +VV5 +p223 +sg50 +VCrown Him the Lord of years,\u000aThe Potentate of time,\u000aCreator of the rolling spheres,\u000aIneffably sublime!\u000aAll hail, Redeemer, hail!\u000aFor Thou hast died for me;\u000aThy praise shall never, never fail\u000aThroughout eternity. +p224 +sg52 +VCrown Him the Lord of years, +p225 +sassa(dp226 +g3 +(dp227 +g5 +(dp228 +g7 +Nsg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +g150 +sg14 +(lp229 +I3 +aI1 +aI5 +aI6 +asg16 +I-1 +sg17 +(lp230 +sg19 +g153 +sg21 +I2 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +V +sg31 +I0 +sg32 +V +sg35 +V +sg36 +g154 +sg38 +(lp231 +sg41 +V +sg42 +g154 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp232 +Vimage_test2.jpg +p233 +assa(dp234 +g3 +(dp235 +g5 +(dp236 +g7 +Nsg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VVideo_1.mov +p237 +sg14 +(lp238 +I16 +aI4 +asg16 +I-1 +sg17 +(lp239 +sg19 +V:/plugins/plugin_media.png +p240 +sg21 +I3 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +V +sg31 +I0 +sg32 +V +sg35 +V +sg36 +Vmedia +p241 +sg38 +(lp242 +sg41 +V +sg42 +g241 +sg43 +I00 +sg44 +I0 +sg45 +VAutomatic +p243 +ssg25 +(lp244 +(dp245 +Vpath +p246 +V/home/openlp/test_media/all_free_to_users +p247 +sVimage +p248 +V:/media/slidecontroller_multimedia.png +p249 +sg12 +g237 +sassa(dp250 +g3 +(dp251 +g5 +(dp252 +g7 +S'\nFill Thou My LifeHoratius BonarFill thou my life, O Lord my God,
In every part with praise,
That my whole being may proclaim
Thy being and Thy ways.
Not for the lip of praise alone,
Nor e\xe2\x80\x99en the praising heart
I ask, but for a life made up
Of praise in every part:
Praise in the common things of life,
Its goings out and in;
Praise in each duty and each deed,
However small and mean.
Fill every part of me with praise;
Let all my being speak
Of Thee and of Thy love, O Lord,
Poor though I be and weak.
So shall Thou, gracious Lord, from me
Receive the glory due;
And so shall I begin on earth
The song forever new.
So shall no part of day or night
From sacredness be free;
But all my life, in every step,
Be fellowship with Thee.
' +p253 +sg9 +I00 +sg10 +I00 +sg11 +I00 +sg12 +VFill Thou My Life +p254 +sg14 +(lp255 +I2 +aI1 +aI5 +aI8 +aI9 +aI13 +asg16 +Nsg17 +(lp256 +sg19 +g20 +sg21 +I1 +sg22 +I0 +sg23 +I00 +sg24 +I0 +sg25 +(dp257 +g27 +VHoratius Bonar +p258 +sg29 +Vfill thou my life@ +p259 +ssg31 +I0 +sg32 +(lp260 +g254 +a(lp261 +g258 +aaV +aV +asg35 +V +sg36 +g37 +sg38 +(lp262 +g254 +aVHoratius Bonar +p263 +aV +asg41 +V +sg42 +g37 +sg43 +I00 +sg44 +I0 +sg45 +Nssg25 +(lp264 +(dp265 +g48 +VV1 +p266 +sg50 +VFill thou my life, O Lord my God,\u000aIn every part with praise,\u000aThat my whole being may proclaim\u000aThy being and Thy ways. +p267 +sg52 +VFill thou my life, O Lord my G +p268 +sa(dp269 +g48 +VV2 +p270 +sg50 +VNot for the lip of praise alone,\u000aNor e\u2019en the praising heart\u000aI ask, but for a life made up\u000aOf praise in every part: +p271 +sg52 +VNot for the lip of praise alon +p272 +sa(dp273 +g48 +VV3 +p274 +sg50 +VPraise in the common things of life,\u000aIts goings out and in;\u000aPraise in each duty and each deed,\u000aHowever small and mean. +p275 +sg52 +VPraise in the common things of +p276 +sa(dp277 +g48 +VV4 +p278 +sg50 +VFill every part of me with praise;\u000aLet all my being speak\u000aOf Thee and of Thy love, O Lord,\u000aPoor though I be and weak. +p279 +sg52 +VFill every part of me with pra +p280 +sa(dp281 +g48 +VV5 +p282 +sg50 +VSo shall Thou, gracious Lord, from me\u000aReceive the glory due;\u000aAnd so shall I begin on earth\u000aThe song forever new. +p283 +sg52 +VSo shall Thou, gracious Lord, +p284 +sa(dp285 +g48 +VV6 +p286 +sg50 +VSo shall no part of day or night\u000aFrom sacredness be free;\u000aBut all my life, in every step,\u000aBe fellowship with Thee. +p287 +sg52 +VSo shall no part of day or nig +p288 +sassa.