Merged with trunk

This commit is contained in:
Tomas Groth 2013-12-27 17:47:40 +00:00
commit 47ea300786
27 changed files with 970 additions and 80 deletions

View File

@ -83,6 +83,8 @@ class UiStrings(object):
self.Error = translate('OpenLP.Ui', 'Error') self.Error = translate('OpenLP.Ui', 'Error')
self.Export = translate('OpenLP.Ui', 'Export') self.Export = translate('OpenLP.Ui', 'Export')
self.File = translate('OpenLP.Ui', 'File') self.File = translate('OpenLP.Ui', 'File')
self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
self.FileNotFoundMessage = translate('OpenLP.Ui', 'File %s not found.\nPlease try selecting it individually.')
self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit') self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
self.Help = translate('OpenLP.Ui', 'Help') self.Help = translate('OpenLP.Ui', 'Help')
self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours') self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')

View File

@ -330,6 +330,7 @@ def create_separated_list(string_list):
from .registry import Registry from .registry import Registry
from .filedialog import FileDialog
from .screen import ScreenList from .screen import ScreenList
from .listwidgetwithdnd import ListWidgetWithDnD from .listwidgetwithdnd import ListWidgetWithDnD
from .treewidgetwithdnd import TreeWidgetWithDnD from .treewidgetwithdnd import TreeWidgetWithDnD
@ -345,4 +346,3 @@ from .dockwidget import OpenLPDockWidget
from .imagemanager import ImageManager from .imagemanager import ImageManager
from .renderer import Renderer from .renderer import Renderer
from .mediamanageritem import MediaManagerItem from .mediamanageritem import MediaManagerItem

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 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 #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
"""
Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
"""
import logging
import os
from urllib import parse
from PyQt4 import QtGui
from openlp.core.common import UiStrings
log = logging.getLogger(__name__)
class FileDialog(QtGui.QFileDialog):
"""
Subclass QFileDialog to work round a bug
"""
@staticmethod
def getOpenFileNames(parent, *args, **kwargs):
"""
Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
files
"""
files = QtGui.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
file_list = []
for file in files:
if not os.path.exists(file):
log.info('File %s not found. Attempting to unquote.')
file = parse.unquote(file)
if not os.path.exists(file):
log.error('File %s not found.' % file)
QtGui.QMessageBox.information(parent, UiStrings().FileNotFound,
UiStrings().FileNotFoundMessage % file)
continue
log.info('File %s found.')
file_list.append(file)
return file_list

View File

@ -36,7 +36,7 @@ import re
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import Settings, UiStrings, translate from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \ from openlp.core.lib import FileDialog, OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \
ServiceItemContext, Registry ServiceItemContext, Registry
from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import create_widget_action, critical_error_message_box from openlp.core.lib.ui import create_widget_action, critical_error_message_box
@ -319,7 +319,7 @@ class MediaManagerItem(QtGui.QWidget):
""" """
Add a file to the list widget to make it available for showing Add a file to the list widget to make it available for showing
""" """
files = QtGui.QFileDialog.getOpenFileNames(self, self.on_new_prompt, files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks) Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks)
log.info('New files(s) %s', files) log.info('New files(s) %s', files)
if files: if files:

View File

@ -37,7 +37,7 @@ import urllib.request
import urllib.parse import urllib.parse
import urllib.error import urllib.error
from tempfile import gettempdir from tempfile import gettempdir
from configparser import SafeConfigParser from configparser import ConfigParser
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
@ -68,7 +68,7 @@ class ThemeScreenshotThread(QtCore.QThread):
filename = config.get('theme_%s' % theme, 'filename') filename = config.get('theme_%s' % theme, 'filename')
screenshot = config.get('theme_%s' % theme, 'screenshot') screenshot = config.get('theme_%s' % theme, 'screenshot')
urllib.request.urlretrieve('%s%s' % (self.parent().web, screenshot), urllib.request.urlretrieve('%s%s' % (self.parent().web, screenshot),
os.path.join(gettempdir(), 'openlp', screenshot)) os.path.join(gettempdir(), 'openlp', screenshot))
item = QtGui.QListWidgetItem(title, self.parent().themes_list_widget) item = QtGui.QListWidgetItem(title, self.parent().themes_list_widget)
item.setData(QtCore.Qt.UserRole, filename) item.setData(QtCore.Qt.UserRole, filename)
item.setCheckState(QtCore.Qt.Unchecked) item.setCheckState(QtCore.Qt.Unchecked)
@ -90,14 +90,16 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self.screens = screens self.screens = screens
# check to see if we have web access # check to see if we have web access
self.web = 'http://openlp.org/files/frw/' self.web = 'http://openlp.org/files/frw/'
self.config = SafeConfigParser() self.config = ConfigParser()
self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg')) user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent))
if self.web_access: if self.web_access:
files = self.web_access.read() files = self.web_access.read()
self.config.read_string(files.decode()) self.config.read_string(files.decode())
self.update_screen_list_combo() self.update_screen_list_combo()
self.was_download_cancelled = False self.was_download_cancelled = False
self.theme_screenshot_thread = None self.theme_screenshot_thread = None
self.has_run_wizard = False
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...') self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...')
self.cancel_button.clicked.connect(self.on_cancel_button_clicked) self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)

View File

@ -74,7 +74,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
if event.key() == QtCore.Qt.Key_Space: if event.key() == QtCore.Qt.Key_Space:
self.keyReleaseEvent(event) self.keyReleaseEvent(event)
elif self.primaryPushButton.isChecked() or self.alternatePushButton.isChecked(): elif self.primaryPushButton.isChecked() or self.alternatePushButton.isChecked():
event.ignore() self.keyReleaseEvent(event)
elif event.key() == QtCore.Qt.Key_Escape: elif event.key() == QtCore.Qt.Key_Escape:
event.accept() event.accept()
self.close() self.close()

View File

