This commit is contained in:
Tim Bentley 2013-10-04 20:28:09 +01:00
commit bd7d412cc9
46 changed files with 1957 additions and 1199 deletions

View File

@ -82,10 +82,17 @@ class MediaManagerItem(QtGui.QWidget):
"""
Constructor to create the media manager item.
"""
super(MediaManagerItem, self).__init__()
super(MediaManagerItem, self).__init__(parent)
self.plugin = plugin
self._setup()
self.setup_item()
def _setup(self):
"""
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
"""
self.hide()
self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
self.plugin = plugin
visible_title = self.plugin.get_string(StringContent.VisibleName)
self.title = str(visible_title['title'])
Registry().register(self.plugin.name, self)
@ -106,6 +113,12 @@ class MediaManagerItem(QtGui.QWidget):
QtCore.QObject.connect(self, QtCore.SIGNAL('%s_go_live' % self.plugin.name), self.go_live_remote)
QtCore.QObject.connect(self, QtCore.SIGNAL('%s_add_to_service' % self.plugin.name), self.add_to_service_remote)
def setup_item(self):
"""
Override this for additional Plugin setup
"""
pass
def required_icons(self):
"""
This method is called to define the icons for the plugin. It provides a default set and the plugin is able to

View File

@ -75,12 +75,6 @@ try:
ICU_VERSION = 'OK'
except ImportError:
ICU_VERSION = '-'
try:
import cherrypy
CHERRYPY_VERSION = cherrypy.__version__
except ImportError:
CHERRYPY_VERSION = '-'
try:
WEBKIT_VERSION = QtWebKit.qWebKitVersion()
except AttributeError:
@ -140,7 +134,6 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
'Chardet: %s\n' % CHARDET_VERSION + \
'PyEnchant: %s\n' % ENCHANT_VERSION + \
'Mako: %s\n' % MAKO_VERSION + \
'CherryPy: %s\n' % CHERRYPY_VERSION + \
'pyICU: %s\n' % ICU_VERSION + \
'pyUNO bridge: %s\n' % self._pyuno_import() + \
'VLC: %s\n' % VLC_VERSION

View File

@ -67,6 +67,9 @@ __default_settings__ = {
class BiblePlugin(Plugin):
"""
The Bible plugin provides a plugin for managing and displaying Bibles.
"""
log.info('Bible Plugin loaded')
def __init__(self):
@ -74,13 +77,14 @@ class BiblePlugin(Plugin):
self.weight = -9
self.icon_path = ':/plugins/plugin_bibles.png'
self.icon = build_icon(self.icon_path)
self.manager = None
self.manager = BibleManager(self)
def initialise(self):
"""
Initialise the Bible plugin.
"""
log.info('bibles Initialising')
if self.manager is None:
self.manager = BibleManager(self)
Plugin.initialise(self)
super(BiblePlugin, self).initialise()
self.import_bible_item.setVisible(True)
action_list = ActionList.get_instance()
action_list.add_action(self.import_bible_item, UiStrings().Import)
@ -107,7 +111,7 @@ class BiblePlugin(Plugin):
"""
Perform tasks on application startup
"""
Plugin.app_startup(self)
super(BiblePlugin, self).app_startup()
if self.manager.old_bible_databases:
if QtGui.QMessageBox.information(self.main_window,
translate('OpenLP', 'Information'),

View File

@ -64,6 +64,11 @@ class BibleMediaItem(MediaManagerItem):
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
MediaManagerItem.__init__(self, parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
# Place to store the search results for both bibles.
self.settings = self.plugin.settings_tab
self.quick_preview_allowed = True

View File

@ -58,6 +58,11 @@ class CustomMediaItem(MediaManagerItem):
def __init__(self, parent, plugin):
self.icon_path = 'custom/custom'
super(CustomMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.edit_custom_form = EditCustomForm(self, self.main_window, self.plugin.manager)
self.single_service_item = False
self.quick_preview_allowed = True
@ -65,7 +70,7 @@ class CustomMediaItem(MediaManagerItem):
# Holds information about whether the edit is remotely triggered and
# which Custom is required.
self.remote_custom = -1
self.manager = plugin.manager
self.manager = self.plugin.manager
def add_end_header_bar(self):
self.toolbar.addSeparator()

View File

@ -52,10 +52,18 @@ class ImageMediaItem(MediaManagerItem):
def __init__(self, parent, plugin):
self.icon_path = 'images/image'
self.manager = None
self.choose_group_form = None
self.add_group_form = None
super(ImageMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.quick_preview_allowed = True
self.has_search = True
self.manager = plugin.manager
self.manager = self.plugin.manager
self.choose_group_form = ChooseGroupForm(self)
self.add_group_form = AddGroupForm(self)
self.fill_groups_combobox(self.choose_group_form.group_combobox)
@ -91,8 +99,8 @@ class ImageMediaItem(MediaManagerItem):
self.list_view.setIconSize(QtCore.QSize(88, 50))
self.list_view.setIndentation(self.list_view.default_indentation)
self.list_view.allow_internal_dnd = True
self.servicePath = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
check_directory_exists(self.servicePath)
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
check_directory_exists(self.service_path)
# Load images from the database
self.load_full_list(
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)
@ -193,7 +201,7 @@ class ImageMediaItem(MediaManagerItem):
"""
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
for image in images:
delete_file(os.path.join(self.servicePath, os.path.split(image.filename)[1]))
delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1]))
self.manager.delete_object(ImageFilenames, image.id)
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
for group in image_groups:
@ -215,7 +223,7 @@ class ImageMediaItem(MediaManagerItem):
if row_item:
item_data = row_item.data(0, QtCore.Qt.UserRole)
if isinstance(item_data, ImageFilenames):
delete_file(os.path.join(self.servicePath, row_item.text(0)))
delete_file(os.path.join(self.service_path, row_item.text(0)))
if item_data.group_id == 0:
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
else:
@ -339,7 +347,7 @@ class ImageMediaItem(MediaManagerItem):
for imageFile in images:
log.debug('Loading image: %s', imageFile.filename)
filename = os.path.split(imageFile.filename)[1]
thumb = os.path.join(self.servicePath, filename)
thumb = os.path.join(self.service_path, filename)
if not os.path.exists(imageFile.filename):
icon = build_icon(':/general/general_delete.png')
else:
@ -672,7 +680,16 @@ class ImageMediaItem(MediaManagerItem):
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
'the image file "%s" no longer exists.') % filename)
def search(self, string, showError):
def search(self, string, show_error=True):
"""
Perform a search on the image file names.
``string``
The glob to search for
``show_error``
Unused.
"""
files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
order_by_ref=ImageFilenames.filename)
results = []

View File

