Bye Bye Remote
9404
openlp/plugins/remotes/html/assets/jquery.js
vendored
@ -1,32 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 364 B |
Before Width: | Height: | Size: 460 B |
Before Width: | Height: | Size: 453 B |
Before Width: | Height: | Size: 519 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 231 B |
@ -1,177 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
|
||||
<title>${app_title}</title>
|
||||
<link rel="stylesheet" href="/assets/jquery.mobile.min.css" />
|
||||
<link rel="stylesheet" href="/css/openlp.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/openlp.js"></script>
|
||||
<script type="text/javascript" src="/assets/jquery.mobile.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
translationStrings = {
|
||||
"go_live": "${go_live}",
|
||||
"add_to_service": "${add_to_service}",
|
||||
"no_results": "${no_results}",
|
||||
"home": "${home}"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div data-role="page" id="home">
|
||||
<div data-role="header">
|
||||
<h1>${app_title}</h1>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<div data-role="controlgroup">
|
||||
<a href="#service-manager" data-role="button" data-icon="arrow-r" data-iconpos="right">${service_manager}</a>
|
||||
<a href="#slide-controller" data-role="button" data-icon="arrow-r" data-iconpos="right">${slide_controller}</a>
|
||||
<a href="#alerts" data-role="button" data-icon="arrow-r" data-iconpos="right">${alerts}</a>
|
||||
<a href="#search" data-role="button" data-icon="arrow-r" data-iconpos="right">${search}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="service-manager">
|
||||
<div data-role="header" data-position="fixed">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${service_manager}</h1>
|
||||
<a href="#" id="service-refresh" data-role="button" data-icon="refresh">${refresh}</a>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager" data-theme="e">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<ul data-role="listview" data-inset="true">
|
||||
</ul>
|
||||
</div>
|
||||
<div data-role="footer" data-theme="b" class="ui-bar" data-position="fixed">
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="service-blank" data-role="button" data-icon="blank">${blank}</a>
|
||||
<a href="#" id="service-theme" data-role="button">${theme}</a>
|
||||
<a href="#" id="service-desktop" data-role="button">${desktop}</a>
|
||||
<a href="#" id="service-show" data-role="button" data-icon="unblank" data-iconpos="right">${show}</a>
|
||||
</div>
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="service-previous" data-role="button" data-icon="arrow-l">${prev}</a>
|
||||
<a href="#" id="service-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="slide-controller">
|
||||
<div data-role="header" data-position="fixed">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${slide_controller}</h1>
|
||||
<a href="#" id="controller-refresh" data-role="button" data-icon="refresh">${refresh}</a>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller" data-theme="e">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<ul data-role="listview" data-inset="true">
|
||||
</ul>
|
||||
</div>
|
||||
<div data-role="footer" data-theme="b" class="ui-bar" data-position="fixed">
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="controller-blank" data-role="button" data-icon="blank">${blank}</a>
|
||||
<a href="#" id="controller-theme" data-role="button">${theme}</a>
|
||||
<a href="#" id="controller-desktop" data-role="button">${desktop}</a>
|
||||
<a href="#" id="controller-show" data-role="button" data-icon="unblank" data-iconpos="right">${show}</a>
|
||||
</div>
|
||||
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
|
||||
<a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">${prev}</a>
|
||||
<a href="#" id="controller-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="alerts">
|
||||
<div data-role="header">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${alerts}</h1>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts" data-theme="e">${alerts}</a></li>
|
||||
<li><a href="#search">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<div data-role="fieldcontain">
|
||||
<label for="alert-text">${text}:</label>
|
||||
<input type="text" name="alert-text" id="alert-text" value="" />
|
||||
</div>
|
||||
<a href="#" id="alert-submit" data-role="button">${show_alert}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="search">
|
||||
<div data-role="header" data-position="fixed">
|
||||
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
|
||||
<h1>${search}</h1>
|
||||
<div data-role="navbar">
|
||||
<ul>
|
||||
<li><a href="#service-manager">${service}</a></li>
|
||||
<li><a href="#slide-controller">${slides}</a></li>
|
||||
<li><a href="#alerts">${alerts}</a></li>
|
||||
<li><a href="#search" data-theme="e">${search}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<div data-role="fieldcontain">
|
||||
<label for="search-plugin">${search}:</label>
|
||||
<select name="search-plugin" id="search-plugin" data-native-menu="false"></select>
|
||||
</div>
|
||||
<div data-role="fieldcontain">
|
||||
<label for="search-text">${text}:</label>
|
||||
<input type="search" name="search-text" id="search-text" value="" />
|
||||
</div>
|
||||
<a href="#" id="search-submit" data-role="button">${search}</a>
|
||||
<ul data-role="listview" data-inset="true"/>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="page" id="options">
|
||||
<div data-role="header" data-position="inline" data-theme="b">
|
||||
<h1>${options}</h1>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
<input type="hidden" id="selected-item" value="" />
|
||||
<a href="#" id="go-live" data-role="button">${go_live}</a>
|
||||
<a href="#" id="add-to-service" data-role="button">${add_to_service}</a>
|
||||
<a href="#" id="add-and-go-to-service" data-role="button">${add_and_go_to_service}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,50 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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();
|
||||
|
@ -1,390 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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();
|
@ -1,178 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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(" <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(" <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();
|
@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${live_title}</title>
|
||||
<link rel="stylesheet" href="/css/main.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img id="image" class="size"/>
|
||||
</body>
|
||||
</html>
|
@ -1,41 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${stage_title}</title>
|
||||
<link rel="stylesheet" href="/css/stage.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/stage.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" id="next-text" value="${next}" />
|
||||
<div id="right">
|
||||
<div id="clock"></div>
|
||||
<div id="notes"></div>
|
||||
</div>
|
||||
<div id="verseorder"></div>
|
||||
<div id="currentslide"></div>
|
||||
<div id="nextslide"></div>
|
||||
</body>
|
||||
</html>
|
@ -1,676 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The :mod:`http` module contains the API web server. This is a lightweight web
|
||||
server used by remotes to interact with OpenLP. It uses JSON to communicate with
|
||||
the remotes.
|
||||
|
||||
*Routes:*
|
||||
|
||||
``/``
|
||||
Go to the web interface.
|
||||
|
||||
``/stage``
|
||||
Show the stage view.
|
||||
|
||||
``/files/{filename}``
|
||||
Serve a static file.
|
||||
|
||||
``/stage/api/poll``
|
||||
Poll to see if there are any changes. Returns a JSON-encoded dict of
|
||||
any changes that occurred::
|
||||
|
||||
{"results": {"type": "controller"}}
|
||||
|
||||
Or, if there were no results, False::
|
||||
|
||||
{"results": False}
|
||||
|
||||
``/api/display/{hide|show}``
|
||||
Blank or unblank the screen.
|
||||
|
||||
``/api/alert``
|
||||
Sends an alert message to the alerts plugin. This method expects a
|
||||
JSON-encoded dict like this::
|
||||
|
||||
{"request": {"text": "<your alert text>"}}
|
||||
|
||||
``/api/controller/{live|preview}/{action}``
|
||||
Perform ``{action}`` on the live or preview controller. Valid actions
|
||||
are:
|
||||
|
||||
``next``
|
||||
Load the next slide.
|
||||
|
||||
``previous``
|
||||
Load the previous slide.
|
||||
|
||||
``set``
|
||||
Set a specific slide. Requires an id return in a JSON-encoded dict like
|
||||
this::
|
||||
|
||||
{"request": {"id": 1}}
|
||||
|
||||
``first``
|
||||
Load the first slide.
|
||||
|
||||
``last``
|
||||
Load the last slide.
|
||||
|
||||
``text``
|
||||
Fetches the text of the current song. The output is a JSON-encoded
|
||||
dict which looks like this::
|
||||
|
||||
{"result": {"slides": ["...", "..."]}}
|
||||
|
||||
``/api/service/{action}``
|
||||
Perform ``{action}`` on the service manager (e.g. go live). Data is
|
||||
passed as a json-encoded ``data`` parameter. Valid actions are:
|
||||
|
||||
``next``
|
||||
Load the next item in the service.
|
||||
|
||||
``previous``
|
||||
Load the previews item in the service.
|
||||
|
||||
``set``
|
||||
Set a specific item in the service. Requires an id returned in a
|
||||
JSON-encoded dict like this::
|
||||
|
||||
{"request": {"id": 1}}
|
||||
|
||||
``list``
|
||||
Request a list of items in the service. Returns a list of items in the
|
||||
current service in a JSON-encoded dict like this::
|
||||
|
||||
{"results": {"items": [{...}, {...}]}}
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from mako.template import Template
|
||||
|
||||
from openlp.core.common import OpenLPMixin, RegistryProperties, AppLocation, Settings, Registry, translate, UiStrings
|
||||
from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities, create_thumb
|
||||
|
||||
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(RegistryProperties, OpenLPMixin):
|
||||
"""
|
||||
This code is called by the HttpServer upon a request and it processes it based on the routing table.
|
||||
This code is stateless and is created on each request.
|
||||
Some variables may look incorrect but this extends BaseHTTPRequestHandler.
|
||||
"""
|
||||
def initialise(self):
|
||||
"""
|
||||
Initialise the router stack and any other variables.
|
||||
"""
|
||||
auth_code = "{user}:{password}".format(user=Settings().value('remotes/user id'),
|
||||
password=Settings().value('remotes/password'))
|
||||
self.openlppoll = Registry().get('poller')
|
||||
try:
|
||||
self.auth = base64.b64encode(auth_code)
|
||||
except TypeError:
|
||||
self.auth = base64.b64encode(auth_code.encode()).decode()
|
||||
self.default_route = {'function': self.serve_file, 'secure': False}
|
||||
self.routes = [
|
||||
('^/$', {'function': self.serve_file, 'secure': False}),
|
||||
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
|
||||
('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}),
|
||||
('^/(main)$', {'function': self.serve_file, 'secure': False}),
|
||||
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
|
||||
(r'^/api/poll$', {'function': self.openlppoll.poll, 'secure': False}),
|
||||
(r'^/main/poll$', {'function': self.openlppoll.main_poll, 'secure': False}),
|
||||
(r'^/main/image$', {'function': self.main_image, 'secure': False}),
|
||||
(r'^/api/controller/(live|preview)/text$', {'function': self.controller_text, 'secure': False}),
|
||||
(r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': True}),
|
||||
(r'^/api/service/list$', {'function': self.service_list, 'secure': False}),
|
||||
(r'^/api/service/(.*)$', {'function': self.service, 'secure': True}),
|
||||
(r'^/api/display/(hide|show|blank|theme|desktop)$', {'function': self.display, 'secure': True}),
|
||||
(r'^/api/alert$', {'function': self.alert, 'secure': True}),
|
||||
(r'^/api/plugin/(search)$', {'function': self.plugin_info, 'secure': False}),
|
||||
(r'^/api/(.*)/search$', {'function': self.search, 'secure': False}),
|
||||
(r'^/api/(.*)/live$', {'function': self.go_live, 'secure': True}),
|
||||
(r'^/api/(.*)/add$', {'function': self.add_to_service, 'secure': True})
|
||||
]
|
||||
self.settings_section = 'remotes'
|
||||
self.translate()
|
||||
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
|
||||
self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
|
||||
|
||||
def do_post_processor(self):
|
||||
"""
|
||||
Handle the POST amd GET requests placed on the server.
|
||||
"""
|
||||
if self.path == '/favicon.ico':
|
||||
return
|
||||
if not hasattr(self, 'auth'):
|
||||
self.initialise()
|
||||
function, args = self.process_http_request(self.path)
|
||||
if not function:
|
||||
self.do_http_error()
|
||||
return
|
||||
self.authorised = self.headers['Authorization'] is None
|
||||
if function['secure'] and Settings().value(self.settings_section + '/authentication enabled'):
|
||||
if self.headers['Authorization'] is None:
|
||||
self.do_authorisation()
|
||||
self.wfile.write(bytes('no auth header received', 'UTF-8'))
|
||||
elif self.headers['Authorization'] == 'Basic {auth}'.format(auth=self.auth):
|
||||
self.do_http_success()
|
||||
self.call_function(function, *args)
|
||||
else:
|
||||
self.do_authorisation()
|
||||
self.wfile.write(bytes(self.headers['Authorization'], 'UTF-8'))
|
||||
self.wfile.write(bytes(' not authenticated', 'UTF-8'))
|
||||
else:
|
||||
self.call_function(function, *args)
|
||||
|
||||
def call_function(self, function, *args):
|
||||
"""
|
||||
Invoke the route function passing the relevant values
|
||||
|
||||
:param function: The function to be called.
|
||||
:param args: Any passed data.
|
||||
"""
|
||||
response = function['function'](*args)
|
||||
if response:
|
||||
self.wfile.write(response)
|
||||
return
|
||||
|
||||
def process_http_request(self, url_path, *args):
|
||||
"""
|
||||
Common function to process HTTP requests
|
||||
|
||||
:param url_path: The requested URL.
|
||||
:param args: Any passed data.
|
||||
"""
|
||||
self.request_data = None
|
||||
url_path_split = urlparse(url_path)
|
||||
url_query = parse_qs(url_path_split.query)
|
||||
# Get data from HTTP request
|
||||
if self.command == 'GET':
|
||||
if 'data' in url_query.keys():
|
||||
self.request_data = url_query['data'][0]
|
||||
elif self.command == 'POST':
|
||||
content_len = int(self.headers['content-length'])
|
||||
self.request_data = self.rfile.read(content_len).decode("utf-8")
|
||||
for route, func in self.routes:
|
||||
match = re.match(route, url_path_split.path)
|
||||
if match:
|
||||
log.debug('Route "{route}" matched "{path}"'.format(route=route, path=url_path))
|
||||
args = []
|
||||
for param in match.groups():
|
||||
args.append(param)
|
||||
return func, args
|
||||
return self.default_route, [url_path_split.path]
|
||||
|
||||
def set_cache_headers(self):
|
||||
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
self.send_header("Pragma", "no-cache")
|
||||
self.send_header("Expires", "0")
|
||||
|
||||
def do_http_success(self):
|
||||
"""
|
||||
Create a success http header.
|
||||
"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_json_header(self):
|
||||
"""
|
||||
Create a header for JSON messages
|
||||
"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_http_error(self):
|
||||
"""
|
||||
Create a error http header.
|
||||
"""
|
||||
self.send_response(404)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_authorisation(self):
|
||||
"""
|
||||
Create a needs authorisation http header.
|
||||
"""
|
||||
self.send_response(401)
|
||||
header = 'Basic realm=\"{}\"'.format(UiStrings().OLPV2)
|
||||
self.send_header('WWW-Authenticate', header)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_not_found(self):
|
||||
"""
|
||||
Create a not found http header.
|
||||
"""
|
||||
self.send_response(404)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.set_cache_headers()
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
|
||||
|
||||
def _get_service_items(self):
|
||||
"""
|
||||
Read the service item in use and return the data as a json object
|
||||
"""
|
||||
service_items = []
|
||||
if self.live_controller.service_item:
|
||||
current_unique_identifier = self.live_controller.service_item.unique_identifier
|
||||
else:
|
||||
current_unique_identifier = None
|
||||
for item in self.service_manager.service_items:
|
||||
service_item = item['service_item']
|
||||
service_items.append({
|
||||
'id': str(service_item.unique_identifier),
|
||||
'title': str(service_item.get_display_title()),
|
||||
'plugin': str(service_item.name),
|
||||
'notes': str(service_item.notes),
|
||||
'selected': (service_item.unique_identifier == current_unique_identifier)
|
||||
})
|
||||
return service_items
|
||||
|
||||
def translate(self):
|
||||
"""
|
||||
Translate various strings in the mobile app.
|
||||
"""
|
||||
remote = translate('RemotePlugin.Mobile', 'Remote')
|
||||
stage = translate('RemotePlugin.Mobile', 'Stage View')
|
||||
live = translate('RemotePlugin.Mobile', 'Live View')
|
||||
self.template_vars = {
|
||||
'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),
|
||||
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
|
||||
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
|
||||
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
|
||||
'search': translate('RemotePlugin.Mobile', 'Search'),
|
||||
'home': translate('RemotePlugin.Mobile', 'Home'),
|
||||
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
|
||||
'blank': translate('RemotePlugin.Mobile', 'Blank'),
|
||||
'theme': translate('RemotePlugin.Mobile', 'Theme'),
|
||||
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
|
||||
'show': translate('RemotePlugin.Mobile', 'Show'),
|
||||
'prev': translate('RemotePlugin.Mobile', 'Prev'),
|
||||
'next': translate('RemotePlugin.Mobile', 'Next'),
|
||||
'text': translate('RemotePlugin.Mobile', 'Text'),
|
||||
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
|
||||
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
|
||||
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
|
||||
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'),
|
||||
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
|
||||
'options': translate('RemotePlugin.Mobile', 'Options'),
|
||||
'service': translate('RemotePlugin.Mobile', 'Service'),
|
||||
'slides': translate('RemotePlugin.Mobile', 'Slides'),
|
||||
'settings': translate('RemotePlugin.Mobile', 'Settings'),
|
||||
}
|
||||
|
||||
def stages(self, url_path, file_name):
|
||||
"""
|
||||
Allow Stage view to be delivered with custom views.
|
||||
|
||||
:param url_path: base path of the URL. Not used but passed by caller
|
||||
:param file_name: file name with path
|
||||
:return:
|
||||
"""
|
||||
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(self.config_dir, file_name))
|
||||
if not path.startswith(self.config_dir):
|
||||
return self.do_not_found()
|
||||
return self._process_file(path)
|
||||
|
||||
def _process_file(self, path):
|
||||
"""
|
||||
Common file processing code
|
||||
|
||||
:param path: path to file to be loaded
|
||||
:return: web resource to be loaded
|
||||
"""
|
||||
content = None
|
||||
ext, content_type = self.get_content_type(path)
|
||||
file_handle = None
|
||||
try:
|
||||
if ext == '.html':
|
||||
variables = self.template_vars
|
||||
content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
|
||||
else:
|
||||
file_handle = open(path, 'rb')
|
||||
log.debug('Opened {path}'.format(path=path))
|
||||
content = file_handle.read()
|
||||
except IOError:
|
||||
log.exception('Failed to open {path}'.format(path=path))
|
||||
return self.do_not_found()
|
||||
finally:
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', content_type)
|
||||
self.end_headers()
|
||||
return content
|
||||
|
||||
def serve_file(self, file_name=None):
|
||||
"""
|
||||
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
|
||||
If subfolders requested return 404, easier for security for the present.
|
||||
|
||||
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
|
||||
where xx is the language, e.g. 'en'
|
||||
"""
|
||||
log.debug('serve file request {name}'.format(name=file_name))
|
||||
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(self.html_dir, file_name))
|
||||
if not path.startswith(self.html_dir):
|
||||
return self.do_not_found()
|
||||
return self._process_file(path)
|
||||
|
||||
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
|
||||
|
||||
:param file_name: name of file
|
||||
"""
|
||||
ext = os.path.splitext(file_name)[1]
|
||||
content_type = FILE_TYPES.get(ext, 'text/plain')
|
||||
return ext, content_type
|
||||
|
||||
def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
|
||||
"""
|
||||
Serve an image file. If not found return 404.
|
||||
|
||||
:param file_name: file name to be served
|
||||
:param dimensions: image size
|
||||
:param controller_name: controller to be called
|
||||
"""
|
||||
log.debug('serve thumbnail {cname}/thumbnails{dim}/{fname}'.format(cname=controller_name,
|
||||
dim=dimensions,
|
||||
fname=file_name))
|
||||
supported_controllers = ['presentations', 'images']
|
||||
# -1 means use the default dimension in ImageManager
|
||||
width = -1
|
||||
height = -1
|
||||
if dimensions:
|
||||
match = re.search('(\d+)x(\d+)', dimensions)
|
||||
if match:
|
||||
# let's make sure that the dimensions are within reason
|
||||
width = sorted([10, int(match.group(1)), 1000])[1]
|
||||
height = sorted([10, int(match.group(2)), 1000])[1]
|
||||
content = ''
|
||||
content_type = None
|
||||
if controller_name and file_name:
|
||||
if controller_name in supported_controllers:
|
||||
full_path = urllib.parse.unquote(file_name)
|
||||
if '..' not in full_path: # no hacking please
|
||||
full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name),
|
||||
'thumbnails/' + full_path))
|
||||
if os.path.exists(full_path):
|
||||
path, just_file_name = os.path.split(full_path)
|
||||
self.image_manager.add_image(full_path, just_file_name, None, width, height)
|
||||
ext, content_type = self.get_content_type(full_path)
|
||||
image = self.image_manager.get_image(full_path, just_file_name, width, height)
|
||||
content = image_to_byte(image, False)
|
||||
if len(content) == 0:
|
||||
return self.do_not_found()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', content_type)
|
||||
self.end_headers()
|
||||
return content
|
||||
|
||||
def main_image(self):
|
||||
"""
|
||||
Return the latest display image as a byte stream.
|
||||
"""
|
||||
result = {
|
||||
'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
|
||||
}
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': result}).encode()
|
||||
|
||||
def display(self, action):
|
||||
"""
|
||||
Hide or show the display screen.
|
||||
This is a cross Thread call and UI is updated so Events need to be used.
|
||||
|
||||
:param action: This is the action, either ``hide`` or ``show``.
|
||||
"""
|
||||
self.live_controller.slidecontroller_toggle_display.emit(action)
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': {'success': True}}).encode()
|
||||
|
||||
def alert(self):
|
||||
"""
|
||||
Send an alert.
|
||||
"""
|
||||
plugin = self.plugin_manager.get_plugin_by_name("alerts")
|
||||
if plugin.status == PluginStatus.Active:
|
||||
try:
|
||||
text = json.loads(self.request_data)['request']['text']
|
||||
except KeyError:
|
||||
return self.do_http_error()
|
||||
text = urllib.parse.unquote(text)
|
||||
self.alerts_manager.alerts_text.emit([text])
|
||||
success = True
|
||||
else:
|
||||
success = False
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': {'success': success}}).encode()
|
||||
|
||||
def controller_text(self, var):
|
||||
"""
|
||||
Perform an action on the slide controller.
|
||||
|
||||
:param var: variable - not used
|
||||
"""
|
||||
log.debug("controller_text var = {var}".format(var=var))
|
||||
current_item = self.live_controller.service_item
|
||||
data = []
|
||||
if current_item:
|
||||
for index, frame in enumerate(current_item.get_frames()):
|
||||
item = {}
|
||||
# Handle text (songs, custom, bibles)
|
||||
if current_item.is_text():
|
||||
if frame['verseTag']:
|
||||
item['tag'] = str(frame['verseTag'])
|
||||
else:
|
||||
item['tag'] = str(index + 1)
|
||||
item['text'] = str(frame['text'])
|
||||
item['html'] = str(frame['html'])
|
||||
# Handle images, unless a custom thumbnail is given or if thumbnails is disabled
|
||||
elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
|
||||
item['tag'] = str(index + 1)
|
||||
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
||||
full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path)
|
||||
# Create thumbnail if it doesn't exists
|
||||
if not os.path.exists(full_thumbnail_path):
|
||||
create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
|
||||
item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path)
|
||||
item['text'] = str(frame['title'])
|
||||
item['html'] = str(frame['title'])
|
||||
else:
|
||||
# Handle presentation etc.
|
||||
item['tag'] = str(index + 1)
|
||||
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
|
||||
item['title'] = str(frame['display_title'])
|
||||
if current_item.is_capable(ItemCapabilities.HasNotes):
|
||||
item['slide_notes'] = str(frame['notes'])
|
||||
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
|
||||
Settings().value('remotes/thumbnails'):
|
||||
# If the file is under our app directory tree send the portion after the match
|
||||
data_path = AppLocation.get_data_path()
|
||||
if frame['image'][0:len(data_path)] == data_path:
|
||||
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
|
||||
item['text'] = str(frame['title'])
|
||||
item['html'] = str(frame['title'])
|
||||
item['selected'] = (self.live_controller.selected_row == index)
|
||||
data.append(item)
|
||||
json_data = {'results': {'slides': data}}
|
||||
if current_item:
|
||||
json_data['results']['item'] = self.live_controller.service_item.unique_identifier
|
||||
self.do_json_header()
|
||||
return json.dumps(json_data).encode()
|
||||
|
||||
def controller(self, display_type, action):
|
||||
"""
|
||||
Perform an action on the slide controller.
|
||||
|
||||
:param display_type: This is the type of slide controller, either ``preview`` or ``live``.
|
||||
:param action: The action to perform.
|
||||
"""
|
||||
event = getattr(self.live_controller, 'slidecontroller_{display}_{action}'.format(display=display_type,
|
||||
action=action))
|
||||
if self.request_data:
|
||||
try:
|
||||
data = json.loads(self.request_data)['request']['id']
|
||||
except KeyError:
|
||||
return self.do_http_error()
|
||||
log.info(data)
|
||||
# This slot expects an int within a list.
|
||||
event.emit([data])
|
||||
else:
|
||||
event.emit()
|
||||
json_data = {'results': {'success': True}}
|
||||
self.do_json_header()
|
||||
return json.dumps(json_data).encode()
|
||||
|
||||
def service_list(self):
|
||||
"""
|
||||
Handles requests for service items in the service manager
|
||||
|
||||
"""
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': {'items': self._get_service_items()}}).encode()
|
||||
|
||||
def service(self, action):
|
||||
"""
|
||||
Handles requests for service items in the service manager
|
||||
|
||||
:param action: The action to perform.
|
||||
"""
|
||||
event = getattr(self.service_manager, 'servicemanager_{action}_item'.format(action=action))
|
||||
if self.request_data:
|
||||
try:
|
||||
data = int(json.loads(self.request_data)['request']['id'])
|
||||
except KeyError:
|
||||
return self.do_http_error()
|
||||
event.emit(data)
|
||||
else:
|
||||
event.emit()
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': {'success': True}}).encode()
|
||||
|
||||
def plugin_info(self, action):
|
||||
"""
|
||||
Return plugin related information, based on the action.
|
||||
|
||||
:param action: The action to perform. If *search* return a list of plugin names which support search.
|
||||
"""
|
||||
if action == 'search':
|
||||
searches = []
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
|
||||
searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])])
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': {'items': searches}}).encode()
|
||||
|
||||
def search(self, plugin_name):
|
||||
"""
|
||||
Return a list of items that match the search text.
|
||||
|
||||
:param plugin_name: The plugin name to search in.
|
||||
"""
|
||||
try:
|
||||
text = json.loads(self.request_data)['request']['text']
|
||||
except KeyError:
|
||||
return self.do_http_error()
|
||||
text = urllib.parse.unquote(text)
|
||||
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
|
||||
results = plugin.media_item.search(text, False)
|
||||
else:
|
||||
results = []
|
||||
self.do_json_header()
|
||||
return json.dumps({'results': {'items': results}}).encode()
|
||||
|
||||
def go_live(self, plugin_name):
|
||||
"""
|
||||
Go live on an item of type ``plugin``.
|
||||
|
||||
:param plugin_name: name of plugin
|
||||
"""
|
||||
try:
|
||||
request_id = json.loads(self.request_data)['request']['id']
|
||||
except KeyError:
|
||||
return self.do_http_error()
|
||||
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||
getattr(plugin.media_item, '{name}_go_live'.format(name=plugin_name)).emit([request_id, True])
|
||||
return self.do_http_success()
|
||||
|
||||
def add_to_service(self, plugin_name):
|
||||
"""
|
||||
Add item of type ``plugin_name`` to the end of the service.
|
||||
|
||||
:param plugin_name: name of plugin to be called
|
||||
"""
|
||||
try:
|
||||
request_id = json.loads(self.request_data)['request']['id']
|
||||
except KeyError:
|
||||
return self.do_http_error()
|
||||
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||
item_id = plugin.media_item.create_item_from_id(request_id)
|
||||
getattr(plugin.media_item, '{name}_add_to_service'.format(name=plugin_name)).emit([item_id, True])
|
||||
self.do_http_success()
|
@ -1,152 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact
|
||||
with OpenLP. It uses JSON to communicate with the remotes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Settings, RegistryProperties, OpenLPMixin
|
||||
|
||||
from openlp.plugins.remotes.lib import HttpRouter
|
||||
|
||||
from socketserver import ThreadingMixIn
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebHandler(BaseHTTPRequestHandler, HttpRouter):
|
||||
"""
|
||||
Stateless session handler to handle the HTTP request and process it.
|
||||
This class handles just the overrides to the base methods and the logic to invoke the methods within the HttpRouter
|
||||
class.
|
||||
DO not try change the structure as this is as per the documentation.
|
||||
"""
|
||||
|
||||
def do_POST(self):
|
||||
"""
|
||||
Present pages / data and invoke URL level user authentication.
|
||||
"""
|
||||
self.do_post_processor()
|
||||
|
||||
def do_GET(self):
|
||||
"""
|
||||
Present pages / data and invoke URL level user authentication.
|
||||
"""
|
||||
self.do_post_processor()
|
||||
|
||||
|
||||
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
pass
|
||||
|
||||
|
||||
class HttpThread(QtCore.QThread):
|
||||
"""
|
||||
A special Qt thread class to allow the HTTP server to run at the same time as the UI.
|
||||
"""
|
||||
def __init__(self, server):
|
||||
"""
|
||||
Constructor for the thread class.
|
||||
|
||||
:param server: The http server class.
|
||||
"""
|
||||
super(HttpThread, self).__init__(None)
|
||||
self.http_server = server
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the thread.
|
||||
"""
|
||||
self.http_server.start_server()
|
||||
|
||||
def stop(self):
|
||||
self.http_server.stop = True
|
||||
|
||||
|
||||
class OpenLPServer(RegistryProperties, OpenLPMixin):
|
||||
"""
|
||||
Wrapper round a server instance
|
||||
"""
|
||||
def __init__(self, secure=False):
|
||||
"""
|
||||
Initialise the http server, and start the server of the correct type http / https
|
||||
"""
|
||||
super(OpenLPServer, self).__init__()
|
||||
self.settings_section = 'remotes'
|
||||
self.secure = secure
|
||||
self.http_thread = HttpThread(self)
|
||||
self.http_thread.start()
|
||||
|
||||
def start_server(self):
|
||||
"""
|
||||
Start the correct server and save the handler
|
||||
"""
|
||||
address = Settings().value(self.settings_section + '/ip address')
|
||||
# Try to start secure server but not enabled.
|
||||
port = Settings().value(self.settings_section + '/port')
|
||||
self.start_server_instance(address, port, ThreadingHTTPServer)
|
||||
# If HTTP server start listening
|
||||
if hasattr(self, 'httpd') and self.httpd:
|
||||
self.httpd.serve_forever()
|
||||
else:
|
||||
log.debug('Failed to start http server on port {port}'.format(port=port))
|
||||
|
||||
def start_server_instance(self, address, port, server_class):
|
||||
"""
|
||||
Start the server
|
||||
|
||||
:param address: The server address
|
||||
:param port: The run port
|
||||
:param server_class: the class to start
|
||||
"""
|
||||
loop = 1
|
||||
while loop < 4:
|
||||
try:
|
||||
self.httpd = server_class((address, port), WebHandler)
|
||||
log.debug("Server started for class {name} {address} {port:d}".format(name=server_class,
|
||||
address=address,
|
||||
port=port))
|
||||
break
|
||||
except OSError:
|
||||
log.debug("failed to start http server thread state "
|
||||
"{loop:d} {running}".format(loop=loop, running=self.http_thread.isRunning()))
|
||||
loop += 1
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
log.error('Failed to start http server {why}'.format(why=e))
|
||||
loop += 1
|
||||
time.sleep(0.1)
|
||||
|
||||
def stop_server(self):
|
||||
"""
|
||||
Stop the server
|
||||
"""
|
||||
if self.http_thread.isRunning():
|
||||
self.http_thread.stop()
|
||||
self.httpd = None
|
||||
log.debug('Stopped the server.')
|
@ -1,265 +0,0 @@
|
||||
# -*- 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 os.path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, translate
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
|
||||
ZERO_URL = '0.0.0.0'
|
||||
|
||||
|
||||
class RemoteTab(SettingsTab):
|
||||
"""
|
||||
RemoteTab is the Remotes settings tab in the settings dialog.
|
||||
"""
|
||||
def __init__(self, parent, title, visible_title, icon_path):
|
||||
super(RemoteTab, self).__init__(parent, title, visible_title, icon_path)
|
||||
|
||||
def setupUi(self):
|
||||
self.setObjectName('RemoteTab')
|
||||
super(RemoteTab, self).setupUi()
|
||||
self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.server_settings_group_box.setObjectName('server_settings_group_box')
|
||||
self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
|
||||
self.server_settings_layout.setObjectName('server_settings_layout')
|
||||
self.address_label = QtWidgets.QLabel(self.server_settings_group_box)
|
||||
self.address_label.setObjectName('address_label')
|
||||
self.address_edit = QtWidgets.QLineEdit(self.server_settings_group_box)
|
||||
self.address_edit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
|
||||
self))
|
||||
self.address_edit.setObjectName('address_edit')
|
||||
self.server_settings_layout.addRow(self.address_label, self.address_edit)
|
||||
self.twelve_hour_check_box = QtWidgets.QCheckBox(self.server_settings_group_box)
|
||||
self.twelve_hour_check_box.setObjectName('twelve_hour_check_box')
|
||||
self.server_settings_layout.addRow(self.twelve_hour_check_box)
|
||||
self.thumbnails_check_box = QtWidgets.QCheckBox(self.server_settings_group_box)
|
||||
self.thumbnails_check_box.setObjectName('thumbnails_check_box')
|
||||
self.server_settings_layout.addRow(self.thumbnails_check_box)
|
||||
self.left_layout.addWidget(self.server_settings_group_box)
|
||||
self.http_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.http_settings_group_box.setObjectName('http_settings_group_box')
|
||||
self.http_setting_layout = QtWidgets.QFormLayout(self.http_settings_group_box)
|
||||
self.http_setting_layout.setObjectName('http_setting_layout')
|
||||
self.port_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.port_label.setObjectName('port_label')
|
||||
self.port_spin_box = QtWidgets.QSpinBox(self.http_settings_group_box)
|
||||
self.port_spin_box.setMaximum(32767)
|
||||
self.port_spin_box.setObjectName('port_spin_box')
|
||||
self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
|
||||
self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.remote_url_label.setObjectName('remote_url_label')
|
||||
self.remote_url = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.remote_url.setObjectName('remote_url')
|
||||
self.remote_url.setOpenExternalLinks(True)
|
||||
self.http_setting_layout.addRow(self.remote_url_label, self.remote_url)
|
||||
self.stage_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.stage_url_label.setObjectName('stage_url_label')
|
||||
self.stage_url = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.stage_url.setObjectName('stage_url')
|
||||
self.stage_url.setOpenExternalLinks(True)
|
||||
self.http_setting_layout.addRow(self.stage_url_label, self.stage_url)
|
||||
self.live_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.live_url_label.setObjectName('live_url_label')
|
||||
self.live_url = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.live_url.setObjectName('live_url')
|
||||
self.live_url.setOpenExternalLinks(True)
|
||||
self.http_setting_layout.addRow(self.live_url_label, self.live_url)
|
||||
self.left_layout.addWidget(self.http_settings_group_box)
|
||||
self.user_login_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.user_login_group_box.setCheckable(True)
|
||||
self.user_login_group_box.setChecked(False)
|
||||
self.user_login_group_box.setObjectName('user_login_group_box')
|
||||
self.user_login_layout = QtWidgets.QFormLayout(self.user_login_group_box)
|
||||
self.user_login_layout.setObjectName('user_login_layout')
|
||||
self.user_id_label = QtWidgets.QLabel(self.user_login_group_box)
|
||||
self.user_id_label.setObjectName('user_id_label')
|
||||
self.user_id = QtWidgets.QLineEdit(self.user_login_group_box)
|
||||
self.user_id.setObjectName('user_id')
|
||||
self.user_login_layout.addRow(self.user_id_label, self.user_id)
|
||||
self.password_label = QtWidgets.QLabel(self.user_login_group_box)
|
||||
self.password_label.setObjectName('password_label')
|
||||
self.password = QtWidgets.QLineEdit(self.user_login_group_box)
|
||||
self.password.setObjectName('password')
|
||||
self.user_login_layout.addRow(self.password_label, self.password)
|
||||
self.left_layout.addWidget(self.user_login_group_box)
|
||||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
||||
self.right_layout.addWidget(self.android_app_group_box)
|
||||
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
|
||||
self.android_qr_layout.setObjectName('android_qr_layout')
|
||||
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
|
||||
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.android_qr_code_label.setObjectName('android_qr_code_label')
|
||||
self.android_qr_layout.addWidget(self.android_qr_code_label)
|
||||
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_description_label.setObjectName('android_qr_description_label')
|
||||
self.android_qr_description_label.setOpenExternalLinks(True)
|
||||
self.android_qr_description_label.setWordWrap(True)
|
||||
self.android_qr_layout.addWidget(self.android_qr_description_label)
|
||||
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.ios_app_group_box.setObjectName('ios_app_group_box')
|
||||
self.right_layout.addWidget(self.ios_app_group_box)
|
||||
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
|
||||
self.ios_qr_layout.setObjectName('ios_qr_layout')
|
||||
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
|
||||
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
|
||||
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
|
||||
self.ios_qr_description_label.setOpenExternalLinks(True)
|
||||
self.ios_qr_description_label.setWordWrap(True)
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
|
||||
self.left_layout.addStretch()
|
||||
self.right_layout.addStretch()
|
||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
|
||||
self.address_edit.textChanged.connect(self.set_urls)
|
||||
self.port_spin_box.valueChanged.connect(self.set_urls)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
|
||||
self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
|
||||
self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
|
||||
self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
|
||||
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
|
||||
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
|
||||
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
||||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
|
||||
self.android_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab',
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
|
||||
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
|
||||
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
|
||||
self.ios_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab',
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
||||
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
||||
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
||||
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
|
||||
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
|
||||
|
||||
def set_urls(self):
|
||||
"""
|
||||
Update the display based on the data input on the screen
|
||||
"""
|
||||
ip_address = self.get_ip_address(self.address_edit.text())
|
||||
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value())
|
||||
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
||||
http_url_temp = http_url + 'stage'
|
||||
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
http_url_temp = http_url + 'main'
|
||||
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
|
||||
def get_ip_address(self, ip_address):
|
||||
"""
|
||||
returns the IP address in dependency of the passed address
|
||||
ip_address == 0.0.0.0: return the IP address of the first valid interface
|
||||
else: return ip_address
|
||||
"""
|
||||
if ip_address == ZERO_URL:
|
||||
interfaces = QtNetwork.QNetworkInterface.allInterfaces()
|
||||
for interface in interfaces:
|
||||
if not interface.isValid():
|
||||
continue
|
||||
if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)):
|
||||
continue
|
||||
for address in interface.addressEntries():
|
||||
ip = address.ip()
|
||||
if ip.protocol() == QtNetwork.QAbstractSocket.IPv4Protocol and \
|
||||
ip != QtNetwork.QHostAddress.LocalHost:
|
||||
return ip.toString()
|
||||
return ip_address
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load the configuration and update the server configuration if necessary
|
||||
"""
|
||||
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
|
||||
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
||||
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
|
||||
self.twelve_hour_check_box.setChecked(self.twelve_hour)
|
||||
self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
|
||||
self.thumbnails_check_box.setChecked(self.thumbnails)
|
||||
self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
|
||||
self.user_id.setText(Settings().value(self.settings_section + '/user id'))
|
||||
self.password.setText(Settings().value(self.settings_section + '/password'))
|
||||
self.set_urls()
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the configuration and update the server configuration if necessary
|
||||
"""
|
||||
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \
|
||||
Settings().value(self.settings_section + '/port') != self.port_spin_box.value():
|
||||
self.settings_form.register_post_process('remotes_config_updated')
|
||||
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
|
||||
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
|
||||
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
|
||||
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
|
||||
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
|
||||
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
||||
Settings().setValue(self.settings_section + '/password', self.password.text())
|
||||
self.generate_icon()
|
||||
|
||||
def on_twelve_hour_check_box_changed(self, check_state):
|
||||
"""
|
||||
Toggle the 12 hour check box.
|
||||
"""
|
||||
self.twelve_hour = False
|
||||
# we have a set value convert to True/False
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.twelve_hour = True
|
||||
|
||||
def on_thumbnails_check_box_changed(self, check_state):
|
||||
"""
|
||||
Toggle the thumbnail check box.
|
||||
"""
|
||||
self.thumbnails = False
|
||||
# we have a set value convert to True/False
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.thumbnails = True
|
||||
|
||||
def generate_icon(self):
|
||||
"""
|
||||
Generate icon for main window
|
||||
"""
|
||||
pass
|
||||
# self.remote_server_icon.hide()
|
||||
# icon = QtGui.QImage(':/remote/network_server.png')
|
||||
# icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
# if Settings().value(self.settings_section + '/authentication enabled'):
|
||||
# overlay = QtGui.QImage(':/remote/network_auth.png')
|
||||
# overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
# painter = QtGui.QPainter(icon)
|
||||
# painter.drawImage(20, 0, overlay)
|
||||
# painter.end()
|
||||
# self.remote_server_icon.setPixmap(QtGui.QPixmap.fromImage(icon))
|
||||
# self.remote_server_icon.show()
|
@ -1,21 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
@ -1,116 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the lib submodule of the Remotes plugin.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from unittest import TestCase
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
|
||||
from openlp.core.common import Settings
|
||||
from openlp.plugins.remotes.lib.remotetab import RemoteTab
|
||||
from tests.functional import patch
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
__default_settings__ = {
|
||||
'remotes/twelve hour': True,
|
||||
'remotes/port': 4316,
|
||||
'remotes/user id': 'openlp',
|
||||
'remotes/password': 'password',
|
||||
'remotes/authentication enabled': False,
|
||||
'remotes/ip address': '0.0.0.0',
|
||||
'remotes/thumbnails': True
|
||||
}
|
||||
ZERO_URL = '0.0.0.0'
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
|
||||
|
||||
|
||||
class TestRemoteTab(TestCase, TestMixin):
|
||||
"""
|
||||
Test the functions in the :mod:`lib` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the UI
|
||||
"""
|
||||
self.setup_application()
|
||||
self.build_settings()
|
||||
Settings().extend_default_settings(__default_settings__)
|
||||
self.parent = QtWidgets.QMainWindow()
|
||||
self.form = RemoteTab(self.parent, 'Remotes', None, None)
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete all the C++ objects at the end so that we don't have a segfault
|
||||
"""
|
||||
del self.parent
|
||||
del self.form
|
||||
self.destroy_settings()
|
||||
|
||||
def get_ip_address_default_test(self):
|
||||
"""
|
||||
Test the get_ip_address function with ZERO_URL
|
||||
"""
|
||||
# WHEN: the default ip address is given
|
||||
ip_address = self.form.get_ip_address(ZERO_URL)
|
||||
# THEN: the default ip address will be returned
|
||||
self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address),
|
||||
'The return value should be a valid ip address')
|
||||
|
||||
def get_ip_address_with_ip_test(self):
|
||||
"""
|
||||
Test the get_ip_address function with given ip address
|
||||
"""
|
||||
# GIVEN: A mocked location
|
||||
# GIVEN: An ip address
|
||||
given_ip = '192.168.1.1'
|
||||
# WHEN: the default ip address is given
|
||||
ip_address = self.form.get_ip_address(given_ip)
|
||||
# THEN: the default ip address will be returned
|
||||
self.assertEqual(ip_address, given_ip, 'The return value should be %s' % given_ip)
|
||||
|
||||
def set_basic_urls_test(self):
|
||||
"""
|
||||
Test the set_urls function with standard defaults
|
||||
"""
|
||||
# GIVEN: A mocked location
|
||||
with patch('openlp.core.common.Settings') as mocked_class, \
|
||||
patch('openlp.core.common.applocation.AppLocation.get_directory') as mocked_get_directory, \
|
||||
patch('openlp.core.common.check_directory_exists') as mocked_check_directory_exists, \
|
||||
patch('openlp.core.common.applocation.os') as mocked_os:
|
||||
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
|
||||
mocked_settings = mocked_class.return_value
|
||||
mocked_settings.contains.return_value = False
|
||||
mocked_get_directory.return_value = 'test/dir'
|
||||
mocked_check_directory_exists.return_value = True
|
||||
mocked_os.path.normpath.return_value = 'test/dir'
|
||||
|
||||
# WHEN: when the set_urls is called having reloaded the form.
|
||||
self.form.load()
|
||||
self.form.set_urls()
|
||||
# THEN: the following screen values should be set
|
||||
self.assertEqual(self.form.address_edit.text(), ZERO_URL, 'The default URL should be set on the screen')
|
||||
self.assertEqual(self.form.user_login_group_box.isChecked(), False,
|
||||
'The authentication box should not be enabled')
|
@ -1,404 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the lib submodule of the Remotes plugin.
|
||||
"""
|
||||
import os
|
||||
import urllib.request
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.api import Poll
|
||||
from openlp.core.common import Settings, Registry
|
||||
from openlp.core.ui import ServiceManager
|
||||
from openlp.plugins.remotes.lib.httpserver import HttpRouter
|
||||
from tests.functional import MagicMock, patch, mock_open
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
__default_settings__ = {
|
||||
'remotes/twelve hour': True,
|
||||
'remotes/port': 4316,
|
||||
'remotes/user id': 'openlp',
|
||||
'remotes/password': 'password',
|
||||
'remotes/authentication enabled': False,
|
||||
'remotes/ip address': '0.0.0.0'
|
||||
}
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
class TestRouter(TestCase, TestMixin):
|
||||
"""
|
||||
Test the functions in the :mod:`lib` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the UI
|
||||
"""
|
||||
Registry.create()
|
||||
self.setup_application()
|
||||
self.build_settings()
|
||||
Settings().extend_default_settings(__default_settings__)
|
||||
self.service_manager = ServiceManager()
|
||||
self.router = HttpRouter()
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete all the C++ objects at the end so that we don't have a segfault
|
||||
"""
|
||||
self.destroy_settings()
|
||||
|
||||
def password_encrypter_test(self):
|
||||
"""
|
||||
Test hash userid and password function
|
||||
"""
|
||||
# GIVEN: A default configuration
|
||||
Settings().setValue('remotes/user id', 'openlp')
|
||||
Settings().setValue('remotes/password', 'password')
|
||||
poll = MagicMock()
|
||||
Registry().register('api_poll', poll)
|
||||
|
||||
# WHEN: called with the defined userid
|
||||
router = HttpRouter()
|
||||
router.initialise()
|
||||
test_value = 'b3BlbmxwOnBhc3N3b3Jk'
|
||||
|
||||
# THEN: the function should return the correct password
|
||||
self.assertEqual(router.auth, test_value,
|
||||
'The result for make_sha_hash should return the correct encrypted password')
|
||||
|
||||
def process_http_request_test(self):
|
||||
"""
|
||||
Test the router control functionality
|
||||
"""
|
||||
# GIVEN: A testing set of Routes
|
||||
mocked_function = MagicMock()
|
||||
test_route = [
|
||||
(r'^/stage/api/poll$', {'function': mocked_function, 'secure': False}),
|
||||
]
|
||||
self.router.routes = test_route
|
||||
self.router.command = 'GET'
|
||||
|
||||
# WHEN: called with a poll route
|
||||
function, args = self.router.process_http_request('/stage/api/poll', None)
|
||||
|
||||
# THEN: the function should have been called only once
|
||||
self.assertEqual(mocked_function, function['function'], 'The mocked function should match defined value.')
|
||||
self.assertFalse(function['secure'], 'The mocked function should not require any security.')
|
||||
|
||||
def process_secure_http_request_test(self):
|
||||
"""
|
||||
Test the router control functionality
|
||||
"""
|
||||
# GIVEN: A testing set of Routes
|
||||
mocked_function = MagicMock()
|
||||
test_route = [
|
||||
(r'^/stage/api/poll$', {'function': mocked_function, 'secure': True}),
|
||||
]
|
||||
self.router.routes = test_route
|
||||
self.router.settings_section = 'remotes'
|
||||
Settings().setValue('remotes/authentication enabled', True)
|
||||
self.router.path = '/stage/api/poll'
|
||||
self.router.auth = ''
|
||||
self.router.headers = {'Authorization': None}
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
self.router.command = 'GET'
|
||||
|
||||
# WHEN: called with a poll route
|
||||
self.router.do_post_processor()
|
||||
|
||||
# THEN: the function should have been called only once
|
||||
self.router.send_response.assert_called_once_with(401)
|
||||
self.assertEqual(self.router.send_header.call_count, 5, 'The header should have been called five times.')
|
||||
|
||||
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 main_poll_test(self):
|
||||
"""
|
||||
Test the main poll logic
|
||||
"""
|
||||
# GIVEN: a defined router with two slides
|
||||
Registry.create()
|
||||
live_controller = MagicMock()
|
||||
Registry().register('live_controller', live_controller)
|
||||
poll = Poll()
|
||||
live_controller.slide_count = 2
|
||||
|
||||
# WHEN: main poll called
|
||||
results = poll.main_poll()
|
||||
|
||||
# THEN: the correct response should be returned
|
||||
self.assertEqual(results.decode('utf-8'), '{"results": {"slide_count": 2}}',
|
||||
'The resulting json strings should match')
|
||||
|
||||
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.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')
|
||||
|
||||
def serve_file_with_partial_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'))
|
||||
|
||||
# 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')
|
||||
|
||||
def serve_thumbnail_without_params_test(self):
|
||||
"""
|
||||
Test the serve_thumbnail routine without params
|
||||
"""
|
||||
# GIVEN: mocked environment
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
|
||||
# WHEN: I request a thumbnail
|
||||
self.router.serve_thumbnail()
|
||||
|
||||
# THEN: The headers should be set correctly
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
self.assertEqual(self.router.send_response.call_count, 1, 'Send response should be called once')
|
||||
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers should be called once')
|
||||
|
||||
def serve_thumbnail_with_invalid_params_test(self):
|
||||
"""
|
||||
Test the serve_thumbnail routine with invalid params
|
||||
"""
|
||||
# GIVEN: Mocked send_header, send_response, end_headers and wfile
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
|
||||
# WHEN: pass a bad controller
|
||||
self.router.serve_thumbnail('badcontroller', 'tecnologia 1.pptx/slide1.png')
|
||||
|
||||
# THEN: a 404 should be returned
|
||||
self.assertEqual(len(self.router.send_header.mock_calls), 4, 'header should be called 4 times')
|
||||
self.assertEqual(len(self.router.send_response.mock_calls), 1, 'One response')
|
||||
self.assertEqual(len(self.router.wfile.mock_calls), 1, 'Once call to write to the socket')
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
|
||||
# WHEN: pass a bad filename
|
||||
self.router.send_response.reset_mock()
|
||||
self.router.serve_thumbnail('presentations', 'tecnologia 1.pptx/badfilename.png')
|
||||
|
||||
# THEN: return a 404
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
|
||||
# WHEN: a dangerous URL is passed
|
||||
self.router.send_response.reset_mock()
|
||||
self.router.serve_thumbnail('presentations', '../tecnologia 1.pptx/slide1.png')
|
||||
|
||||
# THEN: return a 404
|
||||
self.router.send_response.assert_called_once_with(404)
|
||||
|
||||
def serve_thumbnail_with_valid_params_test(self):
|
||||
"""
|
||||
Test the serve_thumbnail routine with valid params
|
||||
"""
|
||||
# GIVEN: Mocked send_header, send_response, end_headers and wfile
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
mocked_image_manager = MagicMock()
|
||||
Registry().register('image_manager', mocked_image_manager)
|
||||
file_name = 'another%20test/slide1.png'
|
||||
full_path = os.path.normpath(os.path.join('thumbnails', file_name))
|
||||
width = 120
|
||||
height = 90
|
||||
with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
|
||||
patch('builtins.open', mock_open(read_data='123')), \
|
||||
patch('openlp.plugins.remotes.lib.httprouter.AppLocation') as mocked_location, \
|
||||
patch('openlp.plugins.remotes.lib.httprouter.image_to_byte') as mocked_image_to_byte:
|
||||
mocked_exists.return_value = True
|
||||
mocked_image_to_byte.return_value = '123'
|
||||
mocked_location.get_section_data_path.return_value = ''
|
||||
|
||||
# WHEN: pass good controller and filename
|
||||
self.router.serve_thumbnail('presentations', '{0}x{1}'.format(width, height), file_name)
|
||||
|
||||
# THEN: a file should be returned
|
||||
self.assertEqual(self.router.send_header.call_count, 1, 'One header')
|
||||
self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
|
||||
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
|
||||
mocked_exists.assert_called_with(urllib.parse.unquote(full_path))
|
||||
self.assertEqual(mocked_image_to_byte.call_count, 1, 'Called once')
|
||||
mocked_image_manager.add_image.assert_any_call(os.path.normpath(os.path.join('thumbnails', 'another test',
|
||||
'slide1.png')),
|
||||
'slide1.png', None, width, height)
|
||||
mocked_image_manager.get_image.assert_any_call(os.path.normpath(os.path.join('thumbnails', 'another test',
|
||||
'slide1.png')),
|
||||
'slide1.png', width, height)
|
||||
|
||||
def remote_next_test(self):
|
||||
"""
|
||||
Test service manager receives api next click properly (bug 1407445)
|
||||
"""
|
||||
# GIVEN: initial setup and mocks
|
||||
self.router.routes = [(r'^/api/service/(.*)$', {'function': self.router.service, 'secure': False})]
|
||||
self.router.request_data = False
|
||||
mocked_next_item = MagicMock()
|
||||
self.service_manager.next_item = mocked_next_item
|
||||
with patch.object(self.service_manager, 'setup_ui'), \
|
||||
patch.object(self.router, 'do_json_header'):
|
||||
self.service_manager.bootstrap_initialise()
|
||||
self.app.processEvents()
|
||||
|
||||
# WHEN: Remote next is received
|
||||
self.router.service(action='next')
|
||||
self.app.processEvents()
|
||||
|
||||
# THEN: service_manager.next_item() should have been called
|
||||
self.assertTrue(mocked_next_item.called, 'next_item() should have been called in service_manager')
|
||||
|
||||
def remote_previous_test(self):
|
||||
"""
|
||||
Test service manager receives api previous click properly (bug 1407445)
|
||||
"""
|
||||
# GIVEN: initial setup and mocks
|
||||
self.router.routes = [(r'^/api/service/(.*)$', {'function': self.router.service, 'secure': False})]
|
||||
self.router.request_data = False
|
||||
mocked_previous_item = MagicMock()
|
||||
self.service_manager.previous_item = mocked_previous_item
|
||||
with patch.object(self.service_manager, 'setup_ui'), \
|
||||
patch.object(self.router, 'do_json_header'):
|
||||
self.service_manager.bootstrap_initialise()
|
||||
self.app.processEvents()
|
||||
|
||||
# WHEN: Remote next is received
|
||||
self.router.service(action='previous')
|
||||
self.app.processEvents()
|
||||
|
||||
# THEN: service_manager.next_item() should have been called
|
||||
self.assertTrue(mocked_previous_item.called, 'previous_item() should have been called in service_manager')
|
||||
|
||||
def remote_stage_personal_html_test(self):
|
||||
"""
|
||||
Test the stage url with a parameter after loaded a url/stage.mako file
|
||||
"""
|
||||
# GIVEN: initial route
|
||||
self.router.config_dir = ''
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
self.router._process_file = MagicMock()
|
||||
|
||||
# WHEN: I call stage with a suffix
|
||||
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.mako'))
|
||||
|
||||
def remote_stage_personal_css_test(self):
|
||||
"""
|
||||
Test the html with reference stages/trb/trb.css then loaded a stages/trb/trb.css file
|
||||
"""
|
||||
# GIVEN: initial route
|
||||
self.router.config_dir = ''
|
||||
self.router.send_response = MagicMock()
|
||||
self.router.send_header = MagicMock()
|
||||
self.router.end_headers = MagicMock()
|
||||
self.router.wfile = MagicMock()
|
||||
self.router._process_file = MagicMock()
|
||||
|
||||
# WHEN: I call stage with a suffix
|
||||
self.router.stages('stages', 'stages/trb/trb.css')
|
||||
|
||||
# THEN: we should use the specific stage file instance
|
||||
self.router._process_file.assert_called_with(os.path.join('trb', 'trb.css'))
|
@ -1,21 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|