This commit is contained in:
Tim Bentley 2016-06-19 07:16:01 +01:00
parent 8b98ee002b
commit fa5ce45db0
35 changed files with 19696 additions and 141 deletions

View File

@ -47,7 +47,7 @@ class ApiController(RegistryMixin, OpenLPMixin, RegistryProperties):
Register the poll return service and start the servers.
"""
self.poller = Poller()
Registry().register('Poller', self.poller)
Registry().register('poller', self.poller)
self.ws_server = WebSocketServer()
self.http_server = HttpServer()

View File

@ -22,6 +22,7 @@
"""
The Endpoint class, which provides plugins with a way to serve their own portion of the API
"""
import os
from mako.template import Template
@ -35,7 +36,6 @@ class Endpoint(object):
"""
Create an endpoint with a URL prefix
"""
print("init")
self.url_prefix = url_prefix
self.static_dir = static_dir
self.template_dir = template_dir
@ -59,5 +59,16 @@ class Endpoint(object):
return func
return decorator
def render_template(self, filename, **kwargs):
"""
Render a mako template
"""
if not self.template_dir:
raise Exception('No template directory specified')
path = os.path.abspath(os.path.join(self.template_dir, filename))
print("path = ", path)
return Template(filename=path, input_encoding='utf-8').render(**kwargs)
from .controller import controller_endpoint
from .core import stage_endpoint
from .service import service_endpoint

View File

@ -31,10 +31,10 @@ from openlp.core.lib import ItemCapabilities, create_thumb
log = logging.getLogger(__name__)
controller_endpoint = Endpoint('api')
controller_endpoint = Endpoint('controller')
@controller_endpoint.route('controller/live/text')
@controller_endpoint.route('live/text')
def controller_text(request):
"""
Perform an action on the slide controller.
@ -90,34 +90,4 @@ def controller_text(request):
return json_data
@controller_endpoint.route('service/list')
def service_list(request):
"""
Handles requests for service items in the service manager
"""
return {'results': {'items': get_service_items()}}
def get_service_items():
"""
Read the service item in use and return the data as a json object
"""
live_controller = Registry().get('live_controller')
service_items = []
if live_controller.service_item:
current_unique_identifier = live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in Registry().get('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
register_endpoint(controller_endpoint)

View File

@ -3,15 +3,18 @@ import os
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http import register_endpoint
from openlp.core.common import AppLocation, UiStrings, translate
from openlp.core.common import Registry, AppLocation, UiStrings, translate
from openlp.core.lib import image_to_byte
from mako.template import Template
template_dir = static_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
static_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static')
log = logging.getLogger(__name__)
stage_endpoint = Endpoint('')
stage_endpoint = Endpoint('stage', template_dir=template_dir, static_dir=static_dir)
main_endpoint = Endpoint('main', template_dir=template_dir, static_dir=static_dir)
blank_endpoint = Endpoint('', template_dir=template_dir, static_dir=static_dir)
FILE_TYPES = {
'.html': 'text/html',
@ -27,7 +30,7 @@ remote = translate('RemotePlugin.Mobile', 'Remote')
stage = translate('RemotePlugin.Mobile', 'Stage View')
live = translate('RemotePlugin.Mobile', 'Live View')
template_vars = {
TRANSLATED_STRINGS = {
'app_title': "{main} {remote}".format(main=UiStrings().OLPV2x, remote=remote),
'stage_title': "{main} {stage}".format(main=UiStrings().OLPV2x, stage=stage),
'live_title': "{main} {live}".format(main=UiStrings().OLPV2x, live=live),
@ -56,101 +59,87 @@ template_vars = {
}
@stage_endpoint.route('$')
@stage_endpoint.route('(stage)$')
@stage_endpoint.route('(main)$')
@stage_endpoint.route('(css)')
@stage_endpoint.route('(js)')
@stage_endpoint.route('(assets)')
def file_access(request):
@stage_endpoint.route('')
def stage_index(request):
"""
Get a list of songs`
Deliver the page for the /stage url
"""
file_name = request.path
html_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'api', 'html')
log.debug('serve file request {name}'.format(name=file_name))
if file_name.startswith('/'):
file_name = file_name[1:]
if not file_name:
file_name = 'index.html'
if '.' not in file_name:
file_name += '.html'
if file_name.startswith('/'):
file_name = file_name[1:]
path = os.path.normpath(os.path.join(html_dir, file_name))
return _process_file(path)
#file_name = request.path
#html_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'api', 'html')
#log.debug('serve file request {name}'.format(name=file_name))
#if file_name.startswith('/'):
# file_name = file_name[1:]
#if not file_name:
# file_name = 'index.mako'
#if '.' not in file_name:
# file_name += '.html'
#if file_name.startswith('/'):
# file_name = file_name[1:]
#path = os.path.normpath(os.path.join(html_dir, file_name))
return stage_endpoint.render_template('stage.mako', **TRANSLATED_STRINGS, static_url=static_dir)
@stage_endpoint.route('(stage)/(.*)$')
def bespoke_file_access(request):
@main_endpoint.route('')
def main_index(request):
"""
Allow Stage view to be delivered with custom views.
:param request: base path of the URL. Not used but passed by caller
:return:
Deliver the page for the /main url
"""
file_name = request.path
config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
log.debug('serve file request {name}'.format(name=file_name))
parts = file_name.split('/')
if len(parts) == 1:
file_name = os.path.join(parts[0], 'stage.html')
elif len(parts) == 3:
file_name = os.path.join(parts[1], parts[2])
path = os.path.normpath(os.path.join(config_dir, file_name))
return _process_file(path)
return stage_endpoint.render_template('main.mako', **TRANSLATED_STRINGS)
@stage_endpoint.route('main/image$')
@blank_endpoint.route('')
def static_file_loader(request):
"""
Dummy endpoint to trigger endpoint creation
:param request:
"""
pass
# @stage_endpoint.route('(stage)/(.*)$')
# def bespoke_file_access(request):
# """
# Allow Stage view to be delivered with custom views.
#
# :param request: base path of the URL. Not used but passed by caller
# :return:
# """
# file_name = request.path
# config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
# log.debug('serve file request {name}'.format(name=file_name))
# parts = file_name.split('/')
# if len(parts) == 1:
# file_name = os.path.join(parts[0], 'stage.mako')
# elif len(parts) == 3:
# file_name = os.path.join(parts[1], parts[2])
# path = os.path.normpath(os.path.join(config_dir, file_name))
# #return _process_file(path)
# pass
@main_endpoint.route('image')
def main_image(request):
"""
Return the latest display image as a byte stream.
:param request: base path of the URL. Not used but passed by caller
:return:
"""
# 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()
pass
live_controller = Registry().get('live_controller')
result = {
'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image))
}
return {'results': result}
@stage_endpoint.route(r'^/(\w+)/thumbnails([^/]+)?/(.*)$')
def main_image(request):
"""
Get a list of songs
:param request: base path of the URL. Not used but passed by caller
:return:
"""
# songs = db.query(Song).get()
# return {'songs': [dictify(song) for song in songs]}
print("AAA")
def _process_file(path):
"""
Common file processing code
:param path: path to file to be loaded
:return: web resource to be loaded
"""
ext, content_type = get_content_type(path)
file_handle = None
try:
if ext == '.html':
variables = template_vars
content = Template(filename=path, input_encoding='utf-8').render(**variables)
else:
file_handle = open(path, 'rb')
log.debug('Opened {path}'.format(path=path))
content = file_handle.read().decode("utf-8")
except IOError:
log.exception('Failed to open {path}'.format(path=path))
return None
finally:
if file_handle:
file_handle.close()
return content
# @stage_endpoint.route(r'^/(\w+)/thumbnails([^/]+)?/(.*)$')
# def main_image(request):
# """
# Get a list of songs
# :param request: base path of the URL. Not used but passed by caller
# :return:
# """
# # songs = db.query(Song).get()
# # return {'songs': [dictify(song) for song in songs]}
# print("AAA")
def get_content_type(file_name):
@ -165,3 +154,5 @@ def get_content_type(file_name):
return ext, content_type
register_endpoint(stage_endpoint)
register_endpoint(blank_endpoint)
register_endpoint(main_endpoint)

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# 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 logging
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http import register_endpoint
from openlp.core.common import Registry
log = logging.getLogger(__name__)
service_endpoint = Endpoint('service')
@service_endpoint.route('list')
def service_list(request):
"""
Handles requests for service items in the service manager
"""
return {'results': {'items': get_service_items()}}
def get_service_items():
"""
Read the service item in use and return the data as a json object
"""
live_controller = Registry().get('live_controller')
service_items = []
if live_controller.service_item:
current_unique_identifier = live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in Registry().get('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
register_endpoint(service_endpoint)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* 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 *
******************************************************************************/
body {
background-color: black;
font-family: sans-serif;
overflow: hidden;
}
.size {
position: absolute;
top: 0px;
vertical-align: middle;
height: 100%;
background-size: cover;
background-repeat: no-repeat;
}

View File

@ -0,0 +1,31 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* 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 *
******************************************************************************/
.ui-icon-blank {
background-image: url(../images/ui-icon-blank.png);
}
.ui-icon-unblank {
background-image: url(../images/ui-icon-unblank.png);
}
/* Overwrite style from jquery-mobile.min.css */
.ui-li .ui-btn-text a.ui-link-inherit{
white-space: normal;
}

View File

@ -0,0 +1,64 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* 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 *
******************************************************************************/
body {
background-color: black;
font-family: sans-serif;
overflow: hidden;
}
#currentslide {
font-size: 40pt;
color: white;
padding-bottom: 0px;
}
#nextslide {
font-size: 40pt;
color: grey;
padding-top: 0px;
padding-bottom: 0px;
}
#right {
float: right;
}
#clock {
font-size: 30pt;
color: yellow;
text-align: right;
}
#notes {
font-size: 36pt;
color: salmon;
text-align: right;
}
#verseorder {
font-size: 30pt;
color: green;
text-align: left;
}
.currenttag {
color: lightgreen;
font-weight: bold;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@ -0,0 +1,50 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* 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 *
******************************************************************************/
window.OpenLP = {
loadSlide: function (event) {
$.getJSON(
"/main/image",
function (data, status) {
var img = document.getElementById('image');
img.src = data.results.slide_image;
img.style.display = 'block';
}
);
},
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4317/main_poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
if (OpenLP.slideCount != msg.results.slide_count) {
OpenLP.slideCount = msg.results.slide_count;
OpenLP.loadSlide();
}
}
} else {
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
}
};
$.ajaxSetup({ cache: false });
OpenLP.pollServer();