@ -61,10 +61,15 @@ class MediaMediaItem(MediaManagerItem):
self.background = False
self.automatic = ''
super(MediaMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.single_service_item = False
self.has_search = True
self.media_object = None
self.display_controller = DisplayController(parent)
self.display_controller = DisplayController(self.parent())
self.display_controller.controller_layout = QtGui.QVBoxLayout()
self.media_controller.register_controller(self.display_controller)
self.media_controller.set_controls_visible(self.display_controller, False)

View File

@ -52,14 +52,26 @@ class PresentationMediaItem(MediaManagerItem):
"""
log.info('Presentations Media Item loaded')
def __init__(self, parent, plugin, icon, controllers):
def __init__(self, parent, plugin, controllers):
"""
Constructor. Setup defaults
"""
self.controllers = controllers
self.icon_path = 'presentations/presentation'
self.Automatic = ''
self.controllers = controllers
super(PresentationMediaItem, self).__init__(parent, plugin)
def retranslateUi(self):
"""
The name of the plugin media displayed in UI
"""
self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)')
self.automatic = translate('PresentationPlugin.MediaItem', 'Automatic')
self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:'))
def setup_item(self):
"""
Do some additional setup.
"""
self.message_listener = MessageListener(self)
self.has_search = True
self.single_service_item = False
@ -68,14 +80,6 @@ class PresentationMediaItem(MediaManagerItem):
# Allow DnD from the desktop
self.list_view.activateDnD()
def retranslateUi(self):
"""
The name of the plugin media displayed in UI
"""
self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)')
self.Automatic = translate('PresentationPlugin.MediaItem', 'Automatic')
self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:'))
def build_file_mask_string(self):
"""
Build the list of file extensions to be used in the Open file dialog.
@ -137,7 +141,7 @@ class PresentationMediaItem(MediaManagerItem):
if self.controllers[item].enabled():
self.display_type_combo_box.addItem(item)
if self.display_type_combo_box.count() > 1:
self.display_type_combo_box.insertItem(0, self.Automatic)
self.display_type_combo_box.insertItem(0, self.automatic)
self.display_type_combo_box.setCurrentIndex(0)
if Settings().value(self.settings_section + '/override app') == QtCore.Qt.Checked:
self.presentation_widget.show()
@ -253,7 +257,7 @@ class PresentationMediaItem(MediaManagerItem):
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
if service_item.processor == self.Automatic:
if service_item.processor == self.automatic:
service_item.processor = self.findControllerByType(filename)
if not service_item.processor:
return False

View File

@ -109,8 +109,7 @@ class PresentationPlugin(Plugin):
"""
Create the Media Manager List.
"""
self.media_item = PresentationMediaItem(
self.main_window.media_dock_manager.media_dock, self, self.icon, self.controllers)
self.media_item = PresentationMediaItem(self.main_window.media_dock_manager.media_dock, self, self.controllers)
def register_controllers(self, controller):
"""

View File

@ -40,6 +40,8 @@ window.OpenLP = {
// defeat Safari bug
targ = targ.parentNode;
}
var isSecure = false;
var isAuthorised = false;
return $(targ);
},
getSearchablePlugins: function () {
@ -147,11 +149,13 @@ window.OpenLP = {
},
pollServer: function () {
$.getJSON(
"/stage/poll",
"/api/poll",
function (data, status) {
var prevItem = OpenLP.currentItem;
OpenLP.currentSlide = data.results.slide;
OpenLP.currentItem = data.results.item;
OpenLP.isSecure = data.results.isSecure;
OpenLP.isAuthorised = data.results.isAuthorised;
if ($("#service-manager").is(":visible")) {
if (OpenLP.currentService != data.results.service) {
OpenLP.currentService = data.results.service;

View File

@ -26,7 +26,7 @@
window.OpenLP = {
loadService: function (event) {
$.getJSON(
"/stage/service/list",
"/api/service/list",
function (data, status) {
OpenLP.nextSong = "";
$("#notes").html("");
@ -46,7 +46,7 @@ window.OpenLP = {
},
loadSlides: function (event) {
$.getJSON(
"/stage/controller/live/text",
"/api/controller/live/text",
function (data, status) {
OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0;
@ -137,7 +137,7 @@ window.OpenLP = {
},
pollServer: function () {
$.getJSON(
"/stage/poll",
"/api/poll",
function (data, status) {
OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item ||

View File

@ -28,6 +28,7 @@
###############################################################################
from .remotetab import RemoteTab
from .httpserver import HttpServer
from .httprouter import HttpRouter
from .httpserver import OpenLPServer
__all__ = ['RemoteTab', 'HttpServer']
__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter']

View File

@ -0,0 +1,638 @@
# -*- 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:`http` module contains the API web server. This is a lightweight web
server used by remotes to interact with OpenLP. It uses JSON to communicate with
the remotes.
*Routes:*
``/``
Go to the web interface.
``/stage``
Show the stage view.
``/files/{filename}``
Serve a static file.
``/stage/api/poll``
Poll to see if there are any changes. Returns a JSON-encoded dict of
any changes that occurred::
{"results": {"type": "controller"}}
Or, if there were no results, False::
{"results": False}
``/api/display/{hide|show}``
Blank or unblank the screen.
``/api/alert``
Sends an alert message to the alerts plugin. This method expects a
JSON-encoded dict like this::
{"request": {"text": "<your alert text>"}}
``/api/controller/{live|preview}/{action}``
Perform ``{action}`` on the live or preview controller. Valid actions
are:
``next``
Load the next slide.
``previous``
Load the previous slide.
``set``
Set a specific slide. Requires an id return in a JSON-encoded dict like
this::
{"request": {"id": 1}}
``first``
Load the first slide.
``last``
Load the last slide.
``text``
Fetches the text of the current song. The output is a JSON-encoded
dict which looks like this::
{"result": {"slides": ["...", "..."]}}
``/api/service/{action}``
Perform ``{action}`` on the service manager (e.g. go live). Data is
passed as a json-encoded ``data`` parameter. Valid actions are:
``next``
Load the next item in the service.
``previous``
Load the previews item in the service.
``set``
Set a specific item in the service. Requires an id returned in a
JSON-encoded dict like this::
{"request": {"id": 1}}
``list``
Request a list of items in the service. Returns a list of items in the
current service in a JSON-encoded dict like this::
{"results": {"items": [{...}, {...}]}}
"""
import base64
import json
import logging
import os
import re
import urllib.request
import urllib.error
from urllib.parse import urlparse, parse_qs
from mako.template import Template
from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte
from openlp.core.utils import AppLocation, translate
log = logging.getLogger(__name__)
class HttpRouter(object):
"""
This code is called by the HttpServer upon a request and it processes it based on the routing table.
This code is stateless and is created on each request.
Some variables may look incorrect but this extends BaseHTTPRequestHandler.
"""
def initialise(self):
"""
Initialise the router stack and any other variables.
"""
authcode = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password'))
try:
self.auth = base64.b64encode(authcode)
except TypeError:
self.auth = base64.b64encode(authcode.encode()).decode()
self.routes = [
('^/$', {'function': self.serve_file, 'secure': False}),
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
(r'^/main/image$', {'function': self.main_image, 'secure': False}),
(r'^/api/controller/(live|preview)/text$', {'function': self.controller_text, 'secure': False}),
(r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': True}),
(r'^/api/service/list$', {'function': self.service_list, 'secure': False}),
(r'^/api/service/(.*)$', {'function': self.service, 'secure': True}),
(r'^/api/display/(hide|show|blank|theme|desktop)$', {'function': self.display, 'secure': True}),
(r'^/api/alert$', {'function': self.alert, 'secure': True}),
(r'^/api/plugin/(search)$', {'function': self.plugin_info, 'secure': False}),
(r'^/api/(.*)/search$', {'function': self.search, 'secure': False}),
(r'^/api/(.*)/live$', {'function': self.go_live, 'secure': True}),
(r'^/api/(.*)/add$', {'function': self.add_to_service, 'secure': True})
]
self.settings_section = 'remotes'
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
def do_post_processor(self):
"""
Handle the POST amd GET requests placed on the server.
"""
if self.path == '/favicon.ico':
return
if not hasattr(self, 'auth'):
self.initialise()
function, args = self.process_http_request(self.path)
if not function:
self.do_http_error()
return
self.authorised = self.headers['Authorization'] is None
if function['secure'] and Settings().value(self.settings_section + '/authentication enabled'):
if self.headers['Authorization'] is None:
self.do_authorisation()
self.wfile.write(bytes('no auth header received', 'UTF-8'))
elif self.headers['Authorization'] == 'Basic %s' % self.auth:
self.do_http_success()
self.call_function(function, *args)
else:
self.do_authorisation()
self.wfile.write(bytes(self.headers['Authorization'], 'UTF-8'))
self.wfile.write(bytes(' not authenticated', 'UTF-8'))
else:
self.call_function(function, *args)
def call_function(self, function, *args):
"""
Invoke the route function passing the relevant values
``function``
The function to be calledL.
``*args``
Any passed data.
"""
response = function['function'](*args)
if response:
self.wfile.write(response)
return
def process_http_request(self, url_path, *args):
"""
Common function to process HTTP requests
``url_path``
The requested URL.
``*args``
Any passed data.
"""
self.request_data = None
url_path_split = urlparse(url_path)
url_query = parse_qs(url_path_split.query)
if 'data' in url_query.keys():
self.request_data = url_query['data'][0]
for route, func in self.routes:
match = re.match(route, url_path_split.path)
if match:
log.debug('Route "%s" matched "%s"', route, url_path)
args = []
for param in match.groups():
args.append(param)
return func, args
return None, None
def do_http_success(self):
"""
Create a success http header.
"""
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_json_header(self):
"""
Create a header for JSON messages
"""
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
def do_http_error(self):
"""
Create a error http header.
"""
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_authorisation(self):
"""
Create a needs authorisation http header.
"""
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"')
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_not_found(self):
"""
Create a not found http header.
"""
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
def _get_service_items(self):
"""
Read the service item in use and return the data as a json object
"""
service_items = []
if self.live_controller.service_item:
current_unique_identifier = self.live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in self.service_manager.service_items:
service_item = item['service_item']
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return service_items
def translate(self):
"""
Translate various strings in the mobile app.
"""
self.template_vars = {
'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'),
'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'),
'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
'search': translate('RemotePlugin.Mobile', 'Search'),
'home': translate('RemotePlugin.Mobile', 'Home'),
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
'blank': translate('RemotePlugin.Mobile', 'Blank'),
'theme': translate('RemotePlugin.Mobile', 'Theme'),
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
'show': translate('RemotePlugin.Mobile', 'Show'),
'prev': translate('RemotePlugin.Mobile', 'Prev'),
'next': translate('RemotePlugin.Mobile', 'Next'),
'text': translate('RemotePlugin.Mobile', 'Text'),
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add &amp; Go to Service'),
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides')
}
def serve_file(self, file_name=None):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
return self.do_not_found()
ext = os.path.splitext(file_name)[1]
html = None
if ext == '.html':
self.send_header('Content-type', 'text/html')
variables = self.template_vars
html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
elif ext == '.css':
self.send_header('Content-type', 'text/css')
elif ext == '.js':
self.send_header('Content-type', 'application/javascript')
elif ext == '.jpg':
self.send_header('Content-type', 'image/jpeg')
elif ext == '.gif':
self.send_header('Content-type', 'image/gif')
elif ext == '.ico':
self.send_header('Content-type', 'image/x-icon')
elif ext == '.png':
self.send_header('Content-type', 'image/png')
else:
self.send_header('Content-type', 'text/plain')
file_handle = None
try:
if html:
content = html
else:
file_handle = open(path, 'rb')
log.debug('Opened %s' % path)
content = file_handle.read()
except IOError:
log.exception('Failed to open %s' % path)
return self.do_not_found()
finally:
if file_handle:
file_handle.close()
return content
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
"""
result = {
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
'twelve': Settings().value('remotes/twelve hour'),
'blank': self.live_controller.blank_screen.isChecked(),
'theme': self.live_controller.theme_screen.isChecked(),
'display': self.live_controller.desktop_screen.isChecked(),
'version': 2,
'isSecure': Settings().value(self.settings_section + '/authentication enabled'),
'isAuthorised': self.authorised
}
self.do_json_header()
return json.dumps({'results': result}).encode()
def main_poll(self):
"""
Poll OpenLP to determine the current slide count.
"""
result = {
'slide_count': self.live_controller.slide_count
}
self.do_json_header()
return json.dumps({'results': result}).encode()
def main_image(self):
"""
Return the latest display image as a byte stream.
"""
result = {
'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
}
self.do_json_header()
return json.dumps({'results': result}).encode()
def display(self, action):
"""
Hide or show the display screen.
This is a cross Thread call and UI is updated so Events need to be used.
``action``
This is the action, either ``hide`` or ``show``.
"""
self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action)
self.do_json_header()
return json.dumps({'results': {'success': True}}).encode()
def alert(self):
"""
Send an alert.
"""
plugin = self.plugin_manager.get_plugin_by_name("alerts")
if plugin.status == PluginStatus.Active:
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self.do_http_error()
text = urllib.parse.unquote(text)
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
success = True
else:
success = False
self.do_json_header()
return json.dumps({'results': {'success': success}}).encode()
def controller_text(self, var):
"""
Perform an action on the slide controller.
"""
current_item = self.live_controller.service_item
data = []
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
json_data['results']['item'] = self.live_controller.service_item.unique_identifier
self.do_json_header()
return json.dumps(json_data).encode()
def controller(self, display_type, action):
"""
Perform an action on the slide controller.
``display_type``
This is the type of slide controller, either ``preview`` or ``live``.
``action``
The action to perform.
"""
event = 'slidecontroller_%s_%s' % (display_type, action)
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self.do_http_error()
log.info(data)
# This slot expects an int within a list.
self.live_controller.emit(QtCore.SIGNAL(event), [data])
else:
self.live_controller.emit(QtCore.SIGNAL(event))
json_data = {'results': {'success': True}}
self.do_json_header()
return json.dumps(json_data).encode()
def service_list(self):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
self.do_json_header()
return json.dumps({'results': {'items': self._get_service_items()}}).encode()
def service(self, action):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
event = 'servicemanager_%s_item' % action
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError:
return self.do_http_error()
self.service_manager.emit(QtCore.SIGNAL(event), data)
else:
Registry().execute(event)
self.do_json_header()
return json.dumps({'results': {'success': True}}).encode()
def plugin_info(self, action):
"""
Return plugin related information, based on the action.
``action``
The action to perform. If *search* return a list of plugin names
which support search.
"""
if action == 'search':
searches = []
for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])])
self.do_json_header()
return json.dumps({'results': {'items': searches}}).encode()
def search(self, plugin_name):
"""
Return a list of items that match the search text.
``plugin``
The plugin name to search in.
"""
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self.do_http_error()
text = urllib.parse.unquote(text)
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
else:
results = []
self.do_json_header()
return json.dumps({'results': {'items': results}}).encode()
def go_live(self, plugin_name):
"""
Go live on an item of type ``plugin``.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self.do_http_error()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True])
return self.do_http_success()
def add_to_service(self, plugin_name):
"""
Add item of type ``plugin_name`` to the end of the service.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self.do_http_error()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True])
self.do_http_success()
def _get_service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, '_service_manager'):
self._service_manager = Registry().get('service_manager')
return self._service_manager
service_manager = property(_get_service_manager)
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, '_live_controller'):
self._live_controller = Registry().get('live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
def _get_plugin_manager(self):
"""
Adds the plugin manager to the class dynamically
"""
if not hasattr(self, '_plugin_manager'):
self._plugin_manager = Registry().get('plugin_manager')
return self._plugin_manager
plugin_manager = property(_get_plugin_manager)
def _get_alerts_manager(self):
"""
Adds the alerts manager to the class dynamically
"""
if not hasattr(self, '_alerts_manager'):
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
alerts_manager = property(_get_alerts_manager)

View File

@ -31,661 +31,122 @@
The :mod:`http` module contains the API web server. This is a lightweight web
server used by remotes to interact with OpenLP. It uses JSON to communicate with
the remotes.
*Routes:*
``/``
Go to the web interface.
``/stage``
Show the stage view.
``/files/{filename}``
Serve a static file.
``/stage/api/poll``
Poll to see if there are any changes. Returns a JSON-encoded dict of
any changes that occurred::
{"results": {"type": "controller"}}
Or, if there were no results, False::
{"results": False}
``/api/display/{hide|show}``
Blank or unblank the screen.
``/api/alert``
Sends an alert message to the alerts plugin. This method expects a
JSON-encoded dict like this::
{"request": {"text": "<your alert text>"}}
``/api/controller/{live|preview}/{action}``
Perform ``{action}`` on the live or preview controller. Valid actions
are:
``next``
Load the next slide.
``previous``
Load the previous slide.
``set``
Set a specific slide. Requires an id return in a JSON-encoded dict like
this::
{"request": {"id": 1}}
``first``
Load the first slide.
``last``
Load the last slide.
``text``
Fetches the text of the current song. The output is a JSON-encoded
dict which looks like this::
{"result": {"slides": ["...", "..."]}}
``/api/service/{action}``
Perform ``{action}`` on the service manager (e.g. go live). Data is
passed as a json-encoded ``data`` parameter. Valid actions are:
``next``
Load the next item in the service.
``previous``
Load the previews item in the service.
``set``
Set a specific item in the service. Requires an id returned in a
JSON-encoded dict like this::
{"request": {"id": 1}}
``list``
Request a list of items in the service. Returns a list of items in the
current service in a JSON-encoded dict like this::
{"results": {"items": [{...}, {...}]}}
"""
import json
import logging
import ssl
import socket
import os
import re
import urllib.request, urllib.parse, urllib.error
import urllib.parse
import cherrypy
import logging
from urllib.parse import urlparse, parse_qs
from mako.template import Template
from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte
from openlp.core.utils import AppLocation, translate
from openlp.core.lib import Settings
from openlp.core.utils import AppLocation
from hashlib import sha1
from openlp.plugins.remotes.lib import HttpRouter
from socketserver import BaseServer, ThreadingMixIn
from http.server import BaseHTTPRequestHandler, HTTPServer
log = logging.getLogger(__name__)
def make_sha_hash(password):
class CustomHandler(BaseHTTPRequestHandler, HttpRouter):
"""
Create an encrypted password for the given password.
Stateless session handler to handle the HTTP request and process it.
This class handles just the overrides to the base methods and the logic to invoke the
methods within the HttpRouter class.
DO not try change the structure as this is as per the documentation.
"""
log.debug("make_sha_hash")
return sha1(password.encode()).hexdigest()
def do_POST(self):
"""
Present pages / data and invoke URL level user authentication.
"""
self.do_post_processor()
def do_GET(self):
"""
Present pages / data and invoke URL level user authentication.
"""
self.do_post_processor()
def fetch_password(username):
"""
Fetch the password for a provided user.
"""
log.debug("Fetch Password")
if username != Settings().value('remotes/user id'):
return None
return make_sha_hash(Settings().value('remotes/password'))
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
pass
class HttpServer(object):
class HttpThread(QtCore.QThread):
"""
Ability to control OpenLP via a web browser.
This class controls the Cherrypy server and configuration.
A special Qt thread class to allow the HTTP server to run at the same time as the UI.
"""
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
def __init__(self, server):
"""
Constructor for the thread class.
``server``
The http server class.
"""
super(HttpThread, self).__init__(None)
self.http_server = server
def run(self):
"""
Run the thread.
"""
self.http_server.start_server()
class OpenLPServer():
def __init__(self):
"""
Initialise the http server, and start the server.
Initialise the http server, and start the server of the correct type http / https
"""
log.debug('Initialise httpserver')
self.settings_section = 'remotes'
self.router = HttpRouter()
self.http_thread = HttpThread(self)
self.http_thread.start()
def start_server(self):
"""
Start the http server based on configuration.
"""
log.debug('Start CherryPy server')
# Define to security levels and inject the router code
self.root = self.Public()
self.root.files = self.Files()
self.root.stage = self.Stage()
self.root.main = self.Main()
self.root.router = self.router
self.root.files.router = self.router
self.root.stage.router = self.router
self.root.main.router = self.router
cherrypy.tree.mount(self.root, '/', config=self.define_config())
# Turn off the flood of access messages cause by poll
cherrypy.log.access_log.propagate = False
cherrypy.engine.start()
def define_config(self):
"""
Define the configuration of the server.
Start the correct server and save the handler
"""
address = Settings().value(self.settings_section + '/ip address')
if Settings().value(self.settings_section + '/https enabled'):
port = Settings().value(self.settings_section + '/https port')
address = Settings().value(self.settings_section + '/ip address')
local_data = AppLocation.get_directory(AppLocation.DataDir)
cherrypy.config.update({'server.socket_host': str(address),
'server.socket_port': port,
'server.ssl_certificate': os.path.join(local_data, 'remotes', 'openlp.crt'),
'server.ssl_private_key': os.path.join(local_data, 'remotes', 'openlp.key')})
self.httpd = HTTPSServer((address, port), CustomHandler)
log.debug('Started ssl httpd...')
else:
port = Settings().value(self.settings_section + '/port')
address = Settings().value(self.settings_section + '/ip address')
cherrypy.config.update({'server.socket_host': str(address)})
cherrypy.config.update({'server.socket_port': port})
cherrypy.config.update({'environment': 'embedded'})
cherrypy.config.update({'engine.autoreload_on': False})
directory_config = {'/': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': Settings().value('remotes/authentication enabled'),
'tools.basic_auth.realm': 'OpenLP Remote Login',
'tools.basic_auth.users': fetch_password,
'tools.basic_auth.encrypt': make_sha_hash},
'/files': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': False},
'/stage': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': False},
'/main': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': False}}
return directory_config
self.httpd = ThreadingHTTPServer((address, port), CustomHandler)
log.debug('Started non ssl httpd...')
self.httpd.serve_forever()
class Public(object):
def stop_server(self):
"""
Main access class with may have security enabled on it.
Stop the server
"""
@cherrypy.expose
def default(self, *args, **kwargs):
self.router.request_data = None
if isinstance(kwargs, dict):
self.router.request_data = kwargs.get('data', None)
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Files(object):
"""
Provides access to files and has no security available. These are read only accesses
"""
@cherrypy.expose
def default(self, *args, **kwargs):
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Stage(object):
"""
Stage view is read only so security is not relevant and would reduce it's usability
"""
@cherrypy.expose
def default(self, *args, **kwargs):
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Main(object):
"""
Main view is read only so security is not relevant and would reduce it's usability
"""
@cherrypy.expose
def default(self, *args, **kwargs):
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
def close(self):
"""
Close down the http server.
"""
log.debug('close http server')
cherrypy.engine.exit()
self.http_thread.exit(0)
self.httpd = None
log.debug('Stopped the server.')
class HttpRouter(object):
"""
This code is called by the HttpServer upon a request and it processes it based on the routing table.
"""
def __init__(self):
class HTTPSServer(HTTPServer):
def __init__(self, address, handler):
"""
Initialise the router
Initialise the secure handlers for the SSL server if required.s
"""
self.routes = [
('^/$', self.serve_file),
('^/(stage)$', self.serve_file),
('^/(main)$', self.serve_file),
(r'^/files/(.*)$', self.serve_file),
(r'^/api/poll$', self.poll),
(r'^/stage/poll$', self.poll),
(r'^/main/poll$', self.main_poll),
(r'^/main/image$', self.main_image),
(r'^/api/controller/(live|preview)/(.*)$', self.controller),
(r'^/stage/controller/(live|preview)/(.*)$', self.controller),
(r'^/api/service/(.*)$', self.service),
(r'^/stage/service/(.*)$', self.service),
(r'^/api/display/(hide|show|blank|theme|desktop)$', self.display),
(r'^/api/alert$', self.alert),
(r'^/api/plugin/(search)$', self.plugin_info),
(r'^/api/(.*)/search$', self.search),
(r'^/api/(.*)/live$', self.go_live),
(r'^/api/(.*)/add$', self.add_to_service)
]
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
BaseServer.__init__(self, address, handler)
local_data = AppLocation.get_directory(AppLocation.DataDir)
self.socket = ssl.SSLSocket(
sock=socket.socket(self.address_family, self.socket_type),
ssl_version=ssl.PROTOCOL_TLSv1,
certfile=os.path.join(local_data, 'remotes', 'openlp.crt'),
keyfile=os.path.join(local_data, 'remotes', 'openlp.key'),
server_side=True)
self.server_bind()
self.server_activate()
def process_http_request(self, url_path, *args):
"""
Common function to process HTTP requests
``url_path``
The requested URL.
``*args``
Any passed data.
"""
response = None
for route, func in self.routes:
match = re.match(route, url_path)
if match:
log.debug('Route "%s" matched "%s"', route, url_path)
args = []
for param in match.groups():
args.append(param)
response = func(*args)
break
if response:
return response
else:
log.debug('Path not found %s', url_path)
return self._http_not_found()
def _get_service_items(self):
"""
Read the service item in use and return the data as a json object
"""
service_items = []
if self.live_controller.service_item:
current_unique_identifier = self.live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in self.service_manager.service_items:
service_item = item['service_item']
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return service_items
def translate(self):
"""
Translate various strings in the mobile app.
"""
self.template_vars = {
'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'),
'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'),
'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
'search': translate('RemotePlugin.Mobile', 'Search'),
'home': translate('RemotePlugin.Mobile', 'Home'),
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
'blank': translate('RemotePlugin.Mobile', 'Blank'),
'theme': translate('RemotePlugin.Mobile', 'Theme'),
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
'show': translate('RemotePlugin.Mobile', 'Show'),
'prev': translate('RemotePlugin.Mobile', 'Prev'),
'next': translate('RemotePlugin.Mobile', 'Next'),
'text': translate('RemotePlugin.Mobile', 'Text'),
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add &amp; Go to Service'),
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides')
}
def serve_file(self, file_name=None):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
return self._http_not_found()
ext = os.path.splitext(file_name)[1]
html = None
if ext == '.html':
mimetype = 'text/html'
variables = self.template_vars
html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
elif ext == '.css':
mimetype = 'text/css'
elif ext == '.js':
mimetype = 'application/x-javascript'
elif ext == '.jpg':
mimetype = 'image/jpeg'
elif ext == '.gif':
mimetype = 'image/gif'
elif ext == '.png':
mimetype = 'image/png'
else:
mimetype = 'text/plain'
file_handle = None
try:
if html:
content = html
else:
file_handle = open(path, 'rb')
log.debug('Opened %s' % path)
content = file_handle.read()
except IOError:
log.exception('Failed to open %s' % path)
return self._http_not_found()
finally:
if file_handle:
file_handle.close()
cherrypy.response.headers['Content-Type'] = mimetype
return content
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
"""
result = {
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
'twelve': Settings().value('remotes/twelve hour'),
'blank': self.live_controller.blank_screen.isChecked(),
'theme': self.live_controller.theme_screen.isChecked(),
'display': self.live_controller.desktop_screen.isChecked()
}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': result}).encode()
def main_poll(self):
"""
Poll OpenLP to determine the current slide count.
"""
result = {
'slide_count': self.live_controller.slide_count
}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': result}).encode()
def main_image(self):
"""
Return the latest display image as a byte stream.
"""
result = {
'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': result}).encode()
def display(self, action):
"""
Hide or show the display screen.
This is a cross Thread call and UI is updated so Events need to be used.
``action``
This is the action, either ``hide`` or ``show``.
"""
self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action)
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'success': True}}).encode()
def alert(self):
"""
Send an alert.
"""
plugin = self.plugin_manager.get_plugin_by_name("alerts")
if plugin.status == PluginStatus.Active:
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self._http_bad_request()
text = urllib.parse.unquote(text)
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
success = True
else:
success = False
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'success': success}}).encode()
def controller(self, display_type, action):
"""
Perform an action on the slide controller.
``display_type``
This is the type of slide controller, either ``preview`` or ``live``.
``action``
The action to perform.
"""
event = 'slidecontroller_%s_%s' % (display_type, action)
if action == 'text':
current_item = self.live_controller.service_item
data = []
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
json_data['results']['item'] = self.live_controller.service_item.unique_identifier
else:
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self._http_bad_request()
log.info(data)
# This slot expects an int within a list.
self.live_controller.emit(QtCore.SIGNAL(event), [data])
else:
self.live_controller.emit(QtCore.SIGNAL(event))
json_data = {'results': {'success': True}}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps(json_data).encode()
def service(self, action):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
event = 'servicemanager_%s' % action
if action == 'list':
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'items': self._get_service_items()}}).encode()
event += '_item'
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError:
return self._http_bad_request()
self.service_manager.emit(QtCore.SIGNAL(event), data)
else:
Registry().execute(event)
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'success': True}}).encode()
def plugin_info(self, action):
"""
Return plugin related information, based on the action.
``action``
The action to perform. If *search* return a list of plugin names
which support search.
"""
if action == 'search':
searches = []
for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])])
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'items': searches}}).encode()
def search(self, plugin_name):
"""
Return a list of items that match the search text.
``plugin``
The plugin name to search in.
"""
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self._http_bad_request()
text = urllib.parse.unquote(text)
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
else:
results = []
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'items': results}}).encode()
def go_live(self, plugin_name):
"""
Go live on an item of type ``plugin``.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self._http_bad_request()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True])
return self._http_success()
def add_to_service(self, plugin_name):
"""
Add item of type ``plugin_name`` to the end of the service.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self._http_bad_request()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True])
self._http_success()
def _http_success(self):
"""
Set the HTTP success return code.
"""
cherrypy.response.status = 200
def _http_bad_request(self):
"""
Set the HTTP bad response return code.
"""
cherrypy.response.status = 400
def _http_not_found(self):
"""
Set the HTTP not found return code.
"""
cherrypy.response.status = 404
cherrypy.response.body = [b'<html><body>Sorry, an error occurred </body></html>']
def _get_service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, '_service_manager'):
self._service_manager = Registry().get('service_manager')
return self._service_manager
service_manager = property(_get_service_manager)
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, '_live_controller'):
self._live_controller = Registry().get('live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
def _get_plugin_manager(self):
"""
Adds the plugin manager to the class dynamically
"""
if not hasattr(self, '_plugin_manager'):
self._plugin_manager = Registry().get('plugin_manager')
return self._plugin_manager
plugin_manager = property(_get_plugin_manager)
def _get_alerts_manager(self):
"""
Adds the alerts manager to the class dynamically
"""
if not hasattr(self, '_alerts_manager'):
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
alerts_manager = property(_get_alerts_manager)

View File

@ -207,8 +207,8 @@ class RemoteTab(SettingsTab):
https_url_temp = https_url + 'stage'
self.stage_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp))
self.stage_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))
http_url_temp = http_url + 'live'
https_url_temp = https_url + 'live'
http_url_temp = http_url + 'main'
https_url_temp = https_url + 'main'
self.live_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp))
self.live_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))

View File

@ -28,11 +28,10 @@
###############################################################################
import logging
from PyQt4 import QtGui
import time
from openlp.core.lib import Plugin, StringContent, translate, build_icon
from openlp.plugins.remotes.lib import RemoteTab, HttpServer
from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer
log = logging.getLogger(__name__)
@ -67,8 +66,7 @@ class RemotesPlugin(Plugin):
"""
log.debug('initialise')
super(RemotesPlugin, self).initialise()
self.server = HttpServer()
self.server.start_server()
self.server = OpenLPServer()
def finalise(self):
"""
@ -77,7 +75,7 @@ class RemotesPlugin(Plugin):
log.debug('finalise')
super(RemotesPlugin, self).finalise()
if self.server:
self.server.close()
self.server.stop_server()
self.server = None
def about(self):
@ -109,5 +107,6 @@ class RemotesPlugin(Plugin):
Called when Config is changed to restart the server on new address or port
"""
log.debug('remote config changed')
self.main_window.information_message(translate('RemotePlugin', 'Configuration Change'),
translate('RemotePlugin', 'OpenLP will need to be restarted for the Remote changes to become active.'))
self.finalise()
time.sleep(0.5)
self.initialise()