@ -33,13 +33,12 @@ import os
import zipfile import zipfile
import shutil import shutil
import logging import logging
import re
from xml.etree.ElementTree import ElementTree, XML from xml.etree.ElementTree import ElementTree, XML
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import AppLocation, Settings, check_directory_exists, UiStrings, translate from openlp.core.common import AppLocation, Settings, check_directory_exists, UiStrings, translate
from openlp.core.lib import ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \ from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import ThemeXML, BackgroundType from openlp.core.lib.theme import ThemeXML, BackgroundType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action from openlp.core.lib.ui import critical_error_message_box, create_widget_action
@ -59,7 +58,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
""" """
super(ThemeManager, self).__init__(parent) super(ThemeManager, self).__init__(parent)
Registry().register('theme_manager', self) Registry().register('theme_manager', self)
Registry().register_function('bootstrap_initialise', self.load_first_time_themes) Registry().register_function('bootstrap_initialise', self.initialise)
Registry().register_function('bootstrap_post_set_up', self._push_themes) Registry().register_function('bootstrap_post_set_up', self._push_themes)
self.settings_section = 'themes' self.settings_section = 'themes'
self.theme_form = ThemeForm(self) self.theme_form = ThemeForm(self)
@ -135,15 +134,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
Registry().register_function('theme_update_global', self.change_global_from_tab) Registry().register_function('theme_update_global', self.change_global_from_tab)
# Variables # Variables
self.theme_list = [] self.theme_list = []
self.path = AppLocation.get_section_data_path(self.settings_section)
check_directory_exists(self.path)
self.thumb_path = os.path.join(self.path, 'thumbnails')
check_directory_exists(self.thumb_path)
self.theme_form.path = self.path
self.old_background_image = None self.old_background_image = None
self.bad_v1_name_chars = re.compile(r'[%+\[\]]')
# Last little bits of setting up
self.global_theme = Settings().value(self.settings_section + '/global theme')
def check_list_state(self, item): def check_list_state(self, item):
""" """
@ -374,7 +365,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
those files. This process will load both OpenLP version 1 and version 2 themes. those files. This process will load both OpenLP version 1 and version 2 themes.
""" """
files = QtGui.QFileDialog.getOpenFileNames(self, files = FileDialog.getOpenFileNames(self,
translate('OpenLP.ThemeManager', 'Select Theme Import File'), translate('OpenLP.ThemeManager', 'Select Theme Import File'),
Settings().value(self.settings_section + '/last directory import'), Settings().value(self.settings_section + '/last directory import'),
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)')) translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)'))
@ -392,6 +383,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
""" """
Imports any themes on start up and makes sure there is at least one theme Imports any themes on start up and makes sure there is at least one theme
""" """
log.debug('load_first_time_themes called')
self.application.set_busy_cursor() self.application.set_busy_cursor()
files = AppLocation.get_files(self.settings_section, '.otz') files = AppLocation.get_files(self.settings_section, '.otz')
for theme_file in files: for theme_file in files:
@ -410,8 +402,8 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
def load_themes(self): def load_themes(self):
""" """
Loads the theme lists and triggers updates across the whole system Loads the theme lists and triggers updates across the whole system using direct calls or core functions and
using direct calls or core functions and events for the plugins. events for the plugins.
The plugins will call back in to get the real list if they want it. The plugins will call back in to get the real list if they want it.
""" """
log.debug('Load themes from dir') log.debug('Load themes from dir')
@ -636,18 +628,18 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
self.main_window.finished_progress_bar() self.main_window.finished_progress_bar()
self.load_themes() self.load_themes()
def generate_image(self, theme_data, forcePage=False): def generate_image(self, theme_data, force_page=False):
""" """
Call the renderer to build a Sample Image Call the renderer to build a Sample Image
``theme_data`` ``theme_data``
The theme to generated a preview for. The theme to generated a preview for.
``forcePage`` ``force_page``
Flag to tell message lines per page need to be generated. Flag to tell message lines per page need to be generated.
""" """
log.debug('generate_image \n%s ', theme_data) log.debug('generate_image \n%s ', theme_data)
return self.renderer.generate_preview(theme_data, forcePage) return self.renderer.generate_preview(theme_data, force_page)
def get_preview_image(self, theme): def get_preview_image(self, theme):
""" """
@ -672,7 +664,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
theme.extend_image_filename(path) theme.extend_image_filename(path)
return theme return theme
def _validate_theme_action(self, select_text, confirm_title, confirm_text, testPlugin=True, confirm=True): def _validate_theme_action(self, select_text, confirm_title, confirm_text, test_plugin=True, confirm=True):
""" """
Check to see if theme has been selected and the destructive action Check to see if theme has been selected and the destructive action
is allowed. is allowed.
@ -694,7 +686,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
message=translate('OpenLP.ThemeManager', 'You are unable to delete the default theme.')) message=translate('OpenLP.ThemeManager', 'You are unable to delete the default theme.'))
return False return False
# check for use in the system else where. # check for use in the system else where.
if testPlugin: if test_plugin:
for plugin in self.plugin_manager.plugins: for plugin in self.plugin_manager.plugins:
if plugin.uses_theme(theme): if plugin.uses_theme(theme):
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'), critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),

View File

@ -29,10 +29,34 @@
""" """
The Theme Controller helps manages adding, deleteing and modifying of themes. The Theme Controller helps manages adding, deleteing and modifying of themes.
""" """
import logging
import os
from openlp.core.common import AppLocation, Settings, check_directory_exists
log = logging.getLogger(__name__)
class ThemeManagerHelper(object): class ThemeManagerHelper(object):
""" """
Manages the non ui theme functions. Manages the non ui theme functions.
""" """
pass def initialise(self):
"""
Setup the manager
"""
log.debug('initialise called')
self.global_theme = Settings().value(self.settings_section + '/global theme')
self.build_theme_path()
self.load_first_time_themes()
def build_theme_path(self):
"""
Set up the theme path variables
"""
log.debug('build theme path called')
self.path = AppLocation.get_section_data_path(self.settings_section)
check_directory_exists(self.path)
self.thumb_path = os.path.join(self.path, 'thumbnails')
check_directory_exists(self.thumb_path)
self.theme_form.path = self.path

View File