View File

@ -0,0 +1,390 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* 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 *
******************************************************************************/
window.OpenLP = {
getElement: function(event) {
var targ;
if (!event) {
var event = window.event;
}
if (event.target) {
targ = event.target;
}
else if (event.srcElement) {
targ = event.srcElement;
}
if (targ.nodeType == 3) {
// defeat Safari bug
targ = targ.parentNode;
}
var isSecure = false;
var isAuthorised = false;
return $(targ);
},
getSearchablePlugins: function () {
$.getJSON(
"/api/plugin/search",
function (data, status) {
var select = $("#search-plugin");
select.html("");
$.each(data.results.items, function (idx, value) {
select.append("<option value='" + value[0] + "'>" + value[1] + "</option>");
});
select.selectmenu("refresh");
}
);
},
loadService: function (event) {
if (event) {
event.preventDefault();
}
$.getJSON(
"/api/service/list",
function (data, status) {
var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]");
ul.html("");
$.each(data.results.items, function (idx, value) {
var text = value["title"];
if (value["notes"]) {
text += ' - ' + value["notes"];
}
var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).text(text));
li.attr("uuid", value["id"])
li.children("a").click(OpenLP.setItem);
ul.append(li);
});
ul.listview("refresh");
}
);
},
loadController: function (event) {
if (event) {
event.preventDefault();
}
$.getJSON(
"/api/controller/live/text",
function (data, status) {
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
var indexInt = parseInt(idx,10);
var slide = data.results.slides[idx];
var text = slide["tag"];
if (text != "") {
text = text + ": ";
}
if (slide["title"]) {
text += slide["title"]
} else {
text += slide["text"];
}
if (slide["slide_notes"]) {
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["slide_notes"] + "</div>");
}
text = text.replace(/\n/g, '<br />');
if (slide["img"]) {
text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails88x88/") + "'>";
}
var li = $("<li data-icon=\"false\">").append($("<a href=\"#\">").html(text));
if (slide["selected"]) {
li.attr("data-theme", "e");
}
li.children("a").click(OpenLP.setSlide);
li.find("*").attr("value", indexInt );
ul.append(li);
}
OpenLP.currentItem = data.results.item;
ul.listview("refresh");
}
);
},
setItem: function (event) {
event.preventDefault();
var item = OpenLP.getElement(event);
var id = item.attr("value");
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/service/set",
{"data": text},
function (data, status) {
$.mobile.changePage("#slide-controller");
$("#service-manager > div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
$("#service-manager > div[data-role=content] ul[data-role=listview]").listview("refresh");
}
);
},
setSlide: function (event) {
event.preventDefault();
var slide = OpenLP.getElement(event);
var id = slide.attr("value");
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/controller/live/set",
{"data": text},
function (data, status) {
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
while (slide[0].tagName != "LI") {
slide = slide.parent();
}
slide.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
}
);
},
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
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;
OpenLP.loadService();
}
$("#service-manager div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
$("#service-manager div[data-role=content] ul[data-role=listview] li a").each(function () {
var item = $(this);
while (item[0].tagName != "LI") {
item = item.parent();
}
if (item.attr("uuid") == OpenLP.currentItem) {
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
return false;
}
});
$("#service-manager div[data-role=content] ul[data-role=listview]").listview("refresh");
}
if ($("#slide-controller").is(":visible")) {
if (prevItem != OpenLP.currentItem) {
OpenLP.loadController();
return;
}
var idx = 0;
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
$("#slide-controller div[data-role=content] ul[data-role=listview] li a").each(function () {
var item = $(this);
if (idx == OpenLP.currentSlide) {
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
return false;
}
idx++;
});
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
}
}
} else {
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
},
nextItem: function (event) {
event.preventDefault();
$.getJSON("/api/service/next");
},
previousItem: function (event) {
event.preventDefault();
$.getJSON("/api/service/previous");
},
nextSlide: function (event) {
event.preventDefault();
$.getJSON("/api/controller/live/next");
},
previousSlide: function (event) {
event.preventDefault();
$.getJSON("/api/controller/live/previous");
},
blankDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/blank");
},
themeDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/theme");
},
desktopDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/desktop");
},
showDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/show");
},
showAlert: function (event) {
event.preventDefault();
var alert = OpenLP.escapeString($("#alert-text").val())
var text = "{\"request\": {\"text\": \"" + alert + "\"}}";
$.getJSON(
"/api/alert",
{"data": text},
function () {
$("#alert-text").val("");
}
);
},
search: function (event) {
event.preventDefault();
var query = OpenLP.escapeString($("#search-text").val())
var text = "{\"request\": {\"text\": \"" + query + "\"}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/search",
{"data": text},
function (data, status) {
var ul = $("#search > div[data-role=content] > ul[data-role=listview]");
ul.html("");
if (data.results.items.length == 0) {
var li = $("<li data-icon=\"false\">").text(translationStrings["no_results"]);
ul.append(li);
}
else {
$.each(data.results.items, function (idx, value) {
if (typeof value[0] !== "number"){
value[0] = OpenLP.escapeString(value[0])
}
var txt = "";
if (value.length > 2) {
txt = value[1] + " ( " + value[2] + " )";
} else {
txt = value[1];
}
ul.append($("<li>").append($("<a>").attr("href", "#options")
.attr("data-rel", "dialog").attr("value", value[0])
.click(OpenLP.showOptions).text(txt)));
});
}
ul.listview("refresh");
}
);
},
showOptions: function (event) {
event.preventDefault();
var element = OpenLP.getElement(event);
$("#selected-item").val(element.attr("value"));
},
goLive: function (event) {
event.preventDefault();
var id = $("#selected-item").val();
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/live",
{"data": text}
);
$.mobile.changePage("#slide-controller");
},
addToService: function (event) {
event.preventDefault();
var id = $("#selected-item").val();
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/add",
{"data": text},
function () {
$("#options").dialog("close");
}
);
},
addAndGoToService: function (event) {
event.preventDefault();
var id = $("#selected-item").val();
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/add",
{"data": text},
function () {
//$("#options").dialog("close");
$.mobile.changePage("#service-manager");
}
);
},
escapeString: function (string) {
return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")
}
}
// Initial jQueryMobile options
$(document).bind("mobileinit", function(){
$.mobile.defaultDialogTransition = "none";
$.mobile.defaultPageTransition = "none";
});
// Service Manager
$("#service-manager").live("pagebeforeshow", OpenLP.loadService);
$("#service-refresh").live("click", OpenLP.loadService);
$("#service-next").live("click", OpenLP.nextItem);
$("#service-previous").live("click", OpenLP.previousItem);
$("#service-blank").live("click", OpenLP.blankDisplay);
$("#service-theme").live("click", OpenLP.themeDisplay);
$("#service-desktop").live("click", OpenLP.desktopDisplay);
$("#service-show").live("click", OpenLP.showDisplay);
// Slide Controller
$("#slide-controller").live("pagebeforeshow", OpenLP.loadController);
$("#controller-refresh").live("click", OpenLP.loadController);
$("#controller-next").live("click", OpenLP.nextSlide);
$("#controller-previous").live("click", OpenLP.previousSlide);
$("#controller-blank").live("click", OpenLP.blankDisplay);
$("#controller-theme").live("click", OpenLP.themeDisplay);
$("#controller-desktop").live("click", OpenLP.desktopDisplay);
$("#controller-show").live("click", OpenLP.showDisplay);
// Alerts
$("#alert-submit").live("click", OpenLP.showAlert);
// Search
$("#search-submit").live("click", OpenLP.search);
$("#search-text").live("keypress", function(event) {
if (event.which == 13)
{
OpenLP.search(event);
}
});
$("#go-live").live("click", OpenLP.goLive);
$("#add-to-service").live("click", OpenLP.addToService);
$("#add-and-go-to-service").live("click", OpenLP.addAndGoToService);
// Poll the server twice a second to get any updates.
$.ajaxSetup({cache: false});
$("#search").live("pageinit", function (event) {
OpenLP.getSearchablePlugins();
});
OpenLP.pollServer();

