This commit is contained in:
Andreas Preikschat 2013-07-17 14:21:44 +02:00
commit 48d2956bdd
10 changed files with 97 additions and 29 deletions

View File

@ -93,14 +93,14 @@ class GeneralTab(SettingsTab):
self.monitor_layout.addWidget(self.custom_width_label, 3, 3) self.monitor_layout.addWidget(self.custom_width_label, 3, 3)
self.custom_width_value_edit = QtGui.QSpinBox(self.monitor_group_box) self.custom_width_value_edit = QtGui.QSpinBox(self.monitor_group_box)
self.custom_width_value_edit.setObjectName(u'custom_width_value_edit') self.custom_width_value_edit.setObjectName(u'custom_width_value_edit')
self.custom_width_value_edit.setMaximum(9999) self.custom_width_value_edit.setRange(1, 9999)
self.monitor_layout.addWidget(self.custom_width_value_edit, 4, 3) self.monitor_layout.addWidget(self.custom_width_value_edit, 4, 3)
self.custom_height_label = QtGui.QLabel(self.monitor_group_box) self.custom_height_label = QtGui.QLabel(self.monitor_group_box)
self.custom_height_label.setObjectName(u'custom_height_label') self.custom_height_label.setObjectName(u'custom_height_label')
self.monitor_layout.addWidget(self.custom_height_label, 3, 4) self.monitor_layout.addWidget(self.custom_height_label, 3, 4)
self.custom_height_value_edit = QtGui.QSpinBox(self.monitor_group_box) self.custom_height_value_edit = QtGui.QSpinBox(self.monitor_group_box)
self.custom_height_value_edit.setObjectName(u'custom_height_value_edit') self.custom_height_value_edit.setObjectName(u'custom_height_value_edit')
self.custom_height_value_edit.setMaximum(9999) self.custom_height_value_edit.setRange(1, 9999)
self.monitor_layout.addWidget(self.custom_height_value_edit, 4, 4) self.monitor_layout.addWidget(self.custom_height_value_edit, 4, 4)
self.display_on_monitor_check = QtGui.QCheckBox(self.monitor_group_box) self.display_on_monitor_check = QtGui.QCheckBox(self.monitor_group_box)
self.display_on_monitor_check.setObjectName(u'monitor_combo_box') self.display_on_monitor_check.setObjectName(u'monitor_combo_box')

View File

@ -38,7 +38,7 @@ from openlp.core.lib import UiStrings, Registry, translate
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import ThemeLayoutForm from openlp.core.ui import ThemeLayoutForm
from openlp.core.utils import get_images_filter from openlp.core.utils import get_images_filter, is_not_image_file
from themewizard import Ui_ThemeWizard from themewizard import Ui_ThemeWizard
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -178,7 +178,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
""" """
background_image = BackgroundType.to_string(BackgroundType.Image) background_image = BackgroundType.to_string(BackgroundType.Image)
if self.page(self.currentId()) == self.backgroundPage and \ if self.page(self.currentId()) == self.backgroundPage and \
self.theme.background_type == background_image and not self.imageFileEdit.text(): self.theme.background_type == background_image and is_not_image_file(self.imageFileEdit.text()):
QtGui.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'), QtGui.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
translate('OpenLP.ThemeWizard', 'You have not selected a ' translate('OpenLP.ThemeWizard', 'You have not selected a '
'background image. Please select one before continuing.')) 'background image. Please select one before continuing.'))

View File

@ -246,6 +246,23 @@ def get_images_filter():
return IMAGES_FILTER return IMAGES_FILTER
def is_not_image_file(file_name):
"""
Validate that the file is not an image file.
``file_name``
File name to be checked.
"""
if not file_name:
return True
else:
formats = [unicode(fmt).lower() for fmt in QtGui.QImageReader.supportedImageFormats()]
file_part, file_extension = os.path.splitext(unicode(file_name))
if file_extension[1:].lower() in formats and os.path.exists(file_name):
return False
return True
def split_filename(path): def split_filename(path):
""" """
Return a list of the parts in a given path. Return a list of the parts in a given path.