@ -40,6 +40,7 @@ import sys
import urllib.request import urllib.request
import urllib.error import urllib.error
import urllib.parse import urllib.parse
from random import randint
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
@ -61,10 +62,29 @@ APPLICATION_VERSION = {}
IMAGES_FILTER = None IMAGES_FILTER = None
ICU_COLLATOR = None ICU_COLLATOR = None
UNO_CONNECTION_TYPE = 'pipe' UNO_CONNECTION_TYPE = 'pipe'
#UNO_CONNECTION_TYPE = u'socket'
CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE) CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE) INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE) DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
USER_AGENTS = {
'win32': [
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36'
],
'darwin': [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 Safari/537.31',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11',
],
'linux2': [
'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.22 (KHTML, like Gecko) Ubuntu Chromium/25.0.1364.160 Chrome/25.0.1364.160 Safari/537.22',
'Mozilla/5.0 (X11; CrOS armv7l 2913.260.0) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.99 Safari/537.11',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.27 (KHTML, like Gecko) Chrome/26.0.1389.0 Safari/537.27'
],
'default': [
'Mozilla/5.0 (X11; NetBSD amd64; rv:18.0) Gecko/20130120 Firefox/18.0'
]
}
class VersionThread(QtCore.QThread): class VersionThread(QtCore.QThread):
@ -298,6 +318,17 @@ def delete_file(file_path_name):
return False return False
def _get_user_agent():
"""
Return a user agent customised for the platform the user is on.
"""
browser_list = USER_AGENTS.get(sys.platform, None)
if not browser_list:
browser_list = USER_AGENTS['default']
random_index = randint(0, len(browser_list) - 1)
return browser_list[random_index]
def get_web_page(url, header=None, update_openlp=False): def get_web_page(url, header=None, update_openlp=False):
""" """
Attempts to download the webpage at url and returns that page or None. Attempts to download the webpage at url and returns that page or None.
@ -318,6 +349,9 @@ def get_web_page(url, header=None, update_openlp=False):
if not url: if not url:
return None return None
req = urllib.request.Request(url) req = urllib.request.Request(url)
if not header or header[0].lower() != 'user-agent':
user_agent = _get_user_agent()
req.add_header('User-Agent', user_agent)
if header: if header:
req.add_header(header[0], header[1]) req.add_header(header[0], header[1])
page = None page = None

View File

@ -379,7 +379,7 @@ class BSExtract(object):
send_error_message('parse') send_error_message('parse')
return None return None
content = content.find_all('li') content = content.find_all('li')
return [book.contents[0].contents[0] for book in content] return [book.contents[0].contents[0] for book in content if len(book.contents[0].contents)]
def _get_application(self): def _get_application(self):
""" """

View File

@ -283,7 +283,7 @@ class Controller(object):
class MessageListener(object): class MessageListener(object):
""" """
This is the Presentation listener who acts on events from the slide controller and passes the messages on the the This is the Presentation listener who acts on events from the slide controller and passes the messages on the
correct presentation handlers correct presentation handlers
""" """
log.info('Message Listener loaded') log.info('Message Listener loaded')

View File

@ -292,8 +292,8 @@ class PresentationDocument(object):
class PresentationController(object): class PresentationController(object):
""" """
This class is used to control interactions with presentation applications by creating a runtime environment. This is This class is used to control interactions with presentation applications by creating a runtime environment.
a base class for presentation controllers to inherit from. This is a base class for presentation controllers to inherit from.
To create a new controller, take a copy of this file and name it so it ends with ``controller.py``, i.e. To create a new controller, take a copy of this file and name it so it ends with ``controller.py``, i.e.
``foobarcontroller.py``. Make sure it inherits ``foobarcontroller.py``. Make sure it inherits
@ -341,8 +341,7 @@ class PresentationController(object):
""" """
log.info('PresentationController loaded') log.info('PresentationController loaded')
def __init__(self, plugin=None, name='PresentationController', def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument):
document_class=PresentationDocument):
""" """
This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins
to populate common data. This method *must* be overridden, like so:: to populate common data. This method *must* be overridden, like so::

View File

@ -128,6 +128,15 @@ from openlp.core.common import AppLocation, Settings, translate
from openlp.core.lib import Registry, PluginStatus, StringContent, image_to_byte from openlp.core.lib import Registry, PluginStatus, StringContent, image_to_byte
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
FILE_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.png': 'image/png'
}
class HttpRouter(object): class HttpRouter(object):
@ -346,30 +355,13 @@ class HttpRouter(object):
path = os.path.normpath(os.path.join(self.html_dir, file_name)) path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir): if not path.startswith(self.html_dir):
return self.do_not_found() return self.do_not_found()
ext = os.path.splitext(file_name)[1] content = None
html = None ext, content_type = self.get_content_type(path)
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 file_handle = None
try: try:
if html: if ext == '.html':
content = html variables = self.template_vars
content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
else: else:
file_handle = open(path, 'rb') file_handle = open(path, 'rb')
log.debug('Opened %s' % path) log.debug('Opened %s' % path)
@ -380,8 +372,22 @@ class HttpRouter(object):
finally: finally:
if file_handle: if file_handle:
file_handle.close() file_handle.close()
self.send_response(200)
self.send_header('Content-type', content_type)
self.end_headers()
return content return content
def get_content_type(self, file_name):
"""
Examines the extension of the file and determines
what the content_type should be, defaults to text/plain
Returns the extension and the content_type
"""
content_type = 'text/plain'
ext = os.path.splitext(file_name)[1]
content_type = FILE_TYPES.get(ext, 'text/plain')
return ext, content_type
def poll(self): def poll(self):
""" """
Poll OpenLP to determine the current slide number and item name. Poll OpenLP to determine the current slide number and item name.

View File

@ -39,7 +39,7 @@ import shutil
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import AppLocation, UiStrings, check_directory_exists, translate from openlp.core.common import AppLocation, UiStrings, check_directory_exists, translate
from openlp.core.lib import Registry, PluginStatus, MediaType, create_separated_list from openlp.core.lib import FileDialog, Registry, PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.plugins.songs.lib import VerseType, clean_song from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
@ -758,7 +758,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
Loads file(s) from the filesystem. Loads file(s) from the filesystem.
""" """
filters = '%s (*)' % UiStrings().AllFiles filters = '%s (*)' % UiStrings().AllFiles
filenames = QtGui.QFileDialog.getOpenFileNames(self, filenames = FileDialog.getOpenFileNames(self,
translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters) translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters)
for filename in filenames: for filename in filenames:
item = QtGui.QListWidgetItem(os.path.split(str(filename))[1]) item = QtGui.QListWidgetItem(os.path.split(str(filename))[1])