View File

@ -0,0 +1,178 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* 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 *
******************************************************************************/
window.OpenLP = {
loadService: function (event) {
$.getJSON(
"/api/service/list",
function (data, status) {
OpenLP.nextSong = "";
$("#notes").html("");
for (idx in data.results.items) {
idx = parseInt(idx, 10);
if (data.results.items[idx]["selected"]) {
$("#notes").html(data.results.items[idx]["notes"].replace(/\n/g, "<br />"));
if (data.results.items.length > idx + 1) {
OpenLP.nextSong = data.results.items[idx + 1]["title"];
}
break;
}
}
OpenLP.updateSlide();
}
);
},
loadSlides: function (event) {
$.getJSON(
"/api/controller/live/text",
function (data, status) {
OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0;
OpenLP.currentTags = Array();
var div = $("#verseorder");
div.html("");
var tag = "";
var tags = 0;
var lastChange = 0;
$.each(data.results.slides, function(idx, slide) {
var prevtag = tag;
tag = slide["tag"];
if (tag != prevtag) {
// If the tag has changed, add new one to the list
lastChange = idx;
tags = tags + 1;
div.append("&nbsp;<span>");
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
}
else {
if ((slide["text"] == data.results.slides[lastChange]["text"]) &&
(data.results.slides.length >= idx + (idx - lastChange))) {
// If the tag hasn't changed, check to see if the same verse
// has been repeated consecutively. Note the verse may have been
// split over several slides, so search through. If so, repeat the tag.
var match = true;
for (var idx2 = 0; idx2 < idx - lastChange; idx2++) {
if(data.results.slides[lastChange + idx2]["text"] != data.results.slides[idx + idx2]["text"]) {
match = false;
break;
}
}
if (match) {
lastChange = idx;
tags = tags + 1;
div.append("&nbsp;<span>");
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
}
}
}
OpenLP.currentTags[idx] = tags;
if (slide["selected"])
OpenLP.currentSlide = idx;
})
OpenLP.loadService();
}
);
},
updateSlide: function() {
// Show the current slide on top. Any trailing slides for the same verse
// are shown too underneath in grey.
// Then leave a blank line between following verses
$("#verseorder span").removeClass("currenttag");
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
var text = "";
// use title if available
if (slide["title"]) {
text = slide["title"];
} else {
text = slide["text"];
}
// use thumbnail if available
if (slide["img"]) {
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
}
// use notes if available
if (slide["slide_notes"]) {
text += '<br />' + slide["slide_notes"];
}
text = text.replace(/\n/g, "<br />");
$("#currentslide").html(text);
text = "";
if (OpenLP.currentSlide < OpenLP.currentSlides.length - 1) {
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "<p class=\"nextslide\">";
if (OpenLP.currentSlides[idx]["title"]) {
text = text + OpenLP.currentSlides[idx]["title"];
} else {
text = text + OpenLP.currentSlides[idx]["text"];
}
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "</p>";
else
text = text + "<br />";
}
text = text.replace(/\n/g, "<br />");
$("#nextslide").html(text);
}
else {
text = "<p class=\"nextslide\">" + $("#next-text").val() + ": " + OpenLP.nextSong + "</p>";
$("#nextslide").html(text);
}
},
updateClock: function() {
var div = $("#clock");
var t = new Date();
var h = t.getHours();
if (OpenLP.twelve && h > 12)
h = h - 12;
var m = t.getMinutes();
if (m < 10)
m = '0' + m + '';
div.html(h + ":" + m);
},
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
OpenLP.twelve = msg.results.twelve;
OpenLP.updateClock();
if (OpenLP.currentItem != msg.results.item ||
OpenLP.currentService != msg.results.service) {
OpenLP.currentItem = msg.results.item;
OpenLP.currentService = msg.results.service;
OpenLP.loadSlides();
}
else if (OpenLP.currentSlide != msg.results.slide) {
OpenLP.currentSlide = parseInt(msg.results.slide, 10);
OpenLP.updateSlide();
}
}
} else {
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
},
};
$.ajaxSetup({ cache: false });
setInterval("OpenLP.updateClock();", 1000);
OpenLP.pollServer();
OpenLP.updateClock();

