forked from openlp/openlp
merge trunk
This commit is contained in:
commit
c6aa669ee8
@ -33,6 +33,7 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from traceback import format_exception
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -153,10 +154,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||
self.processEvents()
|
||||
if not has_run_wizard:
|
||||
self.main_window.first_time()
|
||||
# update_check = Settings().value('core/update check')
|
||||
# if update_check:
|
||||
# version = VersionThread(self.main_window)
|
||||
# version.start()
|
||||
version = VersionThread(self.main_window)
|
||||
version.start()
|
||||
self.main_window.is_display_blank()
|
||||
self.main_window.app_startup()
|
||||
return self.exec()
|
||||
@ -181,7 +180,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||
"""
|
||||
Check if the data folder path exists.
|
||||
"""
|
||||
data_folder_path = AppLocation.get_data_path()
|
||||
data_folder_path = str(AppLocation.get_data_path())
|
||||
if not os.path.exists(data_folder_path):
|
||||
log.critical('Database was not found in: ' + data_folder_path)
|
||||
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
|
||||
@ -253,7 +252,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||
'a backup of the old data folder?'),
|
||||
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
|
||||
# Create copy of data folder
|
||||
data_folder_path = AppLocation.get_data_path()
|
||||
data_folder_path = str(AppLocation.get_data_path())
|
||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||
data_folder_backup_path = data_folder_path + '-' + timestamp
|
||||
try:
|
||||
@ -337,6 +336,8 @@ def parse_options(args=None):
|
||||
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
|
||||
help='Ignore the version file and pull the version directly from Bazaar')
|
||||
parser.add_argument('-s', '--style', dest='style', help='Set the Qt5 style (passed directly to Qt5).')
|
||||
parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_false',
|
||||
help='Turn off the Web and Socket Server ')
|
||||
parser.add_argument('rargs', nargs='?', default=[])
|
||||
# Parse command line options and deal with them. Use args supplied pragmatically if possible.
|
||||
return parser.parse_args(args) if args else parser.parse_args()
|
||||
@ -346,15 +347,18 @@ def set_up_logging(log_path):
|
||||
"""
|
||||
Setup our logging using log_path
|
||||
|
||||
:param log_path: the path
|
||||
:param pathlib.Path log_path: The file to save the log to
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
check_directory_exists(log_path, True)
|
||||
filename = os.path.join(log_path, 'openlp.log')
|
||||
logfile = logging.FileHandler(filename, 'w', encoding="UTF-8")
|
||||
file_path = log_path / 'openlp.log'
|
||||
# TODO: FileHandler accepts a Path object in Py3.6
|
||||
logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8')
|
||||
logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
|
||||
log.addHandler(logfile)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
print('Logging to: {name}'.format(name=filename))
|
||||
print('Logging to: {name}'.format(name=file_path))
|
||||
|
||||
|
||||
def main(args=None):
|
||||
@ -390,19 +394,19 @@ def main(args=None):
|
||||
application.setApplicationName('OpenLPPortable')
|
||||
Settings.setDefaultFormat(Settings.IniFormat)
|
||||
# Get location OpenLPPortable.ini
|
||||
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||
set_up_logging(os.path.abspath(os.path.join(application_path, '..', '..', 'Other')))
|
||||
portable_path = (AppLocation.get_directory(AppLocation.AppDir) / '..' / '..').resolve()
|
||||
data_path = portable_path / 'Data'
|
||||
set_up_logging(portable_path / 'Other')
|
||||
log.info('Running portable')
|
||||
portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))
|
||||
portable_settings_path = data_path / 'OpenLP.ini'
|
||||
# Make this our settings file
|
||||
log.info('INI file: {name}'.format(name=portable_settings_file))
|
||||
Settings.set_filename(portable_settings_file)
|
||||
log.info('INI file: {name}'.format(name=portable_settings_path))
|
||||
Settings.set_filename(str(portable_settings_path))
|
||||
portable_settings = Settings()
|
||||
# Set our data path
|
||||
data_path = os.path.abspath(os.path.join(application_path, '..', '..', 'Data',))
|
||||
log.info('Data path: {name}'.format(name=data_path))
|
||||
# Point to our data path
|
||||
portable_settings.setValue('advanced/data path', data_path)
|
||||
portable_settings.setValue('advanced/data path', str(data_path))
|
||||
portable_settings.setValue('advanced/is portable', True)
|
||||
portable_settings.sync()
|
||||
else:
|
||||
@ -410,6 +414,7 @@ def main(args=None):
|
||||
set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
|
||||
Registry.create()
|
||||
Registry().register('application', application)
|
||||
Registry().set_flag('no_web_server', args.no_web_server)
|
||||
application.setApplicationVersion(get_application_version()['version'])
|
||||
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
|
||||
if application.is_already_running():
|
||||
|
@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
@ -19,16 +19,10 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${live_title}</title>
|
||||
<link rel="stylesheet" href="/css/main.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img id="image" class="size"/>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http import register_endpoint, requires_auth
|
||||
from openlp.core.api.tab import ApiTab
|
||||
from openlp.core.api.poll import Poller
|
||||
|
||||
__all__ = ['Endpoint', 'ApiTab', 'register_endpoint', 'requires_auth']
|
@ -19,9 +19,7 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
from .remotetab import RemoteTab
|
||||
from .httprouter import HttpRouter
|
||||
from .httpserver import OpenLPServer
|
||||
|
||||
__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter']
|
||||
"""
|
||||
The Endpoint class, which provides plugins with a way to serve their own portion of the API
|
||||
"""
|
||||
from .pluginhelpers import search, live, service
|
144
openlp/core/api/endpoint/controller.py
Normal file
144
openlp/core/api/endpoint/controller.py
Normal file
@ -0,0 +1,144 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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
|
||||
import json
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http import requires_auth
|
||||
from openlp.core.common import Registry, AppLocation, Settings
|
||||
from openlp.core.lib import ItemCapabilities, create_thumb
|
||||
|
||||
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['verseTag']:
|
||||
item['tag'] = str(frame['verseTag'])
|
||||
else:
|
||||
item['tag'] = str(index + 1)
|
||||
item['chords_text'] = str(frame['chords_text'])
|
||||
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('api/thumbnails'):
|
||||
item['tag'] = str(index + 1)
|
||||
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
||||
full_thumbnail_path = str(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)
|
||||
Registry().get('image_manager').add_image(full_thumbnail_path, frame['title'], None, 88, 88)
|
||||
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('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)
|
||||
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('{action:next|previous}')
|
||||
@requires_auth
|
||||
def controller_direction(request, controller, action):
|
||||
"""
|
||||
Handles requests for setting service items in the slide controller
|
||||
11
|
||||
: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
|
||||
11
|
||||
: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}}
|
182
openlp/core/api/endpoint/core.py
Normal file
182
openlp/core/api/endpoint/core.py
Normal file
@ -0,0 +1,182 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http import requires_auth
|
||||
from openlp.core.common import Registry, UiStrings, translate
|
||||
from openlp.core.lib import image_to_byte, 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}
|
||||
|
||||
|
||||
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
|
135
openlp/core/api/endpoint/pluginhelpers.py
Normal file
135
openlp/core/api/endpoint/pluginhelpers.py
Normal file
@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import urllib
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from webob import Response
|
||||
|
||||
from openlp.core.api.http.errors import NotFound
|
||||
from openlp.core.common import Registry, AppLocation
|
||||
from openlp.core.lib import PluginStatus, image_to_byte
|
||||
|
||||
|
||||
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 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('(\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
|
||||
if slide:
|
||||
full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name / slide)
|
||||
else:
|
||||
full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name)
|
||||
if os.path.exists(full_path):
|
||||
path, just_file_name = os.path.split(full_path)
|
||||
Registry().get('image_manager').add_image(full_path, just_file_name, None, width, height)
|
||||
image = Registry().get('image_manager').get_image(full_path, just_file_name, width, height)
|
||||
return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8')
|
100
openlp/core/api/endpoint/service.py
Normal file
100
openlp/core/api/endpoint/service.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 json
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http import register_endpoint, requires_auth
|
||||
from openlp.core.common 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
|
110
openlp/core/api/http/__init__.py
Normal file
110
openlp/core/api/http/__init__.py
Normal file
@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 base64
|
||||
from functools import wraps
|
||||
from webob import Response
|
||||
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.api.http.wsgiapp import WSGIApplication
|
||||
from .errors import NotFound, ServerError, HttpError
|
||||
|
||||
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):
|
||||
"""
|
||||
This function is called to check if a username password combination is valid.
|
||||
|
||||
:param auth: the authorisation object which needs to be tested
|
||||
:return Whether authentication have been successful
|
||||
"""
|
||||
auth_code = "{user}:{password}".format(user=Settings().value('api/user id'),
|
||||
password=Settings().value('api/password'))
|
||||
try:
|
||||
auth_base = base64.b64encode(auth_code)
|
||||
except TypeError:
|
||||
auth_base = base64.b64encode(auth_code.encode()).decode()
|
||||
if auth[1] == auth_base:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def authenticate():
|
||||
"""
|
||||
Sends a 401 response that enables basic auth to be triggered
|
||||
"""
|
||||
resp = Response(status=401)
|
||||
resp.www_authenticate = 'Basic realm="OpenLP Login Required"'
|
||||
return resp
|
||||
|
||||
|
||||
def requires_auth(f):
|
||||
"""
|
||||
Decorates a function which needs to be authenticated before it can be used from the remote.
|
||||
|
||||
:param f: The function which has been wrapped
|
||||
:return: the called function or a request to authenticate
|
||||
"""
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
if not Settings().value('api/authentication enabled'):
|
||||
return f(*args, **kwargs)
|
||||
req = args[0]
|
||||
if not hasattr(req, 'authorization'):
|
||||
return authenticate()
|
||||
else:
|
||||
auth = req.authorization
|
||||
if auth and check_auth(auth):
|
||||
return f(*args, **kwargs)
|
||||
else:
|
||||
return authenticate()
|
||||
return decorated
|
79
openlp/core/api/http/endpoint.py
Normal file
79
openlp/core/api/http/endpoint.py
Normal file
@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 Endpoint class, which provides plugins with a way to serve their own portion of the API
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common import AppLocation
|
||||
from mako.template import Template
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
root = str(AppLocation.get_section_data_path('remotes'))
|
||||
if not self.template_dir:
|
||||
raise Exception('No template directory specified')
|
||||
path = os.path.join(root, 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=path, input_encoding='utf-8').render(**kwargs)
|
65
openlp/core/api/http/errors.py
Normal file
65
openlp/core/api/http/errors.py
Normal file
@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
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')
|
97
openlp/core/api/http/server.py
Normal file
97
openlp/core/api/http/server.py
Normal file
@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact
|
||||
with OpenLP. It uses JSON to communicate with the remotes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from waitress import serve
|
||||
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.api.http import application
|
||||
from openlp.core.common import RegistryMixin, RegistryProperties, OpenLPMixin, Settings, Registry
|
||||
from openlp.core.api.poll import Poller
|
||||
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
|
||||
from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
|
||||
from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HttpWorker(QtCore.QObject):
|
||||
"""
|
||||
A special Qt thread class to allow the HTTP server to run at the same time as the UI.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor for the thread class.
|
||||
|
||||
:param server: The http server class.
|
||||
"""
|
||||
super(HttpWorker, self).__init__()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the thread.
|
||||
"""
|
||||
address = Settings().value('api/ip address')
|
||||
port = Settings().value('api/port')
|
||||
serve(application, host=address, port=port)
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
|
||||
"""
|
||||
Wrapper round a server instance
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Initialise the http server, and start the http server
|
||||
"""
|
||||
super(HttpServer, self).__init__(parent)
|
||||
self.worker = HttpWorker()
|
||||
self.thread = QtCore.QThread()
|
||||
self.worker.moveToThread(self.thread)
|
||||
self.thread.started.connect(self.worker.run)
|
||||
self.thread.start()
|
||||
|
||||
def bootstrap_post_set_up(self):
|
||||
"""
|
||||
Register the poll return service and start the servers.
|
||||
"""
|
||||
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)
|
185
openlp/core/api/http/wsgiapp.py
Normal file
185
openlp/core/api/http/wsgiapp.py
Normal file
@ -0,0 +1,185 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
# pylint: disable=logging-format-interpolation
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
App stuff
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from webob import Request, Response
|
||||
from webob.static import DirectoryApp
|
||||
|
||||
from openlp.core.common import AppLocation
|
||||
from openlp.core.api.http.errors import HttpError, NotFound, ServerError
|
||||
|
||||
|
||||
ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _route_to_regex(route):
|
||||
"""
|
||||
Convert a route to a regular expression
|
||||
|
||||
For example:
|
||||
|
||||
'songs/{song_id}' becomes 'songs/(?P<song_id>[^/]+)'
|
||||
|
||||
and
|
||||
|
||||
'songs/{song_id:\d+}' becomes 'songs/(?P<song_id>\d+)'
|
||||
|
||||
"""
|
||||
route_regex = ''
|
||||
last_pos = 0
|
||||
for match in ARGS_REGEX.finditer(route):
|
||||
route_regex += re.escape(route[last_pos:match.start()])
|
||||
arg_name = match.group(1)
|
||||
expr = match.group(2) or '[^/]+'
|
||||
expr = '(?P<%s>%s)' % (arg_name, expr)
|
||||
route_regex += expr
|
||||
last_pos = match.end()
|
||||
route_regex += re.escape(route[last_pos:])
|
||||
route_regex = '^%s$' % route_regex
|
||||
return route_regex
|
||||
|
||||
|
||||
def _make_response(view_result):
|
||||
"""
|
||||
Create a Response object from response
|
||||
"""
|
||||
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')
|
||||
|
||||
|
||||
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:
|
||||
root = str(AppLocation.get_section_data_path('remotes'))
|
||||
static_path = os.path.abspath(os.path.join(root, static_dir))
|
||||
if not os.path.exists(static_path):
|
||||
log.error('Static path "%s" does not exist. Skipping creating static route/', static_path)
|
||||
return
|
||||
self.static_routes[route] = DirectoryApp(static_path)
|
||||
|
||||
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)
|
130
openlp/core/api/poll.py
Normal file
130
openlp/core/api/poll.py
Normal file
@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 json
|
||||
|
||||
from openlp.core.common import RegistryProperties, Settings
|
||||
from openlp.core.common.httputils import get_web_page
|
||||
|
||||
|
||||
class Poller(RegistryProperties):
|
||||
"""
|
||||
Accessed by the web layer to get status type information from the application
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Constructor for the poll builder class.
|
||||
"""
|
||||
super(Poller, self).__init__()
|
||||
self.live_cache = None
|
||||
self.stage_cache = None
|
||||
self.chords_cache = None
|
||||
|
||||
def raw_poll(self):
|
||||
return {
|
||||
'service': self.service_manager.service_id,
|
||||
'slide': self.live_controller.selected_row or 0,
|
||||
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
|
||||
'twelve': Settings().value('api/twelve hour'),
|
||||
'blank': self.live_controller.blank_screen.isChecked(),
|
||||
'theme': self.live_controller.theme_screen.isChecked(),
|
||||
'display': self.live_controller.desktop_screen.isChecked(),
|
||||
'version': 3,
|
||||
'isSecure': Settings().value('api/authentication enabled'),
|
||||
'isAuthorised': False,
|
||||
'chordNotation': Settings().value('songs/chord notation'),
|
||||
'isStagedActive': self.is_stage_active(),
|
||||
'isLiveActive': self.is_live_active(),
|
||||
'isChordsActive': self.is_chords_active()
|
||||
}
|
||||
|
||||
def poll(self):
|
||||
"""
|
||||
Poll OpenLP to determine the current slide number and item name.
|
||||
"""
|
||||
return {'results': self.raw_poll()}
|
||||
|
||||
def main_poll(self):
|
||||
"""
|
||||
Poll OpenLP to determine the current slide count.
|
||||
"""
|
||||
result = {
|
||||
'slide_count': self.live_controller.slide_count
|
||||
}
|
||||
return json.dumps({'results': result}).encode()
|
||||
|
||||
def reset_cache(self):
|
||||
"""
|
||||
Reset the caches as the web has changed
|
||||
:return:
|
||||
"""
|
||||
self.stage_cache = None
|
||||
self.live_cache = None
|
||||
self.chords.cache = None
|
||||
|
||||
def is_stage_active(self):
|
||||
"""
|
||||
Is stage active - call it and see but only once
|
||||
:return: if stage is active or not
|
||||
"""
|
||||
if self.stage_cache is None:
|
||||
try:
|
||||
page = get_web_page("http://localhost:4316/stage")
|
||||
except:
|
||||
page = None
|
||||
if page:
|
||||
self.stage_cache = True
|
||||
else:
|
||||
self.stage_cache = False
|
||||
return self.stage_cache
|
||||
|
||||
def is_live_active(self):
|
||||
"""
|
||||
Is main active - call it and see but only once
|
||||
:return: if live is active or not
|
||||
"""
|
||||
if self.live_cache is None:
|
||||
try:
|
||||
page = get_web_page("http://localhost:4316/main")
|
||||
except:
|
||||
page = None
|
||||
if page:
|
||||
self.live_cache = True
|
||||
else:
|
||||
self.live_cache = False
|
||||
return self.live_cache
|
||||
|
||||
def is_chords_active(self):
|
||||
"""
|
||||
Is chords active - call it and see but only once
|
||||
:return: if live is active or not
|
||||
"""
|
||||
if self.chords_cache is None:
|
||||
try:
|
||||
page = get_web_page("http://localhost:4316/chords")
|
||||
except:
|
||||
page = None
|
||||
if page:
|
||||
self.chords_cache = True
|
||||
else:
|
||||
self.chords_cache = False
|
||||
return self.chords_cache
|
@ -20,26 +20,28 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import os.path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, translate
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
from openlp.core.common import UiStrings, Registry, Settings, translate
|
||||
from openlp.core.lib import SettingsTab
|
||||
|
||||
ZERO_URL = '0.0.0.0'
|
||||
|
||||
|
||||
class RemoteTab(SettingsTab):
|
||||
class ApiTab(SettingsTab):
|
||||
"""
|
||||
RemoteTab is the Remotes settings tab in the settings dialog.
|
||||
"""
|
||||
def __init__(self, parent, title, visible_title, icon_path):
|
||||
super(RemoteTab, self).__init__(parent, title, visible_title, icon_path)
|
||||
def __init__(self, parent):
|
||||
self.icon_path = ':/plugins/plugin_remote.png'
|
||||
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
|
||||
super(ApiTab, self).__init__(parent, 'api', advanced_translated)
|
||||
self.define_main_window_icon()
|
||||
self.generate_icon()
|
||||
|
||||
def setupUi(self):
|
||||
self.setObjectName('RemoteTab')
|
||||
super(RemoteTab, self).setupUi()
|
||||
self.setObjectName('ApiTab')
|
||||
super(ApiTab, self).setupUi()
|
||||
self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.server_settings_group_box.setObjectName('server_settings_group_box')
|
||||
self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
|
||||
@ -65,8 +67,7 @@ class RemoteTab(SettingsTab):
|
||||
self.http_setting_layout.setObjectName('http_setting_layout')
|
||||
self.port_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.port_label.setObjectName('port_label')
|
||||
self.port_spin_box = QtWidgets.QSpinBox(self.http_settings_group_box)
|
||||
self.port_spin_box.setMaximum(32767)
|
||||
self.port_spin_box = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
self.port_spin_box.setObjectName('port_spin_box')
|
||||
self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
|
||||
self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||
@ -111,6 +112,23 @@ class RemoteTab(SettingsTab):
|
||||
self.password.setObjectName('password')
|
||||
self.user_login_layout.addRow(self.password_label, self.password)
|
||||
self.left_layout.addWidget(self.user_login_group_box)
|
||||
self.update_site_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||
self.update_site_group_box.setCheckable(True)
|
||||
self.update_site_group_box.setChecked(False)
|
||||
self.update_site_group_box.setObjectName('update_site_group_box')
|
||||
self.update_site_layout = QtWidgets.QFormLayout(self.update_site_group_box)
|
||||
self.update_site_layout.setObjectName('update_site_layout')
|
||||
self.current_version_label = QtWidgets.QLabel(self.update_site_group_box)
|
||||
self.current_version_label.setObjectName('current_version_label')
|
||||
self.current_version_value = QtWidgets.QLabel(self.update_site_group_box)
|
||||
self.current_version_value.setObjectName('current_version_value')
|
||||
self.update_site_layout.addRow(self.current_version_label, self.current_version_value)
|
||||
self.master_version_label = QtWidgets.QLabel(self.update_site_group_box)
|
||||
self.master_version_label.setObjectName('master_version_label')
|
||||
self.master_version_value = QtWidgets.QLabel(self.update_site_group_box)
|
||||
self.master_version_value.setObjectName('master_version_value')
|
||||
self.update_site_layout.addRow(self.master_version_label, self.master_version_value)
|
||||
self.left_layout.addWidget(self.update_site_group_box)
|
||||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
||||
self.right_layout.addWidget(self.android_app_group_box)
|
||||
@ -146,16 +164,34 @@ class RemoteTab(SettingsTab):
|
||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
|
||||
self.address_edit.textChanged.connect(self.set_urls)
|
||||
self.port_spin_box.valueChanged.connect(self.set_urls)
|
||||
|
||||
def define_main_window_icon(self):
|
||||
"""
|
||||
Define an icon on the main window to show the state of the server
|
||||
:return:
|
||||
"""
|
||||
self.remote_server_icon = QtWidgets.QLabel(self.main_window.status_bar)
|
||||
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
size_policy.setHorizontalStretch(0)
|
||||
size_policy.setVerticalStretch(0)
|
||||
size_policy.setHeightForWidth(self.remote_server_icon.sizePolicy().hasHeightForWidth())
|
||||
self.remote_server_icon.setSizePolicy(size_policy)
|
||||
self.remote_server_icon.setFrameShadow(QtWidgets.QFrame.Plain)
|
||||
self.remote_server_icon.setLineWidth(1)
|
||||
self.remote_server_icon.setScaledContents(True)
|
||||
self.remote_server_icon.setFixedSize(20, 20)
|
||||
self.remote_server_icon.setObjectName('remote_server_icon')
|
||||
self.main_window.status_bar.insertPermanentWidget(2, self.remote_server_icon)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface')
|
||||
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
|
||||
self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
|
||||
self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
|
||||
self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
|
||||
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
|
||||
self.chords_url_label.setText(translate('RemotePlugin.RemoteTab', 'Chords view URL:'))
|
||||
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
|
||||
self.chords_url_label.setText(translate('RemotePlugin.RemoteTab', 'Chords view URL:'))
|
||||
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
||||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||
@ -170,22 +206,27 @@ class RemoteTab(SettingsTab):
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
||||
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
||||
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
||||
self.aa = UiStrings()
|
||||
self.update_site_group_box.setTitle(UiStrings().WebDownloadText)
|
||||
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
|
||||
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
|
||||
self.current_version_label.setText(translate('RemotePlugin.RemoteTab', 'Current Version number:'))
|
||||
self.master_version_label.setText(translate('RemotePlugin.RemoteTab', 'Latest Version number:'))
|
||||
|
||||
def set_urls(self):
|
||||
"""
|
||||
Update the display based on the data input on the screen
|
||||
"""
|
||||
ip_address = self.get_ip_address(self.address_edit.text())
|
||||
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value())
|
||||
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.text())
|
||||
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
||||
http_url_temp = http_url + 'stage'
|
||||
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
http_url_temp = http_url + 'main'
|
||||
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
|
||||
def get_ip_address(self, ip_address):
|
||||
@staticmethod
|
||||
def get_ip_address(ip_address):
|
||||
"""
|
||||
returns the IP address in dependency of the passed address
|
||||
ip_address == 0.0.0.0: return the IP address of the first valid interface
|
||||
@ -209,7 +250,7 @@ class RemoteTab(SettingsTab):
|
||||
"""
|
||||
Load the configuration and update the server configuration if necessary
|
||||
"""
|
||||
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
|
||||
self.port_spin_box.setText(str(Settings().value(self.settings_section + '/port')))
|
||||
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
||||
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
|
||||
self.twelve_hour_check_box.setChecked(self.twelve_hour)
|
||||
@ -218,16 +259,18 @@ class RemoteTab(SettingsTab):
|
||||
self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
|
||||
self.user_id.setText(Settings().value(self.settings_section + '/user id'))
|
||||
self.password.setText(Settings().value(self.settings_section + '/password'))
|
||||
self.current_version_value.setText(Settings().value('remotes/download version'))
|
||||
self.master_version_value.setText(Registry().get_flag('website_version'))
|
||||
if self.master_version_value.text() == self.current_version_value.text():
|
||||
self.update_site_group_box.setEnabled(False)
|
||||
self.set_urls()
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the configuration and update the server configuration if necessary
|
||||
"""
|
||||
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \
|
||||
Settings().value(self.settings_section + '/port') != self.port_spin_box.value():
|
||||
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text():
|
||||
self.settings_form.register_post_process('remotes_config_updated')
|
||||
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
|
||||
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
|
||||
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
|
||||
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
|
||||
@ -235,6 +278,8 @@ class RemoteTab(SettingsTab):
|
||||
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
||||
Settings().setValue(self.settings_section + '/password', self.password.text())
|
||||
self.generate_icon()
|
||||
if self.update_site_group_box.isChecked():
|
||||
self.settings_form.register_post_process('download_website')
|
||||
|
||||
def on_twelve_hour_check_box_changed(self, check_state):
|
||||
"""
|
142
openlp/core/api/websockets.py
Normal file
142
openlp/core/api/websockets.py
Normal file
@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact
|
||||
with OpenLP. It uses JSON to communicate with the remotes.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Settings, RegistryProperties, OpenLPMixin, Registry
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebSocketWorker(QtCore.QObject):
|
||||
"""
|
||||
A special Qt thread class to allow the WebSockets server to run at the same time as the UI.
|
||||
"""
|
||||
def __init__(self, server):
|
||||
"""
|
||||
Constructor for the thread class.
|
||||
|
||||
:param server: The http server class.
|
||||
"""
|
||||
self.ws_server = server
|
||||
super(WebSocketWorker, self).__init__()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the thread.
|
||||
"""
|
||||
self.ws_server.start_server()
|
||||
|
||||
def stop(self):
|
||||
self.ws_server.stop = True
|
||||
|
||||
|
||||
class WebSocketServer(RegistryProperties, OpenLPMixin):
|
||||
"""
|
||||
Wrapper round a server instance
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialise and start the WebSockets server
|
||||
"""
|
||||
super(WebSocketServer, self).__init__()
|
||||
self.settings_section = 'api'
|
||||
self.worker = WebSocketWorker(self)
|
||||
self.thread = QtCore.QThread()
|
||||
self.worker.moveToThread(self.thread)
|
||||
self.thread.started.connect(self.worker.run)
|
||||
self.thread.start()
|
||||
|
||||
def start_server(self):
|
||||
"""
|
||||
Start the correct server and save the handler
|
||||
"""
|
||||
address = Settings().value(self.settings_section + '/ip address')
|
||||
port = Settings().value(self.settings_section + '/websocket port')
|
||||
self.start_websocket_instance(address, port)
|
||||
# If web socket server start listening
|
||||
if hasattr(self, 'ws_server') and self.ws_server:
|
||||
event_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(event_loop)
|
||||
event_loop.run_until_complete(self.ws_server)
|
||||
event_loop.run_forever()
|
||||
else:
|
||||
log.debug('Failed to start ws server on port {port}'.format(port=port))
|
||||
|
||||
def start_websocket_instance(self, address, port):
|
||||
"""
|
||||
Start the server
|
||||
|
||||
:param address: The server address
|
||||
:param port: The run port
|
||||
"""
|
||||
loop = 1
|
||||
while loop < 4:
|
||||
try:
|
||||
self.ws_server = websockets.serve(self.handle_websocket, address, port)
|
||||
log.debug("Web Socket Server started for class {address} {port}".format(address=address, port=port))
|
||||
break
|
||||
except Exception as e:
|
||||
log.error('Failed to start ws server {why}'.format(why=e))
|
||||
loop += 1
|
||||
time.sleep(0.1)
|
||||
|
||||
@staticmethod
|
||||
async def handle_websocket(request, path):
|
||||
"""
|
||||
Handle web socket requests and return the poll information.
|
||||
Check ever 0.2 seconds to get the latest position and send if changed.
|
||||
Only gets triggered when 1st client attaches
|
||||
|
||||
:param request: request from client
|
||||
:param path: determines the endpoints supported
|
||||
:return:
|
||||
"""
|
||||
log.debug("web socket handler registered with client")
|
||||
previous_poll = None
|
||||
previous_main_poll = None
|
||||
poller = Registry().get('poller')
|
||||
if path == '/state':
|
||||
while True:
|
||||
current_poll = poller.poll()
|
||||
if current_poll != previous_poll:
|
||||
await request.send(json.dumps(current_poll).encode())
|
||||
previous_poll = current_poll
|
||||
await asyncio.sleep(0.2)
|
||||
elif path == '/live_changed':
|
||||
while True:
|
||||
main_poll = poller.main_poll()
|
||||
if main_poll != previous_main_poll:
|
||||
await request.send(main_poll)
|
||||
previous_main_poll = main_poll
|
||||
await asyncio.sleep(0.2)
|
@ -32,7 +32,6 @@ import sys
|
||||
import traceback
|
||||
from chardet.universaldetector import UniversalDetector
|
||||
from ipaddress import IPv4Address, IPv6Address, AddressValueError
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
from subprocess import check_output, CalledProcessError, STDOUT
|
||||
|
||||
@ -65,17 +64,19 @@ def trace_error_handler(logger):
|
||||
|
||||
def check_directory_exists(directory, do_not_log=False):
|
||||
"""
|
||||
Check a theme directory exists and if not create it
|
||||
Check a directory exists and if not create it
|
||||
|
||||
:param directory: The directory to make sure exists
|
||||
:param do_not_log: To not log anything. This is need for the start up, when the log isn't ready.
|
||||
:param pathlib.Path directory: The directory to make sure exists
|
||||
:param bool do_not_log: To not log anything. This is need for the start up, when the log isn't ready.
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if not do_not_log:
|
||||
log.debug('check_directory_exists {text}'.format(text=directory))
|
||||
try:
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
except IOError as e:
|
||||
if not directory.exists():
|
||||
directory.mkdir(parents=True)
|
||||
except IOError:
|
||||
if not do_not_log:
|
||||
log.exception('failed to check if directory exists or create directory')
|
||||
|
||||
@ -85,17 +86,13 @@ def extension_loader(glob_pattern, excluded_files=[]):
|
||||
A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
|
||||
importers.
|
||||
|
||||
:param glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
|
||||
application directory. i.e. openlp/plugins/*/*plugin.py
|
||||
:type glob_pattern: str
|
||||
|
||||
:param excluded_files: A list of file names to exclude that the glob pattern may find.
|
||||
:type excluded_files: list of strings
|
||||
|
||||
:param str glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
|
||||
application directory. i.e. plugins/*/*plugin.py
|
||||
:param list[str] excluded_files: A list of file names to exclude that the glob pattern may find.
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
app_dir = Path(AppLocation.get_directory(AppLocation.AppDir)).parent
|
||||
app_dir = AppLocation.get_directory(AppLocation.AppDir)
|
||||
for extension_path in app_dir.glob(glob_pattern):
|
||||
extension_path = extension_path.relative_to(app_dir)
|
||||
if extension_path.name in excluded_files:
|
||||
@ -106,21 +103,19 @@ def extension_loader(glob_pattern, excluded_files=[]):
|
||||
except (ImportError, OSError):
|
||||
# On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
|
||||
log.warning('Failed to import {module_name} on path {extension_path}'
|
||||
.format(module_name=module_name, extension_path=str(extension_path)))
|
||||
.format(module_name=module_name, extension_path=extension_path))
|
||||
|
||||
|
||||
def path_to_module(path):
|
||||
"""
|
||||
Convert a path to a module name (i.e openlp.core.common)
|
||||
|
||||
:param path: The path to convert to a module name.
|
||||
:type path: Path
|
||||
|
||||
:param pathlib.Path path: The path to convert to a module name.
|
||||
:return: The module name.
|
||||
:rtype: str
|
||||
"""
|
||||
module_path = path.with_suffix('')
|
||||
return '.'.join(module_path.parts)
|
||||
return 'openlp.' + '.'.join(module_path.parts)
|
||||
|
||||
|
||||
def get_frozen_path(frozen_option, non_frozen_option):
|
||||
@ -378,20 +373,22 @@ def split_filename(path):
|
||||
return os.path.split(path)
|
||||
|
||||
|
||||
def delete_file(file_path_name):
|
||||
def delete_file(file_path):
|
||||
"""
|
||||
Deletes a file from the system.
|
||||
|
||||
:param file_path_name: The file, including path, to delete.
|
||||
:param pathlib.Path file_path: The file, including path, to delete.
|
||||
:return: True if the deletion was successful, or the file never existed. False otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
if not file_path_name:
|
||||
if not file_path:
|
||||
return False
|
||||
try:
|
||||
if os.path.exists(file_path_name):
|
||||
os.remove(file_path_name)
|
||||
if file_path.exists():
|
||||
file_path.unlink()
|
||||
return True
|
||||
except (IOError, OSError):
|
||||
log.exception("Unable to delete file {text}".format(text=file_path_name))
|
||||
log.exception('Unable to delete file {file_path}'.format(file_path=file_path))
|
||||
return False
|
||||
|
||||
|
||||
@ -411,18 +408,19 @@ def get_images_filter():
|
||||
return IMAGES_FILTER
|
||||
|
||||
|
||||
def is_not_image_file(file_name):
|
||||
def is_not_image_file(file_path):
|
||||
"""
|
||||
Validate that the file is not an image file.
|
||||
|
||||
:param file_name: File name to be checked.
|
||||
:param pathlib.Path file_path: The file to be checked.
|
||||
:return: If the file is not an image
|
||||
:rtype: bool
|
||||
"""
|
||||
if not file_name:
|
||||
if not (file_path and file_path.exists()):
|
||||
return True
|
||||
else:
|
||||
formats = [bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()]
|
||||
file_part, file_extension = os.path.splitext(str(file_name))
|
||||
if file_extension[1:].lower() in formats and os.path.exists(file_name):
|
||||
if file_path.suffix[1:].lower() in formats:
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -431,10 +429,10 @@ def clean_filename(filename):
|
||||
"""
|
||||
Removes invalid characters from the given ``filename``.
|
||||
|
||||
:param filename: The "dirty" file name to clean.
|
||||
:param str filename: The "dirty" file name to clean.
|
||||
:return: The cleaned string
|
||||
:rtype: str
|
||||
"""
|
||||
if not isinstance(filename, str):
|
||||
filename = str(filename, 'utf-8')
|
||||
return INVALID_FILE_CHARS.sub('_', CONTROL_CHARS.sub('', filename))
|
||||
|
||||
|
||||
@ -442,8 +440,9 @@ def check_binary_exists(program_path):
|
||||
"""
|
||||
Function that checks whether a binary exists.
|
||||
|
||||
:param program_path: The full path to the binary to check.
|
||||
:param pathlib.Path program_path: The full path to the binary to check.
|
||||
:return: program output to be parsed
|
||||
:rtype: bytes
|
||||
"""
|
||||
log.debug('testing program_path: {text}'.format(text=program_path))
|
||||
try:
|
||||
@ -453,26 +452,27 @@ def check_binary_exists(program_path):
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
else:
|
||||
startupinfo = None
|
||||
runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo)
|
||||
run_log = check_output([str(program_path), '--help'], stderr=STDOUT, startupinfo=startupinfo)
|
||||
except CalledProcessError as e:
|
||||
runlog = e.output
|
||||
run_log = e.output
|
||||
except Exception:
|
||||
trace_error_handler(log)
|
||||
runlog = ''
|
||||
log.debug('check_output returned: {text}'.format(text=runlog))
|
||||
return runlog
|
||||
run_log = ''
|
||||
log.debug('check_output returned: {text}'.format(text=run_log))
|
||||
return run_log
|
||||
|
||||
|
||||
def get_file_encoding(filename):
|
||||
def get_file_encoding(file_path):
|
||||
"""
|
||||
Utility function to incrementally detect the file encoding.
|
||||
|
||||
:param filename: Filename for the file to determine the encoding for. Str
|
||||
:param pathlib.Path file_path: Filename for the file to determine the encoding for.
|
||||
:return: A dict with the keys 'encoding' and 'confidence'
|
||||
:rtype: dict[str, float]
|
||||
"""
|
||||
detector = UniversalDetector()
|
||||
try:
|
||||
with open(filename, 'rb') as detect_file:
|
||||
with file_path.open('rb') as detect_file:
|
||||
while not detector.done:
|
||||
chunk = detect_file.read(1024)
|
||||
if not chunk:
|
||||
|
@ -25,6 +25,7 @@ The :mod:`openlp.core.common.applocation` module provides an utility for OpenLP
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from openlp.core.common import Settings, is_win, is_macosx
|
||||
|
||||
@ -42,6 +43,9 @@ from openlp.core.common import check_directory_exists, get_frozen_path
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
FROZEN_APP_PATH = Path(sys.argv[0]).parent
|
||||
APP_PATH = Path(openlp.__file__).parent
|
||||
|
||||
|
||||
class AppLocation(object):
|
||||
"""
|
||||
@ -54,29 +58,23 @@ class AppLocation(object):
|
||||
CacheDir = 5
|
||||
LanguageDir = 6
|
||||
|
||||
# Base path where data/config/cache dir is located
|
||||
BaseDir = None
|
||||
|
||||
@staticmethod
|
||||
def get_directory(dir_type=AppDir):
|
||||
"""
|
||||
Return the appropriate directory according to the directory type.
|
||||
|
||||
:param dir_type: The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
|
||||
:type dir_type: AppLocation Enum
|
||||
|
||||
:return: The requested path
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
if dir_type == AppLocation.AppDir:
|
||||
return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
|
||||
if dir_type == AppLocation.AppDir or dir_type == AppLocation.VersionDir:
|
||||
return get_frozen_path(FROZEN_APP_PATH, APP_PATH)
|
||||
elif dir_type == AppLocation.PluginsDir:
|
||||
app_path = os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
return get_frozen_path(os.path.join(app_path, 'plugins'),
|
||||
os.path.join(os.path.dirname(openlp.__file__), 'plugins'))
|
||||
elif dir_type == AppLocation.VersionDir:
|
||||
return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
|
||||
return get_frozen_path(FROZEN_APP_PATH, APP_PATH) / 'plugins'
|
||||
elif dir_type == AppLocation.LanguageDir:
|
||||
app_path = get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), _get_os_dir_path(dir_type))
|
||||
return os.path.join(app_path, 'i18n')
|
||||
elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
|
||||
return os.path.join(AppLocation.BaseDir, 'data')
|
||||
return get_frozen_path(FROZEN_APP_PATH, _get_os_dir_path(dir_type)) / 'i18n'
|
||||
else:
|
||||
return _get_os_dir_path(dir_type)
|
||||
|
||||
@ -84,47 +82,49 @@ class AppLocation(object):
|
||||
def get_data_path():
|
||||
"""
|
||||
Return the path OpenLP stores all its data under.
|
||||
|
||||
:return: The data path to use.
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
# Check if we have a different data location.
|
||||
if Settings().contains('advanced/data path'):
|
||||
path = Settings().value('advanced/data path')
|
||||
path = Path(Settings().value('advanced/data path'))
|
||||
else:
|
||||
path = AppLocation.get_directory(AppLocation.DataDir)
|
||||
check_directory_exists(path)
|
||||
return os.path.normpath(path)
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def get_files(section=None, extension=None):
|
||||
def get_files(section=None, extension=''):
|
||||
"""
|
||||
Get a list of files from the data files path.
|
||||
|
||||
:param section: Defaults to *None*. The section of code getting the files - used to load from a section's
|
||||
data subdirectory.
|
||||
:param extension:
|
||||
Defaults to *None*. The extension to search for. For example::
|
||||
|
||||
'.png'
|
||||
:param None | str section: Defaults to *None*. The section of code getting the files - used to load from a
|
||||
section's data subdirectory.
|
||||
:param str extension: Defaults to ''. The extension to search for. For example::
|
||||
'.png'
|
||||
:return: List of files found.
|
||||
:rtype: list[pathlib.Path]
|
||||
"""
|
||||
path = AppLocation.get_data_path()
|
||||
if section:
|
||||
path = os.path.join(path, section)
|
||||
path = path / section
|
||||
try:
|
||||
files = os.listdir(path)
|
||||
file_paths = path.glob('*' + extension)
|
||||
return [file_path.relative_to(path) for file_path in file_paths]
|
||||
except OSError:
|
||||
return []
|
||||
if extension:
|
||||
return [filename for filename in files if extension == os.path.splitext(filename)[1]]
|
||||
else:
|
||||
# no filtering required
|
||||
return files
|
||||
|
||||
@staticmethod
|
||||
def get_section_data_path(section):
|
||||
"""
|
||||
Return the path a particular module stores its data under.
|
||||
|
||||
:type section: str
|
||||
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
data_path = AppLocation.get_data_path()
|
||||
path = os.path.join(data_path, section)
|
||||
path = AppLocation.get_data_path() / section
|
||||
check_directory_exists(path)
|
||||
return path
|
||||
|
||||
@ -132,36 +132,41 @@ class AppLocation(object):
|
||||
def _get_os_dir_path(dir_type):
|
||||
"""
|
||||
Return a path based on which OS and environment we are running in.
|
||||
|
||||
:param dir_type: AppLocation Enum of the requested path type
|
||||
:return: The requested path
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
# If running from source, return the language directory from the source directory
|
||||
if dir_type == AppLocation.LanguageDir:
|
||||
directory = os.path.abspath(os.path.join(os.path.dirname(openlp.__file__), '..', 'resources'))
|
||||
if os.path.exists(directory):
|
||||
directory = Path(openlp.__file__, '..', '..').resolve() / 'resources'
|
||||
if directory.exists():
|
||||
return directory
|
||||
if is_win():
|
||||
openlp_folder_path = Path(os.getenv('APPDATA'), 'openlp')
|
||||
if dir_type == AppLocation.DataDir:
|
||||
return os.path.join(str(os.getenv('APPDATA')), 'openlp', 'data')
|
||||
return openlp_folder_path / 'data'
|
||||
elif dir_type == AppLocation.LanguageDir:
|
||||
return os.path.dirname(openlp.__file__)
|
||||
return os.path.join(str(os.getenv('APPDATA')), 'openlp')
|
||||
return Path(openlp.__file__).parent
|
||||
return openlp_folder_path
|
||||
elif is_macosx():
|
||||
openlp_folder_path = Path(os.getenv('HOME'), 'Library', 'Application Support', 'openlp')
|
||||
if dir_type == AppLocation.DataDir:
|
||||
return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp', 'Data')
|
||||
return openlp_folder_path / 'Data'
|
||||
elif dir_type == AppLocation.LanguageDir:
|
||||
return os.path.dirname(openlp.__file__)
|
||||
return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp')
|
||||
return Path(openlp.__file__).parent
|
||||
return openlp_folder_path
|
||||
else:
|
||||
if dir_type == AppLocation.LanguageDir:
|
||||
for prefix in ['/usr/local', '/usr']:
|
||||
directory = os.path.join(prefix, 'share', 'openlp')
|
||||
if os.path.exists(directory):
|
||||
return directory
|
||||
return os.path.join('/usr', 'share', 'openlp')
|
||||
directory = Path('/usr', 'local', 'share', 'openlp')
|
||||
if directory.exists():
|
||||
return directory
|
||||
return Path('/usr', 'share', 'openlp')
|
||||
if XDG_BASE_AVAILABLE:
|
||||
if dir_type == AppLocation.DataDir:
|
||||
return os.path.join(str(BaseDirectory.xdg_data_home), 'openlp')
|
||||
if dir_type == AppLocation.DataDir or dir_type == AppLocation.CacheDir:
|
||||
return Path(BaseDirectory.xdg_data_home, 'openlp')
|
||||
elif dir_type == AppLocation.CacheDir:
|
||||
return os.path.join(str(BaseDirectory.xdg_cache_home), 'openlp')
|
||||
return Path(BaseDirectory.xdg_cache_home, 'openlp')
|
||||
if dir_type == AppLocation.DataDir:
|
||||
return os.path.join(str(os.getenv('HOME')), '.openlp', 'data')
|
||||
return os.path.join(str(os.getenv('HOME')), '.openlp')
|
||||
return Path(os.getenv('HOME'), '.openlp', 'data')
|
||||
return Path(os.getenv('HOME'), '.openlp')
|
||||
|
@ -25,8 +25,10 @@ The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
@ -215,6 +217,7 @@ def url_get_file(callback, url, f_path, sha256=None):
|
||||
block_count = 0
|
||||
block_size = 4096
|
||||
retries = 0
|
||||
log.debug("url_get_file: " + url)
|
||||
while True:
|
||||
try:
|
||||
filename = open(f_path, "wb")
|
||||
@ -253,4 +256,17 @@ def url_get_file(callback, url, f_path, sha256=None):
|
||||
return True
|
||||
|
||||
|
||||
def ping(host):
|
||||
"""
|
||||
Returns True if host responds to a ping request
|
||||
"""
|
||||
# Ping parameters as function of OS
|
||||
ping_str = "-n 1" if platform.system().lower() == "windows" else "-c 1"
|
||||
args = "ping " + " " + ping_str + " " + host
|
||||
need_sh = False if platform.system().lower() == "windows" else True
|
||||
|
||||
# Ping
|
||||
return subprocess.call(args, shell=need_sh) == 0
|
||||
|
||||
|
||||
__all__ = ['get_web_page']
|
||||
|
@ -53,7 +53,7 @@ class LanguageManager(object):
|
||||
"""
|
||||
if LanguageManager.auto_language:
|
||||
language = QtCore.QLocale.system().name()
|
||||
lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
|
||||
lang_path = str(AppLocation.get_directory(AppLocation.LanguageDir))
|
||||
app_translator = QtCore.QTranslator()
|
||||
app_translator.load(language, lang_path)
|
||||
# A translator for buttons and other default strings provided by Qt.
|
||||
@ -72,7 +72,7 @@ class LanguageManager(object):
|
||||
Find all available language files in this OpenLP install
|
||||
"""
|
||||
log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir)))
|
||||
trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
|
||||
trans_dir = QtCore.QDir(str(AppLocation.get_directory(AppLocation.LanguageDir)))
|
||||
file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
|
||||
# Remove qm files from the list which start with "qt".
|
||||
file_names = [file_ for file_ in file_names if not file_.startswith('qt')]
|
||||
|
61
openlp/core/common/path.py
Normal file
61
openlp/core/common/path.py
Normal file
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def path_to_str(path):
|
||||
"""
|
||||
A utility function to convert a Path object or NoneType to a string equivalent.
|
||||
|
||||
:param path: The value to convert to a string
|
||||
:type: pathlib.Path or None
|
||||
|
||||
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
|
||||
:rtype: str
|
||||
"""
|
||||
if not isinstance(path, Path) and path is not None:
|
||||
raise TypeError('parameter \'path\' must be of type Path or NoneType')
|
||||
if path is None:
|
||||
return ''
|
||||
else:
|
||||
return str(path)
|
||||
|
||||
|
||||
def str_to_path(string):
|
||||
"""
|
||||
A utility function to convert a str object to a Path or NoneType.
|
||||
|
||||
This function is of particular use because initating a Path object with an empty string causes the Path object to
|
||||
point to the current working directory.
|
||||
|
||||
:param string: The string to convert
|
||||
:type string: str
|
||||
|
||||
:return: None if :param:`string` is empty, or a Path object representation of :param:`string`
|
||||
:rtype: pathlib.Path or None
|
||||
"""
|
||||
if not isinstance(string, str):
|
||||
raise TypeError('parameter \'string\' must be of type str')
|
||||
if string == '':
|
||||
return None
|
||||
return Path(string)
|
@ -121,6 +121,7 @@ class Settings(QtCore.QSettings):
|
||||
'advanced/enable exit confirmation': True,
|
||||
'advanced/expand service item': False,
|
||||
'advanced/hide mouse': True,
|
||||
'advanced/ignore aspect ratio': False,
|
||||
'advanced/is portable': False,
|
||||
'advanced/max recent files': 20,
|
||||
'advanced/print file meta data': False,
|
||||
@ -134,6 +135,14 @@ class Settings(QtCore.QSettings):
|
||||
'advanced/single click service preview': False,
|
||||
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||
'advanced/search as type': True,
|
||||
'api/twelve hour': True,
|
||||
'api/port': 4316,
|
||||
'api/websocket port': 4317,
|
||||
'api/user id': 'openlp',
|
||||
'api/password': 'password',
|
||||
'api/authentication enabled': False,
|
||||
'api/ip address': '0.0.0.0',
|
||||
'api/thumbnails': True,
|
||||
'crashreport/last directory': '',
|
||||
'formattingTags/html_tags': '',
|
||||
'core/audio repeat list': False,
|
||||
@ -214,6 +223,17 @@ class Settings(QtCore.QSettings):
|
||||
('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
|
||||
('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
|
||||
('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
|
||||
('advanced/default image', '/core/logo file', []), # Default image renamed + moved to general after 2.4.
|
||||
('remotes/https enabled', '', []),
|
||||
('remotes/https port', '', []),
|
||||
('remotes/twelve hour', 'api/twelve hour', []),
|
||||
('remotes/port', 'api/port', []),
|
||||
('remotes/websocket port', 'api/websocket port', []),
|
||||
('remotes/user id', 'api/user id', []),
|
||||
('remotes/password', 'api/password', []),
|
||||
('remotes/authentication enabled', 'api/authentication enabled', []),
|
||||
('remotes/ip address', 'api/ip address', []),
|
||||
('remotes/thumbnails', 'api/thumbnails', []),
|
||||
('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
|
||||
('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
|
||||
('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
|
||||
@ -482,31 +502,3 @@ class Settings(QtCore.QSettings):
|
||||
if isinstance(default_value, int):
|
||||
return int(setting)
|
||||
return setting
|
||||
|
||||
def get_files_from_config(self, plugin):
|
||||
"""
|
||||
This removes the settings needed for old way we saved files (e. g. the image paths for the image plugin). A list
|
||||
of file paths are returned.
|
||||
|
||||
**Note**: Only a list of paths is returned; this does not convert anything!
|
||||
|
||||
:param plugin: The Plugin object.The caller has to convert/save the list himself; o
|
||||
"""
|
||||
files_list = []
|
||||
# We need QSettings instead of Settings here to bypass our central settings dict.
|
||||
# Do NOT do this anywhere else!
|
||||
settings = QtCore.QSettings(self.fileName(), Settings.IniFormat)
|
||||
settings.beginGroup(plugin.settings_section)
|
||||
if settings.contains('{name} count'.format(name=plugin.name)):
|
||||
# Get the count.
|
||||
list_count = int(settings.value('{name} count'.format(name=plugin.name), 0))
|
||||
if list_count:
|
||||
for counter in range(list_count):
|
||||
# The keys were named e. g.: "image 0"
|
||||
item = settings.value('{name} {counter:d}'.format(name=plugin.name, counter=counter), '')
|
||||
if item:
|
||||
files_list.append(item)
|
||||
settings.remove('{name} {counter:d}'.format(name=plugin.name, counter=counter))
|
||||
settings.remove('{name} count'.format(name=plugin.name))
|
||||
settings.endGroup()
|
||||
return files_list
|
||||
|
@ -120,9 +120,8 @@ class UiStrings(object):
|
||||
self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')
|
||||
self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
|
||||
self.NoResults = translate('OpenLP.Ui', 'No Search Results')
|
||||
self.OLP = translate('OpenLP.Ui', 'OpenLP')
|
||||
self.OLPV2 = "{name} {version}".format(name=self.OLP, version="2")
|
||||
self.OLPV2x = "{name} {version}".format(name=self.OLP, version="2.4")
|
||||
self.OpenLP = translate('OpenLP.Ui', 'OpenLP')
|
||||
self.OpenLPv2AndUp = translate('OpenLP.Ui', 'OpenLP 2.0 and up')
|
||||
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
|
||||
self.OpenService = translate('OpenLP.Ui', 'Open service.')
|
||||
self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
|
||||
@ -154,6 +153,7 @@ class UiStrings(object):
|
||||
self.Split = translate('OpenLP.Ui', 'Optional &Split')
|
||||
self.SplitToolTip = translate('OpenLP.Ui',
|
||||
'Split a slide into two only if it does not fit on the screen as one slide.')
|
||||
self.StartingImport = translate('OpenLP.Ui', 'Starting import...')
|
||||
self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop')
|
||||
self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End')
|
||||
self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')
|
||||
@ -167,6 +167,7 @@ class UiStrings(object):
|
||||
self.View = translate('OpenLP.Ui', 'View')
|
||||
self.ViewMode = translate('OpenLP.Ui', 'View Mode')
|
||||
self.Video = translate('OpenLP.Ui', 'Video')
|
||||
self.WebDownloadText = translate('OpenLP.Ui', 'Web Interface, Download and Install latest Version')
|
||||
book_chapter = translate('OpenLP.Ui', 'Book Chapter')
|
||||
chapter = translate('OpenLP.Ui', 'Chapter')
|
||||
verse = translate('OpenLP.Ui', 'Verse')
|
||||
|
@ -1,3 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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.common` module downloads the version details for OpenLP.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
@ -12,7 +36,8 @@ from subprocess import Popen, PIPE
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import AppLocation, Settings
|
||||
from openlp.core.common import AppLocation, Registry, Settings
|
||||
from openlp.core.common.httputils import ping
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -42,12 +67,18 @@ class VersionThread(QtCore.QThread):
|
||||
"""
|
||||
self.sleep(1)
|
||||
log.debug('Version thread - run')
|
||||
app_version = get_application_version()
|
||||
version = check_latest_version(app_version)
|
||||
log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
|
||||
version2=LooseVersion(str(app_version['full']))))
|
||||
if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
|
||||
self.main_window.openlp_version_check.emit('{version}'.format(version=version))
|
||||
found = ping("openlp.io")
|
||||
Registry().set_flag('internet_present', found)
|
||||
update_check = Settings().value('core/update check')
|
||||
if found:
|
||||
Registry().execute('get_website_version')
|
||||
if update_check:
|
||||
app_version = get_application_version()
|
||||
version = check_latest_version(app_version)
|
||||
log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
|
||||
version2=LooseVersion(str(app_version['full']))))
|
||||
if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
|
||||
self.main_window.openlp_version_check.emit('{version}'.format(version=version))
|
||||
|
||||
|
||||
def get_application_version():
|
||||
@ -95,7 +126,7 @@ def get_application_version():
|
||||
full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
|
||||
else:
|
||||
# We're not running the development version, let's use the file.
|
||||
file_path = AppLocation.get_directory(AppLocation.VersionDir)
|
||||
file_path = str(AppLocation.get_directory(AppLocation.VersionDir))
|
||||
file_path = os.path.join(file_path, '.version')
|
||||
version_file = None
|
||||
try:
|
||||
|
@ -83,30 +83,28 @@ class ServiceItemAction(object):
|
||||
Next = 3
|
||||
|
||||
|
||||
def get_text_file_string(text_file):
|
||||
def get_text_file_string(text_file_path):
|
||||
"""
|
||||
Open a file and return its content as unicode string. If the supplied file name is not a file then the function
|
||||
Open a file and return its content as a string. If the supplied file path is not a file then the function
|
||||
returns False. If there is an error loading the file or the content can't be decoded then the function will return
|
||||
None.
|
||||
|
||||
:param text_file: The name of the file.
|
||||
:return: The file as a single string
|
||||
:param pathlib.Path text_file_path: The path to the file.
|
||||
:return: The contents of the file, False if the file does not exist, or None if there is an Error reading or
|
||||
decoding the file.
|
||||
:rtype: str | False | None
|
||||
"""
|
||||
if not os.path.isfile(text_file):
|
||||
if not text_file_path.is_file():
|
||||
return False
|
||||
file_handle = None
|
||||
content = None
|
||||
try:
|
||||
file_handle = open(text_file, 'r', encoding='utf-8')
|
||||
if file_handle.read(3) != '\xEF\xBB\xBF':
|
||||
# no BOM was found
|
||||
file_handle.seek(0)
|
||||
content = file_handle.read()
|
||||
with text_file_path.open('r', encoding='utf-8') as file_handle:
|
||||
if file_handle.read(3) != '\xEF\xBB\xBF':
|
||||
# no BOM was found
|
||||
file_handle.seek(0)
|
||||
content = file_handle.read()
|
||||
except (IOError, UnicodeError):
|
||||
log.exception('Failed to open text file {text}'.format(text=text_file))
|
||||
finally:
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
log.exception('Failed to open text file {text}'.format(text=text_file_path))
|
||||
return content
|
||||
|
||||
|
||||
@ -230,7 +228,7 @@ def validate_thumb(file_path, thumb_path):
|
||||
return image_date <= thumb_date
|
||||
|
||||
|
||||
def resize_image(image_path, width, height, background='#000000'):
|
||||
def resize_image(image_path, width, height, background='#000000', ignore_aspect_ratio=False):
|
||||
"""
|
||||
Resize an image to fit on the current screen.
|
||||
|
||||
@ -247,7 +245,7 @@ def resize_image(image_path, width, height, background='#000000'):
|
||||
image_ratio = reader.size().width() / reader.size().height()
|
||||
resize_ratio = width / height
|
||||
# Figure out the size we want to resize the image to (keep aspect ratio).
|
||||
if image_ratio == resize_ratio:
|
||||
if image_ratio == resize_ratio or ignore_aspect_ratio:
|
||||
size = QtCore.QSize(width, height)
|
||||
elif image_ratio < resize_ratio:
|
||||
# Use the image's height as reference for the new size.
|
||||
@ -608,8 +606,42 @@ def create_separated_list(string_list):
|
||||
return list_to_string
|
||||
|
||||
|
||||
def replace_params(args, kwargs, params):
|
||||
"""
|
||||
Apply a transformation function to the specified args or kwargs
|
||||
|
||||
:param args: Positional arguments
|
||||
:type args: (,)
|
||||
|
||||
:param kwargs: Key Word arguments
|
||||
:type kwargs: dict
|
||||
|
||||
:param params: A tuple of tuples with the position and the key word to replace.
|
||||
:type params: ((int, str, path_to_str),)
|
||||
|
||||
:return: The modified positional and keyword arguments
|
||||
:rtype: (tuple, dict)
|
||||
|
||||
|
||||
Usage:
|
||||
Take a method with the following signature, and assume we which to apply the str function to arg2:
|
||||
def method(arg1=None, arg2=None, arg3=None)
|
||||
|
||||
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
|
||||
would call this function as follows:
|
||||
|
||||
replace_params(args, kwargs, ((1, 'arg2', str),))
|
||||
"""
|
||||
args = list(args)
|
||||
for position, key_word, transform in params:
|
||||
if len(args) > position:
|
||||
args[position] = transform(args[position])
|
||||
elif key_word in kwargs:
|
||||
kwargs[key_word] = transform(kwargs[key_word])
|
||||
return tuple(args), kwargs
|
||||
|
||||
|
||||
from .exceptions import ValidationError
|
||||
from .filedialog import FileDialog
|
||||
from .screen import ScreenList
|
||||
from .formattingtags import FormattingTags
|
||||
from .plugin import PluginStatus, StringContent, Plugin
|
||||
@ -621,5 +653,5 @@ from .imagemanager import ImageManager
|
||||
from .renderer import Renderer
|
||||
from .mediamanageritem import MediaManagerItem
|
||||
from .projector.db import ProjectorDB, Projector
|
||||
from .projector.pjlink1 import PJLink
|
||||
from .projector.pjlink import PJLink
|
||||
from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
|
||||
|
@ -274,9 +274,9 @@ def delete_database(plugin_name, db_file_name=None):
|
||||
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
|
||||
"""
|
||||
if db_file_name:
|
||||
db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), db_file_name)
|
||||
db_file_path = AppLocation.get_section_data_path(plugin_name) / db_file_name
|
||||
else:
|
||||
db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), plugin_name)
|
||||
db_file_path = AppLocation.get_section_data_path(plugin_name) / plugin_name
|
||||
return delete_file(db_file_path)
|
||||
|
||||
|
||||
|
@ -31,7 +31,7 @@ import queue
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.common import Registry, Settings
|
||||
from openlp.core.lib import ScreenList, resize_image, image_to_byte
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -56,7 +56,7 @@ class ImageThread(QtCore.QThread):
|
||||
"""
|
||||
Run the thread.
|
||||
"""
|
||||
self.image_manager._process()
|
||||
self.image_manager.process()
|
||||
|
||||
|
||||
class Priority(object):
|
||||
@ -235,8 +235,15 @@ class ImageManager(QtCore.QObject):
|
||||
def get_image(self, path, source, width=-1, height=-1):
|
||||
"""
|
||||
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
|
||||
|
||||
:param: path: The image path
|
||||
:param: source: The source of the image
|
||||
:param: background: The image background colour
|
||||
:param: width: The processed image width
|
||||
:param: height: The processed image height
|
||||
"""
|
||||
log.debug('getImage {path}'.format(path=path))
|
||||
log.debug('get_image {path} {source} {width} {height}'.format(path=path, source=source,
|
||||
width=width, height=height))
|
||||
image = self._cache[(path, source, width, height)]
|
||||
if image.image is None:
|
||||
self._conversion_queue.modify_priority(image, Priority.High)
|
||||
@ -255,8 +262,15 @@ class ImageManager(QtCore.QObject):
|
||||
def get_image_bytes(self, path, source, width=-1, height=-1):
|
||||
"""
|
||||
Returns the byte string for an image. If not present wait for the background thread to process it.
|
||||
|
||||
:param: path: The image path
|
||||
:param: source: The source of the image
|
||||
:param: background: The image background colour
|
||||
:param: width: The processed image width
|
||||
:param: height: The processed image height
|
||||
"""
|
||||
log.debug('get_image_bytes {path}'.format(path=path))
|
||||
log.debug('get_image_bytes {path} {source} {width} {height}'.format(path=path, source=source,
|
||||
width=width, height=height))
|
||||
image = self._cache[(path, source, width, height)]
|
||||
if image.image_bytes is None:
|
||||
self._conversion_queue.modify_priority(image, Priority.Urgent)
|
||||
@ -270,9 +284,16 @@ class ImageManager(QtCore.QObject):
|
||||
def add_image(self, path, source, background, width=-1, height=-1):
|
||||
"""
|
||||
Add image to cache if it is not already there.
|
||||
|
||||
:param: path: The image path
|
||||
:param: source: The source of the image
|
||||
:param: background: The image background colour
|
||||
:param: width: The processed image width
|
||||
:param: height: The processed image height
|
||||
"""
|
||||
log.debug('add_image {path}'.format(path=path))
|
||||
if (path, source, width, height) not in self._cache:
|
||||
log.debug('add_image {path} {source} {width} {height}'.format(path=path, source=source,
|
||||
width=width, height=height))
|
||||
if not (path, source, width, height) in self._cache:
|
||||
image = Image(path, source, background, width, height)
|
||||
self._cache[(path, source, width, height)] = image
|
||||
self._conversion_queue.put((image.priority, image.secondary_priority, image))
|
||||
@ -286,11 +307,11 @@ class ImageManager(QtCore.QObject):
|
||||
if not self.image_thread.isRunning():
|
||||
self.image_thread.start()
|
||||
|
||||
def _process(self):
|
||||
def process(self):
|
||||
"""
|
||||
Controls the processing called from a ``QtCore.QThread``.
|
||||
"""
|
||||
log.debug('_process - started')
|
||||
log.debug('process - started')
|
||||
while not self._conversion_queue.empty() and not self.stop_manager:
|
||||
self._process_cache()
|
||||
log.debug('_process - ended')
|
||||
@ -306,7 +327,8 @@ class ImageManager(QtCore.QObject):
|
||||
# Let's see if the image was requested with specific dimensions
|
||||
width = self.width if image.width == -1 else image.width
|
||||
height = self.height if image.height == -1 else image.height
|
||||
image.image = resize_image(image.path, width, height, image.background)
|
||||
image.image = resize_image(image.path, width, height, image.background,
|
||||
Settings().value('advanced/ignore aspect ratio'))
|
||||
# Set the priority to Lowest and stop here as we need to process more important images first.
|
||||
if image.priority == Priority.Normal:
|
||||
self._conversion_queue.modify_priority(image, Priority.Lowest)
|
||||
|
@ -26,12 +26,14 @@ import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
|
||||
from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext
|
||||
from openlp.core.lib.searchedit import SearchEdit
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
|
||||
@ -309,13 +311,14 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
|
||||
"""
|
||||
Add a file to the list widget to make it available for showing
|
||||
"""
|
||||
files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
|
||||
Settings().value(self.settings_section + '/last directory'),
|
||||
self.on_new_file_masks)
|
||||
log.info('New files(s) {files}'.format(files=files))
|
||||
if files:
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self, self.on_new_prompt,
|
||||
str_to_path(Settings().value(self.settings_section + '/last directory')),
|
||||
self.on_new_file_masks)
|
||||
log.info('New files(s) {file_paths}'.format(file_paths=file_paths))
|
||||
if file_paths:
|
||||
self.application.set_busy_cursor()
|
||||
self.validate_and_load(files)
|
||||
self.validate_and_load([path_to_str(path) for path in file_paths])
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def load_file(self, data):
|
||||
|
61
openlp/core/lib/path.py
Normal file
61
openlp/core/lib/path.py
Normal file
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def path_to_str(path):
|
||||
"""
|
||||
A utility function to convert a Path object or NoneType to a string equivalent.
|
||||
|
||||
:param path: The value to convert to a string
|
||||
:type: pathlib.Path or None
|
||||
|
||||
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
|
||||
:rtype: str
|
||||
"""
|
||||
if not isinstance(path, Path) and path is not None:
|
||||
raise TypeError('parameter \'path\' must be of type Path or NoneType')
|
||||
if path is None:
|
||||
return ''
|
||||
else:
|
||||
return str(path)
|
||||
|
||||
|
||||
def str_to_path(string):
|
||||
"""
|
||||
A utility function to convert a str object to a Path or NoneType.
|
||||
|
||||
This function is of particular use because initating a Path object with an empty string causes the Path object to
|
||||
point to the current working directory.
|
||||
|
||||
:param string: The string to convert
|
||||
:type string: str
|
||||
|
||||
:return: None if :param:`string` is empty, or a Path object representation of :param:`string`
|
||||
:rtype: pathlib.Path or None
|
||||
"""
|
||||
if not isinstance(string, str):
|
||||
raise TypeError('parameter \'string\' must be of type str')
|
||||
if string == '':
|
||||
return None
|
||||
return Path(string)
|
@ -40,7 +40,7 @@ class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
"""
|
||||
super(PluginManager, self).__init__(parent)
|
||||
self.log_info('Plugin manager Initialising')
|
||||
self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
|
||||
self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir)))
|
||||
self.log_debug('Base path {path}'.format(path=self.base_path))
|
||||
self.plugins = []
|
||||
self.log_info('Plugin manager Initialised')
|
||||
@ -69,7 +69,7 @@ class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
"""
|
||||
Scan a directory for objects inheriting from the ``Plugin`` class.
|
||||
"""
|
||||
glob_pattern = os.path.join('openlp', 'plugins', '*', '*plugin.py')
|
||||
glob_pattern = os.path.join('plugins', '*', '*plugin.py')
|
||||
extension_loader(glob_pattern)
|
||||
plugin_classes = Plugin.__subclasses__()
|
||||
plugin_objects = []
|
||||
|
@ -46,7 +46,7 @@ __all__ = ['S_OK', 'E_GENERAL', 'E_NOT_CONNECTED', 'E_FAN', 'E_LAMP', 'E_TEMP',
|
||||
'S_NOT_CONNECTED', 'S_CONNECTING', 'S_CONNECTED',
|
||||
'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_STANDBY', 'S_WARMUP', 'S_ON', 'S_COOLDOWN',
|
||||
'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
|
||||
'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
|
||||
'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_DATA', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
|
||||
'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
|
||||
'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS',
|
||||
'PJLINK_DEFAULT_SOURCES', 'PJLINK_DEFAULT_CODES', 'PJLINK_DEFAULT_ITEMS']
|
||||
@ -154,7 +154,7 @@ PJLINK_VALID_CMD = {
|
||||
},
|
||||
'SRCH': {'version': ['2', ],
|
||||
'description': translate('OpenLP.PJLinkConstants',
|
||||
'UDP broadcast search request for available projectors.')
|
||||
'UDP broadcast search request for available projectors. Reply is ACKN.')
|
||||
},
|
||||
'SVER': {'version': ['2', ],
|
||||
'description': translate('OpenLP.PJLinkConstants',
|
||||
@ -393,11 +393,32 @@ ERROR_MSG = {
|
||||
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
|
||||
}
|
||||
|
||||
# Map ERST return code positions to equipment
|
||||
PJLINK_ERST_DATA = {
|
||||
'DATA_LENGTH': 6,
|
||||
0: 'FAN',
|
||||
1: 'LAMP',
|
||||
2: 'TEMP',
|
||||
3: 'COVER',
|
||||
4: 'FILTER',
|
||||
5: 'OTHER',
|
||||
'FAN': 0,
|
||||
'LAMP': 1,
|
||||
'TEMP': 2,
|
||||
'COVER': 3,
|
||||
'FILTER': 4,
|
||||
'OTHER': 5
|
||||
}
|
||||
|
||||
# Map for ERST return codes to string
|
||||
PJLINK_ERST_STATUS = {
|
||||
'0': ERROR_STRING[E_OK],
|
||||
'0': 'OK',
|
||||
'1': ERROR_STRING[E_WARN],
|
||||
'2': ERROR_STRING[E_ERROR]
|
||||
'2': ERROR_STRING[E_ERROR],
|
||||
'OK': '0',
|
||||
E_OK: '0',
|
||||
E_WARN: '1',
|
||||
E_ERROR: '2'
|
||||
}
|
||||
|
||||
# Map for POWR return codes to status code
|
||||
|
@ -303,7 +303,7 @@ class ProjectorDB(Manager):
|
||||
:param ip: Host IP/Name
|
||||
:returns: Projector() instance
|
||||
"""
|
||||
log.debug('get_projector_by_ip(ip="%s")' % ip)
|
||||
log.debug('get_projector_by_ip(ip="{ip}")'.format(ip=ip))
|
||||
projector = self.get_object_filtered(Projector, Projector.ip == ip)
|
||||
if projector is None:
|
||||
# Not found
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -71,10 +71,10 @@ log = logging.getLogger(__name__)
|
||||
|
||||
log.debug('pjlink2 loaded')
|
||||
|
||||
from PyQt5 import QtCore, QtNetwork
|
||||
from PyQt5 import QtNetwork
|
||||
|
||||
|
||||
class PJLinkUDP(QtNetwork.QTcpSocket):
|
||||
class PJLinkUDP(QtNetwork.QUdpSocket):
|
||||
"""
|
||||
Socket service for handling datagram (UDP) sockets.
|
||||
"""
|
||||
|
@ -25,12 +25,9 @@ backend for the projector setup.
|
||||
"""
|
||||
import logging
|
||||
|
||||
# Not all imports used at this time, but keep for future upgrades
|
||||
from sqlalchemy import Table, Column, types, inspect
|
||||
from sqlalchemy.exc import NoSuchTableError
|
||||
from sqlalchemy import Table, Column, types
|
||||
from sqlalchemy.sql.expression import null
|
||||
|
||||
from openlp.core.common.db import drop_columns
|
||||
from openlp.core.lib.db import get_upgrade_op
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -45,7 +42,7 @@ def upgrade_1(session, metadata):
|
||||
"""
|
||||
Version 1 upgrade - old db might/might not be versioned.
|
||||
"""
|
||||
log.debug('Skipping upgrade_1 of projector DB - not used')
|
||||
log.debug('Skipping projector DB upgrade to version 1 - not used')
|
||||
|
||||
|
||||
def upgrade_2(session, metadata):
|
||||
@ -63,14 +60,14 @@ def upgrade_2(session, metadata):
|
||||
:param session: DB session instance
|
||||
:param metadata: Metadata of current DB
|
||||
"""
|
||||
log.debug('Checking projector DB upgrade to version 2')
|
||||
projector_table = Table('projector', metadata, autoload=True)
|
||||
if 'mac_adx' not in [col.name for col in projector_table.c.values()]:
|
||||
log.debug("Upgrading projector DB to version '2'")
|
||||
upgrade_db = 'mac_adx' not in [col.name for col in projector_table.c.values()]
|
||||
if upgrade_db:
|
||||
new_op = get_upgrade_op(session)
|
||||
new_op.add_column('projector', Column('mac_adx', types.String(18), server_default=null()))
|
||||
new_op.add_column('projector', Column('serial_no', types.String(30), server_default=null()))
|
||||
new_op.add_column('projector', Column('sw_version', types.String(30), server_default=null()))
|
||||
new_op.add_column('projector', Column('model_filter', types.String(30), server_default=null()))
|
||||
new_op.add_column('projector', Column('model_lamp', types.String(30), server_default=null()))
|
||||
else:
|
||||
log.warn("Skipping upgrade_2 of projector DB")
|
||||
log.debug('{status} projector DB upgrade to version 2'.format(status='Updated' if upgrade_db else 'Skipping'))
|
||||
|
@ -335,7 +335,7 @@ class ServiceItem(RegistryProperties):
|
||||
if image and not self.has_original_files and self.name == 'presentations':
|
||||
file_location = os.path.join(path, file_name)
|
||||
file_location_hash = md5_hash(file_location.encode('utf-8'))
|
||||
image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails',
|
||||
image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
|
||||
file_location_hash, ntpath.basename(image))
|
||||
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
|
||||
'display_title': display_title, 'notes': notes})
|
||||
|
@ -158,9 +158,8 @@ class Theme(object):
|
||||
Initialise the theme object.
|
||||
"""
|
||||
# basic theme object with defaults
|
||||
json_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'lib', 'json')
|
||||
json_file = os.path.join(json_dir, 'theme.json')
|
||||
jsn = get_text_file_string(json_file)
|
||||
json_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json'
|
||||
jsn = get_text_file_string(json_path)
|
||||
jsn = json.loads(jsn)
|
||||
self.expand_json(jsn)
|
||||
self.background_filename = ''
|
||||
|
@ -30,6 +30,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
|
||||
from openlp.core.common.languagemanager import format_time
|
||||
from openlp.core.common.path import path_to_str
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
from openlp.core.ui.lib import PathEdit, PathType
|
||||
|
||||
@ -207,6 +208,9 @@ class AdvancedTab(SettingsTab):
|
||||
self.display_workaround_group_box.setObjectName('display_workaround_group_box')
|
||||
self.display_workaround_layout = QtWidgets.QVBoxLayout(self.display_workaround_group_box)
|
||||
self.display_workaround_layout.setObjectName('display_workaround_layout')
|
||||
self.ignore_aspect_ratio_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
|
||||
self.ignore_aspect_ratio_check_box.setObjectName('ignore_aspect_ratio_check_box')
|
||||
self.display_workaround_layout.addWidget(self.ignore_aspect_ratio_check_box)
|
||||
self.x11_bypass_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
|
||||
self.x11_bypass_check_box.setObjectName('x11_bypass_check_box')
|
||||
self.display_workaround_layout.addWidget(self.x11_bypass_check_box)
|
||||
@ -310,6 +314,7 @@ class AdvancedTab(SettingsTab):
|
||||
translate('OpenLP.AdvancedTab', '<strong>WARNING:</strong> New data directory location contains '
|
||||
'OpenLP data files. These files WILL be replaced during a copy.'))
|
||||
self.display_workaround_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Display Workarounds'))
|
||||
self.ignore_aspect_ratio_check_box.setText(translate('OpenLP.AdvancedTab', 'Ignore Aspect Ratio'))
|
||||
self.x11_bypass_check_box.setText(translate('OpenLP.AdvancedTab', 'Bypass X11 Window Manager'))
|
||||
self.alternate_rows_check_box.setText(translate('OpenLP.AdvancedTab', 'Use alternating row colours in lists'))
|
||||
# Slide Limits
|
||||
@ -354,6 +359,7 @@ class AdvancedTab(SettingsTab):
|
||||
default_service_enabled = settings.value('default service enabled')
|
||||
self.service_name_check_box.setChecked(default_service_enabled)
|
||||
self.service_name_check_box_toggled(default_service_enabled)
|
||||
self.ignore_aspect_ratio_check_box.setChecked(settings.value('ignore aspect ratio'))
|
||||
self.x11_bypass_check_box.setChecked(settings.value('x11 bypass wm'))
|
||||
self.slide_limits = settings.value('slide limits')
|
||||
self.is_search_as_you_type_enabled = settings.value('search as type')
|
||||
@ -409,6 +415,7 @@ class AdvancedTab(SettingsTab):
|
||||
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
||||
settings.setValue('alternate rows', self.alternate_rows_check_box.isChecked())
|
||||
settings.setValue('slide limits', self.slide_limits)
|
||||
settings.setValue('ignore aspect ratio', self.ignore_aspect_ratio_check_box.isChecked())
|
||||
if self.x11_bypass_check_box.isChecked() != settings.value('x11 bypass wm'):
|
||||
settings.setValue('x11 bypass wm', self.x11_bypass_check_box.isChecked())
|
||||
self.settings_form.register_post_process('config_screen_changed')
|
||||
@ -500,9 +507,9 @@ class AdvancedTab(SettingsTab):
|
||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||
return
|
||||
# Check if data already exists here.
|
||||
self.check_data_overwrite(new_data_path)
|
||||
self.check_data_overwrite(path_to_str(new_data_path))
|
||||
# Save the new location.
|
||||
self.main_window.set_new_data_path(new_data_path)
|
||||
self.main_window.set_new_data_path(path_to_str(new_data_path))
|
||||
self.data_directory_cancel_button.show()
|
||||
|
||||
def on_data_directory_copy_check_box_toggled(self):
|
||||
|
@ -29,8 +29,9 @@ import time
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
from configparser import ConfigParser, MissingSectionHeaderError, NoOptionError, NoSectionError
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
from configparser import ConfigParser, MissingSectionHeaderError, NoSectionError, NoOptionError
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
@ -202,7 +203,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.themes_url = self.web + self.config.get('themes', 'directory') + '/'
|
||||
self.web_access = True
|
||||
except (NoSectionError, NoOptionError, MissingSectionHeaderError):
|
||||
log.debug('A problem occured while parsing the downloaded config file')
|
||||
log.debug('A problem occurred while parsing the downloaded config file')
|
||||
trace_error_handler(log)
|
||||
self.update_screen_list_combo()
|
||||
self.application.process_events()
|
||||
@ -213,7 +214,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active())
|
||||
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
|
||||
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
|
||||
self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
|
||||
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
|
||||
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
|
||||
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
|
||||
@ -283,7 +283,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self.no_internet_cancel_button.setVisible(False)
|
||||
# Check if this is a re-run of the wizard.
|
||||
self.has_run_wizard = Settings().value('core/has run wizard')
|
||||
check_directory_exists(os.path.join(gettempdir(), 'openlp'))
|
||||
check_directory_exists(Path(gettempdir(), 'openlp'))
|
||||
|
||||
def update_screen_list_combo(self):
|
||||
"""
|
||||
@ -530,7 +530,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
self._set_plugin_status(self.presentation_check_box, 'presentations/status')
|
||||
self._set_plugin_status(self.image_check_box, 'images/status')
|
||||
self._set_plugin_status(self.media_check_box, 'media/status')
|
||||
self._set_plugin_status(self.remote_check_box, 'remotes/status')
|
||||
self._set_plugin_status(self.custom_check_box, 'custom/status')
|
||||
self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
|
||||
self._set_plugin_status(self.alert_check_box, 'alerts/status')
|
||||
@ -554,8 +553,8 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||
"""
|
||||
# Build directories for downloads
|
||||
songs_destination = os.path.join(gettempdir(), 'openlp')
|
||||
bibles_destination = AppLocation.get_section_data_path('bibles')
|
||||
themes_destination = AppLocation.get_section_data_path('themes')
|
||||
bibles_destination = str(AppLocation.get_section_data_path('bibles'))
|
||||
themes_destination = str(AppLocation.get_section_data_path('themes'))
|
||||
missed_files = []
|
||||
# Download songs
|
||||
for i in range(self.songs_list_widget.count()):
|
||||
|
@ -24,6 +24,7 @@ The UI widgets for the first time wizard.
|
||||
"""
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common.uistrings import UiStrings
|
||||
from openlp.core.common import translate, is_macosx, clean_button_text, Settings
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib.ui import add_welcome_page
|
||||
@ -254,8 +255,7 @@ class UiFirstTimeWizard(object):
|
||||
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Presentations – Show .ppt, .odp and .pdf files'))
|
||||
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media – Playback of Audio and Video files'))
|
||||
self.remote_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Remote – Control OpenLP via browser or smart'
|
||||
'phone app'))
|
||||
self.remote_check_box.setText(str(UiStrings().WebDownloadText))
|
||||
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))
|
||||
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Alerts – Display informative messages while showing other slides'))
|
||||
|
@ -23,10 +23,12 @@
|
||||
The general tab of the configuration dialog.
|
||||
"""
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import SettingsTab, ScreenList
|
||||
from openlp.core.ui.lib import ColorButton, PathEdit
|
||||
|
||||
@ -172,7 +174,8 @@ class GeneralTab(SettingsTab):
|
||||
self.logo_layout.setObjectName('logo_layout')
|
||||
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
|
||||
self.logo_file_label.setObjectName('logo_file_label')
|
||||
self.logo_file_path_edit = PathEdit(self.logo_group_box, default_path=':/graphics/openlp-splash-screen.png')
|
||||
self.logo_file_path_edit = PathEdit(self.logo_group_box,
|
||||
default_path=Path(':/graphics/openlp-splash-screen.png'))
|
||||
self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit)
|
||||
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
|
||||
self.logo_color_label.setObjectName('logo_color_label')
|
||||
@ -266,7 +269,7 @@ class GeneralTab(SettingsTab):
|
||||
self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio'))
|
||||
self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused'))
|
||||
self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list'))
|
||||
self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
|
||||
self.logo_file_path_edit.dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
|
||||
self.logo_file_path_edit.filters = '{text};;{names} (*)'.format(
|
||||
text=get_images_filter(), names=UiStrings().AllFiles)
|
||||
|
||||
@ -291,7 +294,7 @@ class GeneralTab(SettingsTab):
|
||||
self.auto_open_check_box.setChecked(settings.value('auto open'))
|
||||
self.show_splash_check_box.setChecked(settings.value('show splash'))
|
||||
self.logo_background_color = settings.value('logo background color')
|
||||
self.logo_file_path_edit.path = settings.value('logo file')
|
||||
self.logo_file_path_edit.path = str_to_path(settings.value('logo file'))
|
||||
self.logo_hide_on_startup_check_box.setChecked(settings.value('logo hide on startup'))
|
||||
self.logo_color_button.color = self.logo_background_color
|
||||
self.check_for_updates_check_box.setChecked(settings.value('update check'))
|
||||
@ -325,7 +328,7 @@ class GeneralTab(SettingsTab):
|
||||
settings.setValue('auto open', self.auto_open_check_box.isChecked())
|
||||
settings.setValue('show splash', self.show_splash_check_box.isChecked())
|
||||
settings.setValue('logo background color', self.logo_background_color)
|
||||
settings.setValue('logo file', self.logo_file_path_edit.path)
|
||||
settings.setValue('logo file', path_to_str(self.logo_file_path_edit.path))
|
||||
settings.setValue('logo hide on startup', self.logo_hide_on_startup_check_box.isChecked())
|
||||
settings.setValue('update check', self.check_for_updates_check_box.isChecked())
|
||||
settings.setValue('save prompt', self.save_check_service_check_box.isChecked())
|
||||
|
113
openlp/core/ui/lib/filedialog.py
Executable file
113
openlp/core/ui/lib/filedialog.py
Executable file
@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
""" Patch the QFileDialog so it accepts and returns Path objects"""
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import replace_params
|
||||
|
||||
|
||||
class FileDialog(QtWidgets.QFileDialog):
|
||||
@classmethod
|
||||
def getExistingDirectory(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getExistingDirectory` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[Path, str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
return_value = super().getExistingDirectory(*args, **kwargs)
|
||||
|
||||
# getExistingDirectory returns a str that represents the path. The string is empty if the user cancels the
|
||||
# dialog.
|
||||
return str_to_path(return_value)
|
||||
|
||||
@classmethod
|
||||
def getOpenFileName(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getOpenFileName` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type filter: str
|
||||
:type initialFilter: str
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[Path, str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
file_name, selected_filter = super().getOpenFileName(*args, **kwargs)
|
||||
|
||||
# getOpenFileName returns a tuple. The first item is a str that represents the path. The string is empty if
|
||||
# the user cancels the dialog.
|
||||
return str_to_path(file_name), selected_filter
|
||||
|
||||
@classmethod
|
||||
def getOpenFileNames(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getOpenFileNames` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type filter: str
|
||||
:type initialFilter: str
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[list[Path], str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
file_names, selected_filter = super().getOpenFileNames(*args, **kwargs)
|
||||
|
||||
# getSaveFileName returns a tuple. The first item is a list of str's that represents the path. The list is
|
||||
# empty if the user cancels the dialog.
|
||||
paths = [str_to_path(path) for path in file_names]
|
||||
return paths, selected_filter
|
||||
|
||||
@classmethod
|
||||
def getSaveFileName(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getSaveFileName` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type filter: str
|
||||
:type initialFilter: str
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[Path or None, str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
file_name, selected_filter = super().getSaveFileName(*args, **kwargs)
|
||||
|
||||
# getSaveFileName returns a tuple. The first item represents the path as a str. The string is empty if the user
|
||||
# cancels the dialog.
|
||||
return str_to_path(file_name), selected_filter
|
426
openlp/core/ui/lib/pathedit.py
Executable file → Normal file
426
openlp/core/ui/lib/pathedit.py
Executable file → Normal file
@ -1,205 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
from enum import Enum
|
||||
import os.path
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings, translate
|
||||
from openlp.core.lib import build_icon
|
||||
|
||||
|
||||
class PathType(Enum):
|
||||
Files = 1
|
||||
Directories = 2
|
||||
|
||||
|
||||
class PathEdit(QtWidgets.QWidget):
|
||||
"""
|
||||
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
|
||||
a file or directory needs to be selected.
|
||||
"""
|
||||
pathChanged = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
|
||||
"""
|
||||
Initalise the PathEdit widget
|
||||
|
||||
:param parent: The parent of the widget. This is just passed to the super method.
|
||||
:type parent: QWidget or None
|
||||
|
||||
:param dialog_caption: Used to customise the caption in the QFileDialog.
|
||||
:type dialog_caption: str
|
||||
|
||||
:param default_path: The default path. This is set as the path when the revert button is clicked
|
||||
:type default_path: str
|
||||
|
||||
:param show_revert: Used to determin if the 'revert button' should be visible.
|
||||
:type show_revert: bool
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.default_path = default_path
|
||||
self.dialog_caption = dialog_caption
|
||||
self._path_type = path_type
|
||||
self._path = None
|
||||
self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles)
|
||||
self._setup(show_revert)
|
||||
|
||||
def _setup(self, show_revert):
|
||||
"""
|
||||
Set up the widget
|
||||
:param show_revert: Show or hide the revert button
|
||||
:type show_revert: bool
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
widget_layout = QtWidgets.QHBoxLayout()
|
||||
widget_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.line_edit = QtWidgets.QLineEdit(self)
|
||||
self.line_edit.setText(self._path)
|
||||
widget_layout.addWidget(self.line_edit)
|
||||
self.browse_button = QtWidgets.QToolButton(self)
|
||||
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
|
||||
widget_layout.addWidget(self.browse_button)
|
||||
self.revert_button = QtWidgets.QToolButton(self)
|
||||
self.revert_button.setIcon(build_icon(':/general/general_revert.png'))
|
||||
self.revert_button.setVisible(show_revert)
|
||||
widget_layout.addWidget(self.revert_button)
|
||||
self.setLayout(widget_layout)
|
||||
# Signals and Slots
|
||||
self.browse_button.clicked.connect(self.on_browse_button_clicked)
|
||||
self.revert_button.clicked.connect(self.on_revert_button_clicked)
|
||||
self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished)
|
||||
self.update_button_tool_tips()
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
A property getter method to return the selected path.
|
||||
|
||||
:return: The selected path
|
||||
:rtype: str
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@path.setter
|
||||
def path(self, path):
|
||||
"""
|
||||
A Property setter method to set the selected path
|
||||
|
||||
:param path: The path to set the widget to
|
||||
:type path: str
|
||||
"""
|
||||
self._path = path
|
||||
self.line_edit.setText(path)
|
||||
self.line_edit.setToolTip(path)
|
||||
|
||||
@property
|
||||
def path_type(self):
|
||||
"""
|
||||
A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to
|
||||
selecting a file or directory.
|
||||
|
||||
:return: The type selected
|
||||
:rtype: Enum of PathEdit
|
||||
"""
|
||||
return self._path_type
|
||||
|
||||
@path_type.setter
|
||||
def path_type(self, path_type):
|
||||
"""
|
||||
A Property setter method to set the path type
|
||||
|
||||
:param path: The type of path to select
|
||||
:type path: Enum of PathEdit
|
||||
"""
|
||||
self._path_type = path_type
|
||||
self.update_button_tool_tips()
|
||||
|
||||
def update_button_tool_tips(self):
|
||||
"""
|
||||
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
|
||||
:return: None
|
||||
"""
|
||||
if self._path_type == PathType.Directories:
|
||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
|
||||
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.'))
|
||||
else:
|
||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.'))
|
||||
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.'))
|
||||
|
||||
def on_browse_button_clicked(self):
|
||||
"""
|
||||
A handler to handle a click on the browse button.
|
||||
|
||||
Show the QFileDialog and process the input from the user
|
||||
:return: None
|
||||
"""
|
||||
caption = self.dialog_caption
|
||||
path = ''
|
||||
if self._path_type == PathType.Directories:
|
||||
if not caption:
|
||||
caption = translate('OpenLP.PathEdit', 'Select Directory')
|
||||
path = QtWidgets.QFileDialog.getExistingDirectory(self, caption,
|
||||
self._path, QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
elif self._path_type == PathType.Files:
|
||||
if not caption:
|
||||
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
|
||||
path, filter_used = QtWidgets.QFileDialog.getOpenFileName(self, caption, self._path, self.filters)
|
||||
if path:
|
||||
path = os.path.normpath(path)
|
||||
self.on_new_path(path)
|
||||
|
||||
def on_revert_button_clicked(self):
|
||||
"""
|
||||
A handler to handle a click on the revert button.
|
||||
|
||||
Set the new path to the value of the default_path instance variable.
|
||||
:return: None
|
||||
"""
|
||||
self.on_new_path(self.default_path)
|
||||
|
||||
def on_line_edit_editing_finished(self):
|
||||
"""
|
||||
A handler to handle when the line edit has finished being edited.
|
||||
:return: None
|
||||
"""
|
||||
self.on_new_path(self.line_edit.text())
|
||||
|
||||
def on_new_path(self, path):
|
||||
"""
|
||||
A method called to validate and set a new path.
|
||||
|
||||
Emits the pathChanged Signal
|
||||
|
||||
:param path: The new path
|
||||
:type path: str
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if self._path != path:
|
||||
self.path = path
|
||||
self.pathChanged.emit(path)
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 #
|
||||
###############################################################################
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings, translate
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
|
||||
|
||||
class PathType(Enum):
|
||||
Files = 1
|
||||
Directories = 2
|
||||
|
||||
|
||||
class PathEdit(QtWidgets.QWidget):
|
||||
"""
|
||||
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
|
||||
a file or directory needs to be selected.
|
||||
"""
|
||||
pathChanged = QtCore.pyqtSignal(Path)
|
||||
|
||||
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
|
||||
"""
|
||||
Initialise the PathEdit widget
|
||||
|
||||
:param parent: The parent of the widget. This is just passed to the super method.
|
||||
:type parent: QWidget or None
|
||||
|
||||
:param dialog_caption: Used to customise the caption in the QFileDialog.
|
||||
:type dialog_caption: str
|
||||
|
||||
:param default_path: The default path. This is set as the path when the revert button is clicked
|
||||
:type default_path: pathlib.Path
|
||||
|
||||
:param show_revert: Used to determine if the 'revert button' should be visible.
|
||||
:type show_revert: bool
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.default_path = default_path
|
||||
self.dialog_caption = dialog_caption
|
||||
self._path_type = path_type
|
||||
self._path = None
|
||||
self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles)
|
||||
self._setup(show_revert)
|
||||
|
||||
def _setup(self, show_revert):
|
||||
"""
|
||||
Set up the widget
|
||||
:param show_revert: Show or hide the revert button
|
||||
:type show_revert: bool
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
widget_layout = QtWidgets.QHBoxLayout()
|
||||
widget_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.line_edit = QtWidgets.QLineEdit(self)
|
||||
widget_layout.addWidget(self.line_edit)
|
||||
self.browse_button = QtWidgets.QToolButton(self)
|
||||
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
|
||||
widget_layout.addWidget(self.browse_button)
|
||||
self.revert_button = QtWidgets.QToolButton(self)
|
||||
self.revert_button.setIcon(build_icon(':/general/general_revert.png'))
|
||||
self.revert_button.setVisible(show_revert)
|
||||
widget_layout.addWidget(self.revert_button)
|
||||
self.setLayout(widget_layout)
|
||||
# Signals and Slots
|
||||
self.browse_button.clicked.connect(self.on_browse_button_clicked)
|
||||
self.revert_button.clicked.connect(self.on_revert_button_clicked)
|
||||
self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished)
|
||||
self.update_button_tool_tips()
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
A property getter method to return the selected path.
|
||||
|
||||
:return: The selected path
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@path.setter
|
||||
def path(self, path):
|
||||
"""
|
||||
A Property setter method to set the selected path
|
||||
|
||||
:param path: The path to set the widget to
|
||||
:type path: pathlib.Path
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self._path = path
|
||||
text = path_to_str(path)
|
||||
self.line_edit.setText(text)
|
||||
self.line_edit.setToolTip(text)
|
||||
|
||||
@property
|
||||
def path_type(self):
|
||||
"""
|
||||
A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to
|
||||
selecting a file or directory.
|
||||
|
||||
:return: The type selected
|
||||
:rtype: PathType
|
||||
"""
|
||||
return self._path_type
|
||||
|
||||
@path_type.setter
|
||||
def path_type(self, path_type):
|
||||
"""
|
||||
A Property setter method to set the path type
|
||||
|
||||
:param path_type: The type of path to select
|
||||
:type path_type: PathType
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self._path_type = path_type
|
||||
self.update_button_tool_tips()
|
||||
|
||||
def update_button_tool_tips(self):
|
||||
"""
|
||||
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if self._path_type == PathType.Directories:
|
||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
|
||||
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.'))
|
||||
else:
|
||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.'))
|
||||
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.'))
|
||||
|
||||
def on_browse_button_clicked(self):
|
||||
"""
|
||||
A handler to handle a click on the browse button.
|
||||
|
||||
Show the QFileDialog and process the input from the user
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
caption = self.dialog_caption
|
||||
path = None
|
||||
if self._path_type == PathType.Directories:
|
||||
if not caption:
|
||||
caption = translate('OpenLP.PathEdit', 'Select Directory')
|
||||
path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
|
||||
elif self._path_type == PathType.Files:
|
||||
if not caption:
|
||||
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
|
||||
path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
|
||||
if path:
|
||||
self.on_new_path(path)
|
||||
|
||||
def on_revert_button_clicked(self):
|
||||
"""
|
||||
A handler to handle a click on the revert button.
|
||||
|
||||
Set the new path to the value of the default_path instance variable.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.on_new_path(self.default_path)
|
||||
|
||||
def on_line_edit_editing_finished(self):
|
||||
"""
|
||||
A handler to handle when the line edit has finished being edited.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
path = str_to_path(self.line_edit.text())
|
||||
self.on_new_path(path)
|
||||
|
||||
def on_new_path(self, path):
|
||||
"""
|
||||
A method called to validate and set a new path.
|
||||
|
||||
Emits the pathChanged Signal
|
||||
|
||||
:param path: The new path
|
||||
:type path: pathlib.Path
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if self._path != path:
|
||||
self.path = path
|
||||
self.pathChanged.emit(path)
|
||||
|
@ -484,7 +484,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
service_item = ServiceItem()
|
||||
service_item.title = 'webkit'
|
||||
service_item.processor = 'webkit'
|
||||
path = os.path.join(AppLocation.get_section_data_path('themes'),
|
||||
path = os.path.join(str(AppLocation.get_section_data_path('themes')),
|
||||
self.service_item.theme_data.theme_name)
|
||||
service_item.add_from_command(path,
|
||||
self.service_item.theme_data.background_filename,
|
||||
|
@ -30,16 +30,19 @@ import time
|
||||
from datetime import datetime
|
||||
from distutils import dir_util
|
||||
from distutils.errors import DistutilsFileError
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \
|
||||
from openlp.core.api import websockets
|
||||
from openlp.core.api.http import server
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||
check_directory_exists, translate, is_win, is_macosx, add_actions
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
||||
from openlp.core.lib.ui import UiStrings, create_action
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||
ShortcutListForm, FormattingTagForm, PreviewController
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
@ -49,6 +52,7 @@ from openlp.core.ui.projector.manager import ProjectorManager
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
MEDIA_MANAGER_STYLE = """
|
||||
@ -305,9 +309,9 @@ class Ui_MainWindow(object):
|
||||
# Give QT Extra Hint that this is an About Menu Item
|
||||
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
|
||||
if is_win():
|
||||
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
|
||||
self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm')
|
||||
elif is_macosx():
|
||||
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
|
||||
self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
|
||||
'..', 'Resources', 'OpenLP.help')
|
||||
self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png',
|
||||
can_shortcuts=True, category=UiStrings().Help,
|
||||
@ -370,7 +374,7 @@ class Ui_MainWindow(object):
|
||||
"""
|
||||
Set up the translation system
|
||||
"""
|
||||
main_window.setWindowTitle(UiStrings().OLP)
|
||||
main_window.setWindowTitle(UiStrings().OpenLP)
|
||||
self.file_menu.setTitle(translate('OpenLP.MainWindow', '&File'))
|
||||
self.file_import_menu.setTitle(translate('OpenLP.MainWindow', '&Import'))
|
||||
self.file_export_menu.setTitle(translate('OpenLP.MainWindow', '&Export'))
|
||||
@ -513,6 +517,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
Settings().set_up_default_values()
|
||||
self.about_form = AboutForm(self)
|
||||
MediaController()
|
||||
if Registry().get_flag('no_web_server'):
|
||||
websockets.WebSocketServer()
|
||||
server.HttpServer()
|
||||
SettingsForm(self)
|
||||
self.formatting_tag_form = FormattingTagForm(self)
|
||||
self.shortcut_form = ShortcutListForm(self)
|
||||
@ -540,7 +547,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
self.tools_first_time_wizard.triggered.connect(self.on_first_time_wizard_clicked)
|
||||
self.update_theme_images.triggered.connect(self.on_update_theme_images)
|
||||
self.formatting_tag_item.triggered.connect(self.on_formatting_tag_item_clicked)
|
||||
self.settings_configure_item.triggered.connect(self.on_settings_configure_iem_clicked)
|
||||
self.settings_configure_item.triggered.connect(self.on_settings_configure_item_clicked)
|
||||
self.settings_shortcuts_item.triggered.connect(self.on_settings_shortcuts_item_clicked)
|
||||
self.settings_import_item.triggered.connect(self.on_settings_import_item_clicked)
|
||||
self.settings_export_item.triggered.connect(self.on_settings_export_item_clicked)
|
||||
@ -788,7 +795,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
"""
|
||||
Open data folder
|
||||
"""
|
||||
path = AppLocation.get_data_path()
|
||||
path = str(AppLocation.get_data_path())
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path))
|
||||
|
||||
def on_update_theme_images(self):
|
||||
@ -803,7 +810,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
"""
|
||||
self.formatting_tag_form.exec()
|
||||
|
||||
def on_settings_configure_iem_clicked(self):
|
||||
def on_settings_configure_item_clicked(self):
|
||||
"""
|
||||
Show the Settings dialog
|
||||
"""
|
||||
@ -864,7 +871,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
|
||||
# Copy the settings file to the tmp dir, because we do not want to change the original one.
|
||||
temp_directory = os.path.join(str(gettempdir()), 'openlp')
|
||||
check_directory_exists(temp_directory)
|
||||
check_directory_exists(Path(temp_directory))
|
||||
temp_config = os.path.join(temp_directory, os.path.basename(import_file_name))
|
||||
shutil.copyfile(import_file_name, temp_config)
|
||||
settings = Settings()
|
||||
@ -1150,9 +1157,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
:param file_name: The file name of the service file.
|
||||
"""
|
||||
if modified:
|
||||
title = '{title} - {name}*'.format(title=UiStrings().OLP, name=file_name)
|
||||
title = '{title} - {name}*'.format(title=UiStrings().OpenLP, name=file_name)
|
||||
else:
|
||||
title = '{title} - {name}'.format(title=UiStrings().OLP, name=file_name)
|
||||
title = '{title} - {name}'.format(title=UiStrings().OpenLP, name=file_name)
|
||||
self.setWindowTitle(title)
|
||||
|
||||
def show_status_message(self, message):
|
||||
@ -1438,7 +1445,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue('advanced/data path', self.new_data_path)
|
||||
# Check if the new data path is our default.
|
||||
if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
|
||||
if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):
|
||||
settings.remove('advanced/data path')
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
|
@ -146,5 +146,6 @@ def format_milliseconds(milliseconds):
|
||||
|
||||
from .mediacontroller import MediaController
|
||||
from .playertab import PlayerTab
|
||||
from .endpoint import media_endpoint
|
||||
|
||||
__all__ = ['MediaController', 'PlayerTab']
|
||||
|
@ -19,40 +19,54 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from urllib import parse
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http import requires_auth
|
||||
from openlp.core.common import Registry
|
||||
|
||||
from openlp.core.common import UiStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
media_endpoint = Endpoint('media')
|
||||
|
||||
class FileDialog(QtWidgets.QFileDialog):
|
||||
|
||||
@media_endpoint.route('play')
|
||||
@requires_auth
|
||||
def media_play(request):
|
||||
"""
|
||||
Subclass QFileDialog to work round a bug
|
||||
Handles requests for playing media
|
||||
|
||||
:param request: The http request object.
|
||||
"""
|
||||
@staticmethod
|
||||
def getOpenFileNames(parent, *args, **kwargs):
|
||||
"""
|
||||
Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
|
||||
files
|
||||
"""
|
||||
files, filter_used = QtWidgets.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
|
||||
file_list = []
|
||||
for file in files:
|
||||
if not os.path.exists(file):
|
||||
log.info('File not found. Attempting to unquote.')
|
||||
file = parse.unquote(file)
|
||||
if not os.path.exists(file):
|
||||
log.error('File {text} not found.'.format(text=file))
|
||||
QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,
|
||||
UiStrings().FileNotFoundMessage.format(name=file))
|
||||
continue
|
||||
file_list.append(file)
|
||||
return file_list
|
||||
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}}
|
@ -28,12 +28,13 @@ import os
|
||||
import datetime
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, \
|
||||
extension_loader, translate
|
||||
from openlp.core.lib import ItemCapabilities
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.common import AppLocation
|
||||
from openlp.core.ui import DisplayControllerType
|
||||
from openlp.core.ui.media.endpoint import media_endpoint
|
||||
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
|
||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
|
||||
@ -127,9 +128,11 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
Registry().register_function('media_unblank', self.media_unblank)
|
||||
# Signals for background video
|
||||
Registry().register_function('songs_hide', self.media_hide)
|
||||
Registry().register_function('songs_blank', self.media_blank)
|
||||
Registry().register_function('songs_unblank', self.media_unblank)
|
||||
Registry().register_function('mediaitem_media_rebuild', self._set_active_players)
|
||||
Registry().register_function('mediaitem_suffixes', self._generate_extensions_lists)
|
||||
register_endpoint(media_endpoint)
|
||||
|
||||
def _set_active_players(self):
|
||||
"""
|
||||
@ -174,7 +177,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
Check to see if we have any media Player's available.
|
||||
"""
|
||||
log.debug('_check_available_media_players')
|
||||
controller_dir = os.path.join('openlp', 'core', 'ui', 'media')
|
||||
controller_dir = os.path.join('core', 'ui', 'media')
|
||||
glob_pattern = os.path.join(controller_dir, '*player.py')
|
||||
extension_loader(glob_pattern, ['mediaplayer.py'])
|
||||
player_classes = MediaPlayer.__subclasses__()
|
||||
@ -613,6 +616,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
"""
|
||||
self.media_play(msg[0], status)
|
||||
|
||||
def on_media_play(self):
|
||||
"""
|
||||
Responds to the request to play a loaded video from the web.
|
||||
|
||||
:param msg: First element is the controller which should be used
|
||||
"""
|
||||
self.media_play(Registry().get('live_controller'), False)
|
||||
|
||||
def media_play(self, controller, first_time=True):
|
||||
"""
|
||||
Responds to the request to play a loaded video
|
||||
@ -687,6 +698,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
"""
|
||||
self.media_pause(msg[0])
|
||||
|
||||
def on_media_pause(self):
|
||||
"""
|
||||
Responds to the request to pause a loaded video from the web.
|
||||
|
||||
:param msg: First element is the controller which should be used
|
||||
"""
|
||||
self.media_pause(Registry().get('live_controller'))
|
||||
|
||||
def media_pause(self, controller):
|
||||
"""
|
||||
Responds to the request to pause a loaded video
|
||||
@ -727,6 +746,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
||||
"""
|
||||
self.media_stop(msg[0])
|
||||
|
||||
def on_media_stop(self):
|
||||
"""
|
||||
Responds to the request to stop a loaded video from the web.
|
||||
|
||||
:param msg: First element is the controller which should be used
|
||||
"""
|
||||
self.media_stop(Registry().get('live_controller'))
|
||||
|
||||
def media_stop(self, controller, looping_background=False):
|
||||
"""
|
||||
Responds to the request to stop a loaded video
|
||||
|
@ -176,7 +176,7 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
|
||||
html_data = self._add_element('html')
|
||||
self._add_element('head', parent=html_data)
|
||||
self._add_element('title', self.title_line_edit.text(), html_data.head)
|
||||
css_path = os.path.join(AppLocation.get_data_path(), 'serviceprint', 'service_print.css')
|
||||
css_path = AppLocation.get_data_path() / 'serviceprint' / 'service_print.css'
|
||||
custom_css = get_text_file_string(css_path)
|
||||
if not custom_css:
|
||||
custom_css = DEFAULT_CSS
|
||||
|
@ -38,7 +38,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
|
||||
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
|
||||
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
|
||||
from openlp.core.lib.projector.db import ProjectorDB
|
||||
from openlp.core.lib.projector.pjlink1 import PJLink
|
||||
from openlp.core.lib.projector.pjlink import PJLink
|
||||
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
|
||||
from openlp.core.ui.projector.editform import ProjectorEditForm
|
||||
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
|
||||
@ -420,9 +420,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
:param opt: Needed by PyQt5
|
||||
"""
|
||||
try:
|
||||
ip = opt.link.ip
|
||||
projector = opt
|
||||
projector.link.set_shutter_closed()
|
||||
opt.link.set_shutter_closed()
|
||||
except AttributeError:
|
||||
for list_item in self.projector_list_widget.selectedItems():
|
||||
if list_item is None:
|
||||
@ -455,9 +453,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
:param opt: Needed by PyQt5
|
||||
"""
|
||||
try:
|
||||
ip = opt.link.ip
|
||||
projector = opt
|
||||
projector.link.connect_to_host()
|
||||
opt.link.connect_to_host()
|
||||
except AttributeError:
|
||||
for list_item in self.projector_list_widget.selectedItems():
|
||||
if list_item is None:
|
||||
@ -527,7 +523,8 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
self.projector_list = new_list
|
||||
list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
|
||||
list_item = None
|
||||
deleted = self.projectordb.delete_projector(projector.db_item)
|
||||
if not self.projectordb.delete_projector(projector.db_item):
|
||||
log.warning('Delete projector {item} failed'.format(item=projector.db_item))
|
||||
for item in self.projector_list:
|
||||
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
|
||||
|
||||
@ -538,9 +535,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
:param opt: Needed by PyQt5
|
||||
"""
|
||||
try:
|
||||
ip = opt.link.ip
|
||||
projector = opt
|
||||
projector.link.disconnect_from_host()
|
||||
opt.link.disconnect_from_host()
|
||||
except AttributeError:
|
||||
for list_item in self.projector_list_widget.selectedItems():
|
||||
if list_item is None:
|
||||
@ -573,9 +568,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
:param opt: Needed by PyQt5
|
||||
"""
|
||||
try:
|
||||
ip = opt.link.ip
|
||||
projector = opt
|
||||
projector.link.set_power_off()
|
||||
opt.link.set_power_off()
|
||||
except AttributeError:
|
||||
for list_item in self.projector_list_widget.selectedItems():
|
||||
if list_item is None:
|
||||
@ -593,9 +586,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
:param opt: Needed by PyQt5
|
||||
"""
|
||||
try:
|
||||
ip = opt.link.ip
|
||||
projector = opt
|
||||
projector.link.set_power_on()
|
||||
opt.link.set_power_on()
|
||||
except AttributeError:
|
||||
for list_item in self.projector_list_widget.selectedItems():
|
||||
if list_item is None:
|
||||
@ -613,9 +604,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
:param opt: Needed by PyQt5
|
||||
"""
|
||||
try:
|
||||
ip = opt.link.ip
|
||||
projector = opt
|
||||
projector.link.set_shutter_open()
|
||||
opt.link.set_shutter_open()
|
||||
except AttributeError:
|
||||
for list_item in self.projector_list_widget.selectedItems():
|
||||
if list_item is None:
|
||||
@ -662,9 +651,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
data=translate('OpenLP.ProjectorManager', 'Closed')
|
||||
if projector.link.shutter
|
||||
else translate('OpenLP', 'Open'))
|
||||
message = '%s<b>%s</b>: %s<br />' % (message,
|
||||
translate('OpenLP.ProjectorManager', 'Current source input is'),
|
||||
projector.link.source)
|
||||
message = '{msg}<b>{source}</b>: {selected}<br />'.format(msg=message,
|
||||
source=translate('OpenLP.ProjectorManager',
|
||||
'Current source input is'),
|
||||
selected=projector.link.source)
|
||||
if projector.link.pjlink_class == '2':
|
||||
# Information only available for PJLink Class 2 projectors
|
||||
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||
@ -685,10 +675,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||
'Lamp'),
|
||||
count=count,
|
||||
status=translate('OpenLP.ProjectorManager',
|
||||
' is on')
|
||||
'ON')
|
||||
if item['On']
|
||||
else translate('OpenLP.ProjectorManager',
|
||||
'is off'))
|
||||
'OFF'))
|
||||
|
||||
message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
|
||||
hours=item['Hours'])
|
||||
|
@ -393,9 +393,9 @@ class SourceSelectSingle(QtWidgets.QDialog):
|
||||
QtCore.Qt.WindowCloseButtonHint)
|
||||
self.edit = edit
|
||||
if self.edit:
|
||||
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
|
||||
self.title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
|
||||
else:
|
||||
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
|
||||
self.title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
|
||||
self.setObjectName('source_select_single')
|
||||
self.setWindowIcon(build_icon(':/icon/openlp-log.svg'))
|
||||
self.setModal(True)
|
||||
|
@ -28,6 +28,7 @@ import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from tempfile import mkstemp
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -223,7 +224,7 @@ class Ui_ServiceManager(object):
|
||||
self.service_manager_list.itemExpanded.connect(self.expanded)
|
||||
# Last little bits of setting up
|
||||
self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
|
||||
self.service_path = AppLocation.get_section_data_path('servicemanager')
|
||||
self.service_path = str(AppLocation.get_section_data_path('servicemanager'))
|
||||
# build the drag and drop context menu
|
||||
self.dnd_menu = QtWidgets.QMenu()
|
||||
self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
|
||||
@ -587,7 +588,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
audio_from = os.path.join(self.service_path, audio_from)
|
||||
save_file = os.path.join(self.service_path, audio_to)
|
||||
save_path = os.path.split(save_file)[0]
|
||||
check_directory_exists(save_path)
|
||||
check_directory_exists(Path(save_path))
|
||||
if not os.path.exists(save_file):
|
||||
shutil.copy(audio_from, save_file)
|
||||
zip_file.write(audio_from, audio_to)
|
||||
@ -614,7 +615,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
success = False
|
||||
self.main_window.add_recent_file(path_file_name)
|
||||
self.set_modified(False)
|
||||
delete_file(temp_file_name)
|
||||
delete_file(Path(temp_file_name))
|
||||
return success
|
||||
|
||||
def save_local_file(self):
|
||||
@ -669,7 +670,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
return self.save_file_as()
|
||||
self.main_window.add_recent_file(path_file_name)
|
||||
self.set_modified(False)
|
||||
delete_file(temp_file_name)
|
||||
delete_file(Path(temp_file_name))
|
||||
return success
|
||||
|
||||
def save_file_as(self, field=None):
|
||||
@ -774,7 +775,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
self.set_file_name(file_name)
|
||||
self.main_window.display_progress_bar(len(items))
|
||||
self.process_service_items(items)
|
||||
delete_file(p_file)
|
||||
delete_file(Path(p_file))
|
||||
self.main_window.add_recent_file(file_name)
|
||||
self.set_modified(False)
|
||||
Settings().setValue('servicemanager/last file', file_name)
|
||||
@ -1343,7 +1344,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
Empties the service_path of temporary files on system exit.
|
||||
"""
|
||||
for file_name in os.listdir(self.service_path):
|
||||
file_path = os.path.join(self.service_path, file_name)
|
||||
file_path = Path(self.service_path, file_name)
|
||||
delete_file(file_path)
|
||||
if os.path.exists(os.path.join(self.service_path, 'audio')):
|
||||
shutil.rmtree(os.path.join(self.service_path, 'audio'), True)
|
||||
|
@ -26,6 +26,7 @@ import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.api import ApiTab
|
||||
from openlp.core.common import Registry, RegistryProperties
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||
@ -56,12 +57,13 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
self.projector_tab = None
|
||||
self.advanced_tab = None
|
||||
self.player_tab = None
|
||||
self.api_tab = None
|
||||
|
||||
def exec(self):
|
||||
"""
|
||||
Execute the form
|
||||
"""
|
||||
# load all the
|
||||
# load all the widgets
|
||||
self.setting_list_widget.blockSignals(True)
|
||||
self.setting_list_widget.clear()
|
||||
while self.stacked_layout.count():
|
||||
@ -72,6 +74,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
self.insert_tab(self.advanced_tab)
|
||||
self.insert_tab(self.player_tab)
|
||||
self.insert_tab(self.projector_tab)
|
||||
self.insert_tab(self.api_tab)
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
if plugin.settings_tab:
|
||||
self.insert_tab(plugin.settings_tab, plugin.is_active())
|
||||
@ -93,6 +96,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
list_item = QtWidgets.QListWidgetItem(build_icon(tab_widget.icon_path), tab_widget.tab_title_visible)
|
||||
list_item.setData(QtCore.Qt.UserRole, tab_widget.tab_title)
|
||||
self.setting_list_widget.addItem(list_item)
|
||||
tab_widget.load()
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
@ -154,10 +158,13 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
||||
self.advanced_tab = AdvancedTab(self)
|
||||
# Advanced tab
|
||||
self.player_tab = PlayerTab(self)
|
||||
# Api tab
|
||||
self.api_tab = ApiTab(self)
|
||||
self.general_tab.post_set_up()
|
||||
self.themes_tab.post_set_up()
|
||||
self.advanced_tab.post_set_up()
|
||||
self.player_tab.post_set_up()
|
||||
self.api_tab.post_set_up()
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
if plugin.settings_tab:
|
||||
plugin.settings_tab.post_set_up()
|
||||
|
@ -439,6 +439,10 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
# NOTE: {t} used to keep line length < maxline
|
||||
getattr(self,
|
||||
'slidecontroller_{t}_previous'.format(t=self.type_prefix)).connect(self.on_slide_selected_previous)
|
||||
if self.is_live:
|
||||
getattr(self, 'mediacontroller_live_play').connect(self.media_controller.on_media_play)
|
||||
getattr(self, 'mediacontroller_live_pause').connect(self.media_controller.on_media_pause)
|
||||
getattr(self, 'mediacontroller_live_stop').connect(self.media_controller.on_media_stop)
|
||||
|
||||
def _slide_shortcut_activated(self):
|
||||
"""
|
||||
@ -1530,6 +1534,9 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController):
|
||||
slidecontroller_live_next = QtCore.pyqtSignal()
|
||||
slidecontroller_live_previous = QtCore.pyqtSignal()
|
||||
slidecontroller_toggle_display = QtCore.pyqtSignal(str)
|
||||
mediacontroller_live_play = QtCore.pyqtSignal()
|
||||
mediacontroller_live_pause = QtCore.pyqtSignal()
|
||||
mediacontroller_live_stop = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
|
@ -24,10 +24,12 @@ The Theme wizard
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui import ThemeLayoutForm
|
||||
@ -187,7 +189,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||
"""
|
||||
background_image = BackgroundType.to_string(BackgroundType.Image)
|
||||
if self.page(self.currentId()) == self.background_page and \
|
||||
self.theme.background_type == background_image and is_not_image_file(self.theme.background_filename):
|
||||
self.theme.background_type == background_image and \
|
||||
is_not_image_file(Path(self.theme.background_filename)):
|
||||
QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
|
||||
translate('OpenLP.ThemeWizard', 'You have not selected a '
|
||||
'background image. Please select one before continuing.'))
|
||||
@ -316,11 +319,11 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||
self.setField('background_type', 1)
|
||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
||||
self.image_color_button.color = self.theme.background_border_color
|
||||
self.image_path_edit.path = self.theme.background_filename
|
||||
self.image_path_edit.path = str_to_path(self.theme.background_filename)
|
||||
self.setField('background_type', 2)
|
||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
||||
self.video_color_button.color = self.theme.background_border_color
|
||||
self.video_path_edit.path = self.theme.background_filename
|
||||
self.video_path_edit.path = str_to_path(self.theme.background_filename)
|
||||
self.setField('background_type', 4)
|
||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
||||
self.setField('background_type', 3)
|
||||
@ -448,18 +451,18 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||
"""
|
||||
self.theme.background_end_color = color
|
||||
|
||||
def on_image_path_edit_path_changed(self, filename):
|
||||
def on_image_path_edit_path_changed(self, file_path):
|
||||
"""
|
||||
Background Image button pushed.
|
||||
"""
|
||||
self.theme.background_filename = filename
|
||||
self.theme.background_filename = path_to_str(file_path)
|
||||
self.set_background_page_values()
|
||||
|
||||
def on_video_path_edit_path_changed(self, filename):
|
||||
def on_video_path_edit_path_changed(self, file_path):
|
||||
"""
|
||||
Background video button pushed.
|
||||
"""
|
||||
self.theme.background_filename = filename
|
||||
self.theme.background_filename = path_to_str(file_path)
|
||||
self.set_background_page_values()
|
||||
|
||||
def on_main_color_changed(self, color):
|
||||
|
@ -22,22 +22,24 @@
|
||||
"""
|
||||
The Theme Manager manages adding, deleteing and modifying of themes.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from xml.etree.ElementTree import ElementTree, XML
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||
check_directory_exists, UiStrings, translate, is_win, get_filesystem_encoding, delete_file
|
||||
from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||
check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.theme import Theme, BackgroundType
|
||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||
from openlp.core.ui.lib import OpenLPToolbar
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.common.languagemanager import get_locale_key
|
||||
|
||||
|
||||
@ -159,10 +161,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
"""
|
||||
Set up the theme path variables
|
||||
"""
|
||||
self.path = AppLocation.get_section_data_path(self.settings_section)
|
||||
check_directory_exists(self.path)
|
||||
self.path = str(AppLocation.get_section_data_path(self.settings_section))
|
||||
check_directory_exists(Path(self.path))
|
||||
self.thumb_path = os.path.join(self.path, 'thumbnails')
|
||||
check_directory_exists(self.thumb_path)
|
||||
check_directory_exists(Path(self.thumb_path))
|
||||
|
||||
def check_list_state(self, item, field=None):
|
||||
"""
|
||||
@ -354,8 +356,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
"""
|
||||
self.theme_list.remove(theme)
|
||||
thumb = '{name}.png'.format(name=theme)
|
||||
delete_file(os.path.join(self.path, thumb))
|
||||
delete_file(os.path.join(self.thumb_path, thumb))
|
||||
delete_file(Path(self.path, thumb))
|
||||
delete_file(Path(self.thumb_path, thumb))
|
||||
try:
|
||||
# Windows is always unicode, so no need to encode filenames
|
||||
if is_win():
|
||||
@ -424,15 +426,17 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
those files. This process will only load version 2 themes.
|
||||
:param field:
|
||||
"""
|
||||
files = FileDialog.getOpenFileNames(self,
|
||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||
Settings().value(self.settings_section + '/last directory import'),
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||
self.log_info('New Themes {name}'.format(name=str(files)))
|
||||
if not files:
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self,
|
||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||
str_to_path(Settings().value(self.settings_section + '/last directory import')),
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||
self.log_info('New Themes {file_paths}'.format(file_paths=file_paths))
|
||||
if not file_paths:
|
||||
return
|
||||
self.application.set_busy_cursor()
|
||||
for file_name in files:
|
||||
for file_path in file_paths:
|
||||
file_name = path_to_str(file_path)
|
||||
Settings().setValue(self.settings_section + '/last directory import', str(file_name))
|
||||
self.unzip_theme(file_name, self.path)
|
||||
self.load_themes()
|
||||
@ -445,9 +449,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
self.application.set_busy_cursor()
|
||||
files = AppLocation.get_files(self.settings_section, '.otz')
|
||||
for theme_file in files:
|
||||
theme_file = os.path.join(self.path, theme_file)
|
||||
theme_file = os.path.join(self.path, str(theme_file))
|
||||
self.unzip_theme(theme_file, self.path)
|
||||
delete_file(theme_file)
|
||||
delete_file(Path(theme_file))
|
||||
files = AppLocation.get_files(self.settings_section, '.png')
|
||||
# No themes have been found so create one
|
||||
if not files:
|
||||
@ -470,6 +474,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
|
||||
# now process the file list of png files
|
||||
for name in files:
|
||||
name = str(name)
|
||||
# check to see file is in theme root directory
|
||||
theme = os.path.join(self.path, name)
|
||||
if os.path.exists(theme):
|
||||
@ -510,12 +515,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
:return: The theme object.
|
||||
"""
|
||||
self.log_debug('get theme data for theme {name}'.format(name=theme_name))
|
||||
theme_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.json')
|
||||
theme_data = get_text_file_string(theme_file)
|
||||
theme_file_path = Path(self.path, str(theme_name), '{file_name}.json'.format(file_name=theme_name))
|
||||
theme_data = get_text_file_string(theme_file_path)
|
||||
jsn = True
|
||||
if not theme_data:
|
||||
theme_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.xml')
|
||||
theme_data = get_text_file_string(theme_file)
|
||||
theme_file_path = theme_file_path.with_suffix('.xml')
|
||||
theme_data = get_text_file_string(theme_file_path)
|
||||
jsn = False
|
||||
if not theme_data:
|
||||
self.log_debug('No theme data - using default theme')
|
||||
@ -588,7 +593,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
# is directory or preview file
|
||||
continue
|
||||
full_name = os.path.join(directory, out_name)
|
||||
check_directory_exists(os.path.dirname(full_name))
|
||||
check_directory_exists(Path(os.path.dirname(full_name)))
|
||||
if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json':
|
||||
file_xml = str(theme_zip.read(name), 'utf-8')
|
||||
out_file = open(full_name, 'w', encoding='utf-8')
|
||||
@ -666,10 +671,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
name = theme.theme_name
|
||||
theme_pretty = theme.export_theme()
|
||||
theme_dir = os.path.join(self.path, name)
|
||||
check_directory_exists(theme_dir)
|
||||
check_directory_exists(Path(theme_dir))
|
||||
theme_file = os.path.join(theme_dir, name + '.json')
|
||||
if self.old_background_image and image_to != self.old_background_image:
|
||||
delete_file(self.old_background_image)
|
||||
delete_file(Path(self.old_background_image))
|
||||
out_file = None
|
||||
try:
|
||||
out_file = open(theme_file, 'w', encoding='utf-8')
|
||||
|
@ -22,6 +22,8 @@
|
||||
"""
|
||||
The Create/Edit theme wizard
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings, translate, is_macosx
|
||||
|
@ -24,13 +24,15 @@ import logging
|
||||
|
||||
from PyQt5 import QtGui
|
||||
|
||||
from openlp.core.common import Settings, translate
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.common.actions import ActionList
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||
from openlp.core.lib.db import Manager
|
||||
from openlp.core.lib.theme import VerticalType
|
||||
from openlp.core.lib.ui import create_action, UiStrings
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui import AlertLocation
|
||||
from openlp.plugins.alerts.endpoint import api_alerts_endpoint, alerts_endpoint
|
||||
from openlp.plugins.alerts.forms import AlertForm
|
||||
from openlp.plugins.alerts.lib import AlertsManager, AlertsTab
|
||||
from openlp.plugins.alerts.lib.db import init_schema
|
||||
@ -140,6 +142,8 @@ 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)
|
||||
|
||||
def add_tools_menu_item(self, tools_menu):
|
||||
"""
|
||||
|
60
openlp/plugins/alerts/endpoint.py
Normal file
60
openlp/plugins/alerts/endpoint.py
Normal file
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 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 json
|
||||
import urllib
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http import requires_auth
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib 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}}
|
@ -105,7 +105,7 @@ class AlertsTab(SettingsTab):
|
||||
self.timeout_label.setText(translate('AlertsPlugin.AlertsTab', 'Alert timeout:'))
|
||||
self.timeout_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().Seconds))
|
||||
self.preview_group_box.setTitle(UiStrings().Preview)
|
||||
self.font_preview.setText(UiStrings().OLPV2x)
|
||||
self.font_preview.setText(UiStrings().OpenLP)
|
||||
|
||||
def on_background_color_changed(self, color):
|
||||
"""
|
||||
|
@ -22,9 +22,12 @@
|
||||
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.common import UiStrings
|
||||
from openlp.core.common.actions import ActionList
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
||||
from openlp.core.lib.ui import UiStrings, create_action
|
||||
from openlp.plugins.bibles.endpoint import api_bibles_endpoint, bibles_endpoint
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
|
||||
LanguageSelection
|
||||
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
|
||||
@ -74,6 +77,8 @@ class BiblePlugin(Plugin):
|
||||
self.icon_path = ':/plugins/plugin_bibles.png'
|
||||
self.icon = build_icon(self.icon_path)
|
||||
self.manager = BibleManager(self)
|
||||
register_endpoint(bibles_endpoint)
|
||||
register_endpoint(api_bibles_endpoint)
|
||||
|
||||
def initialise(self):
|
||||
"""
|
||||
|
100
openlp/plugins/bibles/endpoint.py
Normal file
100
openlp/plugins/bibles/endpoint.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http.errors import NotFound
|
||||
from openlp.core.api.endpoint.pluginhelpers import search, live, service
|
||||
from openlp.core.api.http import requires_auth
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
search(request, 'bibles', log)
|
||||
except NotFound:
|
||||
return {'results': {'items': []}}
|
@ -584,7 +584,7 @@ class BibleImportForm(OpenLPWizard):
|
||||
elif self.currentPage() == self.license_details_page:
|
||||
license_version = self.field('license_version')
|
||||
license_copyright = self.field('license_copyright')
|
||||
path = AppLocation.get_section_data_path('bibles')
|
||||
path = str(AppLocation.get_section_data_path('bibles'))
|
||||
if not license_version:
|
||||
critical_error_message_box(
|
||||
UiStrings().EmptyField,
|
||||
|
@ -470,7 +470,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
||||
Return the cursor object. Instantiate one if it doesn't exist yet.
|
||||
"""
|
||||
if BiblesResourcesDB.cursor is None:
|
||||
file_path = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
|
||||
file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)),
|
||||
'bibles', 'resources', 'bibles_resources.sqlite')
|
||||
conn = sqlite3.connect(file_path)
|
||||
BiblesResourcesDB.cursor = conn.cursor()
|
||||
@ -759,7 +759,7 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
|
||||
"""
|
||||
if AlternativeBookNamesDB.cursor is None:
|
||||
file_path = os.path.join(
|
||||
AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite')
|
||||
str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite')
|
||||
if not os.path.exists(file_path):
|
||||
# create new DB, create table alternative_book_names
|
||||
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
|
||||
|
@ -51,6 +51,7 @@ All CSV files are expected to use a comma (',') as the delimiter and double quot
|
||||
"""
|
||||
import csv
|
||||
from collections import namedtuple
|
||||
from pathlib import Path
|
||||
|
||||
from openlp.core.common import get_file_encoding, translate
|
||||
from openlp.core.lib.exceptions import ValidationError
|
||||
@ -100,7 +101,7 @@ class CSVBible(BibleImport):
|
||||
:return: An iterable yielding namedtuples of type results_tuple
|
||||
"""
|
||||
try:
|
||||
encoding = get_file_encoding(filename)['encoding']
|
||||
encoding = get_file_encoding(Path(filename))['encoding']
|
||||
with open(filename, 'r', encoding=encoding, newline='') as csv_file:
|
||||
csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"')
|
||||
return [results_tuple(*line) for line in csv_reader]
|
||||
|
@ -255,7 +255,7 @@ class BGExtract(RegistryProperties):
|
||||
chapter=chapter,
|
||||
version=version)
|
||||
soup = get_soup_for_bible_ref(
|
||||
'http://biblegateway.com/passage/?{url}'.format(url=url_params),
|
||||
'http://www.biblegateway.com/passage/?{url}'.format(url=url_params),
|
||||
pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
|
||||
if not soup:
|
||||
return None
|
||||
@ -284,7 +284,7 @@ class BGExtract(RegistryProperties):
|
||||
"""
|
||||
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
|
||||
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
|
||||
reference_url = 'http://biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
||||
reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
||||
page = get_web_page(reference_url)
|
||||
if not page:
|
||||
send_error_message('download')
|
||||
@ -325,7 +325,7 @@ class BGExtract(RegistryProperties):
|
||||
returns a list in the form [(biblename, biblekey, language_code)]
|
||||
"""
|
||||
log.debug('BGExtract.get_bibles_from_http')
|
||||
bible_url = 'https://biblegateway.com/versions/'
|
||||
bible_url = 'https://www.biblegateway.com/versions/'
|
||||
soup = get_soup_for_bible_ref(bible_url)
|
||||
if not soup:
|
||||
return None
|
||||
@ -773,7 +773,7 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
|
||||
return None
|
||||
try:
|
||||
page = get_web_page(reference_url, header, True)
|
||||
except:
|
||||
except Exception as e:
|
||||
page = None
|
||||
if not page:
|
||||
send_error_message('download')
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from openlp.core.common import AppLocation, OpenLPMixin, RegistryProperties, Settings, translate, delete_file, UiStrings
|
||||
from openlp.plugins.bibles.lib import LanguageSelection, parse_reference
|
||||
@ -111,7 +112,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
||||
self.settings_section = 'bibles'
|
||||
self.web = 'Web'
|
||||
self.db_cache = None
|
||||
self.path = AppLocation.get_section_data_path(self.settings_section)
|
||||
self.path = str(AppLocation.get_section_data_path(self.settings_section))
|
||||
self.proxy_name = Settings().value(self.settings_section + '/proxy name')
|
||||
self.suffix = '.sqlite'
|
||||
self.import_wizard = None
|
||||
@ -124,7 +125,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
||||
of HTTPBible is loaded instead of the BibleDB class.
|
||||
"""
|
||||
log.debug('Reload bibles')
|
||||
files = AppLocation.get_files(self.settings_section, self.suffix)
|
||||
files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)]
|
||||
if 'alternative_book_names.sqlite' in files:
|
||||
files.remove('alternative_book_names.sqlite')
|
||||
log.debug('Bible Files {text}'.format(text=files))
|
||||
@ -137,7 +138,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
||||
# Remove corrupted files.
|
||||
if name is None:
|
||||
bible.session.close_all()
|
||||
delete_file(os.path.join(self.path, filename))
|
||||
delete_file(Path(self.path, filename))
|
||||
continue
|
||||
log.debug('Bible Name: "{name}"'.format(name=name))
|
||||
self.db_cache[name] = bible
|
||||
@ -185,7 +186,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
||||
bible = self.db_cache[name]
|
||||
bible.session.close_all()
|
||||
bible.session = None
|
||||
return delete_file(os.path.join(bible.path, bible.file))
|
||||
return delete_file(Path(bible.path, bible.file))
|
||||
|
||||
def get_bibles(self):
|
||||
"""
|
||||
|
@ -26,8 +26,10 @@ for the Custom Slides plugin.
|
||||
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
||||
from openlp.core.lib.db import Manager
|
||||
from openlp.plugins.custom.endpoint import api_custom_endpoint, custom_endpoint
|
||||
from openlp.plugins.custom.lib import CustomMediaItem, CustomTab
|
||||
from openlp.plugins.custom.lib.db import CustomSlide, init_schema
|
||||
from openlp.plugins.custom.lib.mediaitem import CustomSearch
|
||||
@ -61,6 +63,8 @@ class CustomPlugin(Plugin):
|
||||
self.db_manager = Manager('custom', init_schema)
|
||||
self.icon_path = ':/plugins/plugin_custom.png'
|
||||
self.icon = build_icon(self.icon_path)
|
||||
register_endpoint(custom_endpoint)
|
||||
register_endpoint(api_custom_endpoint)
|
||||
|
||||
@staticmethod
|
||||
def about():
|
||||
|
100
openlp/plugins/custom/endpoint.py
Normal file
100
openlp/plugins/custom/endpoint.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http.errors import NotFound
|
||||
from openlp.core.api.endpoint.pluginhelpers import search, live, service
|
||||
from openlp.core.api.http import requires_auth
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
search(request, 'custom', log)
|
||||
except NotFound:
|
||||
return {'results': {'items': []}}
|
113
openlp/plugins/images/endpoint.py
Normal file
113
openlp/plugins/images/endpoint.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http.errors import NotFound
|
||||
from openlp.core.api.endpoint.pluginhelpers import search, live, service, display_thumbnails
|
||||
from openlp.core.api.http import requires_auth
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
search(request, 'images', log)
|
||||
except NotFound:
|
||||
return {'results': {'items': []}}
|
@ -24,9 +24,11 @@ from PyQt5 import QtGui
|
||||
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.common import Settings, translate
|
||||
from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
|
||||
from openlp.core.lib.db import Manager
|
||||
from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
|
||||
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
|
||||
from openlp.plugins.images.lib.db import init_schema
|
||||
|
||||
@ -51,6 +53,8 @@ class ImagePlugin(Plugin):
|
||||
self.weight = -7
|
||||
self.icon_path = ':/plugins/plugin_images.png'
|
||||
self.icon = build_icon(self.icon_path)
|
||||
register_endpoint(images_endpoint)
|
||||
register_endpoint(api_images_endpoint)
|
||||
|
||||
@staticmethod
|
||||
def about():
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
@ -98,8 +99,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
self.list_view.setIconSize(QtCore.QSize(88, 50))
|
||||
self.list_view.setIndentation(self.list_view.default_indentation)
|
||||
self.list_view.allow_internal_dnd = True
|
||||
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
|
||||
check_directory_exists(self.service_path)
|
||||
self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
|
||||
check_directory_exists(Path(self.service_path))
|
||||
# Load images from the database
|
||||
self.load_full_list(
|
||||
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)
|
||||
@ -210,8 +211,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
"""
|
||||
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
||||
for image in images:
|
||||
delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1]))
|
||||
delete_file(self.generate_thumbnail_path(image))
|
||||
delete_file(Path(self.service_path, os.path.split(image.filename)[1]))
|
||||
delete_file(Path(self.generate_thumbnail_path(image)))
|
||||
self.manager.delete_object(ImageFilenames, image.id)
|
||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
|
||||
for group in image_groups:
|
||||
@ -233,8 +234,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
if row_item:
|
||||
item_data = row_item.data(0, QtCore.Qt.UserRole)
|
||||
if isinstance(item_data, ImageFilenames):
|
||||
delete_file(os.path.join(self.service_path, row_item.text(0)))
|
||||
delete_file(self.generate_thumbnail_path(item_data))
|
||||
delete_file(Path(self.service_path, row_item.text(0)))
|
||||
delete_file(Path(self.generate_thumbnail_path(item_data)))
|
||||
if item_data.group_id == 0:
|
||||
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
||||
else:
|
||||
|
100
openlp/plugins/media/endpoint.py
Normal file
100
openlp/plugins/media/endpoint.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http.errors import NotFound
|
||||
from openlp.core.api.endpoint.pluginhelpers import search, live, service
|
||||
from openlp.core.api.http import requires_auth
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
search(request, 'media', log)
|
||||
except NotFound:
|
||||
return {'results': {'items': []}}
|
@ -22,6 +22,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
@ -300,8 +301,8 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
||||
Initialize media item.
|
||||
"""
|
||||
self.list_view.clear()
|
||||
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
|
||||
check_directory_exists(self.service_path)
|
||||
self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
|
||||
check_directory_exists(Path(self.service_path))
|
||||
self.load_list(Settings().value(self.settings_section + '/media files'))
|
||||
self.rebuild_players()
|
||||
|
||||
|
@ -26,12 +26,14 @@ The Media plugin
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from shutil import which
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, translate, check_binary_exists, is_win
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.common import AppLocation, translate, check_binary_exists
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||
from openlp.plugins.media.endpoint import api_media_endpoint, media_endpoint
|
||||
from openlp.plugins.media.lib import MediaMediaItem, MediaTab
|
||||
|
||||
|
||||
@ -58,6 +60,8 @@ 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)
|
||||
|
||||
def initialise(self):
|
||||
"""
|
||||
@ -75,7 +79,7 @@ class MediaPlugin(Plugin):
|
||||
exists = process_check_binary('mediainfo')
|
||||
# If mediainfo is not in the path, try to find it in the application folder
|
||||
if not exists:
|
||||
exists = process_check_binary(os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'mediainfo'))
|
||||
exists = process_check_binary(os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'mediainfo'))
|
||||
return exists
|
||||
|
||||
def app_startup(self):
|
||||
@ -162,8 +166,7 @@ def process_check_binary(program_path):
|
||||
:param program_path:The full path to the binary to check.
|
||||
:return: If exists or not
|
||||
"""
|
||||
program_type = None
|
||||
runlog = check_binary_exists(program_path)
|
||||
runlog = check_binary_exists(Path(program_path))
|
||||
# Analyse the output to see it the program is mediainfo
|
||||
for line in runlog.splitlines():
|
||||
decoded_line = line.decode()
|
||||
|
114
openlp/plugins/presentations/endpoint.py
Normal file
114
openlp/plugins/presentations/endpoint.py
Normal file
@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.http.errors import NotFound
|
||||
from openlp.core.api.endpoint.pluginhelpers import search, live, service, display_thumbnails
|
||||
from openlp.core.api.http import requires_auth
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
search(request, 'presentations', log)
|
||||
except NotFound:
|
||||
return {'results': {'items': []}}
|
@ -34,6 +34,7 @@
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from openlp.core.common import is_win, Registry, get_uno_command, get_uno_instance, delete_file
|
||||
|
||||
@ -275,7 +276,7 @@ class ImpressDocument(PresentationDocument):
|
||||
try:
|
||||
doc.storeToURL(url_path, properties)
|
||||
self.convert_thumbnail(path, index + 1)
|
||||
delete_file(path)
|
||||
delete_file(Path(path))
|
||||
except ErrorCodeIOException as exception:
|
||||
log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode))
|
||||
except:
|
||||
|
@ -23,6 +23,7 @@
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
from subprocess import check_output, CalledProcessError
|
||||
|
||||
@ -69,7 +70,7 @@ class PdfController(PresentationController):
|
||||
:return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid.
|
||||
"""
|
||||
program_type = None
|
||||
runlog = check_binary_exists(program_path)
|
||||
runlog = check_binary_exists(Path(program_path))
|
||||
# Analyse the output to see it the program is mudraw, ghostscript or neither
|
||||
for line in runlog.splitlines():
|
||||
decoded_line = line.decode()
|
||||
@ -122,10 +123,10 @@ class PdfController(PresentationController):
|
||||
self.mutoolbin = pdf_program
|
||||
else:
|
||||
# Fallback to autodetection
|
||||
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
|
||||
if is_win():
|
||||
# for windows we only accept mudraw.exe or mutool.exe in the base folder
|
||||
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
|
||||
if os.path.isfile(os.path.join(application_path, 'mudraw.exe')):
|
||||
self.mudrawbin = os.path.join(application_path, 'mudraw.exe')
|
||||
elif os.path.isfile(os.path.join(application_path, 'mutool.exe')):
|
||||
@ -142,7 +143,7 @@ class PdfController(PresentationController):
|
||||
self.gsbin = which('gs')
|
||||
# Last option: check if mudraw or mutool is placed in OpenLP base folder
|
||||
if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
|
||||
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
|
||||
if os.path.isfile(os.path.join(application_path, 'mudraw')):
|
||||
self.mudrawbin = os.path.join(application_path, 'mudraw')
|
||||
elif os.path.isfile(os.path.join(application_path, 'mutool')):
|
||||
@ -199,8 +200,8 @@ class PdfDocument(PresentationDocument):
|
||||
:return: The resolution dpi to be used.
|
||||
"""
|
||||
# Use a postscript script to get size of the pdf. It is assumed that all pages have same size
|
||||
gs_resolution_script = AppLocation.get_directory(
|
||||
AppLocation.PluginsDir) + '/presentations/lib/ghostscript_get_resolution.ps'
|
||||
gs_resolution_script = str(AppLocation.get_directory(
|
||||
AppLocation.PluginsDir)) + '/presentations/lib/ghostscript_get_resolution.ps'
|
||||
# Run the script on the pdf to get the size
|
||||
runlog = []
|
||||
try:
|
||||
|
@ -34,15 +34,15 @@ from openlp.core.common import is_win, Settings
|
||||
if is_win():
|
||||
from win32com.client import Dispatch
|
||||
import win32con
|
||||
import winreg
|
||||
import win32ui
|
||||
import win32gui
|
||||
import win32ui
|
||||
import winreg
|
||||
import pywintypes
|
||||
|
||||
|
||||
from openlp.core.common import Registry, UiStrings, trace_error_handler
|
||||
from openlp.core.lib import ScreenList
|
||||
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
|
||||
from openlp.core.common import trace_error_handler, Registry
|
||||
from openlp.core.lib.ui import critical_error_message_box, translate
|
||||
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -85,7 +85,7 @@ class PptviewController(PresentationController):
|
||||
if self.process:
|
||||
return
|
||||
log.debug('start PPTView')
|
||||
dll_path = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
|
||||
dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
|
||||
'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
|
||||
self.process = cdll.LoadLibrary(dll_path)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
|
@ -23,6 +23,7 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
@ -98,7 +99,7 @@ class PresentationDocument(object):
|
||||
"""
|
||||
self.slide_number = 0
|
||||
self.file_path = name
|
||||
check_directory_exists(self.get_thumbnail_folder())
|
||||
check_directory_exists(Path(self.get_thumbnail_folder()))
|
||||
|
||||
def load_presentation(self):
|
||||
"""
|
||||
@ -415,11 +416,12 @@ class PresentationController(object):
|
||||
self.document_class = document_class
|
||||
self.settings_section = self.plugin.settings_section
|
||||
self.available = None
|
||||
self.temp_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), name)
|
||||
self.thumbnail_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
|
||||
self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name)
|
||||
self.thumbnail_folder = os.path.join(
|
||||
str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
|
||||
self.thumbnail_prefix = 'slide'
|
||||
check_directory_exists(self.thumbnail_folder)
|
||||
check_directory_exists(self.temp_folder)
|
||||
check_directory_exists(Path(self.thumbnail_folder))
|
||||
check_directory_exists(Path(self.temp_folder))
|
||||
|
||||
def enabled(self):
|
||||
"""
|
||||
|
@ -20,10 +20,11 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import SettingsTab
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.lib import PathEdit
|
||||
from openlp.plugins.presentations.lib.pdfcontroller import PdfController
|
||||
@ -156,7 +157,7 @@ class PresentationTab(SettingsTab):
|
||||
self.program_path_edit.setEnabled(enable_pdf_program)
|
||||
pdf_program = Settings().value(self.settings_section + '/pdf_program')
|
||||
if pdf_program:
|
||||
self.program_path_edit.path = pdf_program
|
||||
self.program_path_edit.path = str_to_path(pdf_program)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
@ -192,7 +193,7 @@ class PresentationTab(SettingsTab):
|
||||
Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
|
||||
changed = True
|
||||
# Save pdf-settings
|
||||
pdf_program = self.program_path_edit.path
|
||||
pdf_program = path_to_str(self.program_path_edit.path)
|
||||
enable_pdf_program = self.pdf_program_check_box.checkState()
|
||||
# If the given program is blank disable using the program
|
||||
if pdf_program == '':
|
||||
@ -219,12 +220,13 @@ class PresentationTab(SettingsTab):
|
||||
checkbox.setEnabled(controller.is_available())
|
||||
self.set_controller_text(checkbox, controller)
|
||||
|
||||
def on_program_path_edit_path_changed(self, filename):
|
||||
def on_program_path_edit_path_changed(self, new_path):
|
||||
"""
|
||||
Select the mudraw or ghostscript binary that should be used.
|
||||
"""
|
||||
if filename:
|
||||
if not PdfController.process_check_binary(filename):
|
||||
new_path = path_to_str(new_path)
|
||||
if new_path:
|
||||
if not PdfController.process_check_binary(new_path):
|
||||
critical_error_message_box(UiStrings().Error,
|
||||
translate('PresentationPlugin.PresentationTab',
|
||||
'The program is not ghostscript or mudraw which is required.'))
|
||||
|
@ -28,8 +28,10 @@ import logging
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import AppLocation, extension_loader, translate
|
||||
from openlp.core.api.http import register_endpoint
|
||||
from openlp.core.common import extension_loader, translate
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||
from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint
|
||||
from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -66,6 +68,8 @@ class PresentationPlugin(Plugin):
|
||||
self.weight = -8
|
||||
self.icon_path = ':/plugins/plugin_presentations.png'
|
||||
self.icon = build_icon(self.icon_path)
|
||||
register_endpoint(presentations_endpoint)
|
||||
register_endpoint(api_presentations_endpoint)
|
||||
|
||||
def create_settings_tab(self, parent):
|
||||
"""
|
||||
@ -121,7 +125,7 @@ class PresentationPlugin(Plugin):
|
||||
Check to see if we have any presentation software available. If not do not install the plugin.
|
||||
"""
|
||||
log.debug('check_pre_conditions')
|
||||
controller_dir = os.path.join('openlp', 'plugins', 'presentations', 'lib')
|
||||
controller_dir = os.path.join('plugins', 'presentations', 'lib')
|
||||
glob_pattern = os.path.join(controller_dir, '*controller.py')
|
||||
extension_loader(glob_pattern, ['presentationcontroller.py'])
|
||||
controller_classes = PresentationController.__subclasses__()
|
||||
|
@ -19,71 +19,3 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`remotes` plugin allows OpenLP to be controlled from another machine
|
||||
over a network connection.
|
||||
|
||||
Routes:
|
||||
|
||||
``/``
|
||||
Go to the web interface.
|
||||
|
||||
``/files/{filename}``
|
||||
Serve a static file.
|
||||
|
||||
``/api/poll``
|
||||
Poll to see if there are any changes. Returns a JSON-encoded dict of
|
||||
any changes that occurred::
|
||||
|
||||
{"results": {"type": "controller"}}
|
||||
|
||||
Or, if there were no results, False::
|
||||
|
||||
{"results": False}
|
||||
|
||||
``/api/controller/{live|preview}/{action}``
|
||||
Perform ``{action}`` on the live or preview controller. Valid actions
|
||||
are:
|
||||
|
||||
``next``
|
||||
Load the next slide.
|
||||
|
||||
``previous``
|
||||
Load the previous slide.
|
||||
|
||||
``jump``
|
||||
Jump to a specific slide. Requires an id return in a JSON-encoded
|
||||
dict like so::
|
||||
|
||||
{"request": {"id": 1}}
|
||||
|
||||
``first``
|
||||
Load the first slide.
|
||||
|
||||
``last``
|
||||
Load the last slide.
|
||||
|
||||
``text``
|
||||
Request the text of the current slide.
|
||||
|
||||
``/api/service/{action}``
|
||||
Perform ``{action}`` on the service manager (e.g. go live). Data is
|
||||
passed as a json-encoded ``data`` parameter. Valid actions are:
|
||||
|
||||
``next``
|
||||
Load the next item in the service.
|
||||
|
||||
``previous``
|
||||
Load the previews item in the service.
|
||||
|
||||
``jump``
|
||||
Jump to a specific item in the service. Requires an id returned in
|
||||
a JSON-encoded dict like so::
|
||||
|
||||
{"request": {"id": 1}}
|
||||
|
||||
``list``
|
||||
Request a list of items in the service.
|
||||
|
||||
|
||||
"""
|
||||
|
69
openlp/plugins/remotes/deploy.py
Normal file
69
openlp/plugins/remotes/deploy.py
Normal file
@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
import urllib.error
|
||||
|
||||
from openlp.core.common import AppLocation, Registry
|
||||
from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size
|
||||
|
||||
|
||||
def deploy_zipfile(app_root, zip_name):
|
||||
"""
|
||||
Process the downloaded zip file and add to the correct directory
|
||||
|
||||
:param zip_name: the zip file to be processed
|
||||
:param app_root: the directory where the zip get expanded to
|
||||
|
||||
:return: None
|
||||
"""
|
||||
zip_file = os.path.join(app_root, zip_name)
|
||||
web_zip = zipfile.ZipFile(zip_file)
|
||||
web_zip.extractall(app_root)
|
||||
|
||||
|
||||
def download_sha256():
|
||||
"""
|
||||
Download the config file to extract the sha256 and version number
|
||||
"""
|
||||
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
|
||||
try:
|
||||
web_config = get_web_page('{host}{name}'.format(host='https://get.openlp.org/webclient/', name='download.cfg'),
|
||||
header=('User-Agent', user_agent))
|
||||
except (urllib.error.URLError, ConnectionError) as err:
|
||||
return False
|
||||
file_bits = web_config.read().decode('utf-8').split()
|
||||
return file_bits[0], file_bits[2]
|
||||
|
||||
|
||||
def download_and_check(callback=None):
|
||||
"""
|
||||
Download the web site and deploy it.
|
||||
"""
|
||||
sha256, version = download_sha256()
|
||||
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
|
||||
callback.setRange(0, file_size)
|
||||
if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
|
||||
os.path.join(str(AppLocation.get_section_data_path('remotes')), 'site.zip'),
|
||||
sha256=sha256):
|
||||
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
|
@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
@ -19,28 +19,28 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${chords_title}</title>
|
||||
<link rel="stylesheet" href="/css/stage.css" />
|
||||
<link rel="stylesheet" href="/css/chords.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/chords.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" id="next-text" value="${next}" />
|
||||
<div id="right">
|
||||
<div id="clock"></div>
|
||||
<div id="chords" class="button">Toggle Chords</div>
|
||||
<div id="notes"></div>
|
||||
</div>
|
||||
<div id="header">
|
||||
<div id="verseorder"></div>
|
||||
<div id="transpose">Transpose:</div> <div class="button" id="transposedown">-</div> <div id="transposevalue">0</div> <div class="button" id="transposeup">+</div> <div id="capodisplay">(Capo)</div>
|
||||
</div>
|
||||
<div id="currentslide"></div>
|
||||
<div id="nextslide"></div>
|
||||
</body>
|
||||
</html>
|
||||
import logging
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.api.http.endpoint import Endpoint
|
||||
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
|
||||
from openlp.core.common import AppLocation
|
||||
|
||||
|
||||
static_dir = os.path.join(str(AppLocation.get_section_data_path('remotes')))
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
remote_endpoint = Endpoint('remote', template_dir=static_dir, static_dir=static_dir)
|
||||
|
||||
|
||||
@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)
|
9404
openlp/plugins/remotes/html/assets/jquery.js
vendored
9404
openlp/plugins/remotes/html/assets/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,96 +0,0 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2017 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 *
|
||||
******************************************************************************/
|
||||
|
||||
#header {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
#transpose,
|
||||
#transposevalue,
|
||||
#capodisplay {
|
||||
display: inline-block;
|
||||
font-size: 30pt;
|
||||
color: gray;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid gray;
|
||||
border-radius: .3em;
|
||||
padding: 0 .2em;
|
||||
min-width: 1.2em;
|
||||
line-height: 1.2em;
|
||||
font-size: 25pt;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0px 1px 0px white;
|
||||
color: black;
|
||||
background: linear-gradient(to bottom, white 5%, gray 100%);
|
||||
background-color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button:hover {
|
||||
background: linear-gradient(to bottom, white 10%, gray 150%);
|
||||
color: darkslategray ;
|
||||
background-color: gray;
|
||||
}
|
||||
.button:active {
|
||||
position:relative;
|
||||
top:1px;
|
||||
}
|
||||
|
||||
/* Extending existing definition in stage.css */
|
||||
#verseorder {
|
||||
line-height: 1.5;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.chordline {
|
||||
line-height: 2.0;
|
||||
}
|
||||
|
||||
.chordline1 {
|
||||
line-height: 1.0
|
||||
}
|
||||
|
||||
.chordline span.chord span {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chordline span.chord span strong {
|
||||
position: absolute;
|
||||
top: -0.8em;
|
||||
left: 0;
|
||||
font-size: 30pt;
|
||||
font-weight: normal;
|
||||
line-height: normal;
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.ws {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#nextslide .chordline span.chord span strong {
|
||||
color: gray;
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2017 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the Free *
|
||||
* Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT *
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
|
||||
* more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
******************************************************************************/
|
||||
body {
|
||||
background-color: black;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.size {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2017 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the Free *
|
||||
* Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT *
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
|
||||
* more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
******************************************************************************/
|
||||
|
||||
.ui-icon-blank {
|
||||
background-image: url(../images/ui-icon-blank.png);
|
||||
}
|
||||
|
||||
.ui-icon-unblank {
|
||||
background-image: url(../images/ui-icon-unblank.png);
|
||||
}
|
||||
|
||||
/* Overwrite style from jquery-mobile.min.css */
|
||||
.ui-li .ui-btn-text a.ui-link-inherit{
|
||||
white-space: normal;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2017 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the Free *
|
||||
* Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT *
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
|
||||
* more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
******************************************************************************/
|
||||
|
||||
body {
|
||||
background-color: black;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE 10+ */
|
||||
user-select: none; /* Future */
|
||||
}
|
||||
|
||||
#currentslide {
|
||||
font-size: 40pt;
|
||||
color: white;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#nextslide {
|
||||
font-size: 40pt;
|
||||
color: grey;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#clock {
|
||||
font-size: 30pt;
|
||||
color: yellow;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#notes {
|
||||
font-size: 36pt;
|
||||
color: salmon;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#verseorder {
|
||||
font-size: 30pt;
|
||||
color: green;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.currenttag {
|
||||
color: lightgreen;
|
||||
font-weight: bold;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 340 B |
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 364 B |
Binary file not shown.
Before Width: | Height: | Size: 460 B |
Binary file not shown.
Before Width: | Height: | Size: 453 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user