diff --git a/openlp/core/api/__init__.py b/openlp/core/api/__init__.py
index fa1ec5512..0b8b3c015 100644
--- a/openlp/core/api/__init__.py
+++ b/openlp/core/api/__init__.py
@@ -18,3 +18,21 @@
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see . #
##########################################################################
+
+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)
diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py
index 492b6af35..48343712c 100644
--- a/openlp/core/api/deploy.py
+++ b/openlp/core/api/deploy.py
@@ -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')
diff --git a/openlp/core/api/endpoint/__init__.py b/openlp/core/api/endpoint/__init__.py
deleted file mode 100644
index 6fb21355f..000000000
--- a/openlp/core/api/endpoint/__init__.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-The Endpoint class, which provides plugins with a way to serve their own portion of the API
-"""
diff --git a/openlp/core/api/endpoint/controller.py b/openlp/core/api/endpoint/controller.py
deleted file mode 100644
index f9200c1d7..000000000
--- a/openlp/core/api/endpoint/controller.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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}}
diff --git a/openlp/core/api/endpoint/core.py b/openlp/core/api/endpoint/core.py
deleted file mode 100644
index 2177655e2..000000000
--- a/openlp/core/api/endpoint/core.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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 & 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}
diff --git a/openlp/core/api/endpoint/pluginhelpers.py b/openlp/core/api/endpoint/pluginhelpers.py
deleted file mode 100644
index b02c6f20a..000000000
--- a/openlp/core/api/endpoint/pluginhelpers.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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')
diff --git a/openlp/core/api/endpoint/remote.py b/openlp/core/api/endpoint/remote.py
deleted file mode 100644
index dcfde5fd5..000000000
--- a/openlp/core/api/endpoint/remote.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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)
diff --git a/openlp/core/api/endpoint/service.py b/openlp/core/api/endpoint/service.py
deleted file mode 100644
index 305563984..000000000
--- a/openlp/core/api/endpoint/service.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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
diff --git a/openlp/core/api/http/__init__.py b/openlp/core/api/http/__init__.py
index 16b4ede89..d06059d38 100644
--- a/openlp/core/api/http/__init__.py
+++ b/openlp/core/api/http/__init__.py
@@ -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):
"""
diff --git a/openlp/core/api/http/endpoint.py b/openlp/core/api/http/endpoint.py
deleted file mode 100644
index 6d69824d9..000000000
--- a/openlp/core/api/http/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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)
diff --git a/openlp/core/api/http/errors.py b/openlp/core/api/http/errors.py
deleted file mode 100644
index 7012689d2..000000000
--- a/openlp/core/api/http/errors.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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')
diff --git a/openlp/core/api/http/server.py b/openlp/core/api/http/server.py
index eed1fc829..ac3c35f37 100644
--- a/openlp/core/api/http/server.py
+++ b/openlp/core/api/http/server.py
@@ -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):
"""
diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py
deleted file mode 100644
index 723c22c6a..000000000
--- a/openlp/core/api/http/wsgiapp.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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[^/]+)'
-
- and
-
- 'songs/{song_id:\d+}' becomes 'songs/(?P\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)
diff --git a/openlp/core/api/lib.py b/openlp/core/api/lib.py
new file mode 100644
index 000000000..41790b05b
--- /dev/null
+++ b/openlp/core/api/lib.py
@@ -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
diff --git a/openlp/core/api/main.py b/openlp/core/api/main.py
new file mode 100644
index 000000000..fafa51395
--- /dev/null
+++ b/openlp/core/api/main.py
@@ -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('/')
+def assets(path):
+ return send_from_directory(str(AppLocation.get_section_data_path('remotes')), path)
diff --git a/openlp/core/api/versions/__init__.py b/openlp/core/api/versions/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/openlp/core/api/versions/v1/__init__.py b/openlp/core/api/versions/v1/__init__.py
new file mode 100644
index 000000000..3d2e61c46
--- /dev/null
+++ b/openlp/core/api/versions/v1/__init__.py
@@ -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)
diff --git a/openlp/core/api/versions/v1/controller.py b/openlp/core/api/versions/v1/controller.py
new file mode 100644
index 000000000..0d26fd978
--- /dev/null
+++ b/openlp/core/api/versions/v1/controller.py
@@ -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//')
+@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()
diff --git a/openlp/core/api/versions/v1/core.py b/openlp/core/api/versions/v1/core.py
new file mode 100644
index 000000000..4782574b5
--- /dev/null
+++ b/openlp/core/api/versions/v1/core.py
@@ -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/')
+@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})
diff --git a/openlp/core/api/versions/v1/service.py b/openlp/core/api/versions/v1/service.py
new file mode 100644
index 000000000..4e99321c0
--- /dev/null
+++ b/openlp/core/api/versions/v1/service.py
@@ -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/')
+@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()
diff --git a/openlp/core/api/versions/v2/__init__.py b/openlp/core/api/versions/v2/__init__.py
new file mode 100644
index 000000000..9e76f8c11
--- /dev/null
+++ b/openlp/core/api/versions/v2/__init__.py
@@ -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/')
diff --git a/openlp/core/api/versions/v2/controller.py b/openlp/core/api/versions/v2/controller.py
new file mode 100644
index 000000000..fdf5faf3e
--- /dev/null
+++ b/openlp/core/api/versions/v2/controller.py
@@ -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
diff --git a/openlp/core/api/versions/v2/core.py b/openlp/core/api/versions/v2/core.py
new file mode 100644
index 000000000..4a9db0eda
--- /dev/null
+++ b/openlp/core/api/versions/v2/core.py
@@ -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})
diff --git a/openlp/core/api/versions/v2/service.py b/openlp/core/api/versions/v2/service.py
new file mode 100644
index 000000000..89ab62c6c
--- /dev/null
+++ b/openlp/core/api/versions/v2/service.py
@@ -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
diff --git a/openlp/core/ui/media/endpoint.py b/openlp/core/ui/media/endpoint.py
deleted file mode 100644
index 334f7e6bb..000000000
--- a/openlp/core/ui/media/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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}}
diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py
index fa3dbe03c..47d0ca11e 100644
--- a/openlp/core/ui/media/mediacontroller.py
+++ b/openlp/core/ui/media/mediacontroller.py
@@ -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):
"""
diff --git a/openlp/core/ui/media/remote.py b/openlp/core/ui/media/remote.py
new file mode 100644
index 000000000..8bad4796f
--- /dev/null
+++ b/openlp/core/ui/media/remote.py
@@ -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/')
diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py
index d75730458..628ec62fe 100644
--- a/openlp/core/ui/slidecontroller.py
+++ b/openlp/core/ui/slidecontroller.py
@@ -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):
"""
diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py
index cf8f97124..5b4933791 100644
--- a/openlp/plugins/alerts/alertsplugin.py
+++ b/openlp/plugins/alerts/alertsplugin.py
@@ -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())
diff --git a/openlp/plugins/alerts/endpoint.py b/openlp/plugins/alerts/endpoint.py
deleted file mode 100644
index c1b2a53fc..000000000
--- a/openlp/plugins/alerts/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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}}
diff --git a/openlp/plugins/alerts/remote.py b/openlp/plugins/alerts/remote.py
new file mode 100644
index 000000000..3bbbcb133
--- /dev/null
+++ b/openlp/plugins/alerts/remote.py
@@ -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')
diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py
index 3eb53e560..0c2126c95 100644
--- a/openlp/plugins/bibles/bibleplugin.py
+++ b/openlp/plugins/bibles/bibleplugin.py
@@ -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())
diff --git a/openlp/plugins/bibles/endpoint.py b/openlp/plugins/bibles/endpoint.py
deleted file mode 100644
index ed6af0d1d..000000000
--- a/openlp/plugins/bibles/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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': []}}
diff --git a/openlp/plugins/bibles/remote.py b/openlp/plugins/bibles/remote.py
new file mode 100644
index 000000000..d5fc05d96
--- /dev/null
+++ b/openlp/plugins/bibles/remote.py
@@ -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')
diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py
index 40cb430e6..6fad6bd59 100644
--- a/openlp/plugins/custom/customplugin.py
+++ b/openlp/plugins/custom/customplugin.py
@@ -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())
diff --git a/openlp/plugins/custom/endpoint.py b/openlp/plugins/custom/endpoint.py
deleted file mode 100644
index f6e45ed53..000000000
--- a/openlp/plugins/custom/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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': []}}
diff --git a/openlp/plugins/custom/remote.py b/openlp/plugins/custom/remote.py
new file mode 100644
index 000000000..8fb1a34df
--- /dev/null
+++ b/openlp/plugins/custom/remote.py
@@ -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/')
diff --git a/openlp/plugins/images/endpoint.py b/openlp/plugins/images/endpoint.py
deleted file mode 100644
index bc3ab1d5d..000000000
--- a/openlp/plugins/images/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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': []}}
diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py
index 8a21691eb..7b74d908f 100644
--- a/openlp/plugins/images/imageplugin.py
+++ b/openlp/plugins/images/imageplugin.py
@@ -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())
diff --git a/openlp/plugins/images/remote.py b/openlp/plugins/images/remote.py
new file mode 100644
index 000000000..29d4507bd
--- /dev/null
+++ b/openlp/plugins/images/remote.py
@@ -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/')
diff --git a/openlp/plugins/media/endpoint.py b/openlp/plugins/media/endpoint.py
deleted file mode 100644
index cd3f96f65..000000000
--- a/openlp/plugins/media/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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': []}}
diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py
index 728bff9b6..db50db733 100644
--- a/openlp/plugins/media/mediaplugin.py
+++ b/openlp/plugins/media/mediaplugin.py
@@ -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())
diff --git a/openlp/plugins/media/remote.py b/openlp/plugins/media/remote.py
new file mode 100644
index 000000000..4ebddef00
--- /dev/null
+++ b/openlp/plugins/media/remote.py
@@ -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/')
diff --git a/openlp/plugins/presentations/endpoint.py b/openlp/plugins/presentations/endpoint.py
deleted file mode 100644
index ae1ffe0a6..000000000
--- a/openlp/plugins/presentations/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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': []}}
diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py
index 1653cc120..1e2cd2922 100644
--- a/openlp/plugins/presentations/presentationplugin.py
+++ b/openlp/plugins/presentations/presentationplugin.py
@@ -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())
diff --git a/openlp/plugins/presentations/remote.py b/openlp/plugins/presentations/remote.py
new file mode 100644
index 000000000..6cb3fd2ba
--- /dev/null
+++ b/openlp/plugins/presentations/remote.py
@@ -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/')
diff --git a/openlp/plugins/songs/endpoint.py b/openlp/plugins/songs/endpoint.py
deleted file mode 100644
index bdad94e38..000000000
--- a/openlp/plugins/songs/endpoint.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-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': []}}
diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py
index 1e39a04e3..b894bae20 100644
--- a/openlp/plugins/songs/lib/mediaitem.py
+++ b/openlp/plugins/songs/lib/mediaitem.py
@@ -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:
diff --git a/openlp/plugins/songs/remote.py b/openlp/plugins/songs/remote.py
new file mode 100644
index 000000000..207230248
--- /dev/null
+++ b/openlp/plugins/songs/remote.py
@@ -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/')
diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py
index bace7c1b3..ba9cf0bc6 100644
--- a/openlp/plugins/songs/songsplugin.py
+++ b/openlp/plugins/songs/songsplugin.py
@@ -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'):
diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py
index b525114c5..65c711403 100755
--- a/scripts/check_dependencies.py
+++ b/scripts/check_dependencies.py
@@ -83,6 +83,8 @@ MODULES = [
'alembic',
'lxml',
'chardet',
+ 'flask',
+ 'flask_cors',
'bs4',
'mako',
'websockets',
diff --git a/setup.py b/setup.py
index d373650ad..8990d5a92 100644
--- a/setup.py
+++ b/setup.py
@@ -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',
diff --git a/tests/functional/openlp_core/api/endpoint/test_controller.py b/tests/functional/openlp_core/api/endpoint/test_controller.py
index b27fd9fec..3ced8dc86 100644
--- a/tests/functional/openlp_core/api/endpoint/test_controller.py
+++ b/tests/functional/openlp_core/api/endpoint/test_controller.py
@@ -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()
diff --git a/tests/functional/openlp_core/api/endpoint/test_remote.py b/tests/functional/openlp_core/api/endpoint/test_remote.py
deleted file mode 100644
index 9251b5a2f..000000000
--- a/tests/functional/openlp_core/api/endpoint/test_remote.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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'
diff --git a/tests/functional/openlp_core/api/http/test_error.py b/tests/functional/openlp_core/api/http/test_error.py
deleted file mode 100644
index 0ea361441..000000000
--- a/tests/functional/openlp_core/api/http/test_error.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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'
diff --git a/tests/functional/openlp_core/api/http/test_wsgiapp.py b/tests/functional/openlp_core/api/http/test_wsgiapp.py
deleted file mode 100644
index 82d54fa3c..000000000
--- a/tests/functional/openlp_core/api/http/test_wsgiapp.py
+++ /dev/null
@@ -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 . #
-##########################################################################
-"""
-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)
diff --git a/tests/functional/openlp_core/api/v2/__init__.py b/tests/functional/openlp_core/api/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/functional/openlp_core/api/v2/conftest.py b/tests/functional/openlp_core/api/v2/conftest.py
new file mode 100644
index 000000000..976957011
--- /dev/null
+++ b/tests/functional/openlp_core/api/v2/conftest.py
@@ -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()
diff --git a/tests/functional/openlp_core/api/v2/test_controller.py b/tests/functional/openlp_core/api/v2/test_controller.py
new file mode 100644
index 000000000..514e821d0
--- /dev/null
+++ b/tests/functional/openlp_core/api/v2/test_controller.py
@@ -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()
diff --git a/tests/functional/openlp_core/api/v2/test_core.py b/tests/functional/openlp_core/api/v2/test_core.py
new file mode 100644
index 000000000..1a67e63ac
--- /dev/null
+++ b/tests/functional/openlp_core/api/v2/test_core.py
@@ -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'] == '*'
diff --git a/tests/functional/openlp_core/api/v2/test_service.py b/tests/functional/openlp_core/api/v2/test_service.py
new file mode 100644
index 000000000..9341b3711
--- /dev/null
+++ b/tests/functional/openlp_core/api/v2/test_service.py
@@ -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()
diff --git a/tests/functional/openlp_core/ui/test_mainwindow.py b/tests/functional/openlp_core/ui/test_mainwindow.py
index 71c456148..011ed3e50 100644
--- a/tests/functional/openlp_core/ui/test_mainwindow.py
+++ b/tests/functional/openlp_core/ui/test_mainwindow.py
@@ -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, \