View File

@ -30,10 +30,10 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>${live_title}</title> <title>${live_title}</title>
<link rel="stylesheet" href="/files/live.css" /> <link rel="stylesheet" href="/files/main.css" />
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico"> <link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
<script type="text/javascript" src="/files/jquery.js"></script> <script type="text/javascript" src="/files/jquery.js"></script>
<script type="text/javascript" src="/files/live.js"></script> <script type="text/javascript" src="/files/main.js"></script>
</head> </head>
<body> <body>
<img id="image" class="size"/> <img id="image" class="size"/>

View File

@ -26,7 +26,7 @@
window.OpenLP = { window.OpenLP = {
loadSlide: function (event) { loadSlide: function (event) {
$.getJSON( $.getJSON(
"/live/image", "/main/image",
function (data, status) { function (data, status) {
var img = document.getElementById('image'); var img = document.getElementById('image');
img.src = data.results.slide_image; img.src = data.results.slide_image;
@ -36,7 +36,7 @@ window.OpenLP = {
}, },
pollServer: function () { pollServer: function () {
$.getJSON( $.getJSON(
"/live/poll", "/main/poll",
function (data, status) { function (data, status) {
if (OpenLP.slideCount != data.results.slide_count) { if (OpenLP.slideCount != data.results.slide_count) {
OpenLP.slideCount = data.results.slide_count; OpenLP.slideCount = data.results.slide_count;

View File

@ -177,11 +177,11 @@ class HttpServer(object):
self.root = self.Public() self.root = self.Public()
self.root.files = self.Files() self.root.files = self.Files()
self.root.stage = self.Stage() self.root.stage = self.Stage()
self.root.live = self.Live() self.root.main = self.Main()
self.root.router = self.router self.root.router = self.router
self.root.files.router = self.router self.root.files.router = self.router
self.root.stage.router = self.router self.root.stage.router = self.router
self.root.live.router = self.router self.root.main.router = self.router
cherrypy.tree.mount(self.root, '/', config=self.define_config()) cherrypy.tree.mount(self.root, '/', config=self.define_config())
# Turn off the flood of access messages cause by poll # Turn off the flood of access messages cause by poll
cherrypy.log.access_log.propagate = False cherrypy.log.access_log.propagate = False
@ -218,7 +218,7 @@ class HttpServer(object):
u'/stage': {u'tools.staticdir.on': True, u'/stage': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.router.html_dir, u'tools.staticdir.dir': self.router.html_dir,
u'tools.basic_auth.on': False}, u'tools.basic_auth.on': False},
u'/live': {u'tools.staticdir.on': True, u'/main': {u'tools.staticdir.on': True,
u'tools.staticdir.dir': self.router.html_dir, u'tools.staticdir.dir': self.router.html_dir,
u'tools.basic_auth.on': False}} u'tools.basic_auth.on': False}}
return directory_config return directory_config
@ -253,9 +253,9 @@ class HttpServer(object):
url = urlparse.urlparse(cherrypy.url()) url = urlparse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args) return self.router.process_http_request(url.path, *args)
class Live(object): class Main(object):
""" """
Live view is read only so security is not relevant and would reduce it's usability Main view is read only so security is not relevant and would reduce it's usability
""" """
@cherrypy.expose @cherrypy.expose
def default(self, *args, **kwargs): def default(self, *args, **kwargs):
@ -281,12 +281,12 @@ class HttpRouter(object):
self.routes = [ self.routes = [
(u'^/$', self.serve_file), (u'^/$', self.serve_file),
(u'^/(stage)$', self.serve_file), (u'^/(stage)$', self.serve_file),
(u'^/(live)$', self.serve_file), (u'^/(main)$', self.serve_file),
(r'^/files/(.*)$', self.serve_file), (r'^/files/(.*)$', self.serve_file),
(r'^/api/poll$', self.poll), (r'^/api/poll$', self.poll),
(r'^/stage/poll$', self.poll), (r'^/stage/poll$', self.poll),
(r'^/live/poll$', self.live_poll), (r'^/main/poll$', self.main_poll),
(r'^/live/image$', self.live_image), (r'^/main/image$', self.main_image),
(r'^/api/controller/(live|preview)/(.*)$', self.controller), (r'^/api/controller/(live|preview)/(.*)$', self.controller),
(r'^/stage/controller/(live|preview)/(.*)$', self.controller), (r'^/stage/controller/(live|preview)/(.*)$', self.controller),
(r'^/api/service/(.*)$', self.service), (r'^/api/service/(.*)$', self.service),
@ -378,7 +378,7 @@ class HttpRouter(object):
'slides': translate('RemotePlugin.Mobile', 'Slides') 'slides': translate('RemotePlugin.Mobile', 'Slides')
} }
def serve_file(self, filename=None): 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. 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. If subfolders requested return 404, easier for security for the present.
@ -386,17 +386,17 @@ class HttpRouter(object):
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html. 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' where xx is the language, e.g. 'en'
""" """
log.debug(u'serve file request %s' % filename) log.debug(u'serve file request %s' % file_name)
if not filename: if not file_name:
filename = u'index.html' file_name = u'index.html'
elif filename == u'stage': elif file_name == u'stage':
filename = u'stage.html' file_name = u'stage.html'
elif filename == u'live': elif file_name == u'main':
filename = u'live.html' file_name = u'main.html'
path = os.path.normpath(os.path.join(self.html_dir, filename)) 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._http_not_found() return self._http_not_found()
ext = os.path.splitext(filename)[1] ext = os.path.splitext(file_name)[1]
html = None html = None
if ext == u'.html': if ext == u'.html':
mimetype = u'text/html' mimetype = u'text/html'
@ -447,7 +447,7 @@ class HttpRouter(object):
cherrypy.response.headers['Content-Type'] = u'application/json' cherrypy.response.headers['Content-Type'] = u'application/json'
return json.dumps({u'results': result}).encode() return json.dumps({u'results': result}).encode()
def live_poll(self): def main_poll(self):
""" """
Poll OpenLP to determine the current slide count. Poll OpenLP to determine the current slide count.
""" """
@ -457,7 +457,7 @@ class HttpRouter(object):
cherrypy.response.headers['Content-Type'] = u'application/json' cherrypy.response.headers['Content-Type'] = u'application/json'
return json.dumps({u'results': result}).encode() return json.dumps({u'results': result}).encode()
def live_image(self): def main_image(self):
""" """
Return the latest display image as a byte stream. Return the latest display image as a byte stream.
""" """

View File

@ -83,7 +83,6 @@ MODULES = [
'bs4', 'bs4',
'mako', 'mako',
'cherrypy', 'cherrypy',
'migrate',
'uno', 'uno',
'icu', 'icu',
'bs4', 'bs4',

View File

@ -0,0 +1,52 @@
"""
Functional tests to test the AppLocation class and related methods.
"""
import os
from unittest import TestCase
from openlp.core.utils import is_not_image_file
from tests.utils.constants import TEST_RESOURCES_PATH
class TestUtils(TestCase):
"""
A test suite to test out various methods around the Utils functions.
"""
def is_not_image_empty_test(self):
"""
Test the method handles an empty string
"""
# Given and empty string
file_name = ""
# WHEN testing for it
result = is_not_image_file(file_name)
# THEN the result is false
assert result is True, u'The missing file test should return True'
def is_not_image_with_image_file_test(self):
"""
Test the method handles an image file
"""
# Given and empty string
file_name = os.path.join(TEST_RESOURCES_PATH, u'church.jpg')
# WHEN testing for it
result = is_not_image_file(file_name)
# THEN the result is false
assert result is False, u'The file is present so the test should return False'
def is_not_image_with_none_image_file_test(self):
"""
Test the method handles a non image file
"""
# Given and empty string
file_name = os.path.join(TEST_RESOURCES_PATH, u'serviceitem_custom_1.osj')
# WHEN testing for it
result = is_not_image_file(file_name)
# THEN the result is false
assert result is True, u'The file is not an image file so the test should return True'