View File

@ -46,14 +46,12 @@ class HttpWorker(QtCore.QObject):
:param server: The http server class.
"""
print("http init")
super(HttpWorker, self).__init__()
def run(self):
"""
Run the thread.
"""
print("runnnn")
serve(application, host='0.0.0.0', port=4318)
def stop(self):

View File

@ -69,7 +69,8 @@ def _make_response(view_result):
"""
Create a Response object from response
"""
print(view_result)
print("##")
print(type(view_result))
if isinstance(view_result, Response):
return view_result
elif isinstance(view_result, tuple):
@ -86,6 +87,9 @@ def _make_response(view_result):
elif isinstance(view_result, dict):
return Response(body=json.dumps(view_result), status=200,
content_type='application/json', charset='utf8')
elif isinstance(view_result, str):
return Response(body=view_result, status=200,
content_type='text/html', charset='utf8')
def _handle_exception(error):
@ -116,6 +120,7 @@ class WSGIApplication(object):
Add a route
"""
route_regex = _route_to_regex(route)
print("###", route, route_regex)
if route_regex not in self.route_map:
self.route_map[route_regex] = {}
self.route_map[route_regex][method.upper()] = view_func
@ -124,23 +129,36 @@ class WSGIApplication(object):
"""
Add a static directory as a route
"""
if not route in self.static_routes:
if route not in self.static_routes:
self.static_routes[route] = DirectoryApp(os.path.abspath(static_dir))
def dispatch(self, request):
"""
Find the appropriate URL and run the view function
"""
# We are not interested in this so discard
print("rrr",request.path)
if '/favicon.ico' in request.path:
return
# First look to see if this is a static file request
for route, static_app in self.static_routes.items():
print(route, request.path)
if re.match(route, request.path):
print("matched")
# Pop the path info twice in order to get rid of the "/<plugin>/static"
request.path_info_pop()
# request.path_info_pop()
request.path_info_pop()
return request.get_response(static_app)
# If not a static route, try the views
for route, views in self.route_map.items():
match = re.match(route, request.path)
path = request.path
# /api is legacy so we need to handle backwards compatibility
if path.startswith('/api'):
path = path[4:]
print("MMMATCHED")
print(route, request.path, path)
match = re.match(route, path)
print(match)
if match and request.method.upper() in views:
kwargs = match.groupdict()
log.debug('Found {method} {url}'.format(method=request.method, url=request.path))