View File

@ -37,7 +37,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.common import UiStrings, translate from openlp.core.common import UiStrings, translate
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.core.lib import Registry from openlp.core.lib import FileDialog, Registry
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
@ -246,7 +246,7 @@ class SongImportForm(OpenLPWizard):
if filters: if filters:
filters += ';;' filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles filters += '%s (*)' % UiStrings().AllFiles
filenames = QtGui.QFileDialog.getOpenFileNames(self, title, filenames = FileDialog.getOpenFileNames(self, title,
Settings().value(self.plugin.settings_section + '/last directory import'), filters) Settings().value(self.plugin.settings_section + '/last directory import'), filters)
if filenames: if filenames:
listbox.addItems(filenames) listbox.addItems(filenames)

View File

@ -11,9 +11,9 @@ import sys
from PyQt4 import QtGui from PyQt4 import QtGui
if sys.version_info[1] >= 3: if sys.version_info[1] >= 3:
from unittest.mock import patch, MagicMock from unittest.mock import MagicMock, patch, mock_open
else: else:
from mock import patch, MagicMock from mock import MagicMock, patch, mock_open
# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication. # Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication.
application = QtGui.QApplication([]) application = QtGui.QApplication([])

View File

@ -1 +1,28 @@
__author__ = 'tim' # -*- 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 #
###############################################################################

View File

@ -59,7 +59,6 @@ class TestAppLocation(TestCase):
# WHEN: we call AppLocation.get_data_path() # WHEN: we call AppLocation.get_data_path()
data_path = AppLocation.get_data_path() data_path = AppLocation.get_data_path()
print(data_path)
# THEN: check that all the correct methods were called, and the result is correct # THEN: check that all the correct methods were called, and the result is correct
mocked_settings.contains.assert_called_with('advanced/data path') mocked_settings.contains.assert_called_with('advanced/data path')

View File

@ -0,0 +1,73 @@
"""
Package to test the openlp.core.lib.filedialog package.
"""
from unittest import TestCase
from openlp.core.common import UiStrings
from openlp.core.lib.filedialog import FileDialog
from tests.functional import MagicMock, patch
class TestFileDialog(TestCase):
"""
Test the functions in the :mod:`filedialog` module.
"""
def setUp(self):
self.os_patcher = patch('openlp.core.lib.filedialog.os')
self.qt_gui_patcher = patch('openlp.core.lib.filedialog.QtGui')
self.ui_strings_patcher = patch('openlp.core.lib.filedialog.UiStrings')
self.mocked_os = self.os_patcher.start()
self.mocked_qt_gui = self.qt_gui_patcher.start()
self.mocked_ui_strings = self.ui_strings_patcher.start()
self.mocked_parent = MagicMock()
def tearDown(self):
self.os_patcher.stop()
self.qt_gui_patcher.stop()
self.ui_strings_patcher.stop()
def get_open_file_names_canceled_test(self):
"""
Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
(returns an empty QStringList)
"""
self.mocked_os.reset()
# GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = []
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
# THEN: The returned value should be an empty QStringList and os.path.exists should not have been called
assert not self.mocked_os.path.exists.called
self.assertEqual(result, [],
'FileDialog.getOpenFileNames should return and empty list when QFileDialog.getOpenFileNames is canceled')
def returned_file_list_test(self):
"""
Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
returns a good file name, a urlencoded file name and a non-existing file
"""
self.mocked_os.rest()
self.mocked_qt_gui.reset()
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid
# file names.
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = [
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing']
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
'/Valid File', '/url encoded file #1']
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
# THEN: os.path.exists should have been called with known args. QmessageBox.information should have been
# called. The returned result should corrilate with the input.
self.mocked_os.path.exists.assert_callde_with('/Valid File')
self.mocked_os.path.exists.assert_callde_with('/url%20encoded%20file%20%231')
self.mocked_os.path.exists.assert_callde_with('/url encoded file #1')
self.mocked_os.path.exists.assert_callde_with('/non-existing')
self.mocked_os.path.exists.assert_callde_with('/non-existing')
self.mocked_qt_gui.QmessageBox.information.called_with(self.mocked_parent, UiStrings().FileNotFound,
UiStrings().FileNotFoundMessage % '/non-existing')
self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')

View File

@ -3,13 +3,13 @@ Package to test the openlp.core.lib.htmlbuilder module.
""" """
from unittest import TestCase from unittest import TestCase
from mock import MagicMock, patch
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \ from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \
build_lyrics_format_css, build_footer_css build_lyrics_format_css, build_footer_css
from openlp.core.lib.theme import HorizontalType, VerticalType from openlp.core.lib.theme import HorizontalType, VerticalType
from tests.functional import MagicMock, patch
HTML = """ HTML = """

View File

@ -62,9 +62,11 @@ class TestTheme(TestCase):
# THEN: We should get some default behaviours # THEN: We should get some default behaviours
self.assertTrue(default_theme.background_border_color == '#000000', 'The theme should have a black border') self.assertTrue(default_theme.background_border_color == '#000000', 'The theme should have a black border')
self.assertTrue(default_theme.background_type == 'solid', 'There theme should have a solid backgrounds') self.assertTrue(default_theme.background_type == 'solid', 'The theme should have a solid backgrounds')
self.assertTrue(default_theme.display_vertical_align == 0, self.assertTrue(default_theme.display_vertical_align == 0,
'There theme should have display_vertical_align of 0') 'The theme should have a display_vertical_align of 0')
self.assertTrue(default_theme.font_footer_name == "Arial", self.assertTrue(default_theme.font_footer_name == "Arial",
'There theme should has font_footer_name of Arial') 'The theme should have a font_footer_name of Arial')
self.assertTrue(default_theme.font_main_bold is False, 'There theme should has font_main_bold of false') self.assertTrue(default_theme.font_main_bold is False, 'The theme should have a font_main_bold of false')
self.assertTrue(len(default_theme.__dict__) == 47, 'The theme should have 47 variables')

View File

