Update remote API to use Flask, and be more RESTful

This commit is contained in:
Simon Hanna 2020-01-27 22:57:58 +00:00 committed by Raoul Snyman
parent 52d811331c
commit ef2f798f6f
62 changed files with 1602 additions and 1991 deletions

View File

@ -18,3 +18,21 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
from flask import Flask
from flask_cors import CORS
from openlp.core.api.versions import v1
from openlp.core.api.versions import v2
from openlp.core.api.main import main_views
app = Flask(__name__)
CORS(app)
app.register_blueprint(main_views)
v1.register_blueprints(app)
v2.register_blueprints(app)
def register_blueprint(blueprint, url_prefix=None):
app.register_blueprint(blueprint, url_prefix)

View File

@ -65,6 +65,5 @@ def download_and_check(callback=None):
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
callback.setRange(0, file_size)
if download_file(callback, 'https://get.openlp.org/webclient/site.zip',
AppLocation.get_section_data_path('remotes') / 'site.zip',
sha256=sha256):
AppLocation.get_section_data_path('remotes') / 'site.zip'):
deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'site.zip')

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The Endpoint class, which provides plugins with a way to serve their own portion of the API
"""

View File

@ -1,148 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import json
import logging
import os
import urllib.error
import urllib.request
from pathlib import Path
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.common.applocation import AppLocation
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import create_thumb
from openlp.core.lib.serviceitem import ItemCapabilities
log = logging.getLogger(__name__)
controller_endpoint = Endpoint('controller')
api_controller_endpoint = Endpoint('api')
@api_controller_endpoint.route('controller/live/text')
@controller_endpoint.route('live/text')
def controller_text(request):
"""
Perform an action on the slide controller.
:param request: the http request - not used
"""
log.debug('controller_text')
live_controller = Registry().get('live_controller')
current_item = 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['verse']:
item['tag'] = str(frame['verse'])
else:
item['tag'] = str(index + 1)
# TODO: Figure out rendering chords
item['chords_text'] = str(frame.get('chords_text', ''))
item['text'] = frame['text']
item['html'] = current_item.get_rendered_frame(index)
# 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('api/thumbnails'):
item['tag'] = str(index + 1)
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path
# Create thumbnail if it doesn't exists
if not full_thumbnail_path.exists():
create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False)
Registry().get('image_manager').add_image(str(full_thumbnail_path), frame['title'], None, 88, 88)
item['img'] = urllib.request.pathname2url(os.path.sep + str(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('api/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = str(AppLocation.get_data_path())
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
Registry().get('image_manager').add_image(frame['image'], frame['title'], None, 88, 88)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (live_controller.selected_row == index)
item['title'] = current_item.title
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
json_data['results']['item'] = live_controller.service_item.unique_identifier
return json_data
@api_controller_endpoint.route('controller/live/set')
@controller_endpoint.route('live/set')
@requires_auth
def controller_set(request):
"""
Perform an action on the slide controller.
:param request: The action to perform.
"""
event = getattr(Registry().get('live_controller'), 'slidecontroller_live_set')
try:
json_data = request.GET.get('data')
data = int(json.loads(json_data)['request']['id'])
event.emit([data])
except KeyError:
log.error("Endpoint controller/live/set request id not found")
return {'results': {'success': True}}
@controller_endpoint.route('{controller}/{action:next|previous}')
@requires_auth
def controller_direction(request, controller, action):
"""
Handles requests for setting service items in the slide controller
:param request: The http request object.
:param controller: the controller slides forward or backward.
:param action: the controller slides forward or backward.
"""
event = getattr(Registry().get('live_controller'), 'slidecontroller_{controller}_{action}'.
format(controller=controller, action=action))
event.emit()
@api_controller_endpoint.route('controller/{controller}/{action:next|previous}')
@requires_auth
def controller_direction_api(request, controller, action):
"""
Handles requests for setting service items in the slide controller
:param request: The http request object.
:param controller: the controller slides forward or backward.
:param action: the controller slides forward or backward.
"""
controller_direction(request, controller, action)
return {'results': {'success': True}}

View File

@ -1,174 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.api.endpoint.core` module contains the core API endpoints
"""
import logging
import os
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry
from openlp.core.lib import image_to_byte
from openlp.core.lib.plugin import PluginStatus, StringContent
template_dir = 'templates'
static_dir = 'static'
blank_dir = os.path.join(static_dir, 'index')
log = logging.getLogger(__name__)
chords_endpoint = Endpoint('chords', template_dir=template_dir, static_dir=static_dir)
stage_endpoint = Endpoint('stage', template_dir=template_dir, static_dir=static_dir)
main_endpoint = Endpoint('main', template_dir=template_dir, static_dir=static_dir)
blank_endpoint = Endpoint('', template_dir=template_dir, static_dir=blank_dir)
FILE_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.png': 'image/png'
}
remote = translate('RemotePlugin.Mobile', 'Remote')
stage = translate('RemotePlugin.Mobile', 'Stage View')
live = translate('RemotePlugin.Mobile', 'Live View')
chords = translate('RemotePlugin.Mobile', 'Chords View')
TRANSLATED_STRINGS = {
'app_title': "{main} {remote}".format(main=UiStrings().OpenLP, remote=remote),
'stage_title': "{main} {stage}".format(main=UiStrings().OpenLP, stage=stage),
'live_title': "{main} {live}".format(main=UiStrings().OpenLP, live=live),
'chords_title': "{main} {chords}".format(main=UiStrings().OpenLP, chords=chords),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
'search': translate('RemotePlugin.Mobile', 'Search'),
'home': translate('RemotePlugin.Mobile', 'Home'),
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
'blank': translate('RemotePlugin.Mobile', 'Blank'),
'theme': translate('RemotePlugin.Mobile', 'Theme'),
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
'show': translate('RemotePlugin.Mobile', 'Show'),
'prev': translate('RemotePlugin.Mobile', 'Prev'),
'next': translate('RemotePlugin.Mobile', 'Next'),
'text': translate('RemotePlugin.Mobile', 'Text'),
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add &amp; Go to Service'),
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides'),
'settings': translate('RemotePlugin.Mobile', 'Settings'),
}
@stage_endpoint.route('')
def stage_index(request):
"""
Deliver the page for the /stage url
"""
return stage_endpoint.render_template('stage.mako', **TRANSLATED_STRINGS)
@chords_endpoint.route('')
def chords_index(request):
"""
Deliver the page for the /chords url
"""
return chords_endpoint.render_template('chords.mako', **TRANSLATED_STRINGS)
@main_endpoint.route('')
def main_index(request):
"""
Deliver the page for the /main url
"""
return main_endpoint.render_template('main.mako', **TRANSLATED_STRINGS)
@blank_endpoint.route('')
def index(request):
"""
Deliver the page for the / url
:param request:
"""
return blank_endpoint.render_template('index.mako', **TRANSLATED_STRINGS)
@blank_endpoint.route('api/poll')
@blank_endpoint.route('poll')
def poll(request):
"""
Deliver the page for the /poll url
:param request:
"""
return Registry().get('poller').poll()
@blank_endpoint.route('api/display/{display:hide|show|blank|theme|desktop}')
@blank_endpoint.route('display/{display:hide|show|blank|theme|desktop}')
@requires_auth
def toggle_display(request, display):
"""
Deliver the functions for the /display url
:param request: the http request - not used
:param display: the display function to be triggered
"""
Registry().get('live_controller').slidecontroller_toggle_display.emit(display)
return {'results': {'success': True}}
@blank_endpoint.route('api/plugin/search')
@blank_endpoint.route('plugin/search')
def plugin_search_list(request):
"""
Deliver a list of active plugins that support search
:param request: the http request - not used
"""
searches = []
for plugin in Registry().get('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'])])
return {'results': {'items': searches}}
@main_endpoint.route('image')
def main_image(request):
"""
Return the latest display image as a byte stream.
:param request: base path of the URL. Not used but passed by caller
:return:
"""
live_controller = Registry().get('live_controller')
result = {
'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image))
}
return {'results': result}

View File