View File

@ -72,6 +72,11 @@ class SongMediaItem(MediaManagerItem):
def __init__(self, parent, plugin):
self.icon_path = 'songs/song'
super(SongMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.single_service_item = False
# Holds information about whether the edit is remotely triggered and which Song is required.
self.remote_song = -1

View File

@ -44,10 +44,11 @@ from distutils.version import LooseVersion
try:
import nose
except ImportError:
pass
nose = None
IS_WIN = sys.platform.startswith('win')
VERS = {
'Python': '3.0',
'PyQt4': '4.6',
@ -84,26 +85,39 @@ MODULES = [
'enchant',
'bs4',
'mako',
'cherrypy',
'uno',
]
OPTIONAL_MODULES = [
('MySQLdb', ' (MySQL support)'),
('psycopg2', ' (PostgreSQL support)'),
('nose', ' (testing framework)'),
('mock', ' (testing module)'),
('MySQLdb', '(MySQL support)', True),
('psycopg2', '(PostgreSQL support)', True),
('nose', '(testing framework)', True),
('mock', '(testing module)', sys.version_info[1] < 3),
]
w = sys.stdout.write
def check_vers(version, required, text):
"""
Check the version of a dependency. Returns ``True`` if the version is greater than or equal, or False if less than.
``version``
The actual version of the dependency
``required``
The required version of the dependency
``text``
The dependency's name
"""
space = (27 - len(required) - len(text)) * ' '
if not isinstance(version, str):
version = '.'.join(map(str, version))
if not isinstance(required, str):
required = '.'.join(map(str, required))
w(' %s >= %s ... ' % (text, required))
w(' %s >= %s ... ' % (text, required) + space)
if LooseVersion(version) >= LooseVersion(required):
w(version + os.linesep)
return True
@ -111,13 +125,39 @@ def check_vers(version, required, text):
w('FAIL' + os.linesep)
return False
def check_module(mod, text='', indent=' '):
"""
Check that a module is installed.
``mod``
The module to check for.
``text``
The text to display.
``indent``
How much to indent the text by.
"""
space = (31 - len(mod) - len(text)) * ' '
w(indent + '%s %s... ' % (mod, text) + space)
try:
__import__(mod)
w('OK')
except ImportError:
w('FAIL')
w(os.linesep)
def print_vers_fail(required, text):
print(' %s >= %s ... FAIL' % (text, required))
def verify_python():
if not check_vers(list(sys.version_info), VERS['Python'], text='Python'):
exit(1)
def verify_versions():
print('Verifying version of modules...')
try:
@ -138,17 +178,11 @@ def verify_versions():
except ImportError:
print_vers_fail(VERS['enchant'], 'enchant')
def check_module(mod, text='', indent=' '):
space = (30 - len(mod) - len(text)) * ' '
w(indent + '%s%s... ' % (mod, text) + space)
try:
__import__(mod)
w('OK')
except ImportError:
w('FAIL')
w(os.linesep)
def verify_pyenchant():
def print_enchant_backends_and_languages():
"""
Check if PyEnchant is installed.
"""
w('Enchant (spell checker)... ')
try:
import enchant
@ -160,39 +194,43 @@ def verify_pyenchant():
except ImportError:
w('FAIL' + os.linesep)
def verify_pyqt():
def print_qt_image_formats():
"""
Print out the image formats that Qt4 supports.
"""
w('Qt4 image formats... ')
try:
from PyQt4 import QtGui
read_f = ', '.join([str(format).lower()
for format in QtGui.QImageReader.supportedImageFormats()])
write_f = ', '.join([str(format).lower()
for format in QtGui.QImageWriter.supportedImageFormats()])
read_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()])
write_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageWriter.supportedImageFormats()])
w(os.linesep)
print(' read: %s' % read_f)
print(' write: %s' % write_f)
except ImportError:
w('FAIL' + os.linesep)
def main():
verify_python()
def main():
"""
Run the dependency checker.
"""
print('Checking Python version...')
verify_python()
print('Checking for modules...')
for m in MODULES:
check_module(m)
print('Checking for optional modules...')
for m in OPTIONAL_MODULES:
check_module(m[0], text=m[1])
if m[2]:
check_module(m[0], text=m[1])
if IS_WIN:
print('Checking for Windows specific modules...')
for m in WIN32_MODULES:
check_module(m)
verify_versions()
verify_pyqt()
verify_pyenchant()
print_qt_image_formats()
print_enchant_backends_and_languages()
if __name__ == '__main__':
main()