@ -32,14 +32,74 @@ Functional tests to test the AppLocation class and related methods.
from unittest import TestCase from unittest import TestCase
from openlp.core.utils import clean_filename, get_filesystem_encoding, get_locale_key, \ from openlp.core.utils import clean_filename, get_filesystem_encoding, get_locale_key, \
get_natural_key, split_filename get_natural_key, split_filename, _get_user_agent, get_web_page, get_uno_instance, add_actions
from tests.functional import patch from tests.functional import MagicMock, patch
class TestUtils(TestCase): class TestUtils(TestCase):
""" """
A test suite to test out various methods around the AppLocation class. A test suite to test out various methods around the AppLocation class.
""" """
def add_actions_empty_list_test(self):
"""
Test that no actions are added when the list is empty
"""
# GIVEN: a mocked action list, and an empty list
mocked_target = MagicMock()
empty_list = []
# WHEN: The empty list is added to the mocked target
add_actions(mocked_target, empty_list)
# THEN: The add method on the mocked target is never called
self.assertEqual(0, mocked_target.addSeparator.call_count, 'addSeparator method should not have been called')
self.assertEqual(0, mocked_target.addAction.call_count, 'addAction method should not have been called')
def add_actions_none_action_test(self):
"""
Test that a separator is added when a None action is in the list
"""
# GIVEN: a mocked action list, and a list with None in it
mocked_target = MagicMock()
separator_list = [None]
# WHEN: The list is added to the mocked target
add_actions(mocked_target, separator_list)
# THEN: The addSeparator method is called, but the addAction method is never called
mocked_target.addSeparator.assert_called_with()
self.assertEqual(0, mocked_target.addAction.call_count, 'addAction method should not have been called')
def add_actions_add_action_test(self):
"""
Test that an action is added when a valid action is in the list
"""
# GIVEN: a mocked action list, and a list with an action in it
mocked_target = MagicMock()
action_list = ['action']
# WHEN: The list is added to the mocked target
add_actions(mocked_target, action_list)
# THEN: The addSeparator method is not called, and the addAction method is called
self.assertEqual(0, mocked_target.addSeparator.call_count, 'addSeparator method should not have been called')
mocked_target.addAction.assert_called_with('action')
def add_actions_action_and_none_test(self):
"""
Test that an action and a separator are added when a valid action and None are in the list
"""
# GIVEN: a mocked action list, and a list with an action and None in it
mocked_target = MagicMock()
action_list = ['action', None]
# WHEN: The list is added to the mocked target
add_actions(mocked_target, action_list)
# THEN: The addSeparator method is called, and the addAction method is called
mocked_target.addSeparator.assert_called_with()
mocked_target.addAction.assert_called_with('action')
def get_filesystem_encoding_sys_function_not_called_test(self): def get_filesystem_encoding_sys_function_not_called_test(self):
""" """
Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function
@ -153,3 +213,211 @@ class TestUtils(TestCase):
# THEN: We get a properly sorted list # THEN: We get a properly sorted list
self.assertEqual(['1st item', 'item 3b', 'item 10a'], sorted_list, 'Numbers should be sorted naturally') self.assertEqual(['1st item', 'item 3b', 'item 10a'], sorted_list, 'Numbers should be sorted naturally')
def get_uno_instance_pipe_test(self):
"""
Test that when the UNO connection type is "pipe" the resolver is given the "pipe" URI
"""
# GIVEN: A mock resolver object and UNO_CONNECTION_TYPE is "pipe"
mock_resolver = MagicMock()
# WHEN: get_uno_instance() is called
get_uno_instance(mock_resolver)
# THEN: the resolve method is called with the correct argument
mock_resolver.resolve.assert_called_with('uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext')
def get_user_agent_linux_test(self):
"""
Test that getting a user agent on Linux returns a user agent suitable for Linux
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The system is Linux
mocked_sys.platform = 'linux2'
# WHEN: We call _get_user_agent()
user_agent = _get_user_agent()
# THEN: The user agent is a Linux (or ChromeOS) user agent
result = 'Linux' in user_agent or 'CrOS' in user_agent
self.assertTrue(result, u'The user agent should be a valid Linux user agent')
def get_user_agent_windows_test(self):
"""
Test that getting a user agent on Windows returns a user agent suitable for Windows
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The system is Linux
mocked_sys.platform = 'win32'
# WHEN: We call _get_user_agent()
user_agent = _get_user_agent()
# THEN: The user agent is a Linux (or ChromeOS) user agent
self.assertIn('Windows', user_agent, u'The user agent should be a valid Windows user agent')
def get_user_agent_macos_test(self):
"""
Test that getting a user agent on OS X returns a user agent suitable for OS X
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The system is Linux
mocked_sys.platform = 'darwin'
# WHEN: We call _get_user_agent()
user_agent = _get_user_agent()
# THEN: The user agent is a Linux (or ChromeOS) user agent
self.assertIn('Mac OS X', user_agent, u'The user agent should be a valid OS X user agent')
def get_user_agent_default_test(self):
"""
Test that getting a user agent on a non-Linux/Windows/OS X platform returns the default user agent
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The system is Linux
mocked_sys.platform = 'freebsd'
# WHEN: We call _get_user_agent()
user_agent = _get_user_agent()
# THEN: The user agent is a Linux (or ChromeOS) user agent
self.assertIn('NetBSD', user_agent, u'The user agent should be the default user agent')
def get_web_page_no_url_test(self):
"""
Test that sending a URL of None to the get_web_page method returns None
"""
# GIVEN: A None url
test_url = None
# WHEN: We try to get the test URL
result = get_web_page(test_url)
# THEN: None should be returned
self.assertIsNone(result, 'The return value of get_web_page should be None')
def get_web_page_test(self):
"""
Test that the get_web_page method works correctly
"""
with patch('openlp.core.utils.urllib.request.Request') as MockRequest, \
patch('openlp.core.utils.urllib.request.urlopen') as mock_urlopen, \
patch('openlp.core.utils._get_user_agent') as mock_get_user_agent, \
patch('openlp.core.utils.Registry') as MockRegistry:
# GIVEN: Mocked out objects and a fake URL
mocked_request_object = MagicMock()
MockRequest.return_value = mocked_request_object
mocked_page_object = MagicMock()
mock_urlopen.return_value = mocked_page_object
mock_get_user_agent.return_value = 'user_agent'
fake_url = 'this://is.a.fake/url'
# WHEN: The get_web_page() method is called
returned_page = get_web_page(fake_url)
# THEN: The correct methods are called with the correct arguments and a web page is returned
MockRequest.assert_called_with(fake_url)
mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent')
self.assertEqual(1, mocked_request_object.add_header.call_count,
'There should only be 1 call to add_header')
mock_get_user_agent.assert_called_with()
mock_urlopen.assert_called_with(mocked_request_object)
mocked_page_object.geturl.assert_called_with()
self.assertEqual(0, MockRegistry.call_count, 'The Registry() object should have never been called')
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
def get_web_page_with_header_test(self):
"""
Test that adding a header to the call to get_web_page() adds the header to the request
"""
with patch('openlp.core.utils.urllib.request.Request') as MockRequest, \
patch('openlp.core.utils.urllib.request.urlopen') as mock_urlopen, \
patch('openlp.core.utils._get_user_agent') as mock_get_user_agent:
# GIVEN: Mocked out objects, a fake URL and a fake header
mocked_request_object = MagicMock()
MockRequest.return_value = mocked_request_object
mocked_page_object = MagicMock()
mock_urlopen.return_value = mocked_page_object
mock_get_user_agent.return_value = 'user_agent'
fake_url = 'this://is.a.fake/url'
fake_header = ('Fake-Header', 'fake value')
# WHEN: The get_web_page() method is called
returned_page = get_web_page(fake_url, header=fake_header)
# THEN: The correct methods are called with the correct arguments and a web page is returned
MockRequest.assert_called_with(fake_url)
mocked_request_object.add_header.assert_called_with(fake_header[0], fake_header[1])
self.assertEqual(2, mocked_request_object.add_header.call_count,
'There should only be 2 calls to add_header')
mock_get_user_agent.assert_called_with()
mock_urlopen.assert_called_with(mocked_request_object)
mocked_page_object.geturl.assert_called_with()
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
def get_web_page_with_user_agent_in_headers_test(self):
"""
Test that adding a user agent in the header when calling get_web_page() adds that user agent to the request
"""
with patch('openlp.core.utils.urllib.request.Request') as MockRequest, \
patch('openlp.core.utils.urllib.request.urlopen') as mock_urlopen, \
patch('openlp.core.utils._get_user_agent') as mock_get_user_agent:
# GIVEN: Mocked out objects, a fake URL and a fake header
mocked_request_object = MagicMock()
MockRequest.return_value = mocked_request_object
mocked_page_object = MagicMock()
mock_urlopen.return_value = mocked_page_object
fake_url = 'this://is.a.fake/url'
user_agent_header = ('User-Agent', 'OpenLP/2.1.0')
# WHEN: The get_web_page() method is called
returned_page = get_web_page(fake_url, header=user_agent_header)
# THEN: The correct methods are called with the correct arguments and a web page is returned
MockRequest.assert_called_with(fake_url)
mocked_request_object.add_header.assert_called_with(user_agent_header[0], user_agent_header[1])
self.assertEqual(1, mocked_request_object.add_header.call_count,
'There should only be 1 call to add_header')
self.assertEqual(0, mock_get_user_agent.call_count, '_get_user_agent should not have been called')
mock_urlopen.assert_called_with(mocked_request_object)
mocked_page_object.geturl.assert_called_with()
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
def get_web_page_update_openlp_test(self):
"""
Test that passing "update_openlp" as true to get_web_page calls Registry().get('app').process_events()
"""
with patch('openlp.core.utils.urllib.request.Request') as MockRequest, \
patch('openlp.core.utils.urllib.request.urlopen') as mock_urlopen, \
patch('openlp.core.utils._get_user_agent') as mock_get_user_agent, \
patch('openlp.core.utils.Registry') as MockRegistry:
# GIVEN: Mocked out objects, a fake URL
mocked_request_object = MagicMock()
MockRequest.return_value = mocked_request_object
mocked_page_object = MagicMock()
mock_urlopen.return_value = mocked_page_object
mock_get_user_agent.return_value = 'user_agent'
mocked_registry_object = MagicMock()
mocked_application_object = MagicMock()
mocked_registry_object.get.return_value = mocked_application_object
MockRegistry.return_value = mocked_registry_object
fake_url = 'this://is.a.fake/url'
# WHEN: The get_web_page() method is called
returned_page = get_web_page(fake_url, update_openlp=True)
# THEN: The correct methods are called with the correct arguments and a web page is returned
MockRequest.assert_called_with(fake_url)
mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent')
self.assertEqual(1, mocked_request_object.add_header.call_count,
'There should only be 1 call to add_header')
mock_urlopen.assert_called_with(mocked_request_object)
mocked_page_object.geturl.assert_called_with()
mocked_registry_object.get.assert_called_with('application')
mocked_application_object.process_events.assert_called_with()
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')

