diff --git a/openlp/core/api/__init__.py b/openlp/core/api/__init__.py index 135bfa1a9..73a1737f5 100644 --- a/openlp/core/api/__init__.py +++ b/openlp/core/api/__init__.py @@ -39,13 +39,13 @@ def _route_from_url(url_prefix, url): return url -def register_endpoint(endpoint): +def register_endpoint(end_point): """ Register an endpoint with the app """ - print("ep", endpoint) - for url, view_func, method, secure in endpoint.routes: - route = _route_from_url(endpoint.url_prefix, url) + print("ep", end_point) + for url, view_func, method, secure in end_point.routes: + route = _route_from_url(end_point.url_prefix, url) application.add_route(route, view_func, method, secure) from .endpoint import Endpoint diff --git a/openlp/core/api/apicontroller.py b/openlp/core/api/apicontroller.py index f94172ab6..c63e41be2 100644 --- a/openlp/core/api/apicontroller.py +++ b/openlp/core/api/apicontroller.py @@ -23,7 +23,10 @@ import logging from openlp.core.api import OpenWSServer, OpenLPPoll, OpenLPHttpServer from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties -from openlp.core.api.uiinterfaces import stage_endpoint + +# These are here to load the endpoints +from openlp.core.api.coreendpoints import stage_endpoint +from openlp.core.api.controllerendpoints import controller_endpoint log = logging.getLogger(__name__) diff --git a/openlp/core/api/controllerendpoints.py b/openlp/core/api/controllerendpoints.py new file mode 100644 index 000000000..9260129d8 --- /dev/null +++ b/openlp/core/api/controllerendpoints.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +import logging +import os +import urllib.request +import urllib.error + +from openlp.core.api import Endpoint, register_endpoint +from openlp.core.common import Registry, AppLocation, Settings +from openlp.core.lib import ItemCapabilities, create_thumb + +log = logging.getLogger(__name__) + +controller_endpoint = Endpoint('api') + + +@controller_endpoint.route('controller/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['verseTag']: + item['tag'] = str(frame['verseTag']) + else: + item['tag'] = str(index + 1) + item['text'] = str(frame['text']) + item['html'] = str(frame['html']) + # Handle images, unless a custom thumbnail is given or if thumbnails is disabled + elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'): + item['tag'] = str(index + 1) + thumbnail_path = os.path.join('images', 'thumbnails', frame['title']) + full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path) + # Create thumbnail if it doesn't exists + if not os.path.exists(full_thumbnail_path): + create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False) + item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path) + item['text'] = str(frame['title']) + item['html'] = str(frame['title']) + else: + # Handle presentation etc. + item['tag'] = str(index + 1) + if current_item.is_capable(ItemCapabilities.HasDisplayTitle): + item['title'] = str(frame['display_title']) + if current_item.is_capable(ItemCapabilities.HasNotes): + item['slide_notes'] = str(frame['notes']) + if current_item.is_capable(ItemCapabilities.HasThumbnails) and \ + Settings().value('remotes/thumbnails'): + # If the file is under our app directory tree send the portion after the match + data_path = AppLocation.get_data_path() + if frame['image'][0:len(data_path)] == data_path: + item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):]) + item['text'] = str(frame['title']) + item['html'] = str(frame['title']) + item['selected'] = (live_controller.selected_row == index) + data.append(item) + json_data = {'results': {'slides': data}} + if current_item: + json_data['results']['item'] = live_controller.service_item.unique_identifier + return json_data + + +@controller_endpoint.route('service/list') +def service_list(request): + """ + Handles requests for service items in the service manager + + """ + return {'results': {'items': get_service_items()}} + + +def get_service_items(): + """ + Read the service item in use and return the data as a json object + """ + live_controller = Registry().get('live_controller') + service_items = [] + if live_controller.service_item: + current_unique_identifier = live_controller.service_item.unique_identifier + else: + current_unique_identifier = None + for item in Registry().get('service_manager').service_items: + service_item = item['service_item'] + service_items.append({ + 'id': str(service_item.unique_identifier), + 'title': str(service_item.get_display_title()), + 'plugin': str(service_item.name), + 'notes': str(service_item.notes), + 'selected': (service_item.unique_identifier == current_unique_identifier) + }) + return service_items + +register_endpoint(controller_endpoint) diff --git a/openlp/core/api/coreendpoints.py b/openlp/core/api/coreendpoints.py new file mode 100644 index 000000000..7f20c11a7 --- /dev/null +++ b/openlp/core/api/coreendpoints.py @@ -0,0 +1,166 @@ +import logging +import os + +from openlp.core.api import Endpoint, register_endpoint +from openlp.core.common import AppLocation, UiStrings, translate + + +from mako.template import Template + + +log = logging.getLogger(__name__) + +stage_endpoint = Endpoint('') + +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') + +template_vars = { + 'app_title': "{main} {remote}".format(main=UiStrings().OLPV2x, remote=remote), + 'stage_title': "{main} {stage}".format(main=UiStrings().OLPV2x, stage=stage), + 'live_title': "{main} {live}".format(main=UiStrings().OLPV2x, live=live), + 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), + 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), + 'alerts': translate('RemotePlugin.Mobile', 'Alerts'), + 'search': translate('RemotePlugin.Mobile', 'Search'), + 'home': translate('RemotePlugin.Mobile', 'Home'), + 'refresh': translate('RemotePlugin.Mobile', 'Refresh'), + 'blank': translate('RemotePlugin.Mobile', 'Blank'), + 'theme': translate('RemotePlugin.Mobile', 'Theme'), + 'desktop': translate('RemotePlugin.Mobile', 'Desktop'), + 'show': translate('RemotePlugin.Mobile', 'Show'), + 'prev': translate('RemotePlugin.Mobile', 'Prev'), + 'next': translate('RemotePlugin.Mobile', 'Next'), + 'text': translate('RemotePlugin.Mobile', 'Text'), + 'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'), + 'go_live': translate('RemotePlugin.Mobile', 'Go Live'), + 'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'), + 'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'), + 'no_results': translate('RemotePlugin.Mobile', 'No Results'), + 'options': translate('RemotePlugin.Mobile', 'Options'), + 'service': translate('RemotePlugin.Mobile', 'Service'), + 'slides': translate('RemotePlugin.Mobile', 'Slides'), + 'settings': translate('RemotePlugin.Mobile', 'Settings'), +} + + +@stage_endpoint.route('$') +@stage_endpoint.route('(stage)$') +@stage_endpoint.route('(main)$') +@stage_endpoint.route('(css)') +@stage_endpoint.route('(js)') +@stage_endpoint.route('(assets)') +def file_access(request): + """ + Get a list of songs` + """ + file_name = request.path + html_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'api', 'html') + log.debug('serve file request {name}'.format(name=file_name)) + if file_name.startswith('/'): + file_name = file_name[1:] + if not file_name: + file_name = 'index.html' + if '.' not in file_name: + file_name += '.html' + if file_name.startswith('/'): + file_name = file_name[1:] + path = os.path.normpath(os.path.join(html_dir, file_name)) + return _process_file(path) + + +@stage_endpoint.route('(stage)/(.*)$') +def bespoke_file_access(request): + """ + Allow Stage view to be delivered with custom views. + + :param request: base path of the URL. Not used but passed by caller + :return: + """ + file_name = request.path + config_dir = os.path.join(AppLocation.get_data_path(), 'stages') + log.debug('serve file request {name}'.format(name=file_name)) + parts = file_name.split('/') + if len(parts) == 1: + file_name = os.path.join(parts[0], 'stage.html') + elif len(parts) == 3: + file_name = os.path.join(parts[1], parts[2]) + path = os.path.normpath(os.path.join(config_dir, file_name)) + return _process_file(path) + + +@stage_endpoint.route('main/image$') +def main_image(request): + """ + Return the latest display image as a byte stream. + :param request: base path of the URL. Not used but passed by caller + :return: + """ + # result = { + # 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image)) + # } + # self.do_json_header() + # return json.dumps({'results': result}).encode() + pass + +@stage_endpoint.route(r'^/(\w+)/thumbnails([^/]+)?/(.*)$') +def main_image(request): + """ + Get a list of songs + :param request: base path of the URL. Not used but passed by caller + :return: + """ + # songs = db.query(Song).get() + # return {'songs': [dictify(song) for song in songs]} + print("AAA") + + +def _process_file(path): + """ + Common file processing code + + :param path: path to file to be loaded + :return: web resource to be loaded + """ + ext, content_type = get_content_type(path) + file_handle = None + try: + if ext == '.html': + variables = template_vars + content = Template(filename=path, input_encoding='utf-8').render(**variables) + else: + file_handle = open(path, 'rb') + log.debug('Opened {path}'.format(path=path)) + content = file_handle.read().decode("utf-8") + except IOError: + log.exception('Failed to open {path}'.format(path=path)) + return None + finally: + if file_handle: + file_handle.close() + return content + + +def get_content_type(file_name): + """ + Examines the extension of the file and determines what the content_type should be, defaults to text/plain + Returns the extension and the content_type + + :param file_name: name of file + """ + ext = os.path.splitext(file_name)[1] + content_type = FILE_TYPES.get(ext, 'text/plain') + return ext, content_type + +register_endpoint(stage_endpoint) diff --git a/openlp/core/api/httprouter.py b/openlp/core/api/httprouter.py index 6647bff9f..7e7a1c752 100644 --- a/openlp/core/api/httprouter.py +++ b/openlp/core/api/httprouter.py @@ -37,6 +37,7 @@ def _make_response(view_result): """ Create a Response object from response """ + print(view_result) if isinstance(view_result, Response): return view_result elif isinstance(view_result, tuple): @@ -54,8 +55,12 @@ def _make_response(view_result): 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') + if 'body {' in view_result: + return Response(body=view_result, status=200, + content_type='text/css', charset='utf8') + else: + return Response(body=view_result, status=200, + content_type='text/html', charset='utf8') def _handle_exception(error): @@ -92,14 +97,13 @@ class WSGIApplication(object): """ Find the appropriate URL and run the view function """ - print(request.path) - print(self.route_map.items()) for route, views in self.route_map.items(): if re.match(route, request.path): if request.method.upper() in views: log.debug('Found {method} {url}'.format(method=request.method, url=request.path)) view_func = views[request.method.upper()]['function'] return _make_response(view_func(request)) + log.error('Not Found url {url} '.format(url=request.path)) raise NotFound() def wsgi_app(self, environ, start_response): diff --git a/openlp/core/api/uiinterfaces.py b/openlp/core/api/uiinterfaces.py deleted file mode 100644 index 5df7d8d02..000000000 --- a/openlp/core/api/uiinterfaces.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging -import os - -from openlp.core.api import Endpoint, register_endpoint - -log = logging.getLogger(__name__) - -stage_endpoint = Endpoint('') - - -@stage_endpoint.route('$') -@stage_endpoint.route('(stage)$') -@stage_endpoint.route('(main)$') -def file_access(request): - """ - Get a list of songs` - """ - #songs = db.query(Song).get() - #return {'songs': [dictify(song) for song in songs]} - print("AAA") - - -@stage_endpoint.route('(stage)/(.*)$') -def bespoke_file_access(request): - """ - Allow Stage view to be delivered with custom views. - - :param url_path: base path of the URL. Not used but passed by caller - :param file_name: file name with path - :return: - """ - pass - # log.debug('serve file request {name}'.format(name=file_name)) - # parts = file_name.split('/') - # if len(parts) == 1: - # file_name = os.path.join(parts[0], 'stage.html') - # elif len(parts) == 3: - # file_name = os.path.join(parts[1], parts[2]) - # path = os.path.normpath(os.path.join(self.config_dir, file_name)) - # if not path.startswith(self.config_dir): - # return self.do_not_found() - # return _process_file(path) - - -@stage_endpoint.route('main/image$') -def main_image(request): - """ - Return the latest display image as a byte stream. - """ - # result = { - # 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image)) - # } - # self.do_json_header() - # return json.dumps({'results': result}).encode() - pass - -@stage_endpoint.route(r'^/(\w+)/thumbnails([^/]+)?/(.*)$') -def main_image(request): - """ - Get a list of songs - """ - # songs = db.query(Song).get() - # return {'songs': [dictify(song) for song in songs]} - print("AAA") - - -def _process_file(self, path): - """ - Common file processing code - - :param path: path to file to be loaded - :return: web resource to be loaded - """ - # content = None - # ext, content_type = self.get_content_type(path) - # file_handle = None - # try: - # if ext == '.html': - # variables = self.template_vars - # content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) - # else: - # file_handle = open(path, 'rb') - # log.debug('Opened {path}'.format(path=path)) - # content = file_handle.read() - # except IOError: - # log.exception('Failed to open {path}'.format(path=path)) - # return self.do_not_found() - # finally: - # if file_handle: - # file_handle.close() - # self.send_response(200) - # self.send_header('Content-type', content_type) - # self.end_headers() - # return content - pass - -register_endpoint(stage_endpoint)