@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import json
import re
import urllib
from webob import Response
from openlp.core.api.http.errors import NotFound
from openlp.core.common.applocation import AppLocation
from openlp.core.common.registry import Registry
from openlp.core.lib import image_to_byte
from openlp.core.lib.plugin import PluginStatus
def search(request, plugin_name, log):
"""
Handles requests for searching the plugins
:param request: The http request object.
:param plugin_name: The plugin name.
:param log: The class log object.
"""
try:
json_data = request.GET.get('data')
text = json.loads(json_data)['request']['text']
except KeyError:
log.error("Endpoint {text} search request text not found".format(text=plugin_name))
text = ""
text = urllib.parse.unquote(text)
plugin = Registry().get('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)
return {'results': {'items': results}}
else:
raise NotFound()
def live(request, plugin_name, log):
"""
Handles requests for making live of the plugins
:param request: The http request object.
:param plugin_name: The plugin name.
:param log: The class log object.
"""
try:
json_data = request.GET.get('data')
request_id = json.loads(json_data)['request']['id']
except KeyError:
log.error("Endpoint {text} search request text not found".format(text=plugin_name))
return []
plugin = Registry().get('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])
def service(request, plugin_name, log):
"""
Handles requests for adding to a service of the plugins
:param request: The http request object.
:param plugin_name: The plugin name.
:param log: The class log object.
"""
try:
json_data = request.GET.get('data')
request_id = json.loads(json_data)['request']['id']
except KeyError:
log.error("Endpoint {plugin} search request text not found".format(plugin=plugin_name))
return []
plugin = Registry().get('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])
def display_thumbnails(request, controller_name, log, dimensions, file_name, slide=None):
"""
Handles requests for adding a song to the service
Return an image to a web page based on a URL
:param request: Request object
:param controller_name: which controller is requesting the image
:param log: the logger object
:param dimensions: the image size eg 88x88
:param str file_name: the file name of the image
:param slide: the individual image name
:return:
"""
log.debug('serve thumbnail {cname}/thumbnails{dim}/{fname}/{slide}'.format(cname=controller_name,
dim=dimensions,
fname=file_name,
slide=slide))
# -1 means use the default dimension in ImageManager
width = -1
height = -1
image = None
if dimensions:
match = re.search(r'(\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]
if controller_name and file_name:
file_name = urllib.parse.unquote(file_name)
if '..' not in file_name: # no hacking please
full_path = AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name
if slide:
full_path = full_path / slide
if full_path.exists():
Registry().get('image_manager').add_image(full_path, full_path.name, None, width, height)
image = Registry().get('image_manager').get_image(full_path, full_path.name, width, height)
return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8')

View File

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
from openlp.core.api.http.endpoint import Endpoint
log = logging.getLogger(__name__)
remote_endpoint = Endpoint('remote', template_dir='remotes')
@remote_endpoint.route('{view}')
def index(request, view):
"""
Handles requests for /remotes url
:param request: The http request object.
:param view: The view name to be servered.
"""
return remote_endpoint.render_template('{view}.mako'.format(view=view), **TRANSLATED_STRINGS)

View File

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import json
import logging
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
service_endpoint = Endpoint('service')
api_service_endpoint = Endpoint('api/service')
@api_service_endpoint.route('list')
@service_endpoint.route('list')
def list_service(request):
"""
Handles requests for service items in the service manager
:param request: The http request object.
"""
return {'results': {'items': get_service_items()}}
@api_service_endpoint.route('set')
@service_endpoint.route('set')
@requires_auth
def service_set(request):
"""
Handles requests for setting service items in the service manager
:param request: The http request object.
"""
event = getattr(Registry().get('service_manager'), 'servicemanager_set_item')
try:
json_data = request.GET.get('data')
data = int(json.loads(json_data)['request']['id'])
event.emit(data)
except KeyError:
log.error("Endpoint service/set request id not found")
return {'results': {'success': True}}
@api_service_endpoint.route('{action:next|previous}')
@service_endpoint.route('{action:next|previous}')
@requires_auth
def service_direction(request, action):
"""
Handles requests for setting service items in the service manager
:param request: The http request object.
:param action: the the service slides forward or backward.
"""
event = getattr(Registry().get('service_manager'), 'servicemanager_{action}_item'.format(action=action))
event.emit()
return {'results': {'success': True}}
def get_service_items():
"""
Read the service item in use and return the data as a json object
"""
live_controller = Registry().get('live_controller')
service_items = []
if live_controller.service_item:
current_unique_identifier = live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in Registry().get('service_manager').service_items:
service_item = item['service_item']
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return service_items

View File

@ -24,39 +24,8 @@ from functools import wraps
from webob import Response
from openlp.core.api.http.wsgiapp import WSGIApplication
from openlp.core.common.settings import Settings
application = WSGIApplication('api')
def _route_from_url(url_prefix, url):
"""
Create a route from the URL
"""
url_prefix = '/{prefix}/'.format(prefix=url_prefix.strip('/'))
if not url:
url = url_prefix[:-1]
else:
url = url_prefix + url
url = url.replace('//', '/')
return url
def register_endpoint(end_point):
"""
Register an endpoint with the app
"""
for url, view_func, method in end_point.routes:
# Set the view functions
route = _route_from_url(end_point.url_prefix, url)
application.add_route(route, view_func, method)
# Add a static route if necessary
if end_point.static_dir:
static_route = _route_from_url(end_point.url_prefix, 'static')
static_route += '(.*)'
application.add_static_route(static_route, end_point.static_dir)
def check_auth(auth):
"""

View File

@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The Endpoint class, which provides plugins with a way to serve their own portion of the API
"""
from mako.template import Template
from openlp.core.common.applocation import AppLocation
class Endpoint(object):
"""
This is an endpoint for the HTTP API
"""
def __init__(self, url_prefix, template_dir=None, static_dir=None, assets_dir=None):
"""
Create an endpoint with a URL prefix
"""
self.url_prefix = url_prefix
self.static_dir = static_dir
self.template_dir = template_dir
if assets_dir:
self.assets_dir = assets_dir
else:
self.assets_dir = None
self.routes = []
def add_url_route(self, url, view_func, method):
"""
Add a url route to the list of routes
"""
self.routes.append((url, view_func, method))
def route(self, rule, method='GET'):
"""
Set up a URL route
"""
def decorator(func):
"""
Make this a decorator
"""
self.add_url_route(rule, func, method)
return func
return decorator
def render_template(self, filename, **kwargs):
"""
Render a mako template
:param str filename: The file name of the template to render.
:return: The rendered template object.
:rtype: mako.template.Template
"""
root_path = AppLocation.get_section_data_path('remotes')
if not self.template_dir:
raise Exception('No template directory specified')
path = root_path / self.template_dir / filename
if self.static_dir:
kwargs['static_url'] = '/{prefix}/static'.format(prefix=self.url_prefix)
kwargs['static_url'] = kwargs['static_url'].replace('//', '/')
kwargs['assets_url'] = '/assets'
return Template(filename=str(path), input_encoding='utf-8').render(**kwargs)

View File

@ -1,64 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
HTTP Error classes
"""
class HttpError(Exception):
"""
A base HTTP error (aka status code)
"""
def __init__(self, status, message):
"""
Initialise the exception
"""
super(HttpError, self).__init__(message)
self.status = status
self.message = message
def to_response(self):
"""
Convert this exception to a Response object
"""
return self.message, self.status
class NotFound(HttpError):
"""
A 404
"""
def __init__(self):
"""
Make this a 404
"""
super(NotFound, self).__init__(404, 'Not Found')
class ServerError(HttpError):
"""
A 500
"""
def __init__(self):
"""
Make this a 500
"""
super(ServerError, self).__init__(500, 'Server Error')

View File

@ -24,16 +24,12 @@ with OpenLP. It uses JSON to communicate with the remotes.
"""
import logging
import time
from secrets import token_hex
from PyQt5 import QtCore, QtWidgets
from waitress.server import create_server
from openlp.core.api.deploy import download_and_check, download_sha256
from openlp.core.api.endpoint.controller import api_controller_endpoint, controller_endpoint
from openlp.core.api.endpoint.core import blank_endpoint, chords_endpoint, main_endpoint, stage_endpoint
from openlp.core.api.endpoint.remote import remote_endpoint
from openlp.core.api.endpoint.service import api_service_endpoint, service_endpoint
from openlp.core.api.http import application, register_endpoint
from openlp.core.api.poll import Poller
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, translate
@ -43,6 +39,7 @@ from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.threading import ThreadWorker, run_thread
from openlp.core.api import app as application
log = logging.getLogger(__name__)
@ -59,6 +56,7 @@ class HttpWorker(ThreadWorker):
port = Settings().value('api/port')
Registry().execute('get_website_version')
try:
application.static_folder = str(AppLocation.get_section_data_path('remotes') / 'static')
self.server = create_server(application, host=address, port=port)
self.server.run()
except OSError:
@ -85,6 +83,7 @@ class HttpServer(RegistryBase, RegistryProperties, LogMixin):
Initialise the http server, and start the http server
"""
super(HttpServer, self).__init__(parent)
Registry().register('authentication_token', token_hex())
if not Registry().get_flag('no_web_server'):
worker = HttpWorker()
run_thread(worker, 'http_server')
@ -96,31 +95,9 @@ class HttpServer(RegistryBase, RegistryProperties, LogMixin):
"""
Register the poll return service and start the servers.
"""
self.initialise()
create_paths(AppLocation.get_section_data_path('remotes'))
self.poller = Poller()
Registry().register('poller', self.poller)
application.initialise()
register_endpoint(controller_endpoint)
register_endpoint(api_controller_endpoint)
register_endpoint(chords_endpoint)
register_endpoint(stage_endpoint)
register_endpoint(blank_endpoint)
register_endpoint(main_endpoint)
register_endpoint(service_endpoint)
register_endpoint(api_service_endpoint)
register_endpoint(remote_endpoint)
@staticmethod
def initialise():
"""
Create the internal file structure if it does not exist
:return:
"""
create_paths(AppLocation.get_section_data_path('remotes') / 'assets',
AppLocation.get_section_data_path('remotes') / 'images',
AppLocation.get_section_data_path('remotes') / 'static',
AppLocation.get_section_data_path('remotes') / 'static' / 'index',
AppLocation.get_section_data_path('remotes') / 'templates')
def first_time(self):
"""

View File

@ -1,185 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
App stuff
"""
import json
import logging
import re
from webob import Request, Response
from webob.static import DirectoryApp
from openlp.core.api.http.errors import HttpError, NotFound, ServerError
from openlp.core.common.applocation import AppLocation
ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)
log = logging.getLogger(__name__)
def _route_to_regex(route):
r"""
Convert a route to a regular expression
For example:
'songs/{song_id}' becomes 'songs/(?P<song_id>[^/]+)'
and
'songs/{song_id:\d+}' becomes 'songs/(?P<song_id>\d+)'
"""
route_regex = ''
last_pos = 0
for match in ARGS_REGEX.finditer(route):
route_regex += re.escape(route[last_pos:match.start()])
arg_name = match.group(1)
expr = match.group(2) or '[^/]+'
expr = '(?P<%s>%s)' % (arg_name, expr)
route_regex += expr
last_pos = match.end()
route_regex += re.escape(route[last_pos:])
route_regex = '^%s$' % route_regex
return route_regex
def _make_response(view_result):
"""
Create a Response object from response
"""
log.debug("in Make response")
if isinstance(view_result, Response):
return view_result
elif isinstance(view_result, tuple):
content_type = 'text/html'
body = view_result[0]
if isinstance(body, dict):
content_type = 'application/json'
body = json.dumps(body)
response = Response(body=body, status=view_result[1],
content_type=content_type, charset='utf8')
if len(view_result) >= 3:
response.headers.update(view_result[2])
return response
elif isinstance(view_result, dict):
return Response(body=json.dumps(view_result), status=200,
content_type='application/json', charset='utf8')
elif isinstance(view_result, str):
return Response(body=view_result, status=200,
content_type='text/html', charset='utf8')
else:
return Response(body=view_result, status=200,
content_type='text/plain', charset='utf8')
def _handle_exception(error):
"""
Handle exceptions
"""
log.exception(error)
if isinstance(error, HttpError):
return error.to_response()
else:
return ServerError().to_response()
class WSGIApplication(object):
"""
This is the core of the API, the WSGI app
"""
def __init__(self, name):
"""
Create the app object
"""
self.name = name
self.static_routes = {}
self.route_map = {}
def initialise(self):
"""
Set up generic roots for the whole application
:return: None
"""
self.add_static_route('/assets(.*)', '')
self.add_static_route('/images(.*)', '')
pass
def add_route(self, route, view_func, method):
"""
Add a route
"""
route_regex = _route_to_regex(route)
if route_regex not in self.route_map:
self.route_map[route_regex] = {}
self.route_map[route_regex][method.upper()] = view_func
def add_static_route(self, route, static_dir):
"""
Add a static directory as a route
"""
if route not in self.static_routes:
static_path = AppLocation.get_section_data_path('remotes') / static_dir
if not static_path.exists():
log.error('Static path "%s" does not exist. Skipping creating static route/', static_path)
return
self.static_routes[route] = DirectoryApp(str(static_path.resolve()))
def dispatch(self, request):
"""
Find the appropriate URL and run the view function
"""
# If not a static route, try the views
for route, views in self.route_map.items():
match = re.match(route, request.path)
if match and request.method.upper() in views:
kwargs = match.groupdict()
log.debug('Found {method} {url}'.format(method=request.method, url=request.path))
view_func = views[request.method.upper()]
return _make_response(view_func(request, **kwargs))
# Look to see if this is a static file request
for route, static_app in self.static_routes.items():
if re.match(route, request.path):
return request.get_response(static_app)
log.error('URL {url} - Not found'.format(url=request.path))
raise NotFound()
def wsgi_app(self, environ, start_response):
"""
The actual WSGI application.
"""
request = Request(environ)
try:
response = self.dispatch(request)
except Exception as e:
response = _make_response(_handle_exception(e))
response.headers.add("cache-control", "no-cache, no-store, must-revalidate")
response.headers.add("pragma", "no-cache")
response.headers.add("expires", "0")
return response(environ, start_response)
def __call__(self, environ, start_response):
"""
Shortcut for wsgi_app.
"""
return self.wsgi_app(environ, start_response)

50
openlp/core/api/lib.py Normal file
View File

@ -0,0 +1,50 @@
import json
from flask import jsonify, Response, request
from functools import wraps
from openlp.core.common.settings import Settings
from openlp.core.common.registry import Registry
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not Settings().value('api/authentication enabled'):
return f(*args, **kwargs)
token = request.headers.get('Authorization', '')
if token == Registry().get('authentication_token'):
return f(*args, **kwargs)
else:
return '', 401
return decorated
def old_success_response():
return jsonify({'results': {'sucess': True}})
def extract_request(json_string, name):
try:
if json_string:
return json.loads(json_string)['request'][name]
except KeyError:
pass
return None
# ----------------------- OLD AUTH SECTION ---------------------
def check_auth(username, password):
return Settings().value('api/user id') == username and \
Settings().value('api/password') == password
def old_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if not Settings().value('api/authentication enabled'):
return f(*args, **kwargs)
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return Response('', 401, {'WWW-Authenticate': 'Basic realm="OpenLP Login Required"'})
else:
return f(*args, **kwargs)
return decorated

14
openlp/core/api/main.py Normal file
View File

@ -0,0 +1,14 @@
from flask import Blueprint, send_from_directory
from openlp.core.common.applocation import AppLocation
main_views = Blueprint('main', __name__)
@main_views.route('/')
def index():
return send_from_directory(str(AppLocation.get_section_data_path('remotes')), 'index.html')
@main_views.route('/<path>')
def assets(path):
return send_from_directory(str(AppLocation.get_section_data_path('remotes')), path)

View File

View File

@ -0,0 +1,9 @@
from openlp.core.api.versions.v1.controller import controller_views
from openlp.core.api.versions.v1.core import core_views
from openlp.core.api.versions.v1.service import service_views
def register_blueprints(app):
app.register_blueprint(controller_views)
app.register_blueprint(core_views)
app.register_blueprint(service_views)

View File

@ -0,0 +1,83 @@
import os
import logging
import urllib.request
from pathlib import Path
from openlp.core.api.lib import old_auth, old_success_response, extract_request
from openlp.core.common.registry import Registry
from openlp.core.common.applocation import AppLocation
from openlp.core.common.settings import Settings
from openlp.core.lib import create_thumb
from openlp.core.lib.serviceitem import ItemCapabilities
from flask import jsonify, request, Blueprint
controller_views = Blueprint('old_controller', __name__)
log = logging.getLogger(__name__)
@controller_views.route('/api/controller/live/text')
def controller_text_api():
live_controller = Registry().get('live_controller')
current_item = live_controller.service_item
result = {'results': {}}
data = []
if current_item:
result['results']['item'] = current_item.unique_identifier
for index, frame in enumerate(current_item.get_frames()):
item = {}
item['tag'] = index + 1
item['selected'] = live_controller.selected_row == index
item['title'] = current_item.title
if current_item.is_text():
if frame['verse']:
item['tag'] = str(frame['verse'])
item['chords_text'] = str(frame.get('chords_text', ''))
item['text'] = frame['text']
item['html'] = current_item.get_rendered_frame(index)
elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'):
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path
if not full_thumbnail_path.exists():
create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False)
Registry().get('image_manager').add_image(str(full_thumbnail_path), frame['title'], None, 88, 88)
item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path))
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
else:
# presentations and other things
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('api/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = str(AppLocation.get_data_path())
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
Registry().get('image_manager').add_image(frame['image'], frame['title'], None, 88, 88)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
data.append(item)
result['results']['slides'] = data
return jsonify(result)
@controller_views.route('/api/controller/live/set')
@old_auth
def controller_set():
event = Registry().get('live_controller').slidecontroller_live_set
try:
id = int(extract_request(request.args.get('data', ''), 'id'))
event.emit([id])
except (KeyError, ValueError):
log.error('Received malformed request to set live controller')
return old_success_response()
@controller_views.route('/api/controller/<controller>/<action>')
@old_auth
def controller_direction(controller, action):
getattr(Registry().get('live_controller'), 'slidecontroller_{controller}_{action}'.
format(controller=controller, action=action)).emit()
return old_success_response()

View File

@ -0,0 +1,40 @@
from openlp.core.api.lib import old_auth, old_success_response
from openlp.core.common.registry import Registry
from openlp.core.lib import image_to_byte
from openlp.core.lib.plugin import PluginStatus, StringContent
from openlp.core.state import State
from flask import jsonify, Blueprint
core_views = Blueprint('old_core', __name__)
@core_views.route('/api/poll')
def poll():
return jsonify(Registry().get('poller').poll())
@core_views.route('/api/display/<display>')
@old_auth
def toggle_display(display):
ALLOWED_ACTIONS = ['hide', 'show', 'blank', 'theme', 'desktop']
display = display.lower()
if display in ALLOWED_ACTIONS:
Registry().get('live_controller').slidecontroller_toggle_display.emit(display)
return old_success_response()
@core_views.route('/api/plugin/search')
def plugin_list():
searches = []
for plugin in State().list_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'])])
return jsonify({'results': {'items': searches}})
@core_views.route('/main/image')
def main_image():
img = 'data:image/png;base64,{}'.format(image_to_byte(Registry().get('live_controller').grab_maindisplay()))
return jsonify({'slide_image': img})

View File

@ -0,0 +1,48 @@
from openlp.core.api.lib import old_auth, old_success_response, extract_request
from flask import jsonify, request, Blueprint
from openlp.core.common.registry import Registry
service_views = Blueprint('old_service', __name__)
@service_views.route('/api/service/list')
def service_items():
live_controller = Registry().get('live_controller')
service_items = []
if live_controller.service_item:
current_unique_identifier = live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in Registry().get('service_manager').service_items:
service_item = item['service_item']
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return jsonify({'results': {'items': service_items}})
@service_views.route('/api/service/set')
@old_auth
def service_set():
event = Registry().get('service_manager').servicemanager_set_item
try:
data = int(extract_request(request.args.get('data', ''), 'id'))
event.emit(data)
except (KeyError, ValueError):
pass
return old_success_response()
@service_views.route('/api/service/direction/<action>')
@old_auth
def service_direction(action):
ALLOWED_ACTIONS = ['next', 'previous']
action = action.lower()
if action in ALLOWED_ACTIONS:
getattr(Registry().get('service_manager'), 'servicemanager_{action}_item'.format(action=action)).emit()
return old_success_response()

View File

@ -0,0 +1,9 @@
from openlp.core.api.versions.v2.controller import controller_views
from openlp.core.api.versions.v2.core import core
from openlp.core.api.versions.v2.service import service_views
def register_blueprints(app):
app.register_blueprint(controller_views, url_prefix='/api/v2/controller/')
app.register_blueprint(core, url_prefix='/api/v2/core/')
app.register_blueprint(service_views, url_prefix='/api/v2/service/')

View File

@ -0,0 +1,84 @@
import os
import urllib.request
from pathlib import Path
from openlp.core.api.lib import login_required
from openlp.core.common.registry import Registry
from openlp.core.common.applocation import AppLocation
from openlp.core.common.settings import Settings
from openlp.core.lib import create_thumb
from openlp.core.lib.serviceitem import ItemCapabilities
from flask import jsonify, request, abort, Blueprint
controller_views = Blueprint('controller', __name__)
@controller_views.route('/live-item')
def controller_text_api():
live_controller = Registry().get('live_controller')
current_item = live_controller.service_item
data = []
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
item['tag'] = index + 1
item['selected'] = live_controller.selected_row == index
item['title'] = current_item.title
if current_item.is_text():
if frame['verse']:
item['tag'] = str(frame['verse'])
item['chords_text'] = str(frame.get('chords_text', ''))
item['text'] = frame['text']
item['html'] = current_item.get_rendered_frame(index)
elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'):
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path
if not full_thumbnail_path.exists():
create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False)
Registry().get('image_manager').add_image(str(full_thumbnail_path), frame['title'], None, 88, 88)
item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path))
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
else:
# presentations and other things
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('api/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = str(AppLocation.get_data_path())
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
Registry().get('image_manager').add_image(frame['image'], frame['title'], None, 88, 88)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
data.append(item)
return jsonify(data)
@controller_views.route('/show', methods=['POST'])
@login_required
def controller_set():
data = request.json
if not data:
abort(400)
num = data.get('id', -1)
Registry().get('live_controller').slidecontroller_live_set.emit([num])
return '', 204
@controller_views.route('/progress', methods=['POST'])
@login_required
def controller_direction():
ALLOWED_ACTIONS = ['next', 'previous']
data = request.json
if not data:
abort(400)
action = data.get('action', '').lower()
if action not in ALLOWED_ACTIONS:
abort(400)
getattr(Registry().get('live_controller'), 'slidecontroller_live_{action}'.
format(action=action)).emit()
return '', 204

View File

@ -0,0 +1,68 @@
from openlp.core.api.lib import login_required
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import image_to_byte
from openlp.core.lib.plugin import PluginStatus, StringContent
from openlp.core.state import State
from flask import jsonify, request, abort, Blueprint
core = Blueprint('core', __name__)
@core.route('/poll')
def poll():
return jsonify(Registry().get('poller').poll())
@core.route('/display', methods=['POST'])
@login_required
def toggle_display():
ALLOWED_ACTIONS = ['hide', 'show', 'blank', 'theme', 'desktop']
data = request.json
if not data:
abort(400)
display = data.get('display', '').lower()
if display not in ALLOWED_ACTIONS:
abort(400)
Registry().get('live_controller').slidecontroller_toggle_display.emit(display)
return '', 204
@core.route('/plugins')
def plugin_list():
searches = []
for plugin in State().list_plugins():
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
searches.append({'key': plugin.name, 'name': str(plugin.text_strings[StringContent.Name]['plural'])})
return jsonify(searches)
@core.route('/system')
def system_information():
data = {}
data['websocket_port'] = Settings().value('api/websocket port')
data['login_required'] = Settings().value('api/authentication enabled')
return jsonify(data)
@core.route('/login', methods=['POST'])
def login():
data = request.json
if not data:
abort(400)
username = data.get('username', '')
password = data.get('password', '')
if username == Settings().value('api/user id') and password == Settings().value('api/password'):
return jsonify({'token': Registry().get('authentication_token')})
else:
return '', 401
@core.route('/live-image')
def main_image():
controller = Registry().get('live_controller')
img = 'data:image/png;base64,{}'.format(image_to_byte(controller.grab_maindisplay()))
return jsonify({'binary_image': img})

View File

@ -0,0 +1,61 @@
from openlp.core.api.lib import login_required
from flask import jsonify, request, abort, Blueprint
from openlp.core.common.registry import Registry
service_views = Blueprint('service', __name__)
@service_views.route('/items')
def service_items():
live_controller = Registry().get('live_controller')
service_items = []
if live_controller.service_item:
current_unique_identifier = live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in Registry().get('service_manager').service_items:
service_item = item['service_item']
if 'ccli_number' in service_item.data_string:
ccli_number = service_item.data_string['ccli_number']
else:
ccli_number = ''
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'ccli_number': str(ccli_number),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return jsonify(service_items)
@service_views.route('/show', methods=['POST'])
@login_required
def service_set():
data = request.json
if not data:
abort(400)
try:
id = int(data.get('id', -1))
except ValueError:
abort(400)
Registry().get('service_manager').set_item(id)
return '', 204
@service_views.route('/progress', methods=['POST'])
@login_required
def service_direction():
ALLOWED_ACTIONS = ['next', 'previous']
data = request.json
if not data:
abort(400)
action = data.get('action', '').lower()
if action not in ALLOWED_ACTIONS:
abort(400)
getattr(Registry().get('service_manager'), 'servicemanager_{action}_item'.format(action=action)).emit()
return '', 204

View File

@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.api.endpoint` module contains various API endpoints
"""
import logging
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
media_endpoint = Endpoint('media')
@media_endpoint.route('play')
@requires_auth
def media_play(request):
"""
Handles requests for playing media
:param request: The http request object.
"""
media = Registry().get('media_controller')
live = Registry().get('live_controller')
status = media.media_play(live, False)
return {'results': {'success': status}}
@media_endpoint.route('pause')
@requires_auth
def media_pause(request):
"""
Handles requests for pausing media
:param request: The http request object.
"""
media = Registry().get('media_controller')
live = Registry().get('live_controller')
status = media.media_pause(live)
return {'results': {'success': status}}
@media_endpoint.route('stop')
@requires_auth
def media_stop(request):
"""
Handles requests for stopping
:param request: The http request object.
"""
event = getattr(Registry().get('live_controller'), 'mediacontroller_live_stop')
event.emit()
return {'results': {'success': True}}

View File

@ -33,7 +33,6 @@ from subprocess import check_output
from PyQt5 import QtCore
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common import is_linux, is_macosx
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin, RegistryProperties
@ -43,7 +42,7 @@ from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import DisplayControllerType
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, VIDEO_EXT, AUDIO_EXT
from openlp.core.ui.media.endpoint import media_endpoint
from openlp.core.ui.media.remote import register_views
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
@ -81,7 +80,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
Registry().register_function('songs_hide', self.media_hide)
Registry().register_function('songs_blank', self.media_blank)
Registry().register_function('songs_unblank', self.media_unblank)
register_endpoint(media_endpoint)
register_views()
def bootstrap_initialise(self):
"""

View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 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:`~openlp.core.api.endpoint` module contains various API endpoints
"""
import logging
from flask import abort, jsonify, Blueprint
from openlp.core.api import app
from openlp.core.api.lib import login_required, old_auth
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_media = Blueprint('v1-media-controller', __name__)
v2_media = Blueprint('v2-media-controller', __name__)
@v2_media.route('/play', methods=['POST'])
@login_required
def media_play():
media = Registry().get('media_controller')
live = Registry().get('live_controller')
try:
status = media.media_play(live, True)
except Exception:
# The current item probably isn't a media item
abort(400)
if status:
return '', 204
abort(400)
@v2_media.route('/pause', methods=['POST'])
@login_required
def media_pause():
media = Registry().get('media_controller')
live = Registry().get('live_controller')
media.media_pause(live)
return '', 204
@v2_media.route('/stop', methods=['POST'])
@login_required
def media_stop():
Registry().get('live_controller').mediacontroller_live_stop.emit()
return '', 204
# -------------- DEPRECATED ------------------------
@v1_media.route('/play')
@old_auth
def v1_media_play():
media = Registry().get('media_controller')
live = Registry().get('live_controller')
status = media.media_play(live, False)
return jsonify({'success': status})
@v1_media.route('/pause')
@old_auth
def v1_media_pause():
media = Registry().get('media_controller')
live = Registry().get('live_controller')
status = media.media_pause(live)
return jsonify({'success': status})
@v1_media.route('/stop')
@old_auth
def v1_media_stop():
Registry().get('live_controller').mediacontroller_live_stop.emit()
return ''
# -------------- END OF DEPRECATED ------------------------
def register_views():
app.register_blueprint(v2_media, url_prefix='/api/v2/media/')
app.register_blueprint(v1_media, url_prefix='/api/media/')

View File

@ -1238,8 +1238,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
win_image.setDevicePixelRatio(self.preview_display.devicePixelRatio())
# self.slide_preview.setPixmap(win_image)
self.slide_image = win_image
base64_image = image_to_byte(win_image, True)
base64_image = image_to_byte(win_image)
self.preview_display.set_single_image_data('#000', base64_image)
return self.slide_image
def on_slide_selected_next_action(self, checked):
"""

View File

@ -22,7 +22,6 @@
import logging
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common.actions import ActionList
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.settings import Settings
@ -31,7 +30,7 @@ from openlp.core.lib.plugin import Plugin, StringContent
from openlp.core.lib.theme import VerticalType
from openlp.core.lib.ui import create_action
from openlp.core.ui.icons import UiIcons
from openlp.plugins.alerts.endpoint import api_alerts_endpoint, alerts_endpoint
from openlp.plugins.alerts.remote import register_views
from openlp.plugins.alerts.forms.alertform import AlertForm
from openlp.plugins.alerts.lib.alertsmanager import AlertsManager
from openlp.plugins.alerts.lib.alertstab import AlertsTab
@ -129,8 +128,7 @@ class AlertsPlugin(Plugin):
AlertsManager(self)
self.manager = Manager('alerts', init_schema)
self.alert_form = AlertForm(self)
register_endpoint(alerts_endpoint)
register_endpoint(api_alerts_endpoint)
register_views()
State().add_service(self.name, self.weight, is_plugin=True)
State().update_pre_conditions(self.name, self.check_pre_conditions())

View File

@ -1,58 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import json
import logging
import urllib
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.common.registry import Registry
from openlp.core.lib.plugin import PluginStatus
log = logging.getLogger(__name__)
alerts_endpoint = Endpoint('alert')
api_alerts_endpoint = Endpoint('api')
@alerts_endpoint.route('')
@api_alerts_endpoint.route('alert')
@requires_auth
def alert(request):
"""
Handles requests for setting service items in the service manager
:param request: The http request object.
"""
plugin = Registry().get('plugin_manager').get_plugin_by_name("alerts")
if plugin.status == PluginStatus.Active:
try:
json_data = request.GET.get('data')
text = json.loads(json_data)['request']['text']
except KeyError:
log.error("Endpoint alerts request text not found")
text = urllib.parse.unquote(text)
Registry().get('alerts_manager').alerts_text.emit([text])
success = True
else:
success = False
return {'results': {'success': success}}

View File

@ -0,0 +1,39 @@
from flask import Blueprint, request, abort
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_success_response, old_auth
from openlp.core.common.registry import Registry
from openlp.core.lib.plugin import PluginStatus
v1_views = Blueprint('v1-alert-plugin', __name__)
v2_views = Blueprint('v2-alert-plugin', __name__)
@v2_views.route('', methods=['POST'])
@login_required
def alert():
data = request.json
if not data:
abort(400)
alert = data.get('text', '')
if alert:
if Registry().get('plugin_manager').get_plugin_by_name('alerts').status == PluginStatus.Active:
Registry().get('alerts_manager').alerts_text.emit([alert])
return '', 204
abort(400)
@v1_views.route('')
@old_auth
def old_alert():
alert = extract_request(request.args.get('data', ''), 'text')
if alert:
if Registry().get('plugin_manager').get_plugin_by_name('alerts').status == PluginStatus.Active:
Registry().get('alerts_manager').alerts_text.emit([alert])
return old_success_response()
def register_views():
app.register_blueprint(v2_views, url_prefix='/api/v2/plugins/alerts')
app.register_blueprint(v1_views, url_prefix='/api/alert')

View File

@ -22,13 +22,12 @@
import logging
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common.actions import ActionList
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.ui.icons import UiIcons
from openlp.core.lib.plugin import Plugin, StringContent
from openlp.core.lib.ui import create_action
from openlp.plugins.bibles.endpoint import api_bibles_endpoint, bibles_endpoint
from openlp.plugins.bibles.remote import register_views
from openlp.plugins.bibles.lib.biblestab import BiblesTab
from openlp.plugins.bibles.lib.manager import BibleManager
from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem
@ -49,8 +48,7 @@ class BiblePlugin(Plugin):
self.icon_path = UiIcons().bible
self.icon = UiIcons().bible
self.manager = BibleManager(self)
register_endpoint(bibles_endpoint)
register_endpoint(api_bibles_endpoint)
register_views()
State().add_service('bible', self.weight, is_plugin=True)
State().update_pre_conditions('bible', self.check_pre_conditions())

View File

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.pluginhelpers import live, search, service
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
log = logging.getLogger(__name__)
bibles_endpoint = Endpoint('bibles')
api_bibles_endpoint = Endpoint('api')
@bibles_endpoint.route('search')
def bibles_search(request):
"""
Handles requests for searching the bibles plugin
:param request: The http request object.
"""
return search(request, 'bibles', log)
@bibles_endpoint.route('live')
@requires_auth
def bibles_live(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'bibles', log)
@bibles_endpoint.route('add')
@requires_auth
def bibles_service(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
return service(request, 'bibles', log)
@api_bibles_endpoint.route('bibles/search')
def bibles_search_api(request):
"""
Handles requests for searching the bibles plugin
:param request: The http request object.
"""
return search(request, 'bibles', log)
@api_bibles_endpoint.route('bibles/live')
@requires_auth
def bibles_live_api(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'bibles', log)
@api_bibles_endpoint.route('bibles/add')
@requires_auth
def bibles_service_api(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
try:
return search(request, 'bibles', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from flask import request, jsonify, Blueprint
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_auth
from openlp.core.lib.plugin import PluginStatus
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_views = Blueprint('v1-bibles-plugin', __name__)
v2_views = Blueprint('v2-bibles-plugin', __name__)
def search(text):
plugin = Registry().get('plugin_manager').get_plugin_by_name('bibles')
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
return results
return None
def live(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('bibles')
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.bibles_go_live.emit([id, True])
def add(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('bibles')
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.bibles_add_to_service.emit([item_id, True])
@v2_views.route('/search')
@login_required
def search_bible():
text = request.args.get('text', '')
result = search(text)
if result:
return jsonify(result)
return '', 400
@v2_views.route('/live', methods=['POST'])
@login_required
def send_live():
id = request.json.get('id', -1)
live(id)
return '', 204
@v2_views.route('/add', methods=['POST'])
@login_required
def add_to_service():
id = request.json.get('id', -1)
add(id)
return '', 204
# ---------------- DEPRECATED REMOVE AFTER RELEASE --------------
@v1_views.route('/search')
@old_auth
def old_search_bible():
text = extract_request(request.args.get('data', ''), 'text')
return jsonify({'results': {'items': search(text)}})
@v1_views.route('/live')
@old_auth
def old_send_live():
id = extract_request(request.args.get('data', ''), 'id')
live(id)
return '', 204
@v1_views.route('/add')
@old_auth
def old_add_to_service():
id = extract_request(request.args.get('data', ''), 'id')
add(id)
return '', 204
# ------------ END DEPRECATED ----------------------------------
def register_views():
app.register_blueprint(v2_views, url_prefix='/api/v2/plugins/bibles')
app.register_blueprint(v1_views, url_prefix='/api/bibles')

View File

@ -26,13 +26,12 @@ for the Custom Slides plugin.
import logging
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common.i18n import translate
from openlp.core.lib import build_icon
from openlp.core.lib.db import Manager
from openlp.core.lib.plugin import Plugin, StringContent
from openlp.core.ui.icons import UiIcons
from openlp.plugins.custom.endpoint import api_custom_endpoint, custom_endpoint
from openlp.plugins.custom.remote import register_views
from openlp.plugins.custom.lib.db import CustomSlide, init_schema
from openlp.plugins.custom.lib.mediaitem import CustomMediaItem
from openlp.plugins.custom.lib.customtab import CustomTab
@ -56,8 +55,7 @@ class CustomPlugin(Plugin):
self.db_manager = Manager('custom', init_schema)
self.icon_path = UiIcons().clone
self.icon = build_icon(self.icon_path)
register_endpoint(custom_endpoint)
register_endpoint(api_custom_endpoint)
register_views()
State().add_service(self.name, self.weight, is_plugin=True)
State().update_pre_conditions(self.name, self.check_pre_conditions())

View File

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.pluginhelpers import live, search, service
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
log = logging.getLogger(__name__)
custom_endpoint = Endpoint('custom')
api_custom_endpoint = Endpoint('api')
@custom_endpoint.route('search')
def custom_search(request):
"""
Handles requests for searching the custom plugin
:param request: The http request object.
"""
return search(request, 'custom', log)
@custom_endpoint.route('live')
@requires_auth
def custom_live(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'custom', log)
@custom_endpoint.route('add')
@requires_auth
def custom_service(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
return service(request, 'custom', log)
@api_custom_endpoint.route('custom/search')
def custom_search_api(request):
"""
Handles requests for searching the custom plugin
:param request: The http request object.
"""
return search(request, 'custom', log)
@api_custom_endpoint.route('custom/live')
@requires_auth
def custom_live_api(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'custom', log)
@api_custom_endpoint.route('custom/add')
@requires_auth
def custom_service_api(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
try:
return search(request, 'custom', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from flask import abort, request, Blueprint, jsonify
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_auth
from openlp.core.lib.plugin import PluginStatus
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_custom = Blueprint('v1-custom-plugin', __name__)
v2_custom = Blueprint('v2-custom-plugin', __name__)
def search(text):
plugin = Registry().get('plugin_manager').get_plugin_by_name('custom')
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
return results
return None
def live(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('custom')
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.custom_go_live.emit([id, True])
def add(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('custom')
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.custom_add_to_service.emit([item_id, True])
@v2_custom.route('/search')
@login_required
def search_view():
text = request.args.get('text', '')
result = search(text)
if result:
return jsonify(result)
return '', 400
@v2_custom.route('/add', methods=['POST'])
@login_required
def add_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
add(id)
return '', 204
@v2_custom.route('/live', methods=['POST'])
@login_required
def live_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
live(id)
return '', 204
# ----------------- DEPRECATED --------------
@v1_custom.route('/search')
@old_auth
def old_search():
text = extract_request(request.args.get('data', ''), 'text')
return jsonify({'results': {'items': search(text)}})
@v1_custom.route('/add')
@old_auth
def old_add():
id = extract_request(request.args.get('data', ''), 'id')
add(id)
return '', 204
@v1_custom.route('/live')
@old_auth
def old_live():
id = extract_request(request.args.get('data', ''), 'id')
live(id)
return '', 204
# ---------------- END DEPRECATED ----------------
def register_views():
app.register_blueprint(v2_custom, url_prefix='/api/v2/plugins/custom/')
app.register_blueprint(v1_custom, url_prefix='/api/custom/')

View File

@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.pluginhelpers import display_thumbnails, live, search, service
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
log = logging.getLogger(__name__)
images_endpoint = Endpoint('images')
api_images_endpoint = Endpoint('api')
# images/thumbnails/320x240/1.jpg
@images_endpoint.route('thumbnails/{dimensions}/{file_name}')
def images_thumbnails(request, dimensions, file_name):
"""
Return an image to a web page based on a URL
:param request: Request object
:param dimensions: the image size eg 88x88
:param file_name: the individual image name
:return:
"""
return display_thumbnails(request, 'images', log, dimensions, file_name)
@images_endpoint.route('search')
def images_search(request):
"""
Handles requests for searching the images plugin
:param request: The http request object.
"""
return search(request, 'images', log)
@images_endpoint.route('live')
@requires_auth
def images_live(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'images', log)
@images_endpoint.route('add')
@requires_auth
def images_service(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
return service(request, 'images', log)
@api_images_endpoint.route('images/search')
def images_search_api(request):
"""
Handles requests for searching the images plugin
:param request: The http request object.
"""
return search(request, 'images', log)
@api_images_endpoint.route('images/live')
@requires_auth
def images_live_api(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'images', log)
@api_images_endpoint.route('images/add')
@requires_auth
def images_service_api(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
try:
return search(request, 'images', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -24,14 +24,13 @@ import logging
from PyQt5 import QtGui
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings
from openlp.core.lib import ImageSource, build_icon
from openlp.core.lib.db import Manager
from openlp.core.lib.plugin import Plugin, StringContent
from openlp.core.ui.icons import UiIcons
from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
from openlp.plugins.images.remote import register_views
from openlp.plugins.images.lib import upgrade
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
from openlp.plugins.images.lib.imagetab import ImageTab
@ -50,8 +49,7 @@ class ImagePlugin(Plugin):
self.weight = -7
self.icon_path = UiIcons().picture
self.icon = build_icon(self.icon_path)
register_endpoint(images_endpoint)
register_endpoint(api_images_endpoint)
register_views()
State().add_service('image', self.weight, is_plugin=True)
State().update_pre_conditions('image', self.check_pre_conditions())

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from flask import abort, request, Blueprint, jsonify
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_auth
from openlp.core.lib.plugin import PluginStatus
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_images = Blueprint('v1-images-plugin', __name__)
v2_images = Blueprint('v2-images-plugin', __name__)
def search(text):
plugin = Registry().get('plugin_manager').get_plugin_by_name('images')
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
return results
return None
def live(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('images')
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.images_go_live.emit([id, True])
def add(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('images')
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.images_add_to_service.emit([item_id, True])
@v2_images.route('/search')
@login_required
def search_view():
text = request.args.get('text', '')
result = search(text)
if result:
return jsonify(result)
return '', 400
@v2_images.route('/add', methods=['POST'])
@login_required
def add_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
add(id)
return '', 204
@v2_images.route('/live', methods=['POST'])
@login_required
def live_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
live(id)
return '', 204
# ----------------- DEPRECATED --------------
@v1_images.route('/search')
@old_auth
def old_search():
text = extract_request(request.args.get('data', ''), 'text')
return jsonify({'results': {'items': search(text)}})
@v1_images.route('/add')
@old_auth
def old_add():
id = extract_request(request.args.get('data', ''), 'id')
add(id)
return '', 204
@v1_images.route('/live')
@old_auth
def old_live():
id = extract_request(request.args.get('data', ''), 'id')
live(id)
return '', 204
# ---------------- END DEPRECATED ----------------
def register_views():
app.register_blueprint(v2_images, url_prefix='/api/v2/plugins/images/')
app.register_blueprint(v1_images, url_prefix='/api/images/')

View File

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.pluginhelpers import live, search, service
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
log = logging.getLogger(__name__)
media_endpoint = Endpoint('media')
api_media_endpoint = Endpoint('api')
@media_endpoint.route('search')
def media_search(request):
"""
Handles requests for searching the media plugin
:param request: The http request object.
"""
return search(request, 'media', log)
@media_endpoint.route('live')
@requires_auth
def media_live(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'media', log)
@media_endpoint.route('add')
@requires_auth
def media_service(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
return service(request, 'media', log)
@api_media_endpoint.route('media/search')
def media_search_api(request):
"""
Handles requests for searching the media plugin
:param request: The http request object.
"""
return search(request, 'media', log)
@api_media_endpoint.route('media/live')
@requires_auth
def media_live_api(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'media', log)
@api_media_endpoint.route('media/add')
@requires_auth
def media_service_api(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
try:
return search(request, 'media', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -24,12 +24,11 @@ The Media plugin
import logging
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common.i18n import translate
from openlp.core.ui.icons import UiIcons
from openlp.core.lib import build_icon
from openlp.core.lib.plugin import Plugin, StringContent
from openlp.plugins.media.endpoint import api_media_endpoint, media_endpoint
from openlp.plugins.media.remote import register_views
from openlp.plugins.media.lib.mediaitem import MediaMediaItem
@ -51,8 +50,7 @@ class MediaPlugin(Plugin):
self.icon = build_icon(self.icon_path)
# passed with drag and drop messages
self.dnd_id = 'Media'
register_endpoint(media_endpoint)
register_endpoint(api_media_endpoint)
register_views()
State().add_service(self.name, self.weight, requires='mediacontroller', is_plugin=True)
State().update_pre_conditions(self.name, self.check_pre_conditions())

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from flask import abort, request, Blueprint, jsonify
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_auth
from openlp.core.lib.plugin import PluginStatus
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_media = Blueprint('v1-media-plugin', __name__)
v2_media = Blueprint('v2-media-plugin', __name__)
def search(text):
plugin = Registry().get('plugin_manager').get_plugin_by_name('media')
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
return results
return None
def live(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('media')
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.media_go_live.emit([id, True])
def add(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('media')
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.media_add_to_service.emit([item_id, True])
@v2_media.route('/search')
@login_required
def search_view():
text = request.args.get('text', '')
result = search(text)
if result:
return jsonify(result)
return '', 400
@v2_media.route('/add', methods=['POST'])
@login_required
def add_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
add(id)
return '', 204
@v2_media.route('/live', methods=['POST'])
@login_required
def live_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
live(id)
return '', 204
# ----------------- DEPRECATED --------------
@v1_media.route('/search')
@old_auth
def old_search():
text = extract_request(request.args.get('data', ''), 'text')
return jsonify({'results': {'items': search(text)}})
@v1_media.route('/add')
@old_auth
def old_add():
id = extract_request(request.args.get('data', ''), 'id')
add(id)
return '', 204
@v1_media.route('/live')
@old_auth
def old_live():
id = extract_request(request.args.get('data', ''), 'id')
live(id)
return '', 204
# ---------------- END DEPRECATED ----------------
def register_views():
app.register_blueprint(v2_media, url_prefix='/api/v2/plugins/media/')
app.register_blueprint(v1_media, url_prefix='/api/media/')

View File

@ -1,113 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.pluginhelpers import display_thumbnails, live, search, service
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
log = logging.getLogger(__name__)
presentations_endpoint = Endpoint('presentations')
api_presentations_endpoint = Endpoint('api')
# /presentations/thumbnails88x88/PA%20Rota.pdf/slide5.png
@presentations_endpoint.route('thumbnails/{dimensions}/{file_name}/{slide}')
def presentations_thumbnails(request, dimensions, file_name, slide):
"""
Return a presentation to a web page based on a URL
:param request: Request object
:param dimensions: the image size eg 88x88
:param file_name: the file name of the image
:param slide: the individual image name
:return:
"""
return display_thumbnails(request, 'presentations', log, dimensions, file_name, slide)
@presentations_endpoint.route('search')
def presentations_search(request):
"""
Handles requests for searching the presentations plugin
:param request: The http request object.
"""
return search(request, 'presentations', log)
@presentations_endpoint.route('live')
@requires_auth
def presentations_live(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'presentations', log)
@presentations_endpoint.route('add')
@requires_auth
def presentations_service(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
return service(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/search')
def presentations_search_api(request):
"""
Handles requests for searching the presentations plugin
:param request: The http request object.
"""
return search(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/live')
@requires_auth
def presentations_live_api(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/add')
@requires_auth
def presentations_service_api(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
try:
return search(request, 'presentations', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -25,7 +25,7 @@ presentations from a variety of document formats.
import logging
import os
from openlp.core.api.http import register_endpoint
from openlp.core.common import extension_loader
from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings
@ -33,7 +33,7 @@ from openlp.core.lib import build_icon
from openlp.core.lib.plugin import Plugin, StringContent
from openlp.core.state import State
from openlp.core.ui.icons import UiIcons
from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint
from openlp.plugins.presentations.remote import register_views
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
from openlp.plugins.presentations.lib.presentationtab import PresentationTab
@ -59,8 +59,7 @@ class PresentationPlugin(Plugin):
self.weight = -8
self.icon_path = UiIcons().presentation
self.icon = build_icon(self.icon_path)
register_endpoint(presentations_endpoint)
register_endpoint(api_presentations_endpoint)
register_views()
State().add_service('presentation', self.weight, is_plugin=True)
State().update_pre_conditions('presentation', self.check_pre_conditions())

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from flask import abort, request, Blueprint, jsonify
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_auth
from openlp.core.lib.plugin import PluginStatus
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_presentations = Blueprint('v1-presentations-plugin', __name__)
v2_presentations = Blueprint('v2-presentations-plugin', __name__)
def search(text):
plugin = Registry().get('plugin_manager').get_plugin_by_name('presentations')
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
return results
return None
def live(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('presentations')
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.presentations_go_live.emit([id, True])
def add(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('presentations')
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.presentations_add_to_service.emit([item_id, True])
@v2_presentations.route('/search')
@login_required
def search_view():
text = request.args.get('text', '')
result = search(text)
if result:
return jsonify(result)
return '', 400
@v2_presentations.route('/add', methods=['POST'])
@login_required
def add_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
add(id)
return '', 204
@v2_presentations.route('/live', methods=['POST'])
@login_required
def live_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
live(id)
return '', 204
# ----------------- DEPRECATED --------------
@v1_presentations.route('/search')
@old_auth
def old_search():
text = extract_request(request.args.get('data', ''), 'text')
return jsonify({'results': {'items': search(text)}})
@v1_presentations.route('/add')
@old_auth
def old_add():
id = extract_request(request.args.get('data', ''), 'id')
add(id)
return '', 204
@v1_presentations.route('/live')
@old_auth
def old_live():
id = extract_request(request.args.get('data', ''), 'id')
live(id)
return '', 204
# ---------------- END DEPRECATED ----------------
def register_views():
app.register_blueprint(v2_presentations, url_prefix='/api/v2/plugins/presentations/')
app.register_blueprint(v1_presentations, url_prefix='/api/presentations/')

View File

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from openlp.core.api.endpoint.pluginhelpers import live, search, service
from openlp.core.api.http import requires_auth
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
log = logging.getLogger(__name__)
songs_endpoint = Endpoint('songs')
api_songs_endpoint = Endpoint('api')
@songs_endpoint.route('search')
def songs_search(request):
"""
Handles requests for searching the songs plugin
:param request: The http request object.
"""
return search(request, 'songs', log)
@songs_endpoint.route('live')
@requires_auth
def songs_live(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'songs', log)
@songs_endpoint.route('add')
@requires_auth
def songs_service(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
return service(request, 'songs', log)
@api_songs_endpoint.route('songs/search')
def songs_search_api(request):
"""
Handles requests for searching the songs plugin
:param request: The http request object.
"""
return search(request, 'songs', log)
@api_songs_endpoint.route('songs/live')
@requires_auth
def songs_live_api(request):
"""
Handles requests for making a song live
:param request: The http request object.
"""
return live(request, 'songs', log)
@api_songs_endpoint.route('songs/add')
@requires_auth
def songs_service_api(request):
"""
Handles requests for adding a song to the service
:param request: The http request object.
"""
try:
return service(request, 'songs', log)
except NotFound:
return {'results': {'items': []}}

View File

@ -613,7 +613,11 @@ class SongMediaItem(MediaManagerItem):
service_item.add_from_text(split_verse, verse_def)
service_item.title = song.title
author_list = self.generate_footer(service_item, song)
service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)}
service_item.data_string = {
'title': song.search_title,
'authors': ', '.join(author_list),
'ccli_number': song.ccli_number
}
service_item.xml_version = self.open_lyrics.song_to_xml(song)
# Add the audio file to the service item.
if song.media_files:

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from flask import abort, request, Blueprint, jsonify
from openlp.core.api import app
from openlp.core.api.lib import login_required, extract_request, old_auth
from openlp.core.lib.plugin import PluginStatus
from openlp.core.common.registry import Registry
log = logging.getLogger(__name__)
v1_songs = Blueprint('v1-songs-plugin', __name__)
v2_songs = Blueprint('v2-songs-plugin', __name__)
def search(text):
plugin = Registry().get('plugin_manager').get_plugin_by_name('songs')
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
return results
return None
def live(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('songs')
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.songs_go_live.emit([id, True])
def add(id):
plugin = Registry().get('plugin_manager').get_plugin_by_name('songs')
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.songs_add_to_service.emit([item_id, True])
@v2_songs.route('/search')
@login_required
def search_view():
text = request.args.get('text', '')
result = search(text)
if result:
return jsonify(result)
return '', 400
@v2_songs.route('/add', methods=['POST'])
@login_required
def add_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
add(id)
return '', 204
@v2_songs.route('/live', methods=['POST'])
@login_required
def live_view():
data = request.json
if not data:
abort(400)
id = data.get('id', -1)
live(id)
return '', 204
# ----------------- DEPRECATED --------------
@v1_songs.route('/search')
@old_auth
def old_search():
text = extract_request(request.args.get('data', ''), 'text')
return jsonify({'results': {'items': search(text)}})
@v1_songs.route('/add')
@old_auth
def old_add():
id = extract_request(request.args.get('data', ''), 'id')
add(id)
return '', 204
@v1_songs.route('/live')
@old_auth
def old_live():
id = extract_request(request.args.get('data', ''), 'id')
live(id)
return '', 204
# ---------------- END DEPRECATED ----------------
def register_views():
app.register_blueprint(v2_songs, url_prefix='/api/v2/plugins/songs/')
app.register_blueprint(v1_songs, url_prefix='/api/songs/')

View File

@ -31,7 +31,6 @@ from tempfile import gettempdir
from PyQt5 import QtCore, QtWidgets
from openlp.core.state import State
from openlp.core.api.http import register_endpoint
from openlp.core.common.actions import ActionList
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry
@ -41,7 +40,7 @@ from openlp.core.lib.plugin import Plugin, StringContent
from openlp.core.lib.ui import create_action
from openlp.core.ui.icons import UiIcons
from openlp.plugins.songs import reporting
from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint
from openlp.plugins.songs.remote import register_views
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
from openlp.plugins.songs.forms.songselectform import SongSelectForm
from openlp.plugins.songs.lib import clean_song, upgrade
@ -128,8 +127,7 @@ class SongsPlugin(Plugin):
self.icon = build_icon(self.icon_path)
self.songselect_form = None
self.settings.extend_default_settings(song_footer)
register_endpoint(songs_endpoint)
register_endpoint(api_songs_endpoint)
register_views()
State().add_service(self.name, self.weight, is_plugin=True)
State().update_pre_conditions(self.name, self.check_pre_conditions())
if not self.settings.value('songs/last import type'):

View File

@ -83,6 +83,8 @@ MODULES = [
'alembic',
'lxml',
'chardet',
'flask',
'flask_cors',
'bs4',
'mako',
'websockets',

View File

@ -102,6 +102,8 @@ using a computer and a data projector.""",
'chardet',
'dbus-python; platform_system=="Linux"',
'distro; platform_system=="Linux"',
'flask',
'flask-cors',
'lxml',
'Mako',
'pymediainfo >= 2.2',

View File

@ -24,11 +24,12 @@ from unittest.mock import MagicMock, patch
from PyQt5 import QtCore
from openlp.core.api import app as flask_app
from openlp.core.state import State
# Mock QtWebEngineWidgets
# sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock()
from openlp.core.api.endpoint.controller import controller_direction, controller_text
from openlp.core.common.registry import Registry
from openlp.core.display.screens import ScreenList
from openlp.core.lib.serviceitem import ServiceItem
@ -75,6 +76,8 @@ class TestController(TestCase):
self.mocked_renderer.format_slide = self.mocked_slide_formater
Registry().register('live_controller', self.mocked_live_controller)
Registry().register('renderer', self.mocked_renderer)
flask_app.config['TESTING'] = True
self.client = flask_app.test_client()
Registry().register('settings', MagicMock(**{'value.return_value': 'english'}))
def test_controller_text_empty(self):
@ -88,7 +91,7 @@ class TestController(TestCase):
self.mocked_live_controller.service_item = mocked_service_item
# WHEN: I trigger the method
ret = controller_text(MagicMock())
ret = self.client.get('/api/controller/live/text').get_json()
# THEN: I get a basic set of results
assert ret['results']['item'] == 'mock-service-item'
@ -108,7 +111,7 @@ class TestController(TestCase):
self.mocked_live_controller.service_item.set_from_service(line)
self.mocked_live_controller.service_item._create_slides()
# WHEN: I trigger the method
ret = controller_text("SomeText")
ret = self.client.get('/api/controller/live/text').get_json()
# THEN: I get a basic set of results
results = ret['results']
@ -125,8 +128,7 @@ class TestController(TestCase):
self.mocked_live_controller.service_item = MagicMock()
# WHEN: I trigger the method
controller_direction(None, 'live', 'next')
self.client.get('/api/controller/live/next')
# THEN: The correct method is called
mocked_emit.assert_called_once_with()
@ -140,7 +142,6 @@ class TestController(TestCase):
self.mocked_live_controller.service_item = MagicMock()
# WHEN: I trigger the method
controller_direction(None, 'live', 'previous')
self.client.get('/api/controller/live/previous')
# THEN: The correct method is called
mocked_emit.assert_called_once_with()

View File

@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Functional tests to test the remote index
"""
from unittest.mock import MagicMock, patch
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
from openlp.core.api.endpoint.remote import index
@patch('openlp.core.api.endpoint.remote.remote_endpoint')
def test_index(mocked_endpoint):
"""
Test the index method of the remote
"""
# GIVEN: A mocked Endpoint
mocked_endpoint.render_template.return_value = 'test template'
# WHEN: index is called
result = index(MagicMock(), 'index')
# THEN: The result should be "test template" and the right methods should have been called
mocked_endpoint.render_template.assert_called_once_with('index.mako', **TRANSLATED_STRINGS)
assert result == 'test template'

View File

@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Functional tests to test the API Error Class.
"""
from openlp.core.api.http.errors import HttpError, NotFound, ServerError
def test_http_error():
"""
Test the HTTPError class
"""
# GIVEN: An HTTPError class
# WHEN: An instance is created
error = HttpError(400, 'Access Denied')
# THEN: The to_response() method should return the correct information
assert error.to_response() == ('Access Denied', 400), 'to_response() should have returned the correct info'
def test_not_found():
"""
Test the Not Found error displays the correct information
"""
# GIVEN: A NotFound class
# WHEN: An instance is created
error = NotFound()
# THEN: The to_response() method should return the correct information
assert error.to_response() == ('Not Found', 404), 'to_response() should have returned the correct info'
def test_server_error():
"""
Test the server error displays the correct information
"""
# GIVEN: A ServerError class
# WHEN: An instance of the class is created
error = ServerError()
# THEN: The to_response() method should return the correct information
assert error.to_response() == ('Server Error', 500), 'to_response() should have returned the correct info'

View File

@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Functional test the routing code.
"""
import os
from unittest import TestCase
from unittest.mock import MagicMock
from openlp.core.api.http import register_endpoint, application
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http.errors import NotFound
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
test_endpoint = Endpoint('test', template_dir=ROOT_DIR, static_dir=ROOT_DIR)
class TestRouting(TestCase):
"""
Test the HTTP routing
"""
def setUp(self):
"""
Convert the application to a test application
:return:
"""
for route, views in application.route_map.items():
application.route_map[route]['GET'] = MagicMock()
def test_routing(self):
"""
Test the Routing in the new application via dispatch
:return:
"""
# GIVE: I try to request and
# WHEN: when the URL is not correct and dispatch called
rqst = MagicMock()
rqst.path = '/test/api'
rqst.method = 'GET'
with self.assertRaises(NotFound) as context:
application.dispatch(rqst)
# THEN: the not found returned
assert context.exception.args[0] == 'Not Found', 'URL not found in dispatcher'
# WHEN: when the URL is correct and dispatch called
rqst = MagicMock()
rqst.path = '/test/image'
rqst.method = 'GET'
application.dispatch(rqst)
# THEN: the not found id called
route_key = next(iter(application.route_map))
assert '/image' in route_key
assert 1 == application.route_map[route_key]['GET'].call_count, \
'main_index function should have been called'
@test_endpoint.route('image')
def image(request):
pass
@test_endpoint.route('')
def index(request):
pass
register_endpoint(test_endpoint)

View File

@ -0,0 +1,9 @@
import pytest
from openlp.core.api import app as flask_app
@pytest.fixture(scope='module')
def flask_client():
flask_app.config['TESTING'] = True
return flask_app.test_client()

View File

@ -0,0 +1,57 @@
import pytest
from unittest.mock import MagicMock
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
def test_retrieve_live_item(flask_client):
pytest.skip()
res = flask_client.get('/api/v2/controller/live-item').get_json()
assert len(res) == 0
def test_controller_set_requires_login(flask_client):
pytest.skip('Need to figure out how to patch settings for one test only')
# Settings().setValue('api/authentication enabled', True)
res = flask_client.post('/api/v2/controller/show', json=dict())
Settings().setValue('api/authentication enabled', False)
assert res.status_code == 401
def test_controller_set_does_not_accept_get(flask_client):
res = flask_client.get('/api/v2/controller/show')
assert res.status_code == 405
def test_controller_set_calls_live_controller(flask_client):
fake_live_controller = MagicMock()
Registry.create().register('live_controller', fake_live_controller)
res = flask_client.post('/api/v2/controller/show', json=dict(id=400))
assert res.status_code == 204
fake_live_controller.slidecontroller_live_set.emit.assert_called_once_with([400])
def test_controller_direction_requires_login(flask_client):
Settings().setValue('api/authentication enabled', True)
res = flask_client.post('/api/v2/controller/progress', json=dict())
Settings().setValue('api/authentication enabled', False)
assert res.status_code == 401
def test_controller_direction_does_not_accept_get(flask_client):
res = flask_client.get('/api/v2/controller/progress')
assert res.status_code == 405
def test_controller_direction_does_fails_on_wrong_data(flask_client):
res = flask_client.post('/api/v2/controller/progress', json=dict(action='foo'))
assert res.status_code == 400
def test_controller_direction_calls_service_manager(flask_client):
fake_live_controller = MagicMock()
Registry.create().register('live_controller', fake_live_controller)
res = flask_client.post('/api/v2/controller/progress', json=dict(action='next'))
assert res.status_code == 204
fake_live_controller.slidecontroller_live_next.emit.assert_called_once()

View File

@ -0,0 +1,113 @@
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.state import State
from openlp.core.lib.plugin import PluginStatus, StringContent
def test_plugins_returns_list(flask_client):
State().load_settings()
res = flask_client.get('/api/v2/core/plugins').get_json()
assert len(res) == 0
class FakeMediaItem:
has_search = True
class FakePlugin:
name = 'Faked'
is_plugin = True
status = PluginStatus.Active
media_item = FakeMediaItem()
text_strings = {StringContent.Name: {'plural': 'just a text'}}
plugin = FakePlugin()
State().modules['testplug'] = plugin
Registry.create().register('testplug_plugin', plugin)
res = flask_client.get('/api/v2/core/plugins').get_json()
assert len(res) == 1
assert res[0]['key'] == plugin.name
assert res[0]['name'] == plugin.text_strings[StringContent.Name]['plural']
def test_system_information(flask_client):
Settings().setValue('api/authentication enabled', False)
res = flask_client.get('/api/v2/core/system').get_json()
assert res['websocket_port'] > 0
assert not res['login_required']
def test_poll(flask_client):
class FakePoller:
def poll(self):
return {'foo': 'bar'}
Registry.create().register('poller', FakePoller())
res = flask_client.get('/api/v2/core/poll').get_json()
assert res['foo'] == 'bar'
def test_login_get_is_refused(flask_client):
res = flask_client.get('/api/v2/core/login')
assert res.status_code == 405
def test_login_without_data_returns_400(flask_client):
res = flask_client.post('/api/v2/core/login')
assert res.status_code == 400
def test_login_with_invalid_credetials_returns_401(flask_client):
res = flask_client.post('/api/v2/core/login', json=dict(username='openlp', password='invalid'))
assert res.status_code == 401
def test_login_with_valid_credetials_returns_token(flask_client):
Registry().register('authentication_token', 'foobar')
res = flask_client.post('/api/v2/core/login', json=dict(username='openlp', password='password'))
assert res.status_code == 200
assert res.get_json()['token'] == 'foobar'
def test_retrieving_image(flask_client):
class FakeController:
def grab_maindisplay(self):
class FakeImage:
def save(self, first, second):
pass
return FakeImage()
Registry.create().register('live_controller', FakeController())
res = flask_client.get('/api/v2/core/live-image').get_json()
assert res['binary_image'] != ''
def test_toggle_display_requires_login(flask_client):
Settings().setValue('api/authentication enabled', True)
res = flask_client.post('/api/v2/core/display')
Settings().setValue('api/authentication enabled', False)
assert res.status_code == 401
def test_toggle_display_does_not_allow_get(flask_client):
res = flask_client.get('/api/v2/core/display')
assert res.status_code == 405
def test_toggle_display_invalid_action(flask_client):
res = flask_client.post('/api/v2/core/display', json={'display': 'foo'})
assert res.status_code == 400
def test_toggle_display_valid_action_updates_controller(flask_client):
class FakeController:
class Emitter:
def emit(self, value):
self.set = value
slidecontroller_toggle_display = Emitter()
controller = FakeController()
Registry.create().register('live_controller', controller)
res = flask_client.post('/api/v2/core/display', json={'display': 'show'})
assert res.status_code == 204
assert controller.slidecontroller_toggle_display.set == 'show'
def test_cors_headers_are_present(flask_client):
res = flask_client.get('/api/v2/core/system')
assert 'Access-Control-Allow-Origin' in res.headers
assert res.headers['Access-Control-Allow-Origin'] == '*'

View File

@ -0,0 +1,56 @@
import pytest
from unittest.mock import MagicMock
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
def test_retrieve_service_items(flask_client):
pytest.skip()
res = flask_client.get('/api/v2/service/items').get_json()
assert len(res) == 0
def test_service_set_requires_login(flask_client):
Settings().setValue('api/authentication enabled', True)
res = flask_client.post('/api/v2/service/show', json=dict())
Settings().setValue('api/authentication enabled', False)
assert res.status_code == 401
def test_service_set_does_not_accept_get(flask_client):
res = flask_client.get('/api/v2/service/show')
assert res.status_code == 405
def test_service_set_calls_service_manager(flask_client):
fake_service_manager = MagicMock()
Registry.create().register('service_manager', fake_service_manager)
res = flask_client.post('/api/v2/service/show', json=dict(id=400))
assert res.status_code == 204
fake_service_manager.set_item.assert_called_once_with(400)
def test_service_direction_requires_login(flask_client):
Settings().setValue('api/authentication enabled', True)
res = flask_client.post('/api/v2/service/progress', json=dict())
Settings().setValue('api/authentication enabled', False)
assert res.status_code == 401
def test_service_direction_does_not_accept_get(flask_client):
res = flask_client.get('/api/v2/service/progress')
assert res.status_code == 405
def test_service_direction_does_fails_on_wrong_data(flask_client):
res = flask_client.post('/api/v2/service/progress', json=dict(action='foo'))
assert res.status_code == 400
def test_service_direction_calls_service_manager(flask_client):
fake_service_manager = MagicMock()
Registry.create().register('service_manager', fake_service_manager)
res = flask_client.post('/api/v2/service/progress', json=dict(action='next'))
assert res.status_code == 204
fake_service_manager.servicemanager_next_item.emit.assert_called_once()

View File

@ -161,8 +161,8 @@ class TestMainWindow(TestCase, TestMixin):
# WHEN: you check the started functions
# THEN: the following registry functions should have been registered
expected_service_list = ['application', 'main_window', 'http_server', 'settings_form', 'service_manager',
'theme_manager', 'projector_manager']
expected_service_list = ['application', 'main_window', 'http_server', 'authentication_token', 'settings_form',
'service_manager', 'theme_manager', 'projector_manager']
expected_functions_list = ['bootstrap_initialise', 'bootstrap_post_set_up', 'bootstrap_completion',
'theme_update_global', 'config_screen_changed']
assert list(self.registry.service_list.keys()) == expected_service_list, \