View File

@ -0,0 +1,180 @@
# -*- 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 http module of the Bibles plugin.
"""
from unittest import TestCase
from bs4 import BeautifulSoup
from tests.functional import patch, MagicMock
from openlp.plugins.bibles.lib.http import BSExtract
#TODO: Items left to test
# BGExtract
# __init__
# _remove_elements
# _extract_verse
# _clean_soup
# _extract_verses
# _extract_verses_old
# get_bible_chapter
# get_books_from_http
# _get_application
# CWExtract
# __init__
# get_bible_chapter
# get_books_from_http
# _get_application
# HTTPBible
# __init__
# do_import
# get_verses
# get_chapter
# get_books
# get_chapter_count
# get_verse_count
# _get_application
# get_soup_for_bible_ref
# send_error_message
class TestBSExtract(TestCase):
"""
Test the BSExtractClass
"""
#TODO: Items left to test
# BSExtract
# __init__
# get_bible_chapter
# get_books_from_http
# _get_application
def setUp(self):
self.get_soup_for_bible_ref_patcher = patch('openlp.plugins.bibles.lib.http.get_soup_for_bible_ref')
self.log_patcher = patch('openlp.plugins.bibles.lib.http.log')
self.send_error_message_patcher = patch('openlp.plugins.bibles.lib.http.send_error_message')
self.socket_patcher = patch('openlp.plugins.bibles.lib.http.socket')
self.urllib_patcher = patch('openlp.plugins.bibles.lib.http.urllib')
self.mock_get_soup_for_bible_ref = self.get_soup_for_bible_ref_patcher.start()
self.mock_log = self.log_patcher.start()
self.mock_send_error_message = self.send_error_message_patcher.start()
self.mock_socket = self.socket_patcher.start()
self.mock_soup = MagicMock()
self.mock_urllib = self.urllib_patcher.start()
def tearDown(self):
self.get_soup_for_bible_ref_patcher.stop()
self.log_patcher.stop()
self.send_error_message_patcher.stop()
self.socket_patcher.stop()
self.urllib_patcher.stop()
def get_books_from_http_no_soup_test(self):
"""
Test the get_books_from_http method when get_soup_for_bible_ref returns a falsey value
"""
# GIVEN: An instance of BSExtract, and reset log, urllib & get_soup_for_bible_ref mocks
instance = BSExtract()
self.mock_log.debug.reset_mock()
self.mock_urllib.reset_mock()
self.mock_get_soup_for_bible_ref.reset_mock()
# WHEN: get_books_from_http is called with 'NIV' and get_soup_for_bible_ref returns a None value
self.mock_urllib.parse.quote.return_value = 'NIV'
self.mock_get_soup_for_bible_ref.return_value = None
result = instance.get_books_from_http('NIV')
# THEN: The rest mocks should be called with known values and get_books_from_http should return None
self.mock_log.debug.assert_called_once_with('BSExtract.get_books_from_http("%s")', 'NIV')
self.mock_urllib.parse.quote.assert_called_once_with(b'NIV')
self.mock_get_soup_for_bible_ref.assert_called_once_with(
'http://m.bibleserver.com/overlay/selectBook?translation=NIV')
self.assertIsNone(result,
'BSExtract.get_books_from_http should return None when get_soup_for_bible_ref returns a false value')
def get_books_from_http_no_content_test(self):
"""
Test the get_books_from_http method when the specified element cannot be found in the tag object returned from
get_soup_for_bible_ref
"""
# GIVEN: An instance of BSExtract, and reset log, urllib, get_soup_for_bible_ref & soup mocks
instance = BSExtract()
self.mock_log.reset_mock()
self.mock_urllib.reset_mock()
self.mock_get_soup_for_bible_ref.reset_mock()
self.mock_soup.reset_mock()
# WHEN: get_books_from_http is called with 'NIV', get_soup_for_bible_ref returns a mocked_soup object and
# mocked_soup.find returns None
self.mock_urllib.parse.quote.return_value = 'NIV'
self.mock_soup.find.return_value = None
self.mock_get_soup_for_bible_ref.return_value = self.mock_soup
result = instance.get_books_from_http('NIV')
# THEN: The rest mocks should be called with known values and get_books_from_http should return None
self.mock_log.debug.assert_called_once_with('BSExtract.get_books_from_http("%s")', 'NIV')
self.mock_urllib.parse.quote.assert_called_once_with(b'NIV')
self.mock_get_soup_for_bible_ref.assert_called_once_with(
'http://m.bibleserver.com/overlay/selectBook?translation=NIV')
self.mock_soup.find.assert_called_once_with('ul')
self.mock_log.error.assert_called_once_with('No books found in the Bibleserver response.')
self.mock_send_error_message.assert_called_once_with('parse')
self.assertIsNone(result,
'BSExtract.get_books_from_http should return None when get_soup_for_bible_ref returns a false value')
def get_books_from_http_content_test(self):
"""
Test the get_books_from_http method with sample HTML
Also a regression test for bug #1184869. (The anchor tag in the second list item is empty)
"""
# GIVEN: An instance of BSExtract, and reset log, urllib & get_soup_for_bible_ref mocks and sample HTML data
self.test_html = '<ul><li><a href="/overlay/selectChapter?tocBook=1">Genesis</a></li>' \
'<li><a href="/overlay/selectChapter?tocBook=2"></a></li>' \
'<li><a href="/overlay/selectChapter?tocBook=3">Leviticus</a></li></ul>'
self.test_soup = BeautifulSoup(self.test_html)
instance = BSExtract()
self.mock_log.reset_mock()
self.mock_urllib.reset_mock()
self.mock_get_soup_for_bible_ref.reset_mock()
self.mock_send_error_message.reset_mock()
# WHEN: get_books_from_http is called with 'NIV' and get_soup_for_bible_ref returns tag object based on the
# supplied test data.
self.mock_urllib.parse.quote.return_value = 'NIV'
self.mock_get_soup_for_bible_ref.return_value = self.test_soup
result = instance.get_books_from_http('NIV')
# THEN: The rest mocks should be called with known values and get_books_from_http should return the two books
# in the test data
self.mock_log.debug.assert_called_once_with('BSExtract.get_books_from_http("%s")', 'NIV')
self.mock_urllib.parse.quote.assert_called_once_with(b'NIV')
self.mock_get_soup_for_bible_ref.assert_called_once_with(
'http://m.bibleserver.com/overlay/selectBook?translation=NIV')
self.assertFalse(self.mock_log.error.called, 'log.error should not have been called')
self.assertFalse(self.mock_send_error_message.called, 'send_error_message should not have been called')
self.assertEquals(result, ['Genesis', 'Leviticus'])

View File

@ -0,0 +1,53 @@
# -*- 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 Presentation Controller.
"""
from unittest import TestCase
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController
from tests.functional import MagicMock
class TestPresentationController(TestCase):
"""
Test the PresentationController.
"""
def constructor_test(self):
"""
Test the Constructor
"""
# GIVEN: No presentation controller
controller = None
# WHEN: The presentation controller object is created
controller = PresentationController(plugin=MagicMock())
# THEN: The name of the presentation controller should be correct
self.assertEqual('PresentationController', controller.name,
'The name of the presentation controller should be correct')