View File

@ -47,7 +47,6 @@ class WebSocketWorker(QtCore.QObject):
:param server: The http server class.
"""
print("ws init")
self.ws_server = server
super(WebSocketWorker, self).__init__()
@ -55,7 +54,6 @@ class WebSocketWorker(QtCore.QObject):
"""
Run the thread.
"""
print("ws run")
self.ws_server.start_server()
def stop(self):
@ -127,7 +125,7 @@ class WebSocketServer(RegistryProperties, OpenLPMixin):
log.debug("web socket handler registered with client")
previous_poll = None
previous_main_poll = None
poller = Registry().get('Poller')
poller = Registry().get('poller')
# TODO: FIXME: These URLs need to be named better
if path == '/poll':
while True:
@ -143,12 +141,3 @@ class WebSocketServer(RegistryProperties, OpenLPMixin):
yield from request.send(main_poll)
previous_main_poll = main_poll
yield from asyncio.sleep(0.2)
def stop_server(self):
"""
Stop the server
"""
if self.http_thread.isRunning():
self.http_thread.stop()
self.httpd = None
log.debug('Stopped the server.')

View File

@ -359,7 +359,7 @@ class HttpRouter(RegistryProperties, OpenLPMixin):
log.debug('serve file request {name}'.format(name=file_name))
parts = file_name.split('/')
if len(parts) == 1:
file_name = os.path.join(parts[0], 'stage.html')
file_name = os.path.join(parts[0], 'stage.mako')
elif len(parts) == 3:
file_name = os.path.join(parts[1], parts[2])
path = os.path.normpath(os.path.join(self.config_dir, file_name))
@ -406,7 +406,7 @@ class HttpRouter(RegistryProperties, OpenLPMixin):
"""
log.debug('serve file request {name}'.format(name=file_name))
if not file_name:
file_name = 'index.html'
file_name = 'index.mako'
if '.' not in file_name:
file_name += '.html'
if file_name.startswith('/'):

View File

@ -369,7 +369,7 @@ class TestRouter(TestCase, TestMixin):
def remote_stage_personal_html_test(self):
"""
Test the stage url with a parameter after loaded a url/stage.html file
Test the stage url with a parameter after loaded a url/stage.mako file
"""
# GIVEN: initial route
self.router.config_dir = ''
@ -383,7 +383,7 @@ class TestRouter(TestCase, TestMixin):
self.router.stages('stages', 'trb')
# THEN: we should use the specific stage file instance
self.router._process_file.assert_called_with(os.path.join('trb', 'stage.html'))
self.router._process_file.assert_called_with(os.path.join('trb', 'stage.mako'))
def remote_stage_personal_css_test(self):
"""