View File

@ -7,7 +7,13 @@ sip.setapi('QTime', 2)
sip.setapi('QUrl', 2)
sip.setapi('QVariant', 2)
import sys
from PyQt4 import QtGui
if sys.version_info[1] >= 3:
from unittest.mock import patch, MagicMock
else:
from mock import patch, MagicMock
# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication.
application = QtGui.QApplication([])

View File

@ -1,14 +1,42 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
"""
from unittest import TestCase
from mock import MagicMock, patch
from sqlalchemy.pool import NullPool
from sqlalchemy.orm.scoping import ScopedSession
from sqlalchemy import MetaData
from openlp.core.lib.db import init_db, get_upgrade_op
from tests.functional import patch, MagicMock
class TestDB(TestCase):

View File

@ -1,12 +1,39 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.formattingtags package.
"""
import copy
from unittest import TestCase
from mock import patch
from openlp.core.lib import FormattingTags
from tests.functional import patch
TAG = {

View File

@ -1,15 +1,41 @@
"""
Package to test the openlp.core.ui package.
"""
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.ui package.
"""
import os
from unittest import TestCase
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
from openlp.core.lib import Registry, ImageManager, ScreenList
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))

View File

@ -1,3 +1,31 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
"""
@ -6,22 +34,20 @@ import os
from unittest import TestCase
from datetime import datetime, timedelta
from mock import MagicMock, patch
from PyQt4 import QtCore, QtGui
from openlp.core.lib import str_to_bool, create_thumb, translate, check_directory_exists, get_text_file_string, \
build_icon, image_to_byte, check_item_selected, validate_thumb, create_separated_list, clean_tags, expand_tags
from tests.functional import MagicMock, patch
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
class TestLib(TestCase):
def str_to_bool_with_bool_test(self):
def str_to_bool_with_bool_true_test(self):
"""
Test the str_to_bool function with boolean input
Test the str_to_bool function with boolean input of True
"""
# GIVEN: A boolean value set to true
true_boolean = True
@ -30,9 +56,13 @@ class TestLib(TestCase):
true_result = str_to_bool(true_boolean)
# THEN: We should get back a True bool
assert isinstance(true_result, bool), 'The result should be a boolean'
assert true_result is True, 'The result should be True'
self.assertIsInstance(true_result, bool, 'The result should be a boolean')
self.assertTrue(true_result, 'The result should be True')
def str_to_bool_with_bool_false_test(self):
"""
Test the str_to_bool function with boolean input of False
"""
# GIVEN: A boolean value set to false
false_boolean = False
@ -40,12 +70,12 @@ class TestLib(TestCase):
false_result = str_to_bool(false_boolean)
# THEN: We should get back a True bool
assert isinstance(false_result, bool), 'The result should be a boolean'
assert false_result is False, 'The result should be True'
self.assertIsInstance(false_result, bool, 'The result should be a boolean')
self.assertFalse(false_result, 'The result should be True')
def str_to_bool_with_invalid_test(self):
def str_to_bool_with_integer_test(self):
"""
Test the str_to_bool function with a set of invalid inputs
Test the str_to_bool function with an integer input
"""
# GIVEN: An integer value
int_string = 1
@ -54,8 +84,12 @@ class TestLib(TestCase):
int_result = str_to_bool(int_string)
# THEN: we should get back a false
assert int_result is False, 'The result should be False'
self.assertFalse(int_result, 'The result should be False')
def str_to_bool_with_invalid_string_test(self):
"""
Test the str_to_bool function with an invalid string
"""
# GIVEN: An string value with completely invalid input
invalid_string = 'my feet are wet'
@ -63,11 +97,11 @@ class TestLib(TestCase):
str_result = str_to_bool(invalid_string)
# THEN: we should get back a false
assert str_result is False, 'The result should be False'
self.assertFalse(str_result, 'The result should be False')
def str_to_bool_with_false_values_test(self):
def str_to_bool_with_string_false_test(self):
"""
Test the str_to_bool function with a set of false inputs
Test the str_to_bool function with a string saying "false"
"""
# GIVEN: A string set to "false"
false_string = 'false'
@ -76,8 +110,12 @@ class TestLib(TestCase):
false_result = str_to_bool(false_string)
# THEN: we should get back a false
assert false_result is False, 'The result should be False'
self.assertFalse(false_result, 'The result should be False')
def str_to_bool_with_string_no_test(self):
"""
Test the str_to_bool function with a string saying "NO"
"""
# GIVEN: An string set to "NO"
no_string = 'NO'
@ -85,11 +123,11 @@ class TestLib(TestCase):
str_result = str_to_bool(no_string)
# THEN: we should get back a false
assert str_result is False, 'The result should be False'
self.assertFalse(str_result, 'The result should be False')
def str_to_bool_with_true_values_test(self):
def str_to_bool_with_true_string_value_test(self):
"""
Test the str_to_bool function with a set of true inputs
Test the str_to_bool function with a string set to "True"
"""
# GIVEN: A string set to "True"
true_string = 'True'
@ -98,8 +136,12 @@ class TestLib(TestCase):
true_result = str_to_bool(true_string)
# THEN: we should get back a true
assert true_result is True, 'The result should be True'
self.assertTrue(true_result, 'The result should be True')
def str_to_bool_with_yes_string_value_test(self):
"""
Test the str_to_bool function with a string set to "yes"
"""
# GIVEN: An string set to "yes"
yes_string = 'yes'
@ -107,7 +149,7 @@ class TestLib(TestCase):
str_result = str_to_bool(yes_string)
# THEN: we should get back a true
assert str_result is True, 'The result should be True'
self.assertTrue(str_result, 'The result should be True')
def translate_test(self):
"""
@ -126,7 +168,7 @@ class TestLib(TestCase):
# THEN: the translated string should be returned, and the mocked function should have been called
mocked_translate.assert_called_with(context, text, comment, encoding, n)
assert result == 'Translated string', 'The translated string should have been returned'
self.assertEqual('Translated string', result, 'The translated string should have been returned')
def check_directory_exists_test(self):
"""
@ -143,7 +185,7 @@ class TestLib(TestCase):
# THEN: Only os.path.exists should have been called
mocked_exists.assert_called_with(directory_to_check)
assert not mocked_makedirs.called, 'os.makedirs should not have been called'
self.assertIsNot(mocked_makedirs.called, 'os.makedirs should not have been called')
# WHEN: os.path.exists returns False and we check the directory exists
mocked_exists.return_value = False
@ -181,13 +223,14 @@ class TestLib(TestCase):
# THEN: The result should be False
mocked_isfile.assert_called_with(filename)
assert result is False, 'False should be returned if no file exists'
self.assertFalse(result, 'False should be returned if no file exists')
def get_text_file_string_read_error_test(self):
"""
Test the get_text_file_string() method when a read error happens
"""
with patch('openlp.core.lib.os.path.isfile') as mocked_isfile, patch('openlp.core.lib.open', create=True) as mocked_open:
with patch('openlp.core.lib.os.path.isfile') as mocked_isfile, \
patch('openlp.core.lib.open', create=True) as mocked_open:
# GIVEN: A mocked-out open() which raises an exception and isfile returns True
filename = 'testfile.txt'
mocked_isfile.return_value = True
@ -199,13 +242,13 @@ class TestLib(TestCase):
# THEN: None should be returned
mocked_isfile.assert_called_with(filename)
mocked_open.assert_called_with(filename, 'r')
assert result is None, 'None should be returned if the file cannot be opened'
self.assertIsNone(result, 'None should be returned if the file cannot be opened')
def get_text_file_string_decode_error_test(self):
"""
Test the get_text_file_string() method when the contents cannot be decoded
"""
assert True, 'Impossible to test due to conflicts when mocking out the "open" function'
self.skipTest('Impossible to test due to conflicts when mocking out the "open" function')
def build_icon_with_qicon_test(self):
"""
@ -220,7 +263,7 @@ class TestLib(TestCase):
result = build_icon(mocked_icon)
# THEN: The result should be our mocked QIcon
assert result is mocked_icon, 'The result should be the mocked QIcon'
self.assertIs(mocked_icon, result, 'The result should be the mocked QIcon')
def build_icon_with_resource_test(self):
"""
@ -242,7 +285,7 @@ class TestLib(TestCase):
MockedQPixmap.assert_called_with(resource_uri)
# There really should be more assert statements here but due to type checking and things they all break. The
# best we can do is to assert that we get back a MagicMock object.
assert isinstance(result, MagicMock), 'The result should be a MagicMock, because we mocked it out'
self.assertIsInstance(result, MagicMock, 'The result should be a MagicMock, because we mocked it out')
def image_to_byte_test(self):
"""
@ -267,7 +310,8 @@ class TestLib(TestCase):
mocked_buffer.open.assert_called_with('writeonly')
mocked_image.save.assert_called_with(mocked_buffer, "PNG")
mocked_byte_array.toBase64.assert_called_with()
assert result == 'base64mock', 'The result should be the return value of the mocked out base64 method'
self.assertEqual('base64mock', result,
'The result should be the return value of the mocked out base64 method')
def create_thumb_with_size_test(self):
"""
@ -286,16 +330,16 @@ class TestLib(TestCase):
pass
# Only continue when the thumb does not exist.
assert not os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.'
self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created.
assert os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.'
assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon.'
assert not icon.isNull(), 'The icon should not be null.'
assert QtGui.QImageReader(thumb_path).size() == thumb_size, 'The thumb should have the given size.'
self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
self.assertEqual(thumb_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
@ -318,7 +362,7 @@ class TestLib(TestCase):
# THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with()
assert result, 'The result should be True'
self.assertTrue(result, 'The result should be True')
def check_item_selected_false_test(self):
"""
@ -326,7 +370,7 @@ class TestLib(TestCase):
"""
# GIVEN: A mocked out QtGui module and a list widget with selected indexes
with patch('openlp.core.lib.QtGui') as MockedQtGui, \
patch('openlp.core.lib.translate') as mocked_translate:
patch('openlp.core.lib.translate') as mocked_translate:
mocked_translate.return_value = 'mocked translate'
mocked_list_widget = MagicMock()
mocked_list_widget.selectedIndexes.return_value = False
@ -339,7 +383,7 @@ class TestLib(TestCase):
# THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with()
MockedQtGui.QMessageBox.information.assert_called_with('parent', 'mocked translate', 'message')
assert not result, 'The result should be False'
self.assertFalse(result, 'The result should be False')
def clean_tags_test(self):
"""
@ -361,7 +405,7 @@ class TestLib(TestCase):
result_string = clean_tags(string_to_pass)
# THEN: The strings should be identical.
assert result_string == wanted_string, 'The strings should be identical.'
self.assertEqual(wanted_string, result_string, 'The strings should be identical')
def expand_tags_test(self):
"""
@ -400,7 +444,7 @@ class TestLib(TestCase):
result_string = expand_tags(string_to_pass)
# THEN: The strings should be identical.
assert result_string == wanted_string, 'The strings should be identical.'
self.assertEqual(wanted_string, result_string, 'The strings should be identical.')
def validate_thumb_file_does_not_exist_test(self):
"""

View File

@ -1,12 +1,39 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.pluginmanager package.
"""
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib.pluginmanager import PluginManager
from openlp.core.lib import Settings, Registry, PluginStatus
from tests.functional import MagicMock
class TestPluginManager(TestCase):
@ -42,8 +69,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_media_manager()
# THEN: The create_media_manager_item() method should have been called
assert mocked_plugin.create_media_manager_item.call_count == 0, \
'The create_media_manager_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count,
'The create_media_manager_item() method should not have been called.')
def hook_media_manager_with_active_plugin_test(self):
"""
@ -75,8 +102,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs()
# THEN: The hook_settings_tabs() method should have been called
assert mocked_plugin.create_media_manager_item.call_count == 0, \
'The create_media_manager_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count,
'The create_media_manager_item() method should not have been called.')
def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self):
"""
@ -95,8 +122,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs()
# THEN: The create_settings_tab() method should not have been called, but the plugins lists should be the same
assert mocked_plugin.create_settings_tab.call_count == 0, \
'The create_media_manager_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.create_settings_tab.call_count,
'The create_media_manager_item() method should not have been called.')
self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins,
'The plugins on the settings form should be the same as the plugins in the plugin manager')
@ -117,10 +144,10 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs()
# THEN: The create_media_manager_item() method should have been called with the mocked settings form
assert mocked_plugin.create_settings_tab.call_count == 1, \
'The create_media_manager_item() method should have been called once.'
self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins,
'The plugins on the settings form should be the same as the plugins in the plugin manager')
self.assertEqual(1, mocked_plugin.create_settings_tab.call_count,
'The create_media_manager_item() method should have been called once.')
self.assertEqual(plugin_manager.plugins, mocked_settings_form.plugin_manager.plugins,
'The plugins on the settings form should be the same as the plugins in the plugin manager')
def hook_settings_tabs_with_active_plugin_and_no_form_test(self):
"""
@ -152,8 +179,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_import_menu()
# THEN: The create_media_manager_item() method should have been called
assert mocked_plugin.add_import_menu_item.call_count == 0, \
'The add_import_menu_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.add_import_menu_item.call_count,
'The add_import_menu_item() method should not have been called.')
def hook_import_menu_with_active_plugin_test(self):
"""
@ -185,8 +212,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_export_menu()
# THEN: The add_export_menu_Item() method should not have been called
assert mocked_plugin.add_export_menu_Item.call_count == 0, \
'The add_export_menu_Item() method should not have been called.'
self.assertEqual(0, mocked_plugin.add_export_menu_Item.call_count,
'The add_export_menu_Item() method should not have been called.')
def hook_export_menu_with_active_plugin_test(self):
"""
@ -219,8 +246,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_upgrade_plugin_settings(settings)
# THEN: The upgrade_settings() method should not have been called
assert mocked_plugin.upgrade_settings.call_count == 0, \
'The upgrade_settings() method should not have been called.'
self.assertEqual(0, mocked_plugin.upgrade_settings.call_count,
'The upgrade_settings() method should not have been called.')
def hook_upgrade_plugin_settings_with_active_plugin_test(self):
"""
@ -253,8 +280,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_tools_menu()
# THEN: The add_tools_menu_item() method should have been called
assert mocked_plugin.add_tools_menu_item.call_count == 0, \
'The add_tools_menu_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.add_tools_menu_item.call_count,
'The add_tools_menu_item() method should not have been called.')
def hook_tools_menu_with_active_plugin_test(self):
"""
@ -288,7 +315,7 @@ class TestPluginManager(TestCase):
# THEN: The is_active() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.initialise.call_count == 0, 'The initialise() method should not have been called.'
self.assertEqual(0, mocked_plugin.initialise.call_count, 'The initialise() method should not have been called.')
def initialise_plugins_with_active_plugin_test(self):
"""
@ -324,7 +351,7 @@ class TestPluginManager(TestCase):
# THEN: The is_active() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.finalise.call_count == 0, 'The finalise() method should not have been called.'
self.assertEqual(0, mocked_plugin.finalise.call_count, 'The finalise() method should not have been called.')
def finalise_plugins_with_active_plugin_test(self):
"""
@ -392,8 +419,8 @@ class TestPluginManager(TestCase):
# THEN: The isActive() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.new_service_created.call_count == 0,\
'The new_service_created() method should not have been called.'
self.assertEqual(0, mocked_plugin.new_service_created.call_count,
'The new_service_created() method should not have been called.')
def new_service_created_with_active_plugin_test(self):
"""

View File

@ -1,12 +1,39 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
"""
import os
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib import Registry
from tests.functional import MagicMock
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))

View File

@ -1,14 +1,40 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.screenlist package.
"""
from unittest import TestCase
from mock import MagicMock
from PyQt4 import QtGui, QtCore
from openlp.core.lib import Registry, ScreenList
from tests.functional import MagicMock
SCREEN = {
'primary': False,
@ -55,5 +81,6 @@ class TestScreenList(TestCase):
# THEN: The screen should have been added and the screens should be identical
new_screen_count = len(self.screens.screen_list)
assert old_screen_count + 1 == new_screen_count, 'The new_screens list should be bigger'
assert SCREEN == self.screens.screen_list.pop(), 'The 2nd screen should be identical to the first screen'
self.assertEqual(old_screen_count + 1, new_screen_count, 'The new_screens list should be bigger')
self.assertEqual(SCREEN, self.screens.screen_list.pop(),
'The 2nd screen should be identical to the first screen')

View File

@ -1,16 +1,40 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
Package to test the openlp.core.lib package.
"""
import os
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
from tests.functional import MagicMock, patch
from tests.utils import assert_length, convert_file_service_item
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
@ -20,7 +44,6 @@ VERSE = 'The Lord said to {r}Noah{/r}: \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 = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456']
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
@ -36,7 +59,7 @@ class TestServiceItem(TestCase):
Registry().register('renderer', mocked_renderer)
Registry().register('image_manager', MagicMock())
def serviceitem_basic_test(self):
def service_item_basic_test(self):
"""
Test the Service Item - basic test
"""
@ -46,10 +69,10 @@ class TestServiceItem(TestCase):
service_item = ServiceItem(None)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid'
assert service_item.missing_frames() is True, 'There should not be any frames in the service item'
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
self.assertTrue(service_item.missing_frames(), 'There should not be any frames in the service item')
def serviceitem_load_custom_from_service_test(self):
def service_item_load_custom_from_service_test(self):
"""
Test the Service Item - adding a custom slide from a saved service
"""
@ -57,24 +80,29 @@ class TestServiceItem(TestCase):
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding a custom from a saved Service
line = self.convert_file_service_item('serviceitem_custom_1.osj')
# WHEN: We add a custom from a saved service
line = convert_file_service_item(TEST_PATH, '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, 'The new service item should be valid'
assert len(service_item._display_frames) == 0, 'The service item should have no display frames'
assert len(service_item.capabilities) == 5, 'There should be 5 default custom item capabilities'
service_item.render(True)
assert service_item.get_display_title() == 'Test Custom', 'The title should be "Test Custom"'
assert service_item.get_frames()[0]['text'] == VERSE[:-1], \
'The returned text matches the input, except the last line feed'
assert service_item.get_rendered_frame(1) == VERSE.split('\n', 1)[0], 'The first line has been returned'
assert service_item.get_frame_title(0) == 'Slide 1', '"Slide 1" has been returned as the title'
assert service_item.get_frame_title(1) == 'Slide 2', '"Slide 2" has been returned as the title'
assert service_item.get_frame_title(2) == '', 'Blank has been returned as the title of slide 3'
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
assert_length(0, service_item._display_frames, 'The service item should have no display frames')
assert_length(5, service_item.capabilities, 'There should be 5 default custom item capabilities')
def serviceitem_load_image_from_service_test(self):
# WHEN: We render the frames of the service item
service_item.render(True)
# THEN: The frames should also be valid
self.assertEqual('Test Custom', service_item.get_display_title(), 'The title should be "Test Custom"')
self.assertEqual(VERSE[:-1], service_item.get_frames()[0]['text'],
'The returned text matches the input, except the last line feed')
self.assertEqual(VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1),
'The first line has been returned')
self.assertEqual('Slide 1', service_item.get_frame_title(0), '"Slide 1" has been returned as the title')
self.assertEqual('Slide 2', service_item.get_frame_title(1), '"Slide 2" has been returned as the title')
self.assertEqual('', service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3')
def service_item_load_image_from_service_test(self):
"""
Test the Service Item - adding an image from a saved service
"""
@ -87,29 +115,34 @@ class TestServiceItem(TestCase):
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item('serviceitem_image_1.osj')
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj')
with patch('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, 'The new service item should be valid'
assert service_item.get_rendered_frame(0) == test_file, 'The first frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array, 'The return should match frame array1'
assert service_item.get_frame_path(0) == test_file, 'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name, 'The frame title should match the image name'
assert service_item.get_display_title() == image_name, 'The display title should match the first image name'
assert service_item.is_image() is True, 'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
'This service item should be able to be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
'This service item should be able to be be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
'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, \
'This service item should be able to have new items added to it'
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
self.assertEqual(test_file, service_item.get_rendered_frame(0),
'The first frame should match the path to the image')
self.assertEqual(frame_array, service_item.get_frames()[0],
'The return should match frame array1')
self.assertEqual(test_file, service_item.get_frame_path(0),
'The frame path should match the full path to the image')
self.assertEqual(image_name, service_item.get_frame_title(0),
'The frame title should match the image name')
self.assertEqual(image_name, service_item.get_display_title(),
'The display title should match the first image name')
self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain),
'This service item should be able to be Maintained')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview),
'This service item should be able to be be Previewed')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop),
'This service item should be able to be run in a can be made to Loop')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')
def serviceitem_load_image_from_local_service_test(self):
def service_item_load_image_from_local_service_test(self):
"""
Test the Service Item - adding an image from a saved local service
"""
@ -128,50 +161,42 @@ class TestServiceItem(TestCase):
service_item2.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item('serviceitem_image_2.osj')
line2 = self.convert_file_service_item('serviceitem_image_2.osj', 1)
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
with patch('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, 'The first service item should be valid'
assert service_item2.is_valid is True, 'The second service item should be valid'
assert service_item.get_rendered_frame(0) == test_file1, 'The first frame should match the path to the image'
assert service_item2.get_rendered_frame(0) == test_file2, 'The Second frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array1, 'The return should match the frame array1'
assert service_item2.get_frames()[0] == frame_array2, 'The return should match the frame array2'
assert service_item.get_frame_path(0) == test_file1, 'The frame path should match the full path to the image'
assert service_item2.get_frame_path(0) == test_file2, 'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name1, 'The 1st frame title should match the image name'
assert service_item2.get_frame_title(0) == image_name2, 'The 2nd frame title should match the image name'
assert service_item.title.lower() == service_item.name, \
'The plugin name should match the display title, as there are > 1 Images'
assert service_item.is_image() is True, 'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
'This service item should be able to be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
'This service item should be able to be be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
'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, \
'This service item should be able to have new items added to it'
def convert_file_service_item(self, name, row=0):
service_file = os.path.join(TEST_PATH, name)
try:
open_file = open(service_file, 'r')
items = json.load(open_file)
first_line = items[row]
except IOError:
first_line = ''
finally:
open_file.close()
return first_line
self.assertTrue(service_item.is_valid, 'The first service item should be valid')
self.assertTrue(service_item2.is_valid, 'The second service item should be valid')
self.assertEqual(test_file1, service_item.get_rendered_frame(0),
'The first frame should match the path to the image')
self.assertEqual(test_file2, service_item2.get_rendered_frame(0),
'The Second frame should match the path to the image')
self.assertEqual(frame_array1, service_item.get_frames()[0], 'The return should match the frame array1')
self.assertEqual(frame_array2, service_item2.get_frames()[0], 'The return should match the frame array2')
self.assertEqual(test_file1, service_item.get_frame_path(0),
'The frame path should match the full path to the image')
self.assertEqual(test_file2, service_item2.get_frame_path(0),
'The frame path should match the full path to the image')
self.assertEqual(image_name1, service_item.get_frame_title(0),
'The 1st frame title should match the image name')
self.assertEqual(image_name2, service_item2.get_frame_title(0),
'The 2nd frame title should match the image name')
self.assertEqual(service_item.name, service_item.title.lower(),
'The plugin name should match the display title, as there are > 1 Images')
self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain),
'This service item should be able to be Maintained')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview),
'This service item should be able to be be Previewed')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop),
'This service item should be able to be run in a can be made to Loop')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')

View File

@ -1,14 +1,42 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.settings package.
Package to test the openlp.core.lib.settings package.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from openlp.core.lib import Settings
from PyQt4 import QtGui
from openlp.core.lib import Settings
class TestSettings(TestCase):
"""
@ -40,13 +68,13 @@ class TestSettings(TestCase):
default_value = Settings().value('core/has run wizard')
# THEN the default value is returned
assert default_value is False, 'The default value should be False'
self.assertFalse(default_value, 'The default value should be False')
# WHEN a new value is saved into config
Settings().setValue('core/has run wizard', True)
# THEN the new value is returned when re-read
assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned'
self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned')
def settings_override_test(self):
"""
@ -62,13 +90,13 @@ class TestSettings(TestCase):
extend = Settings().value('test/extend')
# THEN the default value is returned
assert extend == 'very wide', 'The default value of "very wide" should be returned'
self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned')
# WHEN a new value is saved into config
Settings().setValue('test/extend', 'very short')
# THEN the new value is returned when re-read
assert Settings().value('test/extend') == 'very short', 'The saved value should be returned'
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def settings_override_with_group_test(self):
"""
@ -86,10 +114,10 @@ class TestSettings(TestCase):
extend = settings.value('extend')
# THEN the default value is returned
assert extend == 'very wide', 'The default value defined should be returned'
self.assertEqual('very wide', extend, 'The default value defined should be returned')
# WHEN a new value is saved into config
Settings().setValue('test/extend', 'very short')
# THEN the new value is returned when re-read
assert Settings().value('test/extend') == 'very short', 'The saved value should be returned'
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')

View File

@ -1,7 +1,34 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.uistrings package.
"""
from unittest import TestCase
from openlp.core.lib import UiStrings
@ -18,6 +45,6 @@ class TestUiStrings(TestCase):
second_instance = UiStrings()
# THEN: Check if the instances are the same.
assert first_instance is second_instance, "They should be the same instance!"
self.assertIs(first_instance, second_instance, 'Two UiStrings objects should be the same instance')

View File

@ -1,3 +1,31 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.utils.actions package.
"""
@ -12,6 +40,9 @@ from openlp.core.utils import ActionList
class TestActionList(TestCase):
"""
Test the ActionList class
"""
def setUp(self):
"""

View File

@ -1,13 +1,39 @@
# -*- 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 #
###############################################################################
"""
Functional tests to test the AppLocation class and related methods.
"""
import copy
from unittest import TestCase
from mock import patch
from openlp.core.utils import AppLocation
from tests.functional import patch
FILE_LIST = ['file1', 'file2', 'file3.txt', 'file4.txt', 'file5.mp3', 'file6.mp3']
@ -38,7 +64,7 @@ class TestAppLocation(TestCase):
mocked_settings.contains.assert_called_with('advanced/data path')
mocked_get_directory.assert_called_with(AppLocation.DataDir)
mocked_check_directory_exists.assert_called_with('test/dir')
assert data_path == 'test/dir', 'Result should be "test/dir"'
self.assertEqual('test/dir', data_path, 'Result should be "test/dir"')
def get_data_path_with_custom_location_test(self):
"""
@ -58,7 +84,7 @@ class TestAppLocation(TestCase):
# THEN: the mocked Settings methods were called and the value returned was our set up value
mocked_settings.contains.assert_called_with('advanced/data path')
mocked_settings.value.assert_called_with('advanced/data path')
assert data_path == 'custom/dir', 'Result should be "custom/dir"'
self.assertEqual('custom/dir', data_path, 'Result should be "custom/dir"')
def get_files_no_section_no_extension_test(self):
"""
@ -74,7 +100,7 @@ class TestAppLocation(TestCase):
result = AppLocation.get_files()
# Then: check if the file lists are identical.
assert result == FILE_LIST, 'The file lists should be identical.'
self.assertListEqual(FILE_LIST, result, 'The file lists should be identical.')
def get_files_test(self):
"""
@ -93,7 +119,7 @@ class TestAppLocation(TestCase):
mocked_listdir.assert_called_with('test/dir/section')
# Then: check if the file lists are identical.
assert result == ['file5.mp3', 'file6.mp3'], 'The file lists should be identical.'
self.assertListEqual(['file5.mp3', 'file6.mp3'], result, 'The file lists should be identical.')
def get_section_data_path_test(self):
"""
@ -110,25 +136,27 @@ class TestAppLocation(TestCase):
# THEN: check that all the correct methods were called, and the result is correct
mocked_check_directory_exists.assert_called_with('test/dir/section')
assert data_path == 'test/dir/section', 'Result should be "test/dir/section"'
self.assertEqual('test/dir/section', data_path, 'Result should be "test/dir/section"')
def get_directory_for_app_dir_test(self):
"""
Test the AppLocation.get_directory() method for AppLocation.AppDir
"""
# GIVEN: A mocked out _get_frozen_path function
with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path:
mocked_get_frozen_path.return_value = 'app/dir'
# WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.AppDir)
# THEN:
assert directory == 'app/dir', 'Directory should be "app/dir"'
# THEN: check that the correct directory is returned
self.assertEqual('app/dir', directory, 'Directory should be "app/dir"')
def get_directory_for_plugins_dir_test(self):
"""
Test the AppLocation.get_directory() method for AppLocation.PluginsDir
"""
# GIVEN: _get_frozen_path, abspath, split and sys are mocked out
with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path, \
patch('openlp.core.utils.applocation.os.path.abspath') as mocked_abspath, \
patch('openlp.core.utils.applocation.os.path.split') as mocked_split, \
@ -142,6 +170,5 @@ class TestAppLocation(TestCase):
# WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN:
assert directory == 'plugins/dir', 'Directory should be "plugins/dir"'
# THEN: The correct directory should be returned
self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"')

View File

@ -1,25 +1,52 @@
# -*- 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 #
###############################################################################
"""
Functional tests to test the AppLocation class and related methods.
"""
from unittest import TestCase
from mock import patch
from openlp.core.utils import clean_filename, get_filesystem_encoding, _get_frozen_path, get_locale_key, \
get_natural_key, split_filename
from tests.functional import patch
class TestUtils(TestCase):
"""
A test suite to test out various methods around the AppLocation class.
"""
def get_filesystem_encoding_test(self):
def get_filesystem_encoding_sys_function_not_called_test(self):
"""
Test the get_filesystem_encoding() function
Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function
"""
# GIVEN: sys.getfilesystemencoding returns "cp1252"
with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \
patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding:
# GIVEN: sys.getfilesystemencoding returns "cp1252"
mocked_getfilesystemencoding.return_value = 'cp1252'
# WHEN: get_filesystem_encoding() is called
@ -27,10 +54,16 @@ class TestUtils(TestCase):
# THEN: getdefaultencoding should have been called
mocked_getfilesystemencoding.assert_called_with()
assert not mocked_getdefaultencoding.called
assert result == 'cp1252', 'The result should be "cp1252"'
self.assertEqual(0, mocked_getdefaultencoding.called, 'getdefaultencoding should not have been called')
self.assertEqual('cp1252', result, 'The result should be "cp1252"')
# GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8"
def get_filesystem_encoding_sys_function_is_called_test(self):
"""
Test the get_filesystem_encoding() function calls the sys.getdefaultencoding() function
"""
# GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8"
with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \
patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding:
mocked_getfilesystemencoding.return_value = None
mocked_getdefaultencoding.return_value = 'utf-8'
@ -40,23 +73,35 @@ class TestUtils(TestCase):
# THEN: getdefaultencoding should have been called
mocked_getfilesystemencoding.assert_called_with()
mocked_getdefaultencoding.assert_called_with()
assert result == 'utf-8', 'The result should be "utf-8"'
self.assertEqual('utf-8', result, 'The result should be "utf-8"')
def get_frozen_path_test(self):
def get_frozen_path_in_unfrozen_app_test(self):
"""
Test the _get_frozen_path() function
Test the _get_frozen_path() function when the application is not frozen (compiled by PyInstaller)
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The sys module "without" a "frozen" attribute
mocked_sys.frozen = None
# WHEN: We call _get_frozen_path() with two parameters
frozen_path = _get_frozen_path('frozen', 'not frozen')
# THEN: The non-frozen parameter is returned
assert _get_frozen_path('frozen', 'not frozen') == 'not frozen', 'Should return "not frozen"'
self.assertEqual('not frozen', frozen_path, '_get_frozen_path should return "not frozen"')
def get_frozen_path_in_frozen_app_test(self):
"""
Test the _get_frozen_path() function when the application is frozen (compiled by PyInstaller)
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The sys module *with* a "frozen" attribute
mocked_sys.frozen = 1
# WHEN: We call _get_frozen_path() with two parameters
frozen_path = _get_frozen_path('frozen', 'not frozen')
# THEN: The frozen parameter is returned
assert _get_frozen_path('frozen', 'not frozen') == 'frozen', 'Should return "frozen"'
self.assertEqual('frozen', frozen_path, 'Should return "frozen"')
def split_filename_with_file_path_test(self):
"""
@ -72,7 +117,7 @@ class TestUtils(TestCase):
result = split_filename(file_path)
# THEN: A tuple should be returned.
assert result == wanted_result, 'A tuple with the directory and file name should have been returned.'
self.assertEqual(wanted_result, result, 'A tuple with the dir and file name should have been returned')
def split_filename_with_dir_path_test(self):
"""
@ -88,8 +133,8 @@ class TestUtils(TestCase):
result = split_filename(file_path)
# THEN: A tuple should be returned.
assert result == wanted_result, \
'A two-entry tuple with the directory and file name (empty) should have been returned.'
self.assertEqual(wanted_result, result,
'A two-entry tuple with the directory and file name (empty) should have been returned.')
def clean_filename_test(self):
"""
@ -103,7 +148,7 @@ class TestUtils(TestCase):
result = clean_filename(invalid_name)
# THEN: The file name should be cleaned.
assert result == wanted_name, 'The file name should not contain any special characters.'
self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.')
def get_locale_key_windows_test(self):
"""
@ -116,13 +161,15 @@ class TestUtils(TestCase):
mocked_get_language.return_value = 'de'
mocked_os.name = 'nt'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
sorted_list = sorted(unsorted_list, key=get_locale_key)
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug']
assert test_passes, 'Strings should be sorted properly'
self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list,
'Strings should be sorted properly')
def get_locale_key_linux_test(self):
"""
Test the get_locale_key(string) function
"""
@ -133,10 +180,13 @@ class TestUtils(TestCase):
mocked_get_language.return_value = 'de'
mocked_os.name = 'linux'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
sorted_list = sorted(unsorted_list, key=get_locale_key)
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug']
assert test_passes, 'Strings should be sorted properly'
self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list,
'Strings should be sorted properly')
def get_natural_key_test(self):
"""
@ -146,7 +196,9 @@ class TestUtils(TestCase):
# GIVEN: The language is English (a language, which sorts digits before letters)
mocked_get_language.return_value = 'en'
unsorted_list = ['item 10a', 'item 3b', '1st item']
# WHEN: We sort the list and use get_natural_key() to generate the sorting keys
sorted_list = sorted(unsorted_list, key=get_natural_key)
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_natural_key) == ['1st item', 'item 3b', 'item 10a']
assert test_passes, 'Numbers should be sorted naturally'
self.assertEqual(['1st item', 'item 3b', 'item 10a'], sorted_list, 'Numbers should be sorted naturally')

View File

@ -1,3 +1,31 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Bibles plugin.
"""

View File

@ -1,15 +1,43 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the versereferencelist submodule of the Bibles plugin.
"""
from unittest import TestCase
from openlp.plugins.bibles.lib.versereferencelist import VerseReferenceList
class TestVerseReferenceList(TestCase):
def setUp(self):
"""
Initializes all we need
"""
"""
Test the VerseReferenceList class
"""
def add_first_verse_test(self):
"""
Test the addition of a verse to the empty list
@ -20,12 +48,12 @@ class TestVerseReferenceList(TestCase):
chapter = 1
verse = 1
version = 'testVersion'
copyright = 'testCopyright'
copyright_ = 'testCopyright'
permission = 'testPermision'
# WHEN: We add it to the verse list
reference_list.add(book, chapter, verse, version, copyright, permission)
reference_list.add(book, chapter, verse, version, copyright_, permission)
# THEN: The entries should be in the first entry of the list
self.assertEqual(reference_list.current_index, 0, 'The current index should be 0')
self.assertEqual(reference_list.verse_list[0]['book'], book, 'The book in first entry should be %s' % book)
@ -33,7 +61,7 @@ class TestVerseReferenceList(TestCase):
self.assertEqual(reference_list.verse_list[0]['start'], verse, 'The start in first entry should be %u' % verse)
self.assertEqual(reference_list.verse_list[0]['version'], version, 'The version in first entry should be %s' % version)
self.assertEqual(reference_list.verse_list[0]['end'], verse, 'The end in first entry should be %u' % verse)
def add_next_verse_test(self):
"""
Test the addition of the following verse
@ -44,17 +72,18 @@ class TestVerseReferenceList(TestCase):
verse = 1
next_verse = 2
version = 'testVersion'
copyright = 'testCopyright'
copyright_ = 'testCopyright'
permission = 'testPermision'
reference_list = VerseReferenceList()
reference_list.add(book, chapter, verse, version, copyright, permission)
reference_list.add(book, chapter, verse, version, copyright_, permission)
# WHEN: We add the following verse to the verse list
reference_list.add(book, chapter, next_verse, version, copyright, permission)
reference_list.add(book, chapter, next_verse, version, copyright_, permission)
# THEN: The current index should be 0 and the end pointer of the entry should be '2'
self.assertEqual(reference_list.current_index, 0, 'The current index should be 0')
self.assertEqual(reference_list.verse_list[0]['end'], next_verse, 'The end in first entry should be %u' % next_verse)
self.assertEqual(reference_list.verse_list[0]['end'], next_verse,
'The end in first entry should be %u' % next_verse)
def add_another_verse_test(self):
"""
@ -64,19 +93,18 @@ class TestVerseReferenceList(TestCase):
book = 'testBook'
chapter = 1
verse = 1
next_verse = 2
another_book = 'testBook2'
another_chapter = 2
another_verse = 5
version = 'testVersion'
copyright = 'testCopyright'
copyright_ = 'testCopyright'
permission = 'testPermision'
reference_list = VerseReferenceList()
reference_list.add(book, chapter, verse, version, copyright, permission)
reference_list.add(book, chapter, verse, version, copyright_, permission)
# WHEN: We add a verse of another book to the verse list
reference_list.add(another_book, another_chapter, another_verse, version, copyright, permission)
reference_list.add(another_book, another_chapter, another_verse, version, copyright_, permission)
# THEN: the current index should be 1
self.assertEqual(reference_list.current_index, 1, 'The current index should be 1')
@ -87,17 +115,18 @@ class TestVerseReferenceList(TestCase):
# GIVEN: version, copyright and permission
reference_list = VerseReferenceList()
version = 'testVersion'
copyright = 'testCopyright'
copyright_ = 'testCopyright'
permission = 'testPermision'
# WHEN: a not existing version will be added
reference_list.add_version(version, copyright, permission)
reference_list.add_version(version, copyright_, permission)
# THEN: the data will be appended to the list
self.assertEqual(len(reference_list.version_list), 1, 'The version data should be appended')
self.assertEqual(reference_list.version_list[0], {'version': version, 'copyright': copyright, 'permission': permission},
self.assertEqual(reference_list.version_list[0],
{'version': version, 'copyright': copyright_, 'permission': permission},
'The version data should be appended')
def add_existing_version_test(self):
"""
Test the addition of an existing version to the list
@ -105,12 +134,12 @@ class TestVerseReferenceList(TestCase):
# GIVEN: version, copyright and permission, added to the version list
reference_list = VerseReferenceList()
version = 'testVersion'
copyright = 'testCopyright'
copyright_ = 'testCopyright'
permission = 'testPermision'
reference_list.add_version(version, copyright, permission)
reference_list.add_version(version, copyright_, permission)
# WHEN: an existing version will be added
reference_list.add_version(version, copyright, permission)
reference_list.add_version(version, copyright_, permission)
# THEN: the data will not be appended to the list
self.assertEqual(len(reference_list.version_list), 1, 'The version data should not be appended')

View File

@ -1,16 +1,40 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Images plugin.
"""
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import Registry
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
from tests.functional import MagicMock, patch
class TestImageMediaItem(TestCase):
@ -24,11 +48,10 @@ class TestImageMediaItem(TestCase):
Registry().register('service_list', MagicMock())
Registry().register('main_window', self.mocked_main_window)
Registry().register('live_controller', MagicMock())
mocked_parent = MagicMock()
mocked_plugin = MagicMock()
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.__init__') as mocked_init:
mocked_init.return_value = None
self.media_item = ImageMediaItem(mocked_parent, mocked_plugin)
with patch('openlp.plugins.images.lib.mediaitem.MediaManagerItem._setup'), \
patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.setup_item'):
self.media_item = ImageMediaItem(None, mocked_plugin)
def save_new_images_list_empty_list_test(self):
"""
@ -36,7 +59,7 @@ class TestImageMediaItem(TestCase):
"""
# GIVEN: An empty image_list
image_list = []
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list:
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list'):
self.media_item.manager = MagicMock()
# WHEN: We run save_new_images_list with the empty list
@ -50,8 +73,8 @@ class TestImageMediaItem(TestCase):
"""
Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
"""
# GIVEN: A list with 1 image
image_list = [ 'test_image.jpg' ]
# GIVEN: A list with 1 image and a mocked out manager
image_list = ['test_image.jpg']
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list:
ImageFilenames.filename = ''
self.media_item.manager = MagicMock()
@ -69,8 +92,8 @@ class TestImageMediaItem(TestCase):
"""
Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
"""
# GIVEN: A list with 1 image
image_list = [ 'test_image.jpg' ]
# GIVEN: A list with 1 image and a mocked out manager
image_list = ['test_image.jpg']
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list:
self.media_item.manager = MagicMock()
@ -126,9 +149,35 @@ class TestImageMediaItem(TestCase):
self.media_item.reset_action.setVisible.assert_called_with(False)
self.media_item.live_controller.display.reset_image.assert_called_with()
def recursively_delete_group_test(self):
"""
Test that recursively_delete_group() works
"""
# GIVEN: An ImageGroups object and mocked functions
with patch('openlp.core.utils.delete_file') as mocked_delete_file:
ImageFilenames.group_id = 1
ImageGroups.parent_id = 1
self.media_item.manager = MagicMock()
self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
self.media_item.service_path = ""
test_group = ImageGroups()
test_group.id = 1
# WHEN: recursively_delete_group() is called
self.media_item.recursively_delete_group(test_group)
# THEN:
assert mocked_delete_file.call_count == 0, 'delete_file() should not be called'
assert self.media_item.manager.delete_object.call_count == 7, \
'manager.delete_object() should be called exactly 7 times'
# CLEANUP: Remove added attribute from ImageFilenames and ImageGroups
delattr(ImageFilenames, 'group_id')
delattr(ImageGroups, 'parent_id')
def _recursively_delete_group_side_effect(*args, **kwargs):
"""
Side effect method that creates custom retun values for the recursively_delete_group method
Side effect method that creates custom return values for the recursively_delete_group method
"""
if args[1] == ImageFilenames and args[2]:
# Create some fake objects that should be removed
@ -150,29 +199,3 @@ class TestImageMediaItem(TestCase):
returned_object1.id = 1
return [returned_object1]
return []
def recursively_delete_group_test(self):
"""
Test that recursively_delete_group() works
"""
# GIVEN: An ImageGroups object and mocked functions
with patch('openlp.core.utils.delete_file') as mocked_delete_file:
ImageFilenames.group_id = 1
ImageGroups.parent_id = 1
self.media_item.manager = MagicMock()
self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
self.media_item.servicePath = ""
test_group = ImageGroups()
test_group.id = 1
# WHEN: recursively_delete_group() is called
self.media_item.recursively_delete_group(test_group)
# THEN:
assert mocked_delete_file.call_count == 0, 'delete_file() should not be called'
assert self.media_item.manager.delete_object.call_count == 7, \
'manager.delete_object() should be called exactly 7 times'
# CLEANUP: Remove added attribute from ImageFilenames and ImageGroups
delattr(ImageFilenames, 'group_id')
delattr(ImageGroups, 'parent_id')

View File

@ -1,17 +1,41 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Presentations plugin.
"""
import os
from tempfile import mkstemp
from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtGui
from openlp.core.lib import Registry
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
from tests.functional import patch, MagicMock
class TestMediaItem(TestCase):
@ -25,11 +49,9 @@ class TestMediaItem(TestCase):
Registry.create()
Registry().register('service_manager', MagicMock())
Registry().register('main_window', MagicMock())
with patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.__init__') as mocked_init:
mocked_init.return_value = None
self.media_item = PresentationMediaItem(MagicMock(), MagicMock, MagicMock(), MagicMock())
with patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup'), \
patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item'):
self.media_item = PresentationMediaItem(None, MagicMock, MagicMock())
self.application = QtGui.QApplication.instance()
def tearDown(self):
@ -65,7 +87,8 @@ class TestMediaItem(TestCase):
mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate
self.media_item.build_file_mask_string()
# THEN: The file mask should be generated.
assert self.media_item.on_new_file_masks == 'Presentations (*.odp *.ppt )', \
'The file mask should contain the odp and ppt extensions'
# THEN: The file mask should be generated correctly
self.assertIn('*.odp', self.media_item.on_new_file_masks,
'The file mask should contain the odp extension')
self.assertIn('*.ppt', self.media_item.on_new_file_masks,
'The file mask should contain the ppt extension')

View File

@ -1,17 +1,44 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
import re
from unittest import TestCase
from tempfile import mkstemp
from mock import patch
from PyQt4 import QtGui
from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.remotetab import RemoteTab
from PyQt4 import QtGui
from tests.functional import patch
__default_settings__ = {
'remotes/twelve hour': True,
@ -23,9 +50,7 @@ __default_settings__ = {
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
}
ZERO_URL = '0.0.0.0'
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
@ -60,7 +85,8 @@ class TestRemoteTab(TestCase):
# WHEN: the default ip address is given
ip_address = self.form.get_ip_address(ZERO_URL)
# THEN: the default ip address will be returned
self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), 'The return value should be a valid ip address')
self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address),
'The return value should be a valid ip address')
def get_ip_address_with_ip_test(self):
"""
@ -80,9 +106,9 @@ class TestRemoteTab(TestCase):
"""
# GIVEN: A mocked location
with patch('openlp.core.utils.applocation.Settings') as mocked_class, \
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False
@ -96,7 +122,7 @@ class TestRemoteTab(TestCase):
# THEN: the following screen values should be set
self.assertEqual(self.form.address_edit.text(), ZERO_URL, 'The default URL should be set on the screen')
self.assertEqual(self.form.https_settings_group_box.isEnabled(), False,
'The Https box should not be enabled')
'The Https box should not be enabled')
self.assertEqual(self.form.https_settings_group_box.isChecked(), False,
'The Https checked box should note be Checked')
self.assertEqual(self.form.user_login_group_box.isChecked(), False,
@ -108,9 +134,9 @@ class TestRemoteTab(TestCase):
"""
# GIVEN: A mocked location
with patch('openlp.core.utils.applocation.Settings') as mocked_class, \
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False

View File

@ -1,15 +1,43 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from mock import MagicMock
from PyQt4 import QtGui
from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.httpserver import HttpRouter, fetch_password, make_sha_hash
from PyQt4 import QtGui
from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock
__default_settings__ = {
'remotes/twelve hour': True,
@ -44,40 +72,22 @@ class TestRouter(TestCase):
del self.application
os.unlink(self.ini_file)
def fetch_password_unknown_test(self):
def password_encrypter_test(self):
"""
Test the fetch password code with an unknown userid
Test hash userid and password function
"""
# GIVEN: A default configuration
# WHEN: called with the defined userid
password = fetch_password('itwinkle')
Settings().setValue('remotes/user id', 'openlp')
Settings().setValue('remotes/password', 'password')
# THEN: the function should return None
self.assertEqual(password, None, 'The result for fetch_password should be None')
def fetch_password_known_test(self):
"""
Test the fetch password code with the defined userid
"""
# GIVEN: A default configuration
# WHEN: called with the defined userid
password = fetch_password('openlp')
required_password = make_sha_hash('password')
router = HttpRouter()
router.initialise()
test_value = 'b3BlbmxwOnBhc3N3b3Jk'
print(router.auth)
# THEN: the function should return the correct password
self.assertEqual(password, required_password, 'The result for fetch_password should be the defined password')
def sha_password_encrypter_test(self):
"""
Test hash password function
"""
# GIVEN: A default configuration
# WHEN: called with the defined userid
required_password = make_sha_hash('password')
test_value = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8'
# THEN: the function should return the correct password
self.assertEqual(required_password, test_value,
self.assertEqual(router.auth, test_value,
'The result for make_sha_hash should return the correct encrypted password')
def process_http_request_test(self):
@ -85,15 +95,18 @@ class TestRouter(TestCase):
Test the router control functionality
"""
# GIVEN: A testing set of Routes
router = HttpRouter()
mocked_function = MagicMock()
test_route = [
(r'^/stage/api/poll$', mocked_function),
(r'^/stage/api/poll$', {'function': mocked_function, 'secure': False}),
]
self.router.routes = test_route
router.routes = test_route
# WHEN: called with a poll route
self.router.process_http_request('/stage/api/poll', None)
function, args = router.process_http_request('/stage/api/poll', None)
# THEN: the function should have been called only once
assert mocked_function.call_count == 1, \
'The mocked function should have been matched and called once.'
assert function['function'] == mocked_function, \
'The mocked function should match defined value.'
assert function['secure'] == False, \
'The mocked function should not require any security.'

View File

@ -1,13 +1,39 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the EasyWorship song importer.
"""
import os
from unittest import TestCase
from mock import patch, MagicMock
from tests.functional import MagicMock, patch
from openlp.plugins.songs.lib.ewimport import EasyWorshipSongImport, FieldDescEntry, FieldType
@ -43,6 +69,7 @@ SONG_TEST_DATA = [
'Just to bow and receive a new blessing,\nIn the beautiful garden of prayer.', 'v3')],
'verse_order_list': []}]
class EasyWorshipSongImportLogger(EasyWorshipSongImport):
"""
This class logs changes in the title instance variable
@ -60,6 +87,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
def title(self, title):
self._title_assignment_list.append(title)
class TestFieldDesc:
def __init__(self, name, field_type, size):
self.name = name

View File

@ -32,9 +32,8 @@ This module contains tests for the SongShow Plus song importer.
import os
from unittest import TestCase
from mock import patch, MagicMock
from tests.functional import patch, MagicMock
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.foilpresenterimport import FoilPresenter
TEST_PATH = os.path.abspath(
@ -192,4 +191,4 @@ class TestFoilPresenter(TestCase):
# THEN: _process_lyrics should return None and the song_import logError method should have been called once
self.assertIsNone(result)
self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String')
self.process_lyrics_patcher.start()
self.process_lyrics_patcher.start()

View File

@ -1,13 +1,39 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Songs plugin.
"""
from unittest import TestCase
from mock import patch, MagicMock
from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf
from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length
from tests.functional import patch, MagicMock
class TestLib(TestCase):
@ -68,10 +94,10 @@ class TestLib(TestCase):
# GIVEN: Two equal songs.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.full_lyrics
# WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be True.
assert result == True, 'The result should be True'
@ -82,10 +108,10 @@ class TestLib(TestCase):
# GIVEN: A song and a short version of the same song.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.short_lyrics
# WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be True.
assert result == True, 'The result should be True'
@ -96,10 +122,10 @@ class TestLib(TestCase):
# GIVEN: A song and the same song with lots of errors.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.error_lyrics
# WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be True.
assert result == True, 'The result should be True'
@ -110,10 +136,10 @@ class TestLib(TestCase):
# GIVEN: Two different songs.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.different_lyrics
# WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be False.
assert result == False, 'The result should be False'

View File

@ -5,13 +5,11 @@ import os
from tempfile import mkstemp
from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry, ServiceItem, Settings
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from tests.functional import patch, MagicMock
class TestMediaItem(TestCase):
@ -25,9 +23,9 @@ class TestMediaItem(TestCase):
Registry.create()
Registry().register('service_list', MagicMock())
Registry().register('main_window', MagicMock())
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem.__init__'), \
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
self.media_item = SongMediaItem(MagicMock(), MagicMock())
self.media_item = SongMediaItem(None, MagicMock())
fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file)

View File

@ -1,13 +1,41 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the SongShow Plus song importer.
"""
import os
from unittest import TestCase
from mock import patch, MagicMock
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport
from tests.functional import patch, MagicMock
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../resources/songshowplussongs'))
SONG_TEST_DATA = {'Amazing Grace.sbsong':

View File

@ -1,15 +1,45 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the WorshipCenter Pro song importer.
"""
import os
from unittest import TestCase, SkipTest
if os.name != 'nt':
raise SkipTest('Not Windows, skipping test')
from unittest import TestCase
from mock import patch, MagicMock
import pyodbc
from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport
from tests.functional import patch, MagicMock
class TestRecord(object):
"""
@ -23,6 +53,7 @@ class TestRecord(object):
self.Field = field
self.Value = value
class WorshipCenterProImportLogger(WorshipCenterProImport):
"""
This class logs changes in the title instance variable
@ -189,4 +220,4 @@ class TestWorshipCenterProSongImport(TestCase):
for call in verse_calls:
mocked_add_verse.assert_any_call(call)
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
'Incorrect number of calls made to addVerse')
'Incorrect number of calls made to addVerse')

View File

@ -1,138 +0,0 @@
"""
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from mock import MagicMock
import urllib.request, urllib.error, urllib.parse
import cherrypy
from bs4 import BeautifulSoup
from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.httpserver import HttpServer
from PyQt4 import QtGui
__default_settings__ = {
'remotes/twelve hour': True,
'remotes/port': 4316,
'remotes/https port': 4317,
'remotes/https enabled': False,
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
}
class TestRouter(TestCase):
"""
Test the functions in the :mod:`lib` module.
"""
def setUp(self):
"""
Create the UI
"""
fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance()
Settings().extend_default_settings(__default_settings__)
self.server = HttpServer()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.application
os.unlink(self.ini_file)
self.server.close()
def start_server(self):
"""
Common function to start server then mock out the router. CherryPy crashes if you mock before you start
"""
self.server.start_server()
self.server.router = MagicMock()
self.server.router.process_http_request = process_http_request
def start_default_server_test(self):
"""
Test the default server serves the correct initial page
"""
# GIVEN: A default configuration
Settings().setValue('remotes/authentication enabled', False)
self.start_server()
# WHEN: called the route location
code, page = call_remote_server('http://localhost:4316')
# THEN: default title will be returned
self.assertEqual(BeautifulSoup(page).title.text, 'OpenLP 2.1 Remote',
'The default menu should be returned')
def start_authenticating_server_test(self):
"""
Test the default server serves the correctly with authentication
"""
# GIVEN: A default authorised configuration
Settings().setValue('remotes/authentication enabled', True)
self.start_server()
# WHEN: called the route location with no user details
code, page = call_remote_server('http://localhost:4316')
# THEN: then server will ask for details
self.assertEqual(code, 401, 'The basic authorisation request should be returned')
# WHEN: called the route location with user details
code, page = call_remote_server('http://localhost:4316', 'openlp', 'password')
# THEN: default title will be returned
self.assertEqual(BeautifulSoup(page).title.text, 'OpenLP 2.1 Remote',
'The default menu should be returned')
# WHEN: called the route location with incorrect user details
code, page = call_remote_server('http://localhost:4316', 'itwinkle', 'password')
# THEN: then server will ask for details
self.assertEqual(code, 401, 'The basic authorisation request should be returned')
def call_remote_server(url, username=None, password=None):
"""
Helper function
``username``
The username.
``password``
The password.
"""
if username:
passman = urllib.request.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, username, password)
authhandler = urllib.request.HTTPBasicAuthHandler(passman)
opener = urllib.request.build_opener(authhandler)
urllib.request.install_opener(opener)
try:
page = urllib.request.urlopen(url)
return 0, page.read()
except urllib.error.HTTPError as e:
return e.code, ''
def process_http_request(url_path, *args):
"""
Override function to make the Mock work but does nothing.
``Url_path``
The url_path.
``*args``
Some args.
"""
cherrypy.response.status = 200
return None

View File

@ -0,0 +1,51 @@
# -*- 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 #
###############################################################################
import os
import json
def assert_length(expected, iterable, msg=None):
if len(iterable) != expected:
if not msg:
msg = 'Expected length %s, got %s' % (expected, len(iterable))
raise AssertionError(msg)
def convert_file_service_item(test_path, name, row=0):
service_file = os.path.join(test_path, name)
open_file = open(service_file, 'r')
try:
items = json.load(open_file)
first_line = items[row]
except IOError:
first_line = ''
finally:
open_file.close()
return first_line