View File

@ -62,7 +62,7 @@ class TestRemoteTab(TestCase):
""" """
Create the UI Create the UI
""" """
fd, self.ini_file = mkstemp('.ini') self.fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file) Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance() self.application = QtGui.QApplication.instance()
Settings().extend_default_settings(__default_settings__) Settings().extend_default_settings(__default_settings__)
@ -76,6 +76,7 @@ class TestRemoteTab(TestCase):
del self.application del self.application
del self.parent del self.parent
del self.form del self.form
os.close(self.fd)
os.unlink(self.ini_file) os.unlink(self.ini_file)
def get_ip_address_default_test(self): def get_ip_address_default_test(self):

View File

@ -37,7 +37,7 @@ from PyQt4 import QtGui
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.plugins.remotes.lib.httpserver import HttpRouter from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock from tests.functional import MagicMock, patch, mock_open
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
@ -50,6 +50,8 @@ __default_settings__ = {
'remotes/ip address': '0.0.0.0' 'remotes/ip address': '0.0.0.0'
} }
TEST_PATH = os.path.abspath(os.path.dirname(__file__))
class TestRouter(TestCase): class TestRouter(TestCase):
""" """
@ -59,7 +61,7 @@ class TestRouter(TestCase):
""" """
Create the UI Create the UI
""" """
fd, self.ini_file = mkstemp('.ini') self.fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file) Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance() self.application = QtGui.QApplication.instance()
Settings().extend_default_settings(__default_settings__) Settings().extend_default_settings(__default_settings__)
@ -70,6 +72,7 @@ class TestRouter(TestCase):
Delete all the C++ objects at the end so that we don't have a segfault Delete all the C++ objects at the end so that we don't have a segfault
""" """
del self.application del self.application
os.close(self.fd)
os.unlink(self.ini_file) os.unlink(self.ini_file)
def password_encrypter_test(self): def password_encrypter_test(self):
@ -88,7 +91,7 @@ class TestRouter(TestCase):
# THEN: the function should return the correct password # THEN: the function should return the correct password
self.assertEqual(router.auth, test_value, self.assertEqual(router.auth, test_value,
'The result for make_sha_hash should return the correct encrypted password') 'The result for make_sha_hash should return the correct encrypted password')
def process_http_request_test(self): def process_http_request_test(self):
""" """
@ -106,7 +109,67 @@ class TestRouter(TestCase):
function, args = 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 # THEN: the function should have been called only once
assert function['function'] == mocked_function, \ self.assertEqual(mocked_function, function['function'], 'The mocked function should match defined value.')
'The mocked function should match defined value.' self.assertFalse(function['secure'], 'The mocked function should not require any security.')
assert function['secure'] == False, \
'The mocked function should not require any security.' def get_content_type_test(self):
"""
Test the get_content_type logic
"""
# GIVEN: a set of files and their corresponding types
headers = [ ['test.html', 'text/html'], ['test.css', 'text/css'],
['test.js', 'application/javascript'], ['test.jpg', 'image/jpeg'],
['test.gif', 'image/gif'], ['test.ico', 'image/x-icon'],
['test.png', 'image/png'], ['test.whatever', 'text/plain'],
['test', 'text/plain'], ['', 'text/plain'],
[os.path.join(TEST_PATH, 'test.html'), 'text/html']]
# WHEN: calling each file type
for header in headers:
ext, content_type = self.router.get_content_type(header[0])
# THEN: all types should match
self.assertEqual(content_type, header[1], 'Mismatch of content type')
def serve_file_without_params_test(self):
"""
Test the serve_file method without params
"""
# GIVEN: mocked environment
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router.html_dir = os.path.normpath('test/dir')
self.router.template_vars = MagicMock()
# WHEN: call serve_file with no file_name
self.router.serve_file()
# THEN: it should return a 404
self.router.send_response.assert_called_once_with(404)
self.router.send_header.assert_called_once_with('Content-type','text/html')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
def serve_file_with_valid_params_test(self):
"""
Test the serve_file method with an existing file
"""
# GIVEN: mocked environment
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router.html_dir = os.path.normpath('test/dir')
self.router.template_vars = MagicMock()
with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
patch('builtins.open', mock_open(read_data='123')):
mocked_exists.return_value = True
# WHEN: call serve_file with an existing html file
self.router.serve_file(os.path.normpath('test/dir/test.html'))
# THEN: it should return a 200 and the file
self.router.send_response.assert_called_once_with(200)
self.router.send_header.assert_called_once_with('Content-type', 'text/html')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')

View File

@ -0,0 +1,99 @@
# -*- 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 #
###############################################################################
"""
Interface tests to test the thememanagerhelper class and related methods.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from openlp.core.common import Settings
from openlp.core.ui import ThemeManagerHelper
from tests.functional import patch, MagicMock
class TestThemeManagerHelper(TestCase):
"""
Test the functions in the ThemeManagerHelp[er module
"""
def setUp(self):
"""
Create the UI
"""
fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file)
self.helper = ThemeManagerHelper()
self.helper.settings_section = "themes"
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
os.unlink(self.ini_file)
os.unlink(Settings().fileName())
def test_initialise(self):
"""
Test the thememanagerhelper initialise - basic test
"""
# GIVEN: A new a call to initialise
Settings().setValue('themes/global theme', 'my_theme')
self.helper.build_theme_path = MagicMock()
self.helper.load_first_time_themes = MagicMock()
# WHEN: the initialistion is run
self.helper.initialise()
# THEN:
self.assertEqual(1, self.helper.build_theme_path.call_count,
'The function build_theme_path should have been called')
self.assertEqual(1, self.helper.load_first_time_themes.call_count,
'The function load_first_time_themes should have been called only once')
self.assertEqual(self.helper.global_theme, 'my_theme',
'The global theme should have been set to my_theme')
def test_build_theme_path(self):
"""
Test the thememanagerhelper build_theme_path - basic test
"""
# GIVEN: A new a call to initialise
with patch('openlp.core.common.applocation.check_directory_exists') as mocked_check_directory_exists:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_check_directory_exists.return_value = True
Settings().setValue('themes/global theme', 'my_theme')
self.helper.theme_form = MagicMock()
#self.helper.load_first_time_themes = MagicMock()
# WHEN: the build_theme_path is run
self.helper.build_theme_path()
# THEN:
self.assertEqual(self.helper.path, self.helper.theme_form.path,
'The theme path and the main path should be the same value')