Status: | Merged |
---|---|
Merged at revision: | 2759 |
Proposed branch: | lp:~trb143/openlp/webfull |
Merge into: | lp:openlp |
Diff against target: |
26912 lines (+3670/-22117) 92 files modified
openlp/core/__init__.py (+5/-4) openlp/core/api/__init__.py (+28/-0) openlp/core/api/endpoint/__init__.py (+25/-0) openlp/core/api/endpoint/controller.py (+144/-0) openlp/core/api/endpoint/core.py (+182/-0) openlp/core/api/endpoint/pluginhelpers.py (+138/-0) openlp/core/api/endpoint/service.py (+100/-0) openlp/core/api/http/__init__.py (+110/-0) openlp/core/api/http/endpoint.py (+80/-0) openlp/core/api/http/errors.py (+65/-0) openlp/core/api/http/server.py (+97/-0) openlp/core/api/http/wsgiapp.py (+181/-0) openlp/core/api/poll.py (+130/-0) openlp/core/api/tab.py (+316/-0) openlp/core/api/websockets.py (+142/-0) openlp/core/common/httputils.py (+16/-0) openlp/core/common/settings.py (+19/-0) openlp/core/common/uistrings.py (+2/-0) openlp/core/common/versionchecker.py (+38/-7) openlp/core/lib/imagemanager.py (+28/-7) openlp/core/ui/firsttimeform.py (+1/-3) openlp/core/ui/firsttimewizard.py (+2/-2) openlp/core/ui/mainwindow.py (+8/-2) openlp/core/ui/media/__init__.py (+1/-0) openlp/core/ui/media/endpoint.py (+72/-0) openlp/core/ui/media/mediacontroller.py (+28/-1) openlp/core/ui/settingsform.py (+8/-1) openlp/core/ui/slidecontroller.py (+7/-0) openlp/plugins/alerts/alertsplugin.py (+4/-0) openlp/plugins/alerts/endpoint.py (+60/-0) openlp/plugins/bibles/bibleplugin.py (+4/-0) openlp/plugins/bibles/endpoint.py (+100/-0) openlp/plugins/custom/customplugin.py (+4/-0) openlp/plugins/custom/endpoint.py (+100/-0) openlp/plugins/images/endpoint.py (+113/-0) openlp/plugins/images/imageplugin.py (+4/-0) openlp/plugins/media/endpoint.py (+100/-0) openlp/plugins/media/mediaplugin.py (+5/-2) openlp/plugins/presentations/endpoint.py (+114/-0) openlp/plugins/presentations/presentationplugin.py (+5/-1) openlp/plugins/remotes/__init__.py (+0/-68) openlp/plugins/remotes/deploy.py (+69/-0) openlp/plugins/remotes/endpoint.py (+46/-0) openlp/plugins/remotes/html/assets/jquery.js (+0/-9404) openlp/plugins/remotes/html/assets/jquery.min.js (+0/-4) openlp/plugins/remotes/html/assets/jquery.mobile.js (+0/-9357) openlp/plugins/remotes/html/assets/jquery.mobile.min.css (+0/-2) openlp/plugins/remotes/html/assets/jquery.mobile.min.js (+0/-2) openlp/plugins/remotes/html/chords.html (+0/-46) openlp/plugins/remotes/html/css/chords.css (+0/-96) openlp/plugins/remotes/html/css/main.css (+0/-32) openlp/plugins/remotes/html/css/openlp.css (+0/-31) openlp/plugins/remotes/html/css/stage.css (+0/-68) openlp/plugins/remotes/html/index.html (+0/-177) openlp/plugins/remotes/html/js/chords.js (+0/-331) openlp/plugins/remotes/html/js/main.js (+0/-45) openlp/plugins/remotes/html/js/openlp.js (+0/-384) openlp/plugins/remotes/html/js/stage.js (+0/-170) openlp/plugins/remotes/html/main.html (+0/-34) openlp/plugins/remotes/html/stage.html (+0/-41) openlp/plugins/remotes/lib/__init__.py (+0/-27) openlp/plugins/remotes/lib/httprouter.py (+0/-709) openlp/plugins/remotes/lib/httpserver.py (+0/-155) openlp/plugins/remotes/lib/remotetab.py (+0/-271) openlp/plugins/remotes/remoteplugin.py (+94/-65) openlp/plugins/songs/endpoint.py (+100/-0) openlp/plugins/songs/lib/mediaitem.py (+1/-1) openlp/plugins/songs/songsplugin.py (+8/-2) scripts/check_dependencies.py (+5/-1) scripts/jenkins_script.py (+1/-1) scripts/websocket_client.py (+37/-0) tests/functional/openlp_core_api/__init__.py (+21/-0) tests/functional/openlp_core_api/test_tab.py (+139/-0) tests/functional/openlp_core_api/test_websockets.py (+119/-0) tests/functional/openlp_core_api_http/__init__.py (+21/-0) tests/functional/openlp_core_api_http/test_error.py (+59/-0) tests/functional/openlp_core_api_http/test_http.py (+57/-0) tests/functional/openlp_core_api_http/test_wsgiapp.py (+86/-0) tests/functional/openlp_core_common/test_httputils.py (+27/-1) tests/functional/openlp_core_lib/test_image_manager.py (+2/-0) tests/functional/openlp_core_ui/test_mainwindow.py (+5/-2) tests/functional/openlp_core_ui/test_settingsform.py (+8/-0) tests/functional/openlp_plugins/media/test_mediaplugin.py (+1/-3) tests/functional/openlp_plugins/presentations/test_impresscontroller.py (+1/-1) tests/functional/openlp_plugins/remotes/test_deploy.py (+64/-0) tests/functional/openlp_plugins/remotes/test_remotetab.py (+0/-134) tests/functional/openlp_plugins/remotes/test_router.py (+0/-399) tests/interfaces/openlp_core_api/__init__.py (+21/-0) tests/interfaces/openlp_core_ui/test_mainwindow.py (+4/-1) tests/interfaces/openlp_core_ui/test_servicemanager.py (+4/-1) tests/interfaces/openlp_plugins/remotes/__init__.py (+0/-21) tests/interfaces/openlp_plugins/remotes/test_deploy.py (+84/-0) |
To merge this branch: | bzr merge lp:~trb143/openlp/webfull |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raoul Snyman | Approve | ||
Review via email: mp+328953@code.launchpad.net |
This proposal supersedes a proposal from 2017-03-05.
Commit message
Description of the change
This is getting to big to stay external any longer.
The web interface works with the existing HTML which has been externalised and can be pulled from OpenLP.
- Add new web and socket servers to API and replace all existing API's
- remove most of the Remote plugin but leave the base there to allow for the html and js code to land there.
- amend the FTW to download a package of html, JS and CSS and install in the remote directory
- add switch to turn off the servers to allow PyCharm to debug. It gets lost in threads if you do not!
Issues
The new UI String has gone wrong and not sure why
The FTW works fine for download but how should the Web Settings have a pop up as well?
It does work fine so have a play, you will need to run the FTW to install a set of web pages as they are missing.
Fixed up the issues Raoul brought up in the last review.
lp:~trb143/openlp/webfull (revision 2833)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[FAILURE] https:/
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
Oh, also, with the new web remote we won't need Mako or the translations because it'll be completely client-side.
Raoul Snyman (raoul-snyman) wrote : | # |
So, I realised that the register_endpoint() method isn't helpful because if the modules are never imported the endpoint is never registered. What if we "moved" register_endpoint into the Plugin class?
Tim Bentley (trb143) wrote : | # |
That could work.
A challenge with this is its size now and the time taken to stop getting it broken.
Due to time scales (renderer) it would be nice to get this in and work from within core code.
Raoul Snyman (raoul-snyman) wrote : | # |
Sure. Let's try to get it working, especially with the current remotes, and then we're definitely closer to having a better solution.
Raoul Snyman (raoul-snyman) wrote : | # |
I can always come in and refine it later.
Tim Bentley (trb143) wrote : | # |
The current remote is packaged and will download and install with this code.
That needs a new truck to be created and defined but it allows the existing functionality to work.
Raoul Snyman (raoul-snyman) wrote : | # |
Note: I haven't done a thorough code review. Let's get this in and start iterating.
Preview Diff
1 | === modified file 'openlp/core/__init__.py' |
2 | --- openlp/core/__init__.py 2017-08-01 20:59:41 +0000 |
3 | +++ openlp/core/__init__.py 2017-08-13 07:11:15 +0000 |
4 | @@ -153,10 +153,8 @@ |
5 | self.processEvents() |
6 | if not has_run_wizard: |
7 | self.main_window.first_time() |
8 | - # update_check = Settings().value('core/update check') |
9 | - # if update_check: |
10 | - # version = VersionThread(self.main_window) |
11 | - # version.start() |
12 | + version = VersionThread(self.main_window) |
13 | + version.start() |
14 | self.main_window.is_display_blank() |
15 | self.main_window.app_startup() |
16 | return self.exec() |
17 | @@ -337,6 +335,8 @@ |
18 | parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true', |
19 | help='Ignore the version file and pull the version directly from Bazaar') |
20 | parser.add_argument('-s', '--style', dest='style', help='Set the Qt5 style (passed directly to Qt5).') |
21 | + parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_false', |
22 | + help='Turn off the Web and Socket Server ') |
23 | parser.add_argument('rargs', nargs='?', default=[]) |
24 | # Parse command line options and deal with them. Use args supplied pragmatically if possible. |
25 | return parser.parse_args(args) if args else parser.parse_args() |
26 | @@ -410,6 +410,7 @@ |
27 | set_up_logging(str(AppLocation.get_directory(AppLocation.CacheDir))) |
28 | Registry.create() |
29 | Registry().register('application', application) |
30 | + Registry().set_flag('no_web_server', args.no_web_server) |
31 | application.setApplicationVersion(get_application_version()['version']) |
32 | # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one |
33 | if application.is_already_running(): |
34 | |
35 | === added directory 'openlp/core/api' |
36 | === added file 'openlp/core/api/__init__.py' |
37 | --- openlp/core/api/__init__.py 1970-01-01 00:00:00 +0000 |
38 | +++ openlp/core/api/__init__.py 2017-08-13 07:11:15 +0000 |
39 | @@ -0,0 +1,28 @@ |
40 | +# -*- coding: utf-8 -*- |
41 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
42 | + |
43 | +############################################################################### |
44 | +# OpenLP - Open Source Lyrics Projection # |
45 | +# --------------------------------------------------------------------------- # |
46 | +# Copyright (c) 2008-2017 OpenLP Developers # |
47 | +# --------------------------------------------------------------------------- # |
48 | +# This program is free software; you can redistribute it and/or modify it # |
49 | +# under the terms of the GNU General Public License as published by the Free # |
50 | +# Software Foundation; version 2 of the License. # |
51 | +# # |
52 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
53 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
54 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
55 | +# more details. # |
56 | +# # |
57 | +# You should have received a copy of the GNU General Public License along # |
58 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
59 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
60 | +############################################################################### |
61 | + |
62 | +from openlp.core.api.http.endpoint import Endpoint |
63 | +from openlp.core.api.http import register_endpoint, requires_auth |
64 | +from openlp.core.api.tab import ApiTab |
65 | +from openlp.core.api.poll import Poller |
66 | + |
67 | +__all__ = ['Endpoint', 'ApiTab', 'register_endpoint', 'requires_auth'] |
68 | |
69 | === added directory 'openlp/core/api/endpoint' |
70 | === added file 'openlp/core/api/endpoint/__init__.py' |
71 | --- openlp/core/api/endpoint/__init__.py 1970-01-01 00:00:00 +0000 |
72 | +++ openlp/core/api/endpoint/__init__.py 2017-08-13 07:11:15 +0000 |
73 | @@ -0,0 +1,25 @@ |
74 | +# -*- coding: utf-8 -*- |
75 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
76 | + |
77 | +############################################################################### |
78 | +# OpenLP - Open Source Lyrics Projection # |
79 | +# --------------------------------------------------------------------------- # |
80 | +# Copyright (c) 2008-2017 OpenLP Developers # |
81 | +# --------------------------------------------------------------------------- # |
82 | +# This program is free software; you can redistribute it and/or modify it # |
83 | +# under the terms of the GNU General Public License as published by the Free # |
84 | +# Software Foundation; version 2 of the License. # |
85 | +# # |
86 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
87 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
88 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
89 | +# more details. # |
90 | +# # |
91 | +# You should have received a copy of the GNU General Public License along # |
92 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
93 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
94 | +############################################################################### |
95 | +""" |
96 | +The Endpoint class, which provides plugins with a way to serve their own portion of the API |
97 | +""" |
98 | +from .pluginhelpers import search, live, service |
99 | |
100 | === added file 'openlp/core/api/endpoint/controller.py' |
101 | --- openlp/core/api/endpoint/controller.py 1970-01-01 00:00:00 +0000 |
102 | +++ openlp/core/api/endpoint/controller.py 2017-08-13 07:11:15 +0000 |
103 | @@ -0,0 +1,144 @@ |
104 | +# -*- coding: utf-8 -*- |
105 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
106 | + |
107 | +############################################################################### |
108 | +# OpenLP - Open Source Lyrics Projection # |
109 | +# --------------------------------------------------------------------------- # |
110 | +# Copyright (c) 2008-2017 OpenLP Developers # |
111 | +# --------------------------------------------------------------------------- # |
112 | +# This program is free software; you can redistribute it and/or modify it # |
113 | +# under the terms of the GNU General Public License as published by the Free # |
114 | +# Software Foundation; version 2 of the License. # |
115 | +# # |
116 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
117 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
118 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
119 | +# more details. # |
120 | +# # |
121 | +# You should have received a copy of the GNU General Public License along # |
122 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
123 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
124 | +############################################################################### |
125 | +import logging |
126 | +import os |
127 | +import urllib.request |
128 | +import urllib.error |
129 | +import json |
130 | + |
131 | +from openlp.core.api.http.endpoint import Endpoint |
132 | +from openlp.core.api.http import requires_auth |
133 | +from openlp.core.common import Registry, AppLocation, Settings |
134 | +from openlp.core.lib import ItemCapabilities, create_thumb |
135 | + |
136 | +log = logging.getLogger(__name__) |
137 | + |
138 | +controller_endpoint = Endpoint('controller') |
139 | +api_controller_endpoint = Endpoint('api') |
140 | + |
141 | + |
142 | +@api_controller_endpoint.route('controller/live/text') |
143 | +@controller_endpoint.route('live/text') |
144 | +def controller_text(request): |
145 | + """ |
146 | + Perform an action on the slide controller. |
147 | + |
148 | + :param request: the http request - not used |
149 | + """ |
150 | + log.debug("controller_text ") |
151 | + live_controller = Registry().get('live_controller') |
152 | + current_item = live_controller.service_item |
153 | + data = [] |
154 | + if current_item: |
155 | + for index, frame in enumerate(current_item.get_frames()): |
156 | + item = {} |
157 | + # Handle text (songs, custom, bibles) |
158 | + if current_item.is_text(): |
159 | + if frame['verseTag']: |
160 | + item['tag'] = str(frame['verseTag']) |
161 | + else: |
162 | + item['tag'] = str(index + 1) |
163 | + item['chords_text'] = str(frame['chords_text']) |
164 | + item['text'] = str(frame['text']) |
165 | + item['html'] = str(frame['html']) |
166 | + # Handle images, unless a custom thumbnail is given or if thumbnails is disabled |
167 | + elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'): |
168 | + item['tag'] = str(index + 1) |
169 | + thumbnail_path = os.path.join('images', 'thumbnails', frame['title']) |
170 | + full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path) |
171 | + # Create thumbnail if it doesn't exists |
172 | + if not os.path.exists(full_thumbnail_path): |
173 | + create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False) |
174 | + Registry().get('image_manager').add_image(full_thumbnail_path, frame['title'], None, 88, 88) |
175 | + item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path) |
176 | + item['text'] = str(frame['title']) |
177 | + item['html'] = str(frame['title']) |
178 | + else: |
179 | + # Handle presentation etc. |
180 | + item['tag'] = str(index + 1) |
181 | + if current_item.is_capable(ItemCapabilities.HasDisplayTitle): |
182 | + item['title'] = str(frame['display_title']) |
183 | + if current_item.is_capable(ItemCapabilities.HasNotes): |
184 | + item['slide_notes'] = str(frame['notes']) |
185 | + if current_item.is_capable(ItemCapabilities.HasThumbnails) and \ |
186 | + Settings().value('api/thumbnails'): |
187 | + # If the file is under our app directory tree send the portion after the match |
188 | + data_path = AppLocation.get_data_path() |
189 | + if frame['image'][0:len(data_path)] == data_path: |
190 | + item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):]) |
191 | + Registry().get('image_manager').add_image(frame['image'], frame['title'], None, 88, 88) |
192 | + item['text'] = str(frame['title']) |
193 | + item['html'] = str(frame['title']) |
194 | + item['selected'] = (live_controller.selected_row == index) |
195 | + data.append(item) |
196 | + json_data = {'results': {'slides': data}} |
197 | + if current_item: |
198 | + json_data['results']['item'] = live_controller.service_item.unique_identifier |
199 | + return json_data |
200 | + |
201 | + |
202 | +@api_controller_endpoint.route('controller/live/set') |
203 | +@controller_endpoint.route('live/set') |
204 | +@requires_auth |
205 | +def controller_set(request): |
206 | + """ |
207 | + Perform an action on the slide controller. |
208 | + |
209 | + :param request: The action to perform. |
210 | + """ |
211 | + event = getattr(Registry().get('live_controller'), 'slidecontroller_live_set') |
212 | + try: |
213 | + json_data = request.GET.get('data') |
214 | + data = int(json.loads(json_data)['request']['id']) |
215 | + event.emit([data]) |
216 | + except KeyError: |
217 | + log.error("Endpoint controller/live/set request id not found") |
218 | + return {'results': {'success': True}} |
219 | + |
220 | + |
221 | +@controller_endpoint.route('{action:next|previous}') |
222 | +@requires_auth |
223 | +def controller_direction(request, controller, action): |
224 | + """ |
225 | + Handles requests for setting service items in the slide controller |
226 | +11 |
227 | + :param request: The http request object. |
228 | + :param controller: the controller slides forward or backward. |
229 | + :param action: the controller slides forward or backward. |
230 | + """ |
231 | + event = getattr(Registry().get('live_controller'), 'slidecontroller_{controller}_{action}'. |
232 | + format(controller=controller, action=action)) |
233 | + event.emit() |
234 | + |
235 | + |
236 | +@api_controller_endpoint.route('controller/{controller}/{action:next|previous}') |
237 | +@requires_auth |
238 | +def controller_direction_api(request, controller, action): |
239 | + """ |
240 | + Handles requests for setting service items in the slide controller |
241 | +11 |
242 | + :param request: The http request object. |
243 | + :param controller: the controller slides forward or backward. |
244 | + :param action: the controller slides forward or backward. |
245 | + """ |
246 | + controller_direction(request, controller, action) |
247 | + return {'results': {'success': True}} |
248 | |
249 | === added file 'openlp/core/api/endpoint/core.py' |
250 | --- openlp/core/api/endpoint/core.py 1970-01-01 00:00:00 +0000 |
251 | +++ openlp/core/api/endpoint/core.py 2017-08-13 07:11:15 +0000 |
252 | @@ -0,0 +1,182 @@ |
253 | +# -*- coding: utf-8 -*- |
254 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
255 | + |
256 | +############################################################################### |
257 | +# OpenLP - Open Source Lyrics Projection # |
258 | +# --------------------------------------------------------------------------- # |
259 | +# Copyright (c) 2008-2017 OpenLP Developers # |
260 | +# --------------------------------------------------------------------------- # |
261 | +# This program is free software; you can redistribute it and/or modify it # |
262 | +# under the terms of the GNU General Public License as published by the Free # |
263 | +# Software Foundation; version 2 of the License. # |
264 | +# # |
265 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
266 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
267 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
268 | +# more details. # |
269 | +# # |
270 | +# You should have received a copy of the GNU General Public License along # |
271 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
272 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
273 | +############################################################################### |
274 | +import logging |
275 | +import os |
276 | + |
277 | +from openlp.core.api.http.endpoint import Endpoint |
278 | +from openlp.core.api.http import requires_auth |
279 | +from openlp.core.common import Registry, UiStrings, translate |
280 | +from openlp.core.lib import image_to_byte, PluginStatus, StringContent |
281 | + |
282 | + |
283 | +template_dir = 'templates' |
284 | +static_dir = 'static' |
285 | +blank_dir = os.path.join(static_dir, 'index') |
286 | + |
287 | + |
288 | +log = logging.getLogger(__name__) |
289 | + |
290 | +chords_endpoint = Endpoint('chords', template_dir=template_dir, static_dir=static_dir) |
291 | +stage_endpoint = Endpoint('stage', template_dir=template_dir, static_dir=static_dir) |
292 | +main_endpoint = Endpoint('main', template_dir=template_dir, static_dir=static_dir) |
293 | +blank_endpoint = Endpoint('', template_dir=template_dir, static_dir=blank_dir) |
294 | + |
295 | +FILE_TYPES = { |
296 | + '.html': 'text/html', |
297 | + '.css': 'text/css', |
298 | + '.js': 'application/javascript', |
299 | + '.jpg': 'image/jpeg', |
300 | + '.gif': 'image/gif', |
301 | + '.ico': 'image/x-icon', |
302 | + '.png': 'image/png' |
303 | +} |
304 | + |
305 | +remote = translate('RemotePlugin.Mobile', 'Remote') |
306 | +stage = translate('RemotePlugin.Mobile', 'Stage View') |
307 | +live = translate('RemotePlugin.Mobile', 'Live View') |
308 | +chords = translate('RemotePlugin.Mobile', 'Chords View') |
309 | + |
310 | +TRANSLATED_STRINGS = { |
311 | + 'app_title': "{main} {remote}".format(main=UiStrings().OpenLP, remote=remote), |
312 | + 'stage_title': "{main} {stage}".format(main=UiStrings().OpenLP, stage=stage), |
313 | + 'live_title': "{main} {live}".format(main=UiStrings().OpenLP, live=live), |
314 | + 'chords_title': "{main} {chords}".format(main=UiStrings().OpenLP, chords=chords), |
315 | + 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), |
316 | + 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), |
317 | + 'alerts': translate('RemotePlugin.Mobile', 'Alerts'), |
318 | + 'search': translate('RemotePlugin.Mobile', 'Search'), |
319 | + 'home': translate('RemotePlugin.Mobile', 'Home'), |
320 | + 'refresh': translate('RemotePlugin.Mobile', 'Refresh'), |
321 | + 'blank': translate('RemotePlugin.Mobile', 'Blank'), |
322 | + 'theme': translate('RemotePlugin.Mobile', 'Theme'), |
323 | + 'desktop': translate('RemotePlugin.Mobile', 'Desktop'), |
324 | + 'show': translate('RemotePlugin.Mobile', 'Show'), |
325 | + 'prev': translate('RemotePlugin.Mobile', 'Prev'), |
326 | + 'next': translate('RemotePlugin.Mobile', 'Next'), |
327 | + 'text': translate('RemotePlugin.Mobile', 'Text'), |
328 | + 'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'), |
329 | + 'go_live': translate('RemotePlugin.Mobile', 'Go Live'), |
330 | + 'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'), |
331 | + 'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'), |
332 | + 'no_results': translate('RemotePlugin.Mobile', 'No Results'), |
333 | + 'options': translate('RemotePlugin.Mobile', 'Options'), |
334 | + 'service': translate('RemotePlugin.Mobile', 'Service'), |
335 | + 'slides': translate('RemotePlugin.Mobile', 'Slides'), |
336 | + 'settings': translate('RemotePlugin.Mobile', 'Settings'), |
337 | +} |
338 | + |
339 | + |
340 | +@stage_endpoint.route('') |
341 | +def stage_index(request): |
342 | + """ |
343 | + Deliver the page for the /stage url |
344 | + """ |
345 | + return stage_endpoint.render_template('stage.mako', **TRANSLATED_STRINGS) |
346 | + |
347 | + |
348 | +@chords_endpoint.route('') |
349 | +def chords_index(request): |
350 | + """ |
351 | + Deliver the page for the /chords url |
352 | + """ |
353 | + return chords_endpoint.render_template('chords.mako', **TRANSLATED_STRINGS) |
354 | + |
355 | + |
356 | +@main_endpoint.route('') |
357 | +def main_index(request): |
358 | + """ |
359 | + Deliver the page for the /main url |
360 | + """ |
361 | + return main_endpoint.render_template('main.mako', **TRANSLATED_STRINGS) |
362 | + |
363 | + |
364 | +@blank_endpoint.route('') |
365 | +def index(request): |
366 | + """ |
367 | + Deliver the page for the / url |
368 | + :param request: |
369 | + """ |
370 | + return blank_endpoint.render_template('index.mako', **TRANSLATED_STRINGS) |
371 | + |
372 | + |
373 | +@blank_endpoint.route('api/poll') |
374 | +@blank_endpoint.route('poll') |
375 | +def poll(request): |
376 | + """ |
377 | + Deliver the page for the /poll url |
378 | + |
379 | + :param request: |
380 | + """ |
381 | + return Registry().get('poller').poll() |
382 | + |
383 | + |
384 | +@blank_endpoint.route('api/display/{display:hide|show|blank|theme|desktop}') |
385 | +@blank_endpoint.route('display/{display:hide|show|blank|theme|desktop}') |
386 | +@requires_auth |
387 | +def toggle_display(request, display): |
388 | + """ |
389 | + Deliver the functions for the /display url |
390 | + :param request: the http request - not used |
391 | + :param display: the display function to be triggered |
392 | + """ |
393 | + Registry().get('live_controller').slidecontroller_toggle_display.emit(display) |
394 | + return {'results': {'success': True}} |
395 | + |
396 | + |
397 | +@blank_endpoint.route('api/plugin/search') |
398 | +@blank_endpoint.route('plugin/search') |
399 | +def plugin_search_list(request): |
400 | + """ |
401 | + Deliver a list of active plugins that support search |
402 | + :param request: the http request - not used |
403 | + """ |
404 | + searches = [] |
405 | + for plugin in Registry().get('plugin_manager').plugins: |
406 | + if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: |
407 | + searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])]) |
408 | + return {'results': {'items': searches}} |
409 | + |
410 | + |
411 | +@main_endpoint.route('image') |
412 | +def main_image(request): |
413 | + """ |
414 | + Return the latest display image as a byte stream. |
415 | + :param request: base path of the URL. Not used but passed by caller |
416 | + :return: |
417 | + """ |
418 | + live_controller = Registry().get('live_controller') |
419 | + result = { |
420 | + 'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image)) |
421 | + } |
422 | + return {'results': result} |
423 | + |
424 | + |
425 | +def get_content_type(file_name): |
426 | + """ |
427 | + Examines the extension of the file and determines what the content_type should be, defaults to text/plain |
428 | + Returns the extension and the content_type |
429 | + |
430 | + :param file_name: name of file |
431 | + """ |
432 | + ext = os.path.splitext(file_name)[1] |
433 | + content_type = FILE_TYPES.get(ext, 'text/plain') |
434 | + return ext, content_type |
435 | |
436 | === added file 'openlp/core/api/endpoint/pluginhelpers.py' |
437 | --- openlp/core/api/endpoint/pluginhelpers.py 1970-01-01 00:00:00 +0000 |
438 | +++ openlp/core/api/endpoint/pluginhelpers.py 2017-08-13 07:11:15 +0000 |
439 | @@ -0,0 +1,138 @@ |
440 | +# -*- coding: utf-8 -*- |
441 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
442 | + |
443 | +############################################################################### |
444 | +# OpenLP - Open Source Lyrics Projection # |
445 | +# --------------------------------------------------------------------------- # |
446 | +# Copyright (c) 2008-2017 OpenLP Developers # |
447 | +# --------------------------------------------------------------------------- # |
448 | +# This program is free software; you can redistribute it and/or modify it # |
449 | +# under the terms of the GNU General Public License as published by the Free # |
450 | +# Software Foundation; version 2 of the License. # |
451 | +# # |
452 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
453 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
454 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
455 | +# more details. # |
456 | +# # |
457 | +# You should have received a copy of the GNU General Public License along # |
458 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
459 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
460 | +############################################################################### |
461 | +import os |
462 | +import json |
463 | +import re |
464 | +import urllib |
465 | + |
466 | +from urllib.parse import urlparse |
467 | +from webob import Response |
468 | + |
469 | +from openlp.core.api.http.errors import NotFound |
470 | +from openlp.core.common import Registry, AppLocation |
471 | +from openlp.core.lib import PluginStatus, image_to_byte |
472 | + |
473 | + |
474 | +def search(request, plugin_name, log): |
475 | + """ |
476 | + Handles requests for searching the plugins |
477 | + |
478 | + :param request: The http request object. |
479 | + :param plugin_name: The plugin name. |
480 | + :param log: The class log object. |
481 | + """ |
482 | + try: |
483 | + json_data = request.GET.get('data') |
484 | + text = json.loads(json_data)['request']['text'] |
485 | + except KeyError: |
486 | + log.error("Endpoint {text} search request text not found".format(text=plugin_name)) |
487 | + text = "" |
488 | + text = urllib.parse.unquote(text) |
489 | + plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name) |
490 | + if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: |
491 | + results = plugin.media_item.search(text, False) |
492 | + return {'results': {'items': results}} |
493 | + else: |
494 | + raise NotFound() |
495 | + |
496 | + |
497 | +def live(request, plugin_name, log): |
498 | + """ |
499 | + Handles requests for making live of the plugins |
500 | + |
501 | + :param request: The http request object. |
502 | + :param plugin_name: The plugin name. |
503 | + :param log: The class log object. |
504 | + """ |
505 | + try: |
506 | + json_data = request.GET.get('data') |
507 | + request_id = json.loads(json_data)['request']['id'] |
508 | + except KeyError: |
509 | + log.error("Endpoint {text} search request text not found".format(text=plugin_name)) |
510 | + return [] |
511 | + plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name) |
512 | + if plugin.status == PluginStatus.Active and plugin.media_item: |
513 | + getattr(plugin.media_item, '{name}_go_live'.format(name=plugin_name)).emit([request_id, True]) |
514 | + |
515 | + |
516 | +def service(request, plugin_name, log): |
517 | + """ |
518 | + Handles requests for adding to a service of the plugins |
519 | + |
520 | + :param request: The http request object. |
521 | + :param plugin_name: The plugin name. |
522 | + :param log: The class log object. |
523 | + """ |
524 | + try: |
525 | + json_data = request.GET.get('data') |
526 | + request_id = json.loads(json_data)['request']['id'] |
527 | + except KeyError: |
528 | + log.error("Endpoint {plugin} search request text not found".format(plugin=plugin_name)) |
529 | + return [] |
530 | + plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name) |
531 | + if plugin.status == PluginStatus.Active and plugin.media_item: |
532 | + item_id = plugin.media_item.create_item_from_id(request_id) |
533 | + getattr(plugin.media_item, '{name}_add_to_service'.format(name=plugin_name)).emit([item_id, True]) |
534 | + |
535 | + |
536 | +def display_thumbnails(request, controller_name, log, dimensions, file_name, slide=None): |
537 | + """ |
538 | + Handles requests for adding a song to the service |
539 | + |
540 | + Return an image to a web page based on a URL |
541 | + :param request: Request object |
542 | + :param controller_name: which controller is requesting the image |
543 | + :param log: the logger object |
544 | + :param dimensions: the image size eg 88x88 |
545 | + :param file_name: the file name of the image |
546 | + :param slide: the individual image name |
547 | + :return: |
548 | + """ |
549 | + log.debug('serve thumbnail {cname}/thumbnails{dim}/{fname}/{slide}'.format(cname=controller_name, |
550 | + dim=dimensions, |
551 | + fname=file_name, |
552 | + slide=slide)) |
553 | + # -1 means use the default dimension in ImageManager |
554 | + width = -1 |
555 | + height = -1 |
556 | + image = None |
557 | + if dimensions: |
558 | + match = re.search('(\d+)x(\d+)', dimensions) |
559 | + if match: |
560 | + # let's make sure that the dimensions are within reason |
561 | + width = sorted([10, int(match.group(1)), 1000])[1] |
562 | + height = sorted([10, int(match.group(2)), 1000])[1] |
563 | + if controller_name and file_name: |
564 | + file_name = urllib.parse.unquote(file_name) |
565 | + if '..' not in file_name: # no hacking please |
566 | + if slide: |
567 | + full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name), |
568 | + 'thumbnails', file_name, slide)) |
569 | + else: |
570 | + full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name), |
571 | + |
572 | + 'thumbnails', file_name)) |
573 | + if os.path.exists(full_path): |
574 | + path, just_file_name = os.path.split(full_path) |
575 | + Registry().get('image_manager').add_image(full_path, just_file_name, None, width, height) |
576 | + image = Registry().get('image_manager').get_image(full_path, just_file_name, width, height) |
577 | + return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8') |
578 | |
579 | === added file 'openlp/core/api/endpoint/service.py' |
580 | --- openlp/core/api/endpoint/service.py 1970-01-01 00:00:00 +0000 |
581 | +++ openlp/core/api/endpoint/service.py 2017-08-13 07:11:15 +0000 |
582 | @@ -0,0 +1,100 @@ |
583 | +# -*- coding: utf-8 -*- |
584 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
585 | + |
586 | +############################################################################### |
587 | +# OpenLP - Open Source Lyrics Projection # |
588 | +# --------------------------------------------------------------------------- # |
589 | +# Copyright (c) 2008-2017 OpenLP Developers # |
590 | +# --------------------------------------------------------------------------- # |
591 | +# This program is free software; you can redistribute it and/or modify it # |
592 | +# under the terms of the GNU General Public License as published by the Free # |
593 | +# Software Foundation; version 2 of the License. # |
594 | +# # |
595 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
596 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
597 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
598 | +# more details. # |
599 | +# # |
600 | +# You should have received a copy of the GNU General Public License along # |
601 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
602 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
603 | +############################################################################### |
604 | +import logging |
605 | +import json |
606 | + |
607 | +from openlp.core.api.http.endpoint import Endpoint |
608 | +from openlp.core.api.http import register_endpoint, requires_auth |
609 | +from openlp.core.common import Registry |
610 | + |
611 | + |
612 | +log = logging.getLogger(__name__) |
613 | + |
614 | +service_endpoint = Endpoint('service') |
615 | +api_service_endpoint = Endpoint('api/service') |
616 | + |
617 | + |
618 | +@api_service_endpoint.route('list') |
619 | +@service_endpoint.route('list') |
620 | +def list_service(request): |
621 | + """ |
622 | + Handles requests for service items in the service manager |
623 | + |
624 | + :param request: The http request object. |
625 | + """ |
626 | + return {'results': {'items': get_service_items()}} |
627 | + |
628 | + |
629 | +@api_service_endpoint.route('set') |
630 | +@service_endpoint.route('set') |
631 | +@requires_auth |
632 | +def service_set(request): |
633 | + """ |
634 | + Handles requests for setting service items in the service manager |
635 | + |
636 | + :param request: The http request object. |
637 | + """ |
638 | + event = getattr(Registry().get('service_manager'), 'servicemanager_set_item') |
639 | + try: |
640 | + json_data = request.GET.get('data') |
641 | + data = int(json.loads(json_data)['request']['id']) |
642 | + event.emit(data) |
643 | + except KeyError: |
644 | + log.error("Endpoint service/set request id not found") |
645 | + return {'results': {'success': True}} |
646 | + |
647 | + |
648 | +@api_service_endpoint.route('{action:next|previous}') |
649 | +@service_endpoint.route('{action:next|previous}') |
650 | +@requires_auth |
651 | +def service_direction(request, action): |
652 | + """ |
653 | + Handles requests for setting service items in the service manager |
654 | + |
655 | + :param request: The http request object. |
656 | + :param action: the the service slides forward or backward. |
657 | + """ |
658 | + event = getattr(Registry().get('service_manager'), 'servicemanager_{action}_item'.format(action=action)) |
659 | + event.emit() |
660 | + return {'results': {'success': True}} |
661 | + |
662 | + |
663 | +def get_service_items(): |
664 | + """ |
665 | + Read the service item in use and return the data as a json object |
666 | + """ |
667 | + live_controller = Registry().get('live_controller') |
668 | + service_items = [] |
669 | + if live_controller.service_item: |
670 | + current_unique_identifier = live_controller.service_item.unique_identifier |
671 | + else: |
672 | + current_unique_identifier = None |
673 | + for item in Registry().get('service_manager').service_items: |
674 | + service_item = item['service_item'] |
675 | + service_items.append({ |
676 | + 'id': str(service_item.unique_identifier), |
677 | + 'title': str(service_item.get_display_title()), |
678 | + 'plugin': str(service_item.name), |
679 | + 'notes': str(service_item.notes), |
680 | + 'selected': (service_item.unique_identifier == current_unique_identifier) |
681 | + }) |
682 | + return service_items |
683 | |
684 | === added directory 'openlp/core/api/http' |
685 | === added file 'openlp/core/api/http/__init__.py' |
686 | --- openlp/core/api/http/__init__.py 1970-01-01 00:00:00 +0000 |
687 | +++ openlp/core/api/http/__init__.py 2017-08-13 07:11:15 +0000 |
688 | @@ -0,0 +1,110 @@ |
689 | +# -*- coding: utf-8 -*- |
690 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
691 | + |
692 | +############################################################################### |
693 | +# OpenLP - Open Source Lyrics Projection # |
694 | +# --------------------------------------------------------------------------- # |
695 | +# Copyright (c) 2008-2017 OpenLP Developers # |
696 | +# --------------------------------------------------------------------------- # |
697 | +# This program is free software; you can redistribute it and/or modify it # |
698 | +# under the terms of the GNU General Public License as published by the Free # |
699 | +# Software Foundation; version 2 of the License. # |
700 | +# # |
701 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
702 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
703 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
704 | +# more details. # |
705 | +# # |
706 | +# You should have received a copy of the GNU General Public License along # |
707 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
708 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
709 | +############################################################################### |
710 | + |
711 | +import base64 |
712 | +from functools import wraps |
713 | +from webob import Response |
714 | + |
715 | +from openlp.core.common.settings import Settings |
716 | +from openlp.core.api.http.wsgiapp import WSGIApplication |
717 | +from .errors import NotFound, ServerError, HttpError |
718 | + |
719 | +application = WSGIApplication('api') |
720 | + |
721 | + |
722 | +def _route_from_url(url_prefix, url): |
723 | + """ |
724 | + Create a route from the URL |
725 | + """ |
726 | + url_prefix = '/{prefix}/'.format(prefix=url_prefix.strip('/')) |
727 | + if not url: |
728 | + url = url_prefix[:-1] |
729 | + else: |
730 | + url = url_prefix + url |
731 | + url = url.replace('//', '/') |
732 | + return url |
733 | + |
734 | + |
735 | +def register_endpoint(end_point): |
736 | + """ |
737 | + Register an endpoint with the app |
738 | + """ |
739 | + for url, view_func, method in end_point.routes: |
740 | + # Set the view functions |
741 | + route = _route_from_url(end_point.url_prefix, url) |
742 | + application.add_route(route, view_func, method) |
743 | + # Add a static route if necessary |
744 | + if end_point.static_dir: |
745 | + static_route = _route_from_url(end_point.url_prefix, 'static') |
746 | + static_route += '(.*)' |
747 | + application.add_static_route(static_route, end_point.static_dir) |
748 | + |
749 | + |
750 | +def check_auth(auth): |
751 | + """ |
752 | + This function is called to check if a username password combination is valid. |
753 | + |
754 | + :param auth: the authorisation object which needs to be tested |
755 | + :return Whether authentication have been successful |
756 | + """ |
757 | + auth_code = "{user}:{password}".format(user=Settings().value('api/user id'), |
758 | + password=Settings().value('api/password')) |
759 | + try: |
760 | + auth_base = base64.b64encode(auth_code) |
761 | + except TypeError: |
762 | + auth_base = base64.b64encode(auth_code.encode()).decode() |
763 | + if auth[1] == auth_base: |
764 | + return True |
765 | + else: |
766 | + return False |
767 | + |
768 | + |
769 | +def authenticate(): |
770 | + """ |
771 | + Sends a 401 response that enables basic auth to be triggered |
772 | + """ |
773 | + resp = Response(status=401) |
774 | + resp.www_authenticate = 'Basic realm="OpenLP Login Required"' |
775 | + return resp |
776 | + |
777 | + |
778 | +def requires_auth(f): |
779 | + """ |
780 | + Decorates a function which needs to be authenticated before it can be used from the remote. |
781 | + |
782 | + :param f: The function which has been wrapped |
783 | + :return: the called function or a request to authenticate |
784 | + """ |
785 | + @wraps(f) |
786 | + def decorated(*args, **kwargs): |
787 | + if not Settings().value('api/authentication enabled'): |
788 | + return f(*args, **kwargs) |
789 | + req = args[0] |
790 | + if not hasattr(req, 'authorization'): |
791 | + return authenticate() |
792 | + else: |
793 | + auth = req.authorization |
794 | + if auth and check_auth(auth): |
795 | + return f(*args, **kwargs) |
796 | + else: |
797 | + return authenticate() |
798 | + return decorated |
799 | |
800 | === added file 'openlp/core/api/http/endpoint.py' |
801 | --- openlp/core/api/http/endpoint.py 1970-01-01 00:00:00 +0000 |
802 | +++ openlp/core/api/http/endpoint.py 2017-08-13 07:11:15 +0000 |
803 | @@ -0,0 +1,80 @@ |
804 | +# -*- coding: utf-8 -*- |
805 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
806 | + |
807 | +############################################################################### |
808 | +# OpenLP - Open Source Lyrics Projection # |
809 | +# --------------------------------------------------------------------------- # |
810 | +# Copyright (c) 2008-2017 OpenLP Developers # |
811 | +# --------------------------------------------------------------------------- # |
812 | +# This program is free software; you can redistribute it and/or modify it # |
813 | +# under the terms of the GNU General Public License as published by the Free # |
814 | +# Software Foundation; version 2 of the License. # |
815 | +# # |
816 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
817 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
818 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
819 | +# more details. # |
820 | +# # |
821 | +# You should have received a copy of the GNU General Public License along # |
822 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
823 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
824 | +############################################################################### |
825 | +""" |
826 | +The Endpoint class, which provides plugins with a way to serve their own portion of the API |
827 | +""" |
828 | + |
829 | +import os |
830 | + |
831 | +from openlp.core.common import AppLocation |
832 | +from mako.template import Template |
833 | + |
834 | + |
835 | +class Endpoint(object): |
836 | + """ |
837 | + This is an endpoint for the HTTP API |
838 | + """ |
839 | + def __init__(self, url_prefix, template_dir=None, static_dir=None, assets_dir=None): |
840 | + """ |
841 | + Create an endpoint with a URL prefix |
842 | + """ |
843 | + self.url_prefix = url_prefix |
844 | + self.static_dir = static_dir |
845 | + self.template_dir = template_dir |
846 | + if assets_dir: |
847 | + self.assets_dir = assets_dir |
848 | + else: |
849 | + self.assets_dir = None |
850 | + self.routes = [] |
851 | + |
852 | + def add_url_route(self, url, view_func, method): |
853 | + """ |
854 | + Add a url route to the list of routes |
855 | + """ |
856 | + self.routes.append((url, view_func, method)) |
857 | + |
858 | + def route(self, rule, method='GET'): |
859 | + """ |
860 | + Set up a URL route |
861 | + """ |
862 | + def decorator(func): |
863 | + """ |
864 | + Make this a decorator |
865 | + """ |
866 | + self.add_url_route(rule, func, method) |
867 | + return func |
868 | + return decorator |
869 | + |
870 | + def render_template(self, filename, **kwargs): |
871 | + """ |
872 | + Render a mako template |
873 | + """ |
874 | + root = os.path.join(str(AppLocation.get_section_data_path('remotes'))) |
875 | + if not self.template_dir: |
876 | + raise Exception('No template directory specified') |
877 | + path = os.path.join(root, self.template_dir, filename) |
878 | + # path = os.path.abspath(os.path.join(self.template_dir, filename)) |
879 | + if self.static_dir: |
880 | + kwargs['static_url'] = '/{prefix}/static'.format(prefix=self.url_prefix) |
881 | + kwargs['static_url'] = kwargs['static_url'].replace('//', '/') |
882 | + kwargs['assets_url'] = '/assets' |
883 | + return Template(filename=path, input_encoding='utf-8').render(**kwargs) |
884 | |
885 | === added file 'openlp/core/api/http/errors.py' |
886 | --- openlp/core/api/http/errors.py 1970-01-01 00:00:00 +0000 |
887 | +++ openlp/core/api/http/errors.py 2017-08-13 07:11:15 +0000 |
888 | @@ -0,0 +1,65 @@ |
889 | +# -*- coding: utf-8 -*- |
890 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
891 | + |
892 | +############################################################################### |
893 | +# OpenLP - Open Source Lyrics Projection # |
894 | +# --------------------------------------------------------------------------- # |
895 | +# Copyright (c) 2008-2017 OpenLP Developers # |
896 | +# --------------------------------------------------------------------------- # |
897 | +# This program is free software; you can redistribute it and/or modify it # |
898 | +# under the terms of the GNU General Public License as published by the Free # |
899 | +# Software Foundation; version 2 of the License. # |
900 | +# # |
901 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
902 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
903 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
904 | +# more details. # |
905 | +# # |
906 | +# You should have received a copy of the GNU General Public License along # |
907 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
908 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
909 | +############################################################################### |
910 | +""" |
911 | +HTTP Error classes |
912 | +""" |
913 | + |
914 | + |
915 | +class HttpError(Exception): |
916 | + """ |
917 | + A base HTTP error (aka status code) |
918 | + """ |
919 | + def __init__(self, status, message): |
920 | + """ |
921 | + Initialise the exception |
922 | + """ |
923 | + super(HttpError, self).__init__(message) |
924 | + self.status = status |
925 | + self.message = message |
926 | + |
927 | + def to_response(self): |
928 | + """ |
929 | + Convert this exception to a Response object |
930 | + """ |
931 | + return self.message, self.status |
932 | + |
933 | + |
934 | +class NotFound(HttpError): |
935 | + """ |
936 | + A 404 |
937 | + """ |
938 | + def __init__(self): |
939 | + """ |
940 | + Make this a 404 |
941 | + """ |
942 | + super(NotFound, self).__init__(404, 'Not Found') |
943 | + |
944 | + |
945 | +class ServerError(HttpError): |
946 | + """ |
947 | + A 500 |
948 | + """ |
949 | + def __init__(self): |
950 | + """ |
951 | + Make this a 500 |
952 | + """ |
953 | + super(ServerError, self).__init__(500, 'Server Error') |
954 | |
955 | === added file 'openlp/core/api/http/server.py' |
956 | --- openlp/core/api/http/server.py 1970-01-01 00:00:00 +0000 |
957 | +++ openlp/core/api/http/server.py 2017-08-13 07:11:15 +0000 |
958 | @@ -0,0 +1,97 @@ |
959 | +# -*- coding: utf-8 -*- |
960 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
961 | + |
962 | +############################################################################### |
963 | +# OpenLP - Open Source Lyrics Projection # |
964 | +# --------------------------------------------------------------------------- # |
965 | +# Copyright (c) 2008-2017 OpenLP Developers # |
966 | +# --------------------------------------------------------------------------- # |
967 | +# This program is free software; you can redistribute it and/or modify it # |
968 | +# under the terms of the GNU General Public License as published by the Free # |
969 | +# Software Foundation; version 2 of the License. # |
970 | +# # |
971 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
972 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
973 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
974 | +# more details. # |
975 | +# # |
976 | +# You should have received a copy of the GNU General Public License along # |
977 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
978 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
979 | +############################################################################### |
980 | + |
981 | +""" |
982 | +The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact |
983 | +with OpenLP. It uses JSON to communicate with the remotes. |
984 | +""" |
985 | + |
986 | +import logging |
987 | + |
988 | +from PyQt5 import QtCore |
989 | +from waitress import serve |
990 | + |
991 | +from openlp.core.api.http import register_endpoint |
992 | +from openlp.core.api.http import application |
993 | +from openlp.core.common import RegistryMixin, RegistryProperties, OpenLPMixin, Settings, Registry |
994 | +from openlp.core.api.poll import Poller |
995 | +from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint |
996 | +from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint |
997 | +from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint |
998 | + |
999 | +log = logging.getLogger(__name__) |
1000 | + |
1001 | + |
1002 | +class HttpWorker(QtCore.QObject): |
1003 | + """ |
1004 | + A special Qt thread class to allow the HTTP server to run at the same time as the UI. |
1005 | + """ |
1006 | + def __init__(self): |
1007 | + """ |
1008 | + Constructor for the thread class. |
1009 | + |
1010 | + :param server: The http server class. |
1011 | + """ |
1012 | + super(HttpWorker, self).__init__() |
1013 | + |
1014 | + def run(self): |
1015 | + """ |
1016 | + Run the thread. |
1017 | + """ |
1018 | + address = Settings().value('api/ip address') |
1019 | + port = Settings().value('api/port') |
1020 | + serve(application, host=address, port=port) |
1021 | + |
1022 | + def stop(self): |
1023 | + pass |
1024 | + |
1025 | + |
1026 | +class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin): |
1027 | + """ |
1028 | + Wrapper round a server instance |
1029 | + """ |
1030 | + def __init__(self, parent=None): |
1031 | + """ |
1032 | + Initialise the http server, and start the http server |
1033 | + """ |
1034 | + super(HttpServer, self).__init__(parent) |
1035 | + self.worker = HttpWorker() |
1036 | + self.thread = QtCore.QThread() |
1037 | + self.worker.moveToThread(self.thread) |
1038 | + self.thread.started.connect(self.worker.run) |
1039 | + self.thread.start() |
1040 | + |
1041 | + def bootstrap_post_set_up(self): |
1042 | + """ |
1043 | + Register the poll return service and start the servers. |
1044 | + """ |
1045 | + self.poller = Poller() |
1046 | + Registry().register('poller', self.poller) |
1047 | + application.initialise() |
1048 | + register_endpoint(controller_endpoint) |
1049 | + register_endpoint(api_controller_endpoint) |
1050 | + register_endpoint(chords_endpoint) |
1051 | + register_endpoint(stage_endpoint) |
1052 | + register_endpoint(blank_endpoint) |
1053 | + register_endpoint(main_endpoint) |
1054 | + register_endpoint(service_endpoint) |
1055 | + register_endpoint(api_service_endpoint) |
1056 | |
1057 | === added file 'openlp/core/api/http/wsgiapp.py' |
1058 | --- openlp/core/api/http/wsgiapp.py 1970-01-01 00:00:00 +0000 |
1059 | +++ openlp/core/api/http/wsgiapp.py 2017-08-13 07:11:15 +0000 |
1060 | @@ -0,0 +1,181 @@ |
1061 | +# -*- coding: utf-8 -*- |
1062 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1063 | +# pylint: disable=logging-format-interpolation |
1064 | + |
1065 | +############################################################################### |
1066 | +# OpenLP - Open Source Lyrics Projection # |
1067 | +# --------------------------------------------------------------------------- # |
1068 | +# Copyright (c) 2008-2017 OpenLP Developers # |
1069 | +# --------------------------------------------------------------------------- # |
1070 | +# This program is free software; you can redistribute it and/or modify it # |
1071 | +# under the terms of the GNU General Public License as published by the Free # |
1072 | +# Software Foundation; version 2 of the License. # |
1073 | +# # |
1074 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1075 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1076 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1077 | +# more details. # |
1078 | +# # |
1079 | +# You should have received a copy of the GNU General Public License along # |
1080 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1081 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1082 | +############################################################################### |
1083 | +""" |
1084 | +App stuff |
1085 | +""" |
1086 | +import json |
1087 | +import logging |
1088 | +import os |
1089 | +import re |
1090 | + |
1091 | +from webob import Request, Response |
1092 | +from webob.static import DirectoryApp |
1093 | + |
1094 | +from openlp.core.common import AppLocation |
1095 | +from openlp.core.api.http.errors import HttpError, NotFound, ServerError |
1096 | + |
1097 | + |
1098 | +ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE) |
1099 | + |
1100 | +log = logging.getLogger(__name__) |
1101 | + |
1102 | + |
1103 | +def _route_to_regex(route): |
1104 | + """ |
1105 | + Convert a route to a regular expression |
1106 | + |
1107 | + For example: |
1108 | + |
1109 | + 'songs/{song_id}' becomes 'songs/(?P<song_id>[^/]+)' |
1110 | + |
1111 | + and |
1112 | + |
1113 | + 'songs/{song_id:\d+}' becomes 'songs/(?P<song_id>\d+)' |
1114 | + |
1115 | + """ |
1116 | + route_regex = '' |
1117 | + last_pos = 0 |
1118 | + for match in ARGS_REGEX.finditer(route): |
1119 | + route_regex += re.escape(route[last_pos:match.start()]) |
1120 | + arg_name = match.group(1) |
1121 | + expr = match.group(2) or '[^/]+' |
1122 | + expr = '(?P<%s>%s)' % (arg_name, expr) |
1123 | + route_regex += expr |
1124 | + last_pos = match.end() |
1125 | + route_regex += re.escape(route[last_pos:]) |
1126 | + route_regex = '^%s$' % route_regex |
1127 | + return route_regex |
1128 | + |
1129 | + |
1130 | +def _make_response(view_result): |
1131 | + """ |
1132 | + Create a Response object from response |
1133 | + """ |
1134 | + if isinstance(view_result, Response): |
1135 | + return view_result |
1136 | + elif isinstance(view_result, tuple): |
1137 | + content_type = 'text/html' |
1138 | + body = view_result[0] |
1139 | + if isinstance(body, dict): |
1140 | + content_type = 'application/json' |
1141 | + body = json.dumps(body) |
1142 | + response = Response(body=body, status=view_result[1], |
1143 | + content_type=content_type, charset='utf8') |
1144 | + if len(view_result) >= 3: |
1145 | + response.headers.update(view_result[2]) |
1146 | + return response |
1147 | + elif isinstance(view_result, dict): |
1148 | + return Response(body=json.dumps(view_result), status=200, |
1149 | + content_type='application/json', charset='utf8') |
1150 | + elif isinstance(view_result, str): |
1151 | + return Response(body=view_result, status=200, |
1152 | + content_type='text/html', charset='utf8') |
1153 | + |
1154 | + |
1155 | +def _handle_exception(error): |
1156 | + """ |
1157 | + Handle exceptions |
1158 | + """ |
1159 | + log.exception(error) |
1160 | + if isinstance(error, HttpError): |
1161 | + return error.to_response() |
1162 | + else: |
1163 | + return ServerError().to_response() |
1164 | + |
1165 | + |
1166 | +class WSGIApplication(object): |
1167 | + """ |
1168 | + This is the core of the API, the WSGI app |
1169 | + """ |
1170 | + def __init__(self, name): |
1171 | + """ |
1172 | + Create the app object |
1173 | + """ |
1174 | + self.name = name |
1175 | + self.static_routes = {} |
1176 | + self.route_map = {} |
1177 | + |
1178 | + def initialise(self): |
1179 | + """ |
1180 | + Set up generic roots for the whole application |
1181 | + :return: None |
1182 | + """ |
1183 | + self.add_static_route('/assets(.*)', '') |
1184 | + self.add_static_route('/images(.*)', '') |
1185 | + pass |
1186 | + |
1187 | + def add_route(self, route, view_func, method): |
1188 | + """ |
1189 | + Add a route |
1190 | + """ |
1191 | + route_regex = _route_to_regex(route) |
1192 | + if route_regex not in self.route_map: |
1193 | + self.route_map[route_regex] = {} |
1194 | + self.route_map[route_regex][method.upper()] = view_func |
1195 | + |
1196 | + def add_static_route(self, route, static_dir): |
1197 | + """ |
1198 | + Add a static directory as a route |
1199 | + """ |
1200 | + if route not in self.static_routes: |
1201 | + root = os.path.join(str(AppLocation.get_section_data_path('remotes'))) |
1202 | + self.static_routes[route] = DirectoryApp(os.path.abspath(os.path.join(root, static_dir))) |
1203 | + |
1204 | + def dispatch(self, request): |
1205 | + """ |
1206 | + Find the appropriate URL and run the view function |
1207 | + """ |
1208 | + # If not a static route, try the views |
1209 | + for route, views in self.route_map.items(): |
1210 | + match = re.match(route, request.path) |
1211 | + if match and request.method.upper() in views: |
1212 | + kwargs = match.groupdict() |
1213 | + log.debug('Found {method} {url}'.format(method=request.method, url=request.path)) |
1214 | + view_func = views[request.method.upper()] |
1215 | + return _make_response(view_func(request, **kwargs)) |
1216 | + # Look to see if this is a static file request |
1217 | + for route, static_app in self.static_routes.items(): |
1218 | + if re.match(route, request.path): |
1219 | + return request.get_response(static_app) |
1220 | + log.error('URL {url} - Not found'.format(url=request.path)) |
1221 | + raise NotFound() |
1222 | + |
1223 | + def wsgi_app(self, environ, start_response): |
1224 | + """ |
1225 | + The actual WSGI application. |
1226 | + """ |
1227 | + request = Request(environ) |
1228 | + try: |
1229 | + response = self.dispatch(request) |
1230 | + except Exception as e: |
1231 | + response = _make_response(_handle_exception(e)) |
1232 | + response.headers.add("cache-control", "no-cache, no-store, must-revalidate") |
1233 | + response.headers.add("pragma", "no-cache") |
1234 | + response.headers.add("expires", "0") |
1235 | + return response(environ, start_response) |
1236 | + |
1237 | + def __call__(self, environ, start_response): |
1238 | + """ |
1239 | + Shortcut for wsgi_app. |
1240 | + """ |
1241 | + return self.wsgi_app(environ, start_response) |
1242 | |
1243 | === added file 'openlp/core/api/poll.py' |
1244 | --- openlp/core/api/poll.py 1970-01-01 00:00:00 +0000 |
1245 | +++ openlp/core/api/poll.py 2017-08-13 07:11:15 +0000 |
1246 | @@ -0,0 +1,130 @@ |
1247 | +# -*- coding: utf-8 -*- |
1248 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1249 | + |
1250 | +############################################################################### |
1251 | +# OpenLP - Open Source Lyrics Projection # |
1252 | +# --------------------------------------------------------------------------- # |
1253 | +# Copyright (c) 2008-2017 OpenLP Developers # |
1254 | +# --------------------------------------------------------------------------- # |
1255 | +# This program is free software; you can redistribute it and/or modify it # |
1256 | +# under the terms of the GNU General Public License as published by the Free # |
1257 | +# Software Foundation; version 2 of the License. # |
1258 | +# # |
1259 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1260 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1261 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1262 | +# more details. # |
1263 | +# # |
1264 | +# You should have received a copy of the GNU General Public License along # |
1265 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1266 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1267 | +############################################################################### |
1268 | + |
1269 | +import json |
1270 | + |
1271 | +from openlp.core.common import RegistryProperties, Settings |
1272 | +from openlp.core.common.httputils import get_web_page |
1273 | + |
1274 | + |
1275 | +class Poller(RegistryProperties): |
1276 | + """ |
1277 | + Accessed by the web layer to get status type information from the application |
1278 | + """ |
1279 | + def __init__(self): |
1280 | + """ |
1281 | + Constructor for the poll builder class. |
1282 | + """ |
1283 | + super(Poller, self).__init__() |
1284 | + self.live_cache = None |
1285 | + self.stage_cache = None |
1286 | + self.chords_cache = None |
1287 | + |
1288 | + def raw_poll(self): |
1289 | + return { |
1290 | + 'service': self.service_manager.service_id, |
1291 | + 'slide': self.live_controller.selected_row or 0, |
1292 | + 'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '', |
1293 | + 'twelve': Settings().value('api/twelve hour'), |
1294 | + 'blank': self.live_controller.blank_screen.isChecked(), |
1295 | + 'theme': self.live_controller.theme_screen.isChecked(), |
1296 | + 'display': self.live_controller.desktop_screen.isChecked(), |
1297 | + 'version': 3, |
1298 | + 'isSecure': Settings().value('api/authentication enabled'), |
1299 | + 'isAuthorised': False, |
1300 | + 'chordNotation': Settings().value('songs/chord notation'), |
1301 | + 'isStagedActive': self.is_stage_active(), |
1302 | + 'isLiveActive': self.is_live_active(), |
1303 | + 'isChordsActive': self.is_chords_active() |
1304 | + } |
1305 | + |
1306 | + def poll(self): |
1307 | + """ |
1308 | + Poll OpenLP to determine the current slide number and item name. |
1309 | + """ |
1310 | + return {'results': self.raw_poll()} |
1311 | + |
1312 | + def main_poll(self): |
1313 | + """ |
1314 | + Poll OpenLP to determine the current slide count. |
1315 | + """ |
1316 | + result = { |
1317 | + 'slide_count': self.live_controller.slide_count |
1318 | + } |
1319 | + return json.dumps({'results': result}).encode() |
1320 | + |
1321 | + def reset_cache(self): |
1322 | + """ |
1323 | + Reset the caches as the web has changed |
1324 | + :return: |
1325 | + """ |
1326 | + self.stage_cache = None |
1327 | + self.live_cache = None |
1328 | + self.chords.cache = None |
1329 | + |
1330 | + def is_stage_active(self): |
1331 | + """ |
1332 | + Is stage active - call it and see but only once |
1333 | + :return: if stage is active or not |
1334 | + """ |
1335 | + if self.stage_cache is None: |
1336 | + try: |
1337 | + page = get_web_page("http://localhost:4316/stage") |
1338 | + except: |
1339 | + page = None |
1340 | + if page: |
1341 | + self.stage_cache = True |
1342 | + else: |
1343 | + self.stage_cache = False |
1344 | + return self.stage_cache |
1345 | + |
1346 | + def is_live_active(self): |
1347 | + """ |
1348 | + Is main active - call it and see but only once |
1349 | + :return: if live is active or not |
1350 | + """ |
1351 | + if self.live_cache is None: |
1352 | + try: |
1353 | + page = get_web_page("http://localhost:4316/main") |
1354 | + except: |
1355 | + page = None |
1356 | + if page: |
1357 | + self.live_cache = True |
1358 | + else: |
1359 | + self.live_cache = False |
1360 | + return self.live_cache |
1361 | + |
1362 | + def is_chords_active(self): |
1363 | + """ |
1364 | + Is chords active - call it and see but only once |
1365 | + :return: if live is active or not |
1366 | + """ |
1367 | + if self.chords_cache is None: |
1368 | + try: |
1369 | + page = get_web_page("http://localhost:4316/chords") |
1370 | + except: |
1371 | + page = None |
1372 | + if page: |
1373 | + self.chords_cache = True |
1374 | + else: |
1375 | + self.chords_cache = False |
1376 | + return self.chords_cache |
1377 | |
1378 | === added file 'openlp/core/api/tab.py' |
1379 | --- openlp/core/api/tab.py 1970-01-01 00:00:00 +0000 |
1380 | +++ openlp/core/api/tab.py 2017-08-13 07:11:15 +0000 |
1381 | @@ -0,0 +1,316 @@ |
1382 | +# -*- coding: utf-8 -*- |
1383 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1384 | + |
1385 | +############################################################################### |
1386 | +# OpenLP - Open Source Lyrics Projection # |
1387 | +# --------------------------------------------------------------------------- # |
1388 | +# Copyright (c) 2008-2017 OpenLP Developers # |
1389 | +# --------------------------------------------------------------------------- # |
1390 | +# This program is free software; you can redistribute it and/or modify it # |
1391 | +# under the terms of the GNU General Public License as published by the Free # |
1392 | +# Software Foundation; version 2 of the License. # |
1393 | +# # |
1394 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1395 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1396 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1397 | +# more details. # |
1398 | +# # |
1399 | +# You should have received a copy of the GNU General Public License along # |
1400 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1401 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1402 | +############################################################################### |
1403 | + |
1404 | +from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets |
1405 | + |
1406 | +from openlp.core.common import UiStrings, Registry, Settings, translate |
1407 | +from openlp.core.lib import SettingsTab |
1408 | + |
1409 | +ZERO_URL = '0.0.0.0' |
1410 | + |
1411 | + |
1412 | +class ApiTab(SettingsTab): |
1413 | + """ |
1414 | + RemoteTab is the Remotes settings tab in the settings dialog. |
1415 | + """ |
1416 | + def __init__(self, parent): |
1417 | + self.icon_path = ':/plugins/plugin_remote.png' |
1418 | + advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced') |
1419 | + super(ApiTab, self).__init__(parent, 'api', advanced_translated) |
1420 | + self.define_main_window_icon() |
1421 | + self.generate_icon() |
1422 | + |
1423 | + def setupUi(self): |
1424 | + self.setObjectName('ApiTab') |
1425 | + super(ApiTab, self).setupUi() |
1426 | + self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column) |
1427 | + self.server_settings_group_box.setObjectName('server_settings_group_box') |
1428 | + self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box) |
1429 | + self.server_settings_layout.setObjectName('server_settings_layout') |
1430 | + self.address_label = QtWidgets.QLabel(self.server_settings_group_box) |
1431 | + self.address_label.setObjectName('address_label') |
1432 | + self.address_edit = QtWidgets.QLineEdit(self.server_settings_group_box) |
1433 | + self.address_edit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) |
1434 | + self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), |
1435 | + self)) |
1436 | + self.address_edit.setObjectName('address_edit') |
1437 | + self.server_settings_layout.addRow(self.address_label, self.address_edit) |
1438 | + self.twelve_hour_check_box = QtWidgets.QCheckBox(self.server_settings_group_box) |
1439 | + self.twelve_hour_check_box.setObjectName('twelve_hour_check_box') |
1440 | + self.server_settings_layout.addRow(self.twelve_hour_check_box) |
1441 | + self.thumbnails_check_box = QtWidgets.QCheckBox(self.server_settings_group_box) |
1442 | + self.thumbnails_check_box.setObjectName('thumbnails_check_box') |
1443 | + self.server_settings_layout.addRow(self.thumbnails_check_box) |
1444 | + self.left_layout.addWidget(self.server_settings_group_box) |
1445 | + self.http_settings_group_box = QtWidgets.QGroupBox(self.left_column) |
1446 | + self.http_settings_group_box.setObjectName('http_settings_group_box') |
1447 | + self.http_setting_layout = QtWidgets.QFormLayout(self.http_settings_group_box) |
1448 | + self.http_setting_layout.setObjectName('http_setting_layout') |
1449 | + self.port_label = QtWidgets.QLabel(self.http_settings_group_box) |
1450 | + self.port_label.setObjectName('port_label') |
1451 | + self.port_spin_box = QtWidgets.QLabel(self.http_settings_group_box) |
1452 | + self.port_spin_box.setObjectName('port_spin_box') |
1453 | + self.http_setting_layout.addRow(self.port_label, self.port_spin_box) |
1454 | + self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box) |
1455 | + self.remote_url_label.setObjectName('remote_url_label') |
1456 | + self.remote_url = QtWidgets.QLabel(self.http_settings_group_box) |
1457 | + self.remote_url.setObjectName('remote_url') |
1458 | + self.remote_url.setOpenExternalLinks(True) |
1459 | + self.http_setting_layout.addRow(self.remote_url_label, self.remote_url) |
1460 | + self.stage_url_label = QtWidgets.QLabel(self.http_settings_group_box) |
1461 | + self.stage_url_label.setObjectName('stage_url_label') |
1462 | + self.stage_url = QtWidgets.QLabel(self.http_settings_group_box) |
1463 | + self.stage_url.setObjectName('stage_url') |
1464 | + self.stage_url.setOpenExternalLinks(True) |
1465 | + self.http_setting_layout.addRow(self.stage_url_label, self.stage_url) |
1466 | + self.chords_url_label = QtWidgets.QLabel(self.http_settings_group_box) |
1467 | + self.chords_url_label.setObjectName('chords_url_label') |
1468 | + self.chords_url = QtWidgets.QLabel(self.http_settings_group_box) |
1469 | + self.chords_url.setObjectName('chords_url') |
1470 | + self.chords_url.setOpenExternalLinks(True) |
1471 | + self.http_setting_layout.addRow(self.chords_url_label, self.chords_url) |
1472 | + self.live_url_label = QtWidgets.QLabel(self.http_settings_group_box) |
1473 | + self.live_url_label.setObjectName('live_url_label') |
1474 | + self.live_url = QtWidgets.QLabel(self.http_settings_group_box) |
1475 | + self.live_url.setObjectName('live_url') |
1476 | + self.live_url.setOpenExternalLinks(True) |
1477 | + self.http_setting_layout.addRow(self.live_url_label, self.live_url) |
1478 | + self.left_layout.addWidget(self.http_settings_group_box) |
1479 | + self.user_login_group_box = QtWidgets.QGroupBox(self.left_column) |
1480 | + self.user_login_group_box.setCheckable(True) |
1481 | + self.user_login_group_box.setChecked(False) |
1482 | + self.user_login_group_box.setObjectName('user_login_group_box') |
1483 | + self.user_login_layout = QtWidgets.QFormLayout(self.user_login_group_box) |
1484 | + self.user_login_layout.setObjectName('user_login_layout') |
1485 | + self.user_id_label = QtWidgets.QLabel(self.user_login_group_box) |
1486 | + self.user_id_label.setObjectName('user_id_label') |
1487 | + self.user_id = QtWidgets.QLineEdit(self.user_login_group_box) |
1488 | + self.user_id.setObjectName('user_id') |
1489 | + self.user_login_layout.addRow(self.user_id_label, self.user_id) |
1490 | + self.password_label = QtWidgets.QLabel(self.user_login_group_box) |
1491 | + self.password_label.setObjectName('password_label') |
1492 | + self.password = QtWidgets.QLineEdit(self.user_login_group_box) |
1493 | + self.password.setObjectName('password') |
1494 | + self.user_login_layout.addRow(self.password_label, self.password) |
1495 | + self.left_layout.addWidget(self.user_login_group_box) |
1496 | + self.update_site_group_box = QtWidgets.QGroupBox(self.left_column) |
1497 | + self.update_site_group_box.setCheckable(True) |
1498 | + self.update_site_group_box.setChecked(False) |
1499 | + self.update_site_group_box.setObjectName('update_site_group_box') |
1500 | + self.update_site_layout = QtWidgets.QFormLayout(self.update_site_group_box) |
1501 | + self.update_site_layout.setObjectName('update_site_layout') |
1502 | + self.current_version_label = QtWidgets.QLabel(self.update_site_group_box) |
1503 | + self.current_version_label.setObjectName('current_version_label') |
1504 | + self.current_version_value = QtWidgets.QLabel(self.update_site_group_box) |
1505 | + self.current_version_value.setObjectName('current_version_value') |
1506 | + self.update_site_layout.addRow(self.current_version_label, self.current_version_value) |
1507 | + self.master_version_label = QtWidgets.QLabel(self.update_site_group_box) |
1508 | + self.master_version_label.setObjectName('master_version_label') |
1509 | + self.master_version_value = QtWidgets.QLabel(self.update_site_group_box) |
1510 | + self.master_version_value.setObjectName('master_version_value') |
1511 | + self.update_site_layout.addRow(self.master_version_label, self.master_version_value) |
1512 | + self.left_layout.addWidget(self.update_site_group_box) |
1513 | + self.android_app_group_box = QtWidgets.QGroupBox(self.right_column) |
1514 | + self.android_app_group_box.setObjectName('android_app_group_box') |
1515 | + self.right_layout.addWidget(self.android_app_group_box) |
1516 | + self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box) |
1517 | + self.android_qr_layout.setObjectName('android_qr_layout') |
1518 | + self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box) |
1519 | + self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png')) |
1520 | + self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter) |
1521 | + self.android_qr_code_label.setObjectName('android_qr_code_label') |
1522 | + self.android_qr_layout.addWidget(self.android_qr_code_label) |
1523 | + self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box) |
1524 | + self.android_qr_description_label.setObjectName('android_qr_description_label') |
1525 | + self.android_qr_description_label.setOpenExternalLinks(True) |
1526 | + self.android_qr_description_label.setWordWrap(True) |
1527 | + self.android_qr_layout.addWidget(self.android_qr_description_label) |
1528 | + self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column) |
1529 | + self.ios_app_group_box.setObjectName('ios_app_group_box') |
1530 | + self.right_layout.addWidget(self.ios_app_group_box) |
1531 | + self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box) |
1532 | + self.ios_qr_layout.setObjectName('ios_qr_layout') |
1533 | + self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box) |
1534 | + self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png')) |
1535 | + self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter) |
1536 | + self.ios_qr_code_label.setObjectName('ios_qr_code_label') |
1537 | + self.ios_qr_layout.addWidget(self.ios_qr_code_label) |
1538 | + self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box) |
1539 | + self.ios_qr_description_label.setObjectName('ios_qr_description_label') |
1540 | + self.ios_qr_description_label.setOpenExternalLinks(True) |
1541 | + self.ios_qr_description_label.setWordWrap(True) |
1542 | + self.ios_qr_layout.addWidget(self.ios_qr_description_label) |
1543 | + self.left_layout.addStretch() |
1544 | + self.right_layout.addStretch() |
1545 | + self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed) |
1546 | + self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed) |
1547 | + self.address_edit.textChanged.connect(self.set_urls) |
1548 | + |
1549 | + def define_main_window_icon(self): |
1550 | + """ |
1551 | + Define an icon on the main window to show the state of the server |
1552 | + :return: |
1553 | + """ |
1554 | + self.remote_server_icon = QtWidgets.QLabel(self.main_window.status_bar) |
1555 | + size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) |
1556 | + size_policy.setHorizontalStretch(0) |
1557 | + size_policy.setVerticalStretch(0) |
1558 | + size_policy.setHeightForWidth(self.remote_server_icon.sizePolicy().hasHeightForWidth()) |
1559 | + self.remote_server_icon.setSizePolicy(size_policy) |
1560 | + self.remote_server_icon.setFrameShadow(QtWidgets.QFrame.Plain) |
1561 | + self.remote_server_icon.setLineWidth(1) |
1562 | + self.remote_server_icon.setScaledContents(True) |
1563 | + self.remote_server_icon.setFixedSize(20, 20) |
1564 | + self.remote_server_icon.setObjectName('remote_server_icon') |
1565 | + self.main_window.status_bar.insertPermanentWidget(2, self.remote_server_icon) |
1566 | + |
1567 | + def retranslateUi(self): |
1568 | + self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface') |
1569 | + self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings')) |
1570 | + self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:')) |
1571 | + self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:')) |
1572 | + self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:')) |
1573 | + self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:')) |
1574 | + self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:')) |
1575 | + self.chords_url_label.setText(translate('RemotePlugin.RemoteTab', 'Chords view URL:')) |
1576 | + self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format')) |
1577 | + self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab', |
1578 | + 'Show thumbnails of non-text slides in remote and stage view.')) |
1579 | + self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App')) |
1580 | + self.android_qr_description_label.setText( |
1581 | + translate('RemotePlugin.RemoteTab', |
1582 | + 'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google ' |
1583 | + 'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2')) |
1584 | + self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App')) |
1585 | + self.ios_qr_description_label.setText( |
1586 | + translate('RemotePlugin.RemoteTab', |
1587 | + 'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App ' |
1588 | + 'Store.').format(qr='https://itunes.apple.com/app/id1096218725')) |
1589 | + self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication')) |
1590 | + self.aa = UiStrings() |
1591 | + self.update_site_group_box.setTitle(UiStrings().WebDownloadText) |
1592 | + self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:')) |
1593 | + self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:')) |
1594 | + self.current_version_label.setText(translate('RemotePlugin.RemoteTab', 'Current Version number:')) |
1595 | + self.master_version_label.setText(translate('RemotePlugin.RemoteTab', 'Latest Version number:')) |
1596 | + |
1597 | + def set_urls(self): |
1598 | + """ |
1599 | + Update the display based on the data input on the screen |
1600 | + """ |
1601 | + ip_address = self.get_ip_address(self.address_edit.text()) |
1602 | + http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.text()) |
1603 | + self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url)) |
1604 | + http_url_temp = http_url + 'stage' |
1605 | + self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) |
1606 | + http_url_temp = http_url + 'main' |
1607 | + self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) |
1608 | + |
1609 | + @staticmethod |
1610 | + def get_ip_address(ip_address): |
1611 | + """ |
1612 | + returns the IP address in dependency of the passed address |
1613 | + ip_address == 0.0.0.0: return the IP address of the first valid interface |
1614 | + else: return ip_address |
1615 | + """ |
1616 | + if ip_address == ZERO_URL: |
1617 | + interfaces = QtNetwork.QNetworkInterface.allInterfaces() |
1618 | + for interface in interfaces: |
1619 | + if not interface.isValid(): |
1620 | + continue |
1621 | + if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)): |
1622 | + continue |
1623 | + for address in interface.addressEntries(): |
1624 | + ip = address.ip() |
1625 | + if ip.protocol() == QtNetwork.QAbstractSocket.IPv4Protocol and \ |
1626 | + ip != QtNetwork.QHostAddress.LocalHost: |
1627 | + return ip.toString() |
1628 | + return ip_address |
1629 | + |
1630 | + def load(self): |
1631 | + """ |
1632 | + Load the configuration and update the server configuration if necessary |
1633 | + """ |
1634 | + self.port_spin_box.setText(str(Settings().value(self.settings_section + '/port'))) |
1635 | + self.address_edit.setText(Settings().value(self.settings_section + '/ip address')) |
1636 | + self.twelve_hour = Settings().value(self.settings_section + '/twelve hour') |
1637 | + self.twelve_hour_check_box.setChecked(self.twelve_hour) |
1638 | + self.thumbnails = Settings().value(self.settings_section + '/thumbnails') |
1639 | + self.thumbnails_check_box.setChecked(self.thumbnails) |
1640 | + self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled')) |
1641 | + self.user_id.setText(Settings().value(self.settings_section + '/user id')) |
1642 | + self.password.setText(Settings().value(self.settings_section + '/password')) |
1643 | + self.current_version_value.setText(Settings().value('remotes/download version')) |
1644 | + self.master_version_value.setText(Registry().get_flag('website_version')) |
1645 | + if self.master_version_value.text() == self.current_version_value.text(): |
1646 | + self.update_site_group_box.setEnabled(False) |
1647 | + self.set_urls() |
1648 | + |
1649 | + def save(self): |
1650 | + """ |
1651 | + Save the configuration and update the server configuration if necessary |
1652 | + """ |
1653 | + if Settings().value(self.settings_section + '/ip address') != self.address_edit.text(): |
1654 | + self.settings_form.register_post_process('remotes_config_updated') |
1655 | + Settings().setValue(self.settings_section + '/ip address', self.address_edit.text()) |
1656 | + Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour) |
1657 | + Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails) |
1658 | + Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked()) |
1659 | + Settings().setValue(self.settings_section + '/user id', self.user_id.text()) |
1660 | + Settings().setValue(self.settings_section + '/password', self.password.text()) |
1661 | + self.generate_icon() |
1662 | + if self.update_site_group_box.isChecked(): |
1663 | + self.settings_form.register_post_process('download_website') |
1664 | + |
1665 | + def on_twelve_hour_check_box_changed(self, check_state): |
1666 | + """ |
1667 | + Toggle the 12 hour check box. |
1668 | + """ |
1669 | + self.twelve_hour = False |
1670 | + # we have a set value convert to True/False |
1671 | + if check_state == QtCore.Qt.Checked: |
1672 | + self.twelve_hour = True |
1673 | + |
1674 | + def on_thumbnails_check_box_changed(self, check_state): |
1675 | + """ |
1676 | + Toggle the thumbnail check box. |
1677 | + """ |
1678 | + self.thumbnails = False |
1679 | + # we have a set value convert to True/False |
1680 | + if check_state == QtCore.Qt.Checked: |
1681 | + self.thumbnails = True |
1682 | + |
1683 | + def generate_icon(self): |
1684 | + """ |
1685 | + Generate icon for main window |
1686 | + """ |
1687 | + self.remote_server_icon.hide() |
1688 | + icon = QtGui.QImage(':/remote/network_server.png') |
1689 | + icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) |
1690 | + if Settings().value(self.settings_section + '/authentication enabled'): |
1691 | + overlay = QtGui.QImage(':/remote/network_auth.png') |
1692 | + overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) |
1693 | + painter = QtGui.QPainter(icon) |
1694 | + painter.drawImage(20, 0, overlay) |
1695 | + painter.end() |
1696 | + self.remote_server_icon.setPixmap(QtGui.QPixmap.fromImage(icon)) |
1697 | + self.remote_server_icon.show() |
1698 | |
1699 | === added file 'openlp/core/api/websockets.py' |
1700 | --- openlp/core/api/websockets.py 1970-01-01 00:00:00 +0000 |
1701 | +++ openlp/core/api/websockets.py 2017-08-13 07:11:15 +0000 |
1702 | @@ -0,0 +1,142 @@ |
1703 | +# -*- coding: utf-8 -*- |
1704 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1705 | + |
1706 | +############################################################################### |
1707 | +# OpenLP - Open Source Lyrics Projection # |
1708 | +# --------------------------------------------------------------------------- # |
1709 | +# Copyright (c) 2008-2017 OpenLP Developers # |
1710 | +# --------------------------------------------------------------------------- # |
1711 | +# This program is free software; you can redistribute it and/or modify it # |
1712 | +# under the terms of the GNU General Public License as published by the Free # |
1713 | +# Software Foundation; version 2 of the License. # |
1714 | +# # |
1715 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1716 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1717 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1718 | +# more details. # |
1719 | +# # |
1720 | +# You should have received a copy of the GNU General Public License along # |
1721 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1722 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1723 | +############################################################################### |
1724 | + |
1725 | +""" |
1726 | +The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact |
1727 | +with OpenLP. It uses JSON to communicate with the remotes. |
1728 | +""" |
1729 | + |
1730 | +import asyncio |
1731 | +import websockets |
1732 | +import json |
1733 | +import logging |
1734 | +import time |
1735 | + |
1736 | +from PyQt5 import QtCore |
1737 | + |
1738 | +from openlp.core.common import Settings, RegistryProperties, OpenLPMixin, Registry |
1739 | + |
1740 | +log = logging.getLogger(__name__) |
1741 | + |
1742 | + |
1743 | +class WebSocketWorker(QtCore.QObject): |
1744 | + """ |
1745 | + A special Qt thread class to allow the WebSockets server to run at the same time as the UI. |
1746 | + """ |
1747 | + def __init__(self, server): |
1748 | + """ |
1749 | + Constructor for the thread class. |
1750 | + |
1751 | + :param server: The http server class. |
1752 | + """ |
1753 | + self.ws_server = server |
1754 | + super(WebSocketWorker, self).__init__() |
1755 | + |
1756 | + def run(self): |
1757 | + """ |
1758 | + Run the thread. |
1759 | + """ |
1760 | + self.ws_server.start_server() |
1761 | + |
1762 | + def stop(self): |
1763 | + self.ws_server.stop = True |
1764 | + |
1765 | + |
1766 | +class WebSocketServer(RegistryProperties, OpenLPMixin): |
1767 | + """ |
1768 | + Wrapper round a server instance |
1769 | + """ |
1770 | + def __init__(self): |
1771 | + """ |
1772 | + Initialise and start the WebSockets server |
1773 | + """ |
1774 | + super(WebSocketServer, self).__init__() |
1775 | + self.settings_section = 'api' |
1776 | + self.worker = WebSocketWorker(self) |
1777 | + self.thread = QtCore.QThread() |
1778 | + self.worker.moveToThread(self.thread) |
1779 | + self.thread.started.connect(self.worker.run) |
1780 | + self.thread.start() |
1781 | + |
1782 | + def start_server(self): |
1783 | + """ |
1784 | + Start the correct server and save the handler |
1785 | + """ |
1786 | + address = Settings().value(self.settings_section + '/ip address') |
1787 | + port = Settings().value(self.settings_section + '/websocket port') |
1788 | + self.start_websocket_instance(address, port) |
1789 | + # If web socket server start listening |
1790 | + if hasattr(self, 'ws_server') and self.ws_server: |
1791 | + event_loop = asyncio.new_event_loop() |
1792 | + asyncio.set_event_loop(event_loop) |
1793 | + event_loop.run_until_complete(self.ws_server) |
1794 | + event_loop.run_forever() |
1795 | + else: |
1796 | + log.debug('Failed to start ws server on port {port}'.format(port=port)) |
1797 | + |
1798 | + def start_websocket_instance(self, address, port): |
1799 | + """ |
1800 | + Start the server |
1801 | + |
1802 | + :param address: The server address |
1803 | + :param port: The run port |
1804 | + """ |
1805 | + loop = 1 |
1806 | + while loop < 4: |
1807 | + try: |
1808 | + self.ws_server = websockets.serve(self.handle_websocket, address, port) |
1809 | + log.debug("Web Socket Server started for class {address} {port}".format(address=address, port=port)) |
1810 | + break |
1811 | + except Exception as e: |
1812 | + log.error('Failed to start ws server {why}'.format(why=e)) |
1813 | + loop += 1 |
1814 | + time.sleep(0.1) |
1815 | + |
1816 | + @staticmethod |
1817 | + async def handle_websocket(request, path): |
1818 | + """ |
1819 | + Handle web socket requests and return the poll information. |
1820 | + Check ever 0.2 seconds to get the latest position and send if changed. |
1821 | + Only gets triggered when 1st client attaches |
1822 | + |
1823 | + :param request: request from client |
1824 | + :param path: determines the endpoints supported |
1825 | + :return: |
1826 | + """ |
1827 | + log.debug("web socket handler registered with client") |
1828 | + previous_poll = None |
1829 | + previous_main_poll = None |
1830 | + poller = Registry().get('poller') |
1831 | + if path == '/state': |
1832 | + while True: |
1833 | + current_poll = poller.poll() |
1834 | + if current_poll != previous_poll: |
1835 | + await request.send(json.dumps(current_poll).encode()) |
1836 | + previous_poll = current_poll |
1837 | + await asyncio.sleep(0.2) |
1838 | + elif path == '/live_changed': |
1839 | + while True: |
1840 | + main_poll = poller.main_poll() |
1841 | + if main_poll != previous_main_poll: |
1842 | + await request.send(main_poll) |
1843 | + previous_main_poll = main_poll |
1844 | + await asyncio.sleep(0.2) |
1845 | |
1846 | === modified file 'openlp/core/common/httputils.py' |
1847 | --- openlp/core/common/httputils.py 2017-02-26 21:14:49 +0000 |
1848 | +++ openlp/core/common/httputils.py 2017-08-13 07:11:15 +0000 |
1849 | @@ -25,8 +25,10 @@ |
1850 | import hashlib |
1851 | import logging |
1852 | import os |
1853 | +import platform |
1854 | import socket |
1855 | import sys |
1856 | +import subprocess |
1857 | import time |
1858 | import urllib.error |
1859 | import urllib.parse |
1860 | @@ -215,6 +217,7 @@ |
1861 | block_count = 0 |
1862 | block_size = 4096 |
1863 | retries = 0 |
1864 | + log.debug("url_get_file: " + url) |
1865 | while True: |
1866 | try: |
1867 | filename = open(f_path, "wb") |
1868 | @@ -253,4 +256,17 @@ |
1869 | return True |
1870 | |
1871 | |
1872 | +def ping(host): |
1873 | + """ |
1874 | + Returns True if host responds to a ping request |
1875 | + """ |
1876 | + # Ping parameters as function of OS |
1877 | + ping_str = "-n 1" if platform.system().lower() == "windows" else "-c 1" |
1878 | + args = "ping " + " " + ping_str + " " + host |
1879 | + need_sh = False if platform.system().lower() == "windows" else True |
1880 | + |
1881 | + # Ping |
1882 | + return subprocess.call(args, shell=need_sh) == 0 |
1883 | + |
1884 | + |
1885 | __all__ = ['get_web_page'] |
1886 | |
1887 | === modified file 'openlp/core/common/settings.py' |
1888 | --- openlp/core/common/settings.py 2017-06-05 06:05:54 +0000 |
1889 | +++ openlp/core/common/settings.py 2017-08-13 07:11:15 +0000 |
1890 | @@ -134,6 +134,14 @@ |
1891 | 'advanced/single click service preview': False, |
1892 | 'advanced/x11 bypass wm': X11_BYPASS_DEFAULT, |
1893 | 'advanced/search as type': True, |
1894 | + 'api/twelve hour': True, |
1895 | + 'api/port': 4316, |
1896 | + 'api/websocket port': 4317, |
1897 | + 'api/user id': 'openlp', |
1898 | + 'api/password': 'password', |
1899 | + 'api/authentication enabled': False, |
1900 | + 'api/ip address': '0.0.0.0', |
1901 | + 'api/thumbnails': True, |
1902 | 'crashreport/last directory': '', |
1903 | 'formattingTags/html_tags': '', |
1904 | 'core/audio repeat list': False, |
1905 | @@ -214,6 +222,17 @@ |
1906 | ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system |
1907 | ('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting |
1908 | ('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4. |
1909 | + ('advanced/default image', '/core/logo file', []), # Default image renamed + moved to general after 2.4. |
1910 | + ('remotes/https enabled', '', []), |
1911 | + ('remotes/https port', '', []), |
1912 | + ('remotes/twelve hour', 'api/twelve hour', []), |
1913 | + ('remotes/port', 'api/port', []), |
1914 | + ('remotes/websocket port', 'api/websocket port', []), |
1915 | + ('remotes/user id', 'api/user id', []), |
1916 | + ('remotes/password', 'api/password', []), |
1917 | + ('remotes/authentication enabled', 'api/authentication enabled', []), |
1918 | + ('remotes/ip address', 'api/ip address', []), |
1919 | + ('remotes/thumbnails', 'api/thumbnails', []), |
1920 | ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4. |
1921 | ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6. |
1922 | ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. |
1923 | |
1924 | === modified file 'openlp/core/common/uistrings.py' |
1925 | --- openlp/core/common/uistrings.py 2017-07-04 22:30:41 +0000 |
1926 | +++ openlp/core/common/uistrings.py 2017-08-13 07:11:15 +0000 |
1927 | @@ -153,6 +153,7 @@ |
1928 | self.Split = translate('OpenLP.Ui', 'Optional &Split') |
1929 | self.SplitToolTip = translate('OpenLP.Ui', |
1930 | 'Split a slide into two only if it does not fit on the screen as one slide.') |
1931 | + self.StartingImport = translate('OpenLP.Ui', 'Starting import...') |
1932 | self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop') |
1933 | self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End') |
1934 | self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular') |
1935 | @@ -166,6 +167,7 @@ |
1936 | self.View = translate('OpenLP.Ui', 'View') |
1937 | self.ViewMode = translate('OpenLP.Ui', 'View Mode') |
1938 | self.Video = translate('OpenLP.Ui', 'Video') |
1939 | + self.WebDownloadText = translate('OpenLP.Ui', 'Web Interface, Download and Install latest Version') |
1940 | book_chapter = translate('OpenLP.Ui', 'Book Chapter') |
1941 | chapter = translate('OpenLP.Ui', 'Chapter') |
1942 | verse = translate('OpenLP.Ui', 'Verse') |
1943 | |
1944 | === modified file 'openlp/core/common/versionchecker.py' |
1945 | --- openlp/core/common/versionchecker.py 2017-08-01 20:59:41 +0000 |
1946 | +++ openlp/core/common/versionchecker.py 2017-08-13 07:11:15 +0000 |
1947 | @@ -1,3 +1,27 @@ |
1948 | +# -*- coding: utf-8 -*- |
1949 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1950 | + |
1951 | +############################################################################### |
1952 | +# OpenLP - Open Source Lyrics Projection # |
1953 | +# --------------------------------------------------------------------------- # |
1954 | +# Copyright (c) 2008-2017 OpenLP Developers # |
1955 | +# --------------------------------------------------------------------------- # |
1956 | +# This program is free software; you can redistribute it and/or modify it # |
1957 | +# under the terms of the GNU General Public License as published by the Free # |
1958 | +# Software Foundation; version 2 of the License. # |
1959 | +# # |
1960 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1961 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1962 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1963 | +# more details. # |
1964 | +# # |
1965 | +# You should have received a copy of the GNU General Public License along # |
1966 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1967 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1968 | +############################################################################### |
1969 | +""" |
1970 | +The :mod:`openlp.core.common` module downloads the version details for OpenLP. |
1971 | +""" |
1972 | import logging |
1973 | import os |
1974 | import platform |
1975 | @@ -12,7 +36,8 @@ |
1976 | |
1977 | from PyQt5 import QtCore |
1978 | |
1979 | -from openlp.core.common import AppLocation, Settings |
1980 | +from openlp.core.common import AppLocation, Registry, Settings |
1981 | +from openlp.core.common.httputils import ping |
1982 | |
1983 | log = logging.getLogger(__name__) |
1984 | |
1985 | @@ -42,12 +67,18 @@ |
1986 | """ |
1987 | self.sleep(1) |
1988 | log.debug('Version thread - run') |
1989 | - app_version = get_application_version() |
1990 | - version = check_latest_version(app_version) |
1991 | - log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)), |
1992 | - version2=LooseVersion(str(app_version['full'])))) |
1993 | - if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])): |
1994 | - self.main_window.openlp_version_check.emit('{version}'.format(version=version)) |
1995 | + found = ping("openlp.io") |
1996 | + Registry().set_flag('internet_present', found) |
1997 | + update_check = Settings().value('core/update check') |
1998 | + if found: |
1999 | + Registry().execute('get_website_version') |
2000 | + if update_check: |
2001 | + app_version = get_application_version() |
2002 | + version = check_latest_version(app_version) |
2003 | + log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)), |
2004 | + version2=LooseVersion(str(app_version['full'])))) |
2005 | + if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])): |
2006 | + self.main_window.openlp_version_check.emit('{version}'.format(version=version)) |
2007 | |
2008 | |
2009 | def get_application_version(): |
2010 | |
2011 | === modified file 'openlp/core/lib/imagemanager.py' |
2012 | --- openlp/core/lib/imagemanager.py 2017-05-30 18:42:35 +0000 |
2013 | +++ openlp/core/lib/imagemanager.py 2017-08-13 07:11:15 +0000 |
2014 | @@ -56,7 +56,7 @@ |
2015 | """ |
2016 | Run the thread. |
2017 | """ |
2018 | - self.image_manager._process() |
2019 | + self.image_manager.process() |
2020 | |
2021 | |
2022 | class Priority(object): |
2023 | @@ -235,8 +235,15 @@ |
2024 | def get_image(self, path, source, width=-1, height=-1): |
2025 | """ |
2026 | Return the ``QImage`` from the cache. If not present wait for the background thread to process it. |
2027 | + |
2028 | + :param: path: The image path |
2029 | + :param: source: The source of the image |
2030 | + :param: background: The image background colour |
2031 | + :param: width: The processed image width |
2032 | + :param: height: The processed image height |
2033 | """ |
2034 | - log.debug('getImage {path}'.format(path=path)) |
2035 | + log.debug('get_image {path} {source} {width} {height}'.format(path=path, source=source, |
2036 | + width=width, height=height)) |
2037 | image = self._cache[(path, source, width, height)] |
2038 | if image.image is None: |
2039 | self._conversion_queue.modify_priority(image, Priority.High) |
2040 | @@ -255,8 +262,15 @@ |
2041 | def get_image_bytes(self, path, source, width=-1, height=-1): |
2042 | """ |
2043 | Returns the byte string for an image. If not present wait for the background thread to process it. |
2044 | + |
2045 | + :param: path: The image path |
2046 | + :param: source: The source of the image |
2047 | + :param: background: The image background colour |
2048 | + :param: width: The processed image width |
2049 | + :param: height: The processed image height |
2050 | """ |
2051 | - log.debug('get_image_bytes {path}'.format(path=path)) |
2052 | + log.debug('get_image_bytes {path} {source} {width} {height}'.format(path=path, source=source, |
2053 | + width=width, height=height)) |
2054 | image = self._cache[(path, source, width, height)] |
2055 | if image.image_bytes is None: |
2056 | self._conversion_queue.modify_priority(image, Priority.Urgent) |
2057 | @@ -270,9 +284,16 @@ |
2058 | def add_image(self, path, source, background, width=-1, height=-1): |
2059 | """ |
2060 | Add image to cache if it is not already there. |
2061 | + |
2062 | + :param: path: The image path |
2063 | + :param: source: The source of the image |
2064 | + :param: background: The image background colour |
2065 | + :param: width: The processed image width |
2066 | + :param: height: The processed image height |
2067 | """ |
2068 | - log.debug('add_image {path}'.format(path=path)) |
2069 | - if (path, source, width, height) not in self._cache: |
2070 | + log.debug('add_image {path} {source} {width} {height}'.format(path=path, source=source, |
2071 | + width=width, height=height)) |
2072 | + if not (path, source, width, height) in self._cache: |
2073 | image = Image(path, source, background, width, height) |
2074 | self._cache[(path, source, width, height)] = image |
2075 | self._conversion_queue.put((image.priority, image.secondary_priority, image)) |
2076 | @@ -286,11 +307,11 @@ |
2077 | if not self.image_thread.isRunning(): |
2078 | self.image_thread.start() |
2079 | |
2080 | - def _process(self): |
2081 | + def process(self): |
2082 | """ |
2083 | Controls the processing called from a ``QtCore.QThread``. |
2084 | """ |
2085 | - log.debug('_process - started') |
2086 | + log.debug('process - started') |
2087 | while not self._conversion_queue.empty() and not self.stop_manager: |
2088 | self._process_cache() |
2089 | log.debug('_process - ended') |
2090 | |
2091 | === modified file 'openlp/core/ui/firsttimeform.py' |
2092 | --- openlp/core/ui/firsttimeform.py 2017-08-01 20:59:41 +0000 |
2093 | +++ openlp/core/ui/firsttimeform.py 2017-08-13 07:11:15 +0000 |
2094 | @@ -202,7 +202,7 @@ |
2095 | self.themes_url = self.web + self.config.get('themes', 'directory') + '/' |
2096 | self.web_access = True |
2097 | except (NoSectionError, NoOptionError, MissingSectionHeaderError): |
2098 | - log.debug('A problem occured while parsing the downloaded config file') |
2099 | + log.debug('A problem occurred while parsing the downloaded config file') |
2100 | trace_error_handler(log) |
2101 | self.update_screen_list_combo() |
2102 | self.application.process_events() |
2103 | @@ -213,7 +213,6 @@ |
2104 | self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active()) |
2105 | self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active()) |
2106 | self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active()) |
2107 | - self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active()) |
2108 | self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active()) |
2109 | self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active()) |
2110 | self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active()) |
2111 | @@ -530,7 +529,6 @@ |
2112 | self._set_plugin_status(self.presentation_check_box, 'presentations/status') |
2113 | self._set_plugin_status(self.image_check_box, 'images/status') |
2114 | self._set_plugin_status(self.media_check_box, 'media/status') |
2115 | - self._set_plugin_status(self.remote_check_box, 'remotes/status') |
2116 | self._set_plugin_status(self.custom_check_box, 'custom/status') |
2117 | self._set_plugin_status(self.song_usage_check_box, 'songusage/status') |
2118 | self._set_plugin_status(self.alert_check_box, 'alerts/status') |
2119 | |
2120 | === modified file 'openlp/core/ui/firsttimewizard.py' |
2121 | --- openlp/core/ui/firsttimewizard.py 2016-12-31 11:01:36 +0000 |
2122 | +++ openlp/core/ui/firsttimewizard.py 2017-08-13 07:11:15 +0000 |
2123 | @@ -24,6 +24,7 @@ |
2124 | """ |
2125 | from PyQt5 import QtCore, QtGui, QtWidgets |
2126 | |
2127 | +from openlp.core.common.uistrings import UiStrings |
2128 | from openlp.core.common import translate, is_macosx, clean_button_text, Settings |
2129 | from openlp.core.lib import build_icon |
2130 | from openlp.core.lib.ui import add_welcome_page |
2131 | @@ -254,8 +255,7 @@ |
2132 | self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard', |
2133 | 'Presentations – Show .ppt, .odp and .pdf files')) |
2134 | self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media – Playback of Audio and Video files')) |
2135 | - self.remote_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Remote – Control OpenLP via browser or smart' |
2136 | - 'phone app')) |
2137 | + self.remote_check_box.setText(str(UiStrings().WebDownloadText)) |
2138 | self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor')) |
2139 | self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard', |
2140 | 'Alerts – Display informative messages while showing other slides')) |
2141 | |
2142 | === modified file 'openlp/core/ui/mainwindow.py' |
2143 | --- openlp/core/ui/mainwindow.py 2017-08-03 04:21:19 +0000 |
2144 | +++ openlp/core/ui/mainwindow.py 2017-08-13 07:11:15 +0000 |
2145 | @@ -34,6 +34,8 @@ |
2146 | |
2147 | from PyQt5 import QtCore, QtGui, QtWidgets |
2148 | |
2149 | +from openlp.core.api import websockets |
2150 | +from openlp.core.api.http import server |
2151 | from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \ |
2152 | check_directory_exists, translate, is_win, is_macosx, add_actions |
2153 | from openlp.core.common.actions import ActionList, CategoryOrder |
2154 | @@ -49,6 +51,7 @@ |
2155 | from openlp.core.ui.lib.dockwidget import OpenLPDockWidget |
2156 | from openlp.core.ui.lib.mediadockmanager import MediaDockManager |
2157 | |
2158 | + |
2159 | log = logging.getLogger(__name__) |
2160 | |
2161 | MEDIA_MANAGER_STYLE = """ |
2162 | @@ -513,6 +516,9 @@ |
2163 | Settings().set_up_default_values() |
2164 | self.about_form = AboutForm(self) |
2165 | MediaController() |
2166 | + if Registry().get_flag('no_web_server'): |
2167 | + websockets.WebSocketServer() |
2168 | + server.HttpServer() |
2169 | SettingsForm(self) |
2170 | self.formatting_tag_form = FormattingTagForm(self) |
2171 | self.shortcut_form = ShortcutListForm(self) |
2172 | @@ -540,7 +546,7 @@ |
2173 | self.tools_first_time_wizard.triggered.connect(self.on_first_time_wizard_clicked) |
2174 | self.update_theme_images.triggered.connect(self.on_update_theme_images) |
2175 | self.formatting_tag_item.triggered.connect(self.on_formatting_tag_item_clicked) |
2176 | - self.settings_configure_item.triggered.connect(self.on_settings_configure_iem_clicked) |
2177 | + self.settings_configure_item.triggered.connect(self.on_settings_configure_item_clicked) |
2178 | self.settings_shortcuts_item.triggered.connect(self.on_settings_shortcuts_item_clicked) |
2179 | self.settings_import_item.triggered.connect(self.on_settings_import_item_clicked) |
2180 | self.settings_export_item.triggered.connect(self.on_settings_export_item_clicked) |
2181 | @@ -803,7 +809,7 @@ |
2182 | """ |
2183 | self.formatting_tag_form.exec() |
2184 | |
2185 | - def on_settings_configure_iem_clicked(self): |
2186 | + def on_settings_configure_item_clicked(self): |
2187 | """ |
2188 | Show the Settings dialog |
2189 | """ |
2190 | |
2191 | === modified file 'openlp/core/ui/media/__init__.py' |
2192 | --- openlp/core/ui/media/__init__.py 2017-01-25 21:17:27 +0000 |
2193 | +++ openlp/core/ui/media/__init__.py 2017-08-13 07:11:15 +0000 |
2194 | @@ -146,5 +146,6 @@ |
2195 | |
2196 | from .mediacontroller import MediaController |
2197 | from .playertab import PlayerTab |
2198 | +from .endpoint import media_endpoint |
2199 | |
2200 | __all__ = ['MediaController', 'PlayerTab'] |
2201 | |
2202 | === added file 'openlp/core/ui/media/endpoint.py' |
2203 | --- openlp/core/ui/media/endpoint.py 1970-01-01 00:00:00 +0000 |
2204 | +++ openlp/core/ui/media/endpoint.py 2017-08-13 07:11:15 +0000 |
2205 | @@ -0,0 +1,72 @@ |
2206 | +# -*- coding: utf-8 -*- |
2207 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2208 | + |
2209 | +############################################################################### |
2210 | +# OpenLP - Open Source Lyrics Projection # |
2211 | +# --------------------------------------------------------------------------- # |
2212 | +# Copyright (c) 2008-2017 OpenLP Developers # |
2213 | +# --------------------------------------------------------------------------- # |
2214 | +# This program is free software; you can redistribute it and/or modify it # |
2215 | +# under the terms of the GNU General Public License as published by the Free # |
2216 | +# Software Foundation; version 2 of the License. # |
2217 | +# # |
2218 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
2219 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2220 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2221 | +# more details. # |
2222 | +# # |
2223 | +# You should have received a copy of the GNU General Public License along # |
2224 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2225 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2226 | +############################################################################### |
2227 | +import logging |
2228 | + |
2229 | +from openlp.core.api.http.endpoint import Endpoint |
2230 | +from openlp.core.api.http import requires_auth |
2231 | +from openlp.core.common import Registry |
2232 | + |
2233 | + |
2234 | +log = logging.getLogger(__name__) |
2235 | + |
2236 | +media_endpoint = Endpoint('media') |
2237 | + |
2238 | + |
2239 | +@media_endpoint.route('play') |
2240 | +@requires_auth |
2241 | +def media_play(request): |
2242 | + """ |
2243 | + Handles requests for playing media |
2244 | + |
2245 | + :param request: The http request object. |
2246 | + """ |
2247 | + media = Registry().get('media_controller') |
2248 | + live = Registry().get('live_controller') |
2249 | + status = media.media_play(live, False) |
2250 | + return {'results': {'success': status}} |
2251 | + |
2252 | + |
2253 | +@media_endpoint.route('pause') |
2254 | +@requires_auth |
2255 | +def media_pause(request): |
2256 | + """ |
2257 | + Handles requests for pausing media |
2258 | + |
2259 | + :param request: The http request object. |
2260 | + """ |
2261 | + media = Registry().get('media_controller') |
2262 | + live = Registry().get('live_controller') |
2263 | + status = media.media_pause(live) |
2264 | + return {'results': {'success': status}} |
2265 | + |
2266 | + |
2267 | +@media_endpoint.route('stop') |
2268 | +@requires_auth |
2269 | +def media_stop(request): |
2270 | + """ |
2271 | + Handles requests for stopping |
2272 | + |
2273 | + :param request: The http request object. |
2274 | + """ |
2275 | + event = getattr(Registry().get('live_controller'), 'mediacontroller_live_stop') |
2276 | + event.emit() |
2277 | + return {'results': {'success': True}} |
2278 | |
2279 | === modified file 'openlp/core/ui/media/mediacontroller.py' |
2280 | --- openlp/core/ui/media/mediacontroller.py 2017-05-30 18:50:39 +0000 |
2281 | +++ openlp/core/ui/media/mediacontroller.py 2017-08-13 07:11:15 +0000 |
2282 | @@ -28,12 +28,13 @@ |
2283 | import datetime |
2284 | from PyQt5 import QtCore, QtWidgets |
2285 | |
2286 | +from openlp.core.api.http import register_endpoint |
2287 | from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, \ |
2288 | extension_loader, translate |
2289 | from openlp.core.lib import ItemCapabilities |
2290 | from openlp.core.lib.ui import critical_error_message_box |
2291 | -from openlp.core.common import AppLocation |
2292 | from openlp.core.ui import DisplayControllerType |
2293 | +from openlp.core.ui.media.endpoint import media_endpoint |
2294 | from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper |
2295 | from openlp.core.ui.media.mediaplayer import MediaPlayer |
2296 | from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\ |
2297 | @@ -127,9 +128,11 @@ |
2298 | Registry().register_function('media_unblank', self.media_unblank) |
2299 | # Signals for background video |
2300 | Registry().register_function('songs_hide', self.media_hide) |
2301 | + Registry().register_function('songs_blank', self.media_blank) |
2302 | Registry().register_function('songs_unblank', self.media_unblank) |
2303 | Registry().register_function('mediaitem_media_rebuild', self._set_active_players) |
2304 | Registry().register_function('mediaitem_suffixes', self._generate_extensions_lists) |
2305 | + register_endpoint(media_endpoint) |
2306 | |
2307 | def _set_active_players(self): |
2308 | """ |
2309 | @@ -611,6 +614,14 @@ |
2310 | """ |
2311 | self.media_play(msg[0], status) |
2312 | |
2313 | + def on_media_play(self): |
2314 | + """ |
2315 | + Responds to the request to play a loaded video from the web. |
2316 | + |
2317 | + :param msg: First element is the controller which should be used |
2318 | + """ |
2319 | + self.media_play(Registry().get('live_controller'), False) |
2320 | + |
2321 | def media_play(self, controller, first_time=True): |
2322 | """ |
2323 | Responds to the request to play a loaded video |
2324 | @@ -685,6 +696,14 @@ |
2325 | """ |
2326 | self.media_pause(msg[0]) |
2327 | |
2328 | + def on_media_pause(self): |
2329 | + """ |
2330 | + Responds to the request to pause a loaded video from the web. |
2331 | + |
2332 | + :param msg: First element is the controller which should be used |
2333 | + """ |
2334 | + self.media_pause(Registry().get('live_controller')) |
2335 | + |
2336 | def media_pause(self, controller): |
2337 | """ |
2338 | Responds to the request to pause a loaded video |
2339 | @@ -725,6 +744,14 @@ |
2340 | """ |
2341 | self.media_stop(msg[0]) |
2342 | |
2343 | + def on_media_stop(self): |
2344 | + """ |
2345 | + Responds to the request to stop a loaded video from the web. |
2346 | + |
2347 | + :param msg: First element is the controller which should be used |
2348 | + """ |
2349 | + self.media_stop(Registry().get('live_controller')) |
2350 | + |
2351 | def media_stop(self, controller, looping_background=False): |
2352 | """ |
2353 | Responds to the request to stop a loaded video |
2354 | |
2355 | === modified file 'openlp/core/ui/settingsform.py' |
2356 | --- openlp/core/ui/settingsform.py 2017-06-04 12:14:23 +0000 |
2357 | +++ openlp/core/ui/settingsform.py 2017-08-13 07:11:15 +0000 |
2358 | @@ -26,6 +26,7 @@ |
2359 | |
2360 | from PyQt5 import QtCore, QtWidgets |
2361 | |
2362 | +from openlp.core.api import ApiTab |
2363 | from openlp.core.common import Registry, RegistryProperties |
2364 | from openlp.core.lib import build_icon |
2365 | from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab |
2366 | @@ -56,12 +57,13 @@ |
2367 | self.projector_tab = None |
2368 | self.advanced_tab = None |
2369 | self.player_tab = None |
2370 | + self.api_tab = None |
2371 | |
2372 | def exec(self): |
2373 | """ |
2374 | Execute the form |
2375 | """ |
2376 | - # load all the |
2377 | + # load all the widgets |
2378 | self.setting_list_widget.blockSignals(True) |
2379 | self.setting_list_widget.clear() |
2380 | while self.stacked_layout.count(): |
2381 | @@ -72,6 +74,7 @@ |
2382 | self.insert_tab(self.advanced_tab) |
2383 | self.insert_tab(self.player_tab) |
2384 | self.insert_tab(self.projector_tab) |
2385 | + self.insert_tab(self.api_tab) |
2386 | for plugin in self.plugin_manager.plugins: |
2387 | if plugin.settings_tab: |
2388 | self.insert_tab(plugin.settings_tab, plugin.is_active()) |
2389 | @@ -93,6 +96,7 @@ |
2390 | list_item = QtWidgets.QListWidgetItem(build_icon(tab_widget.icon_path), tab_widget.tab_title_visible) |
2391 | list_item.setData(QtCore.Qt.UserRole, tab_widget.tab_title) |
2392 | self.setting_list_widget.addItem(list_item) |
2393 | + tab_widget.load() |
2394 | |
2395 | def accept(self): |
2396 | """ |
2397 | @@ -154,10 +158,13 @@ |
2398 | self.advanced_tab = AdvancedTab(self) |
2399 | # Advanced tab |
2400 | self.player_tab = PlayerTab(self) |
2401 | + # Api tab |
2402 | + self.api_tab = ApiTab(self) |
2403 | self.general_tab.post_set_up() |
2404 | self.themes_tab.post_set_up() |
2405 | self.advanced_tab.post_set_up() |
2406 | self.player_tab.post_set_up() |
2407 | + self.api_tab.post_set_up() |
2408 | for plugin in self.plugin_manager.plugins: |
2409 | if plugin.settings_tab: |
2410 | plugin.settings_tab.post_set_up() |
2411 | |
2412 | === modified file 'openlp/core/ui/slidecontroller.py' |
2413 | --- openlp/core/ui/slidecontroller.py 2017-03-28 05:15:05 +0000 |
2414 | +++ openlp/core/ui/slidecontroller.py 2017-08-13 07:11:15 +0000 |
2415 | @@ -439,6 +439,10 @@ |
2416 | # NOTE: {t} used to keep line length < maxline |
2417 | getattr(self, |
2418 | 'slidecontroller_{t}_previous'.format(t=self.type_prefix)).connect(self.on_slide_selected_previous) |
2419 | + if self.is_live: |
2420 | + getattr(self, 'mediacontroller_live_play').connect(self.media_controller.on_media_play) |
2421 | + getattr(self, 'mediacontroller_live_pause').connect(self.media_controller.on_media_pause) |
2422 | + getattr(self, 'mediacontroller_live_stop').connect(self.media_controller.on_media_stop) |
2423 | |
2424 | def _slide_shortcut_activated(self): |
2425 | """ |
2426 | @@ -1530,6 +1534,9 @@ |
2427 | slidecontroller_live_next = QtCore.pyqtSignal() |
2428 | slidecontroller_live_previous = QtCore.pyqtSignal() |
2429 | slidecontroller_toggle_display = QtCore.pyqtSignal(str) |
2430 | + mediacontroller_live_play = QtCore.pyqtSignal() |
2431 | + mediacontroller_live_pause = QtCore.pyqtSignal() |
2432 | + mediacontroller_live_stop = QtCore.pyqtSignal() |
2433 | |
2434 | def __init__(self, parent): |
2435 | """ |
2436 | |
2437 | === modified file 'openlp/plugins/alerts/alertsplugin.py' |
2438 | --- openlp/plugins/alerts/alertsplugin.py 2017-07-04 23:13:51 +0000 |
2439 | +++ openlp/plugins/alerts/alertsplugin.py 2017-08-13 07:11:15 +0000 |
2440 | @@ -24,6 +24,7 @@ |
2441 | |
2442 | from PyQt5 import QtGui |
2443 | |
2444 | +from openlp.core.api.http import register_endpoint |
2445 | from openlp.core.common import Settings, UiStrings, translate |
2446 | from openlp.core.common.actions import ActionList |
2447 | from openlp.core.lib import Plugin, StringContent, build_icon |
2448 | @@ -31,6 +32,7 @@ |
2449 | from openlp.core.lib.theme import VerticalType |
2450 | from openlp.core.lib.ui import create_action |
2451 | from openlp.core.ui import AlertLocation |
2452 | +from openlp.plugins.alerts.endpoint import api_alerts_endpoint, alerts_endpoint |
2453 | from openlp.plugins.alerts.forms import AlertForm |
2454 | from openlp.plugins.alerts.lib import AlertsManager, AlertsTab |
2455 | from openlp.plugins.alerts.lib.db import init_schema |
2456 | @@ -140,6 +142,8 @@ |
2457 | AlertsManager(self) |
2458 | self.manager = Manager('alerts', init_schema) |
2459 | self.alert_form = AlertForm(self) |
2460 | + register_endpoint(alerts_endpoint) |
2461 | + register_endpoint(api_alerts_endpoint) |
2462 | |
2463 | def add_tools_menu_item(self, tools_menu): |
2464 | """ |
2465 | |
2466 | === added file 'openlp/plugins/alerts/endpoint.py' |
2467 | --- openlp/plugins/alerts/endpoint.py 1970-01-01 00:00:00 +0000 |
2468 | +++ openlp/plugins/alerts/endpoint.py 2017-08-13 07:11:15 +0000 |
2469 | @@ -0,0 +1,60 @@ |
2470 | +# -*- coding: utf-8 -*- |
2471 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2472 | + |
2473 | +############################################################################### |
2474 | +# OpenLP - Open Source Lyrics Projection # |
2475 | +# --------------------------------------------------------------------------- # |
2476 | +# Copyright (c) 2008-2017 OpenLP Developers # |
2477 | +# --------------------------------------------------------------------------- # |
2478 | +# This program is free software; you can redistribute it and/or modify it # |
2479 | +# under the terms of the GNU General Public License as published by the Free # |
2480 | +# Software Foundation; version 2 of the License. # |
2481 | +# # |
2482 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
2483 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2484 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2485 | +# more details. # |
2486 | +# # |
2487 | +# You should have received a copy of the GNU General Public License along # |
2488 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2489 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2490 | +############################################################################### |
2491 | +import logging |
2492 | +import json |
2493 | +import urllib |
2494 | +from urllib.parse import urlparse |
2495 | + |
2496 | +from openlp.core.api.http.endpoint import Endpoint |
2497 | +from openlp.core.api.http import requires_auth |
2498 | +from openlp.core.common import Registry |
2499 | +from openlp.core.lib import PluginStatus |
2500 | + |
2501 | + |
2502 | +log = logging.getLogger(__name__) |
2503 | + |
2504 | +alerts_endpoint = Endpoint('alert') |
2505 | +api_alerts_endpoint = Endpoint('api') |
2506 | + |
2507 | + |
2508 | +@alerts_endpoint.route('') |
2509 | +@api_alerts_endpoint.route('alert') |
2510 | +@requires_auth |
2511 | +def alert(request): |
2512 | + """ |
2513 | + Handles requests for setting service items in the service manager |
2514 | + |
2515 | + :param request: The http request object. |
2516 | + """ |
2517 | + plugin = Registry().get('plugin_manager').get_plugin_by_name("alerts") |
2518 | + if plugin.status == PluginStatus.Active: |
2519 | + try: |
2520 | + json_data = request.GET.get('data') |
2521 | + text = json.loads(json_data)['request']['text'] |
2522 | + except KeyError: |
2523 | + log.error("Endpoint alerts request text not found") |
2524 | + text = urllib.parse.unquote(text) |
2525 | + Registry().get('alerts_manager').alerts_text.emit([text]) |
2526 | + success = True |
2527 | + else: |
2528 | + success = False |
2529 | + return {'results': {'success': success}} |
2530 | |
2531 | === modified file 'openlp/plugins/bibles/bibleplugin.py' |
2532 | --- openlp/plugins/bibles/bibleplugin.py 2017-07-04 23:13:51 +0000 |
2533 | +++ openlp/plugins/bibles/bibleplugin.py 2017-08-13 07:11:15 +0000 |
2534 | @@ -22,9 +22,11 @@ |
2535 | |
2536 | import logging |
2537 | |
2538 | +from openlp.core.api.http import register_endpoint |
2539 | from openlp.core.common import UiStrings |
2540 | from openlp.core.common.actions import ActionList |
2541 | from openlp.core.lib import Plugin, StringContent, build_icon, translate |
2542 | +from openlp.plugins.bibles.endpoint import api_bibles_endpoint, bibles_endpoint |
2543 | from openlp.core.lib.ui import create_action |
2544 | from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \ |
2545 | LanguageSelection |
2546 | @@ -75,6 +77,8 @@ |
2547 | self.icon_path = ':/plugins/plugin_bibles.png' |
2548 | self.icon = build_icon(self.icon_path) |
2549 | self.manager = BibleManager(self) |
2550 | + register_endpoint(bibles_endpoint) |
2551 | + register_endpoint(api_bibles_endpoint) |
2552 | |
2553 | def initialise(self): |
2554 | """ |
2555 | |
2556 | === added file 'openlp/plugins/bibles/endpoint.py' |
2557 | --- openlp/plugins/bibles/endpoint.py 1970-01-01 00:00:00 +0000 |
2558 | +++ openlp/plugins/bibles/endpoint.py 2017-08-13 07:11:15 +0000 |
2559 | @@ -0,0 +1,100 @@ |
2560 | +# -*- coding: utf-8 -*- |
2561 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2562 | + |
2563 | +############################################################################### |
2564 | +# OpenLP - Open Source Lyrics Projection # |
2565 | +# --------------------------------------------------------------------------- # |
2566 | +# Copyright (c) 2008-2017 OpenLP Developers # |
2567 | +# --------------------------------------------------------------------------- # |
2568 | +# This program is free software; you can redistribute it and/or modify it # |
2569 | +# under the terms of the GNU General Public License as published by the Free # |
2570 | +# Software Foundation; version 2 of the License. # |
2571 | +# # |
2572 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
2573 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2574 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2575 | +# more details. # |
2576 | +# # |
2577 | +# You should have received a copy of the GNU General Public License along # |
2578 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2579 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2580 | +############################################################################### |
2581 | +import logging |
2582 | + |
2583 | +from openlp.core.api.http.endpoint import Endpoint |
2584 | +from openlp.core.api.http.errors import NotFound |
2585 | +from openlp.core.api.endpoint.pluginhelpers import search, live, service |
2586 | +from openlp.core.api.http import requires_auth |
2587 | + |
2588 | + |
2589 | +log = logging.getLogger(__name__) |
2590 | + |
2591 | +bibles_endpoint = Endpoint('bibles') |
2592 | +api_bibles_endpoint = Endpoint('api') |
2593 | + |
2594 | + |
2595 | +@bibles_endpoint.route('search') |
2596 | +def bibles_search(request): |
2597 | + """ |
2598 | + Handles requests for searching the bibles plugin |
2599 | + |
2600 | + :param request: The http request object. |
2601 | + """ |
2602 | + return search(request, 'bibles', log) |
2603 | + |
2604 | + |
2605 | +@bibles_endpoint.route('live') |
2606 | +@requires_auth |
2607 | +def bibles_live(request): |
2608 | + """ |
2609 | + Handles requests for making a song live |
2610 | + |
2611 | + :param request: The http request object. |
2612 | + """ |
2613 | + return live(request, 'bibles', log) |
2614 | + |
2615 | + |
2616 | +@bibles_endpoint.route('add') |
2617 | +@requires_auth |
2618 | +def bibles_service(request): |
2619 | + """ |
2620 | + Handles requests for adding a song to the service |
2621 | + |
2622 | + :param request: The http request object. |
2623 | + """ |
2624 | + service(request, 'bibles', log) |
2625 | + |
2626 | + |
2627 | +@api_bibles_endpoint.route('bibles/search') |
2628 | +def bibles_search_api(request): |
2629 | + """ |
2630 | + Handles requests for searching the bibles plugin |
2631 | + |
2632 | + :param request: The http request object. |
2633 | + """ |
2634 | + return search(request, 'bibles', log) |
2635 | + |
2636 | + |
2637 | +@api_bibles_endpoint.route('bibles/live') |
2638 | +@requires_auth |
2639 | +def bibles_live_api(request): |
2640 | + """ |
2641 | + Handles requests for making a song live |
2642 | + |
2643 | + :param request: The http request object. |
2644 | + """ |
2645 | + return live(request, 'bibles', log) |
2646 | + |
2647 | + |
2648 | +@api_bibles_endpoint.route('bibles/add') |
2649 | +@requires_auth |
2650 | +def bibles_service_api(request): |
2651 | + """ |
2652 | + Handles requests for adding a song to the service |
2653 | + |
2654 | + :param request: The http request object. |
2655 | + """ |
2656 | + try: |
2657 | + search(request, 'bibles', log) |
2658 | + except NotFound: |
2659 | + return {'results': {'items': []}} |
2660 | |
2661 | === modified file 'openlp/plugins/custom/customplugin.py' |
2662 | --- openlp/plugins/custom/customplugin.py 2017-06-04 09:52:15 +0000 |
2663 | +++ openlp/plugins/custom/customplugin.py 2017-08-13 07:11:15 +0000 |
2664 | @@ -26,8 +26,10 @@ |
2665 | |
2666 | import logging |
2667 | |
2668 | +from openlp.core.api.http import register_endpoint |
2669 | from openlp.core.lib import Plugin, StringContent, build_icon, translate |
2670 | from openlp.core.lib.db import Manager |
2671 | +from openlp.plugins.custom.endpoint import api_custom_endpoint, custom_endpoint |
2672 | from openlp.plugins.custom.lib import CustomMediaItem, CustomTab |
2673 | from openlp.plugins.custom.lib.db import CustomSlide, init_schema |
2674 | from openlp.plugins.custom.lib.mediaitem import CustomSearch |
2675 | @@ -61,6 +63,8 @@ |
2676 | self.db_manager = Manager('custom', init_schema) |
2677 | self.icon_path = ':/plugins/plugin_custom.png' |
2678 | self.icon = build_icon(self.icon_path) |
2679 | + register_endpoint(custom_endpoint) |
2680 | + register_endpoint(api_custom_endpoint) |
2681 | |
2682 | @staticmethod |
2683 | def about(): |
2684 | |
2685 | === added file 'openlp/plugins/custom/endpoint.py' |
2686 | --- openlp/plugins/custom/endpoint.py 1970-01-01 00:00:00 +0000 |
2687 | +++ openlp/plugins/custom/endpoint.py 2017-08-13 07:11:15 +0000 |
2688 | @@ -0,0 +1,100 @@ |
2689 | +# -*- coding: utf-8 -*- |
2690 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2691 | + |
2692 | +############################################################################### |
2693 | +# OpenLP - Open Source Lyrics Projection # |
2694 | +# --------------------------------------------------------------------------- # |
2695 | +# Copyright (c) 2008-2017 OpenLP Developers # |
2696 | +# --------------------------------------------------------------------------- # |
2697 | +# This program is free software; you can redistribute it and/or modify it # |
2698 | +# under the terms of the GNU General Public License as published by the Free # |
2699 | +# Software Foundation; version 2 of the License. # |
2700 | +# # |
2701 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
2702 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2703 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2704 | +# more details. # |
2705 | +# # |
2706 | +# You should have received a copy of the GNU General Public License along # |
2707 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2708 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2709 | +############################################################################### |
2710 | +import logging |
2711 | + |
2712 | +from openlp.core.api.http.endpoint import Endpoint |
2713 | +from openlp.core.api.http.errors import NotFound |
2714 | +from openlp.core.api.endpoint.pluginhelpers import search, live, service |
2715 | +from openlp.core.api.http import requires_auth |
2716 | + |
2717 | + |
2718 | +log = logging.getLogger(__name__) |
2719 | + |
2720 | +custom_endpoint = Endpoint('custom') |
2721 | +api_custom_endpoint = Endpoint('api') |
2722 | + |
2723 | + |
2724 | +@custom_endpoint.route('search') |
2725 | +def custom_search(request): |
2726 | + """ |
2727 | + Handles requests for searching the custom plugin |
2728 | + |
2729 | + :param request: The http request object. |
2730 | + """ |
2731 | + return search(request, 'custom', log) |
2732 | + |
2733 | + |
2734 | +@custom_endpoint.route('live') |
2735 | +@requires_auth |
2736 | +def custom_live(request): |
2737 | + """ |
2738 | + Handles requests for making a song live |
2739 | + |
2740 | + :param request: The http request object. |
2741 | + """ |
2742 | + return live(request, 'custom', log) |
2743 | + |
2744 | + |
2745 | +@custom_endpoint.route('add') |
2746 | +@requires_auth |
2747 | +def custom_service(request): |
2748 | + """ |
2749 | + Handles requests for adding a song to the service |
2750 | + |
2751 | + :param request: The http request object. |
2752 | + """ |
2753 | + service(request, 'custom', log) |
2754 | + |
2755 | + |
2756 | +@api_custom_endpoint.route('custom/search') |
2757 | +def custom_search_api(request): |
2758 | + """ |
2759 | + Handles requests for searching the custom plugin |
2760 | + |
2761 | + :param request: The http request object. |
2762 | + """ |
2763 | + return search(request, 'custom', log) |
2764 | + |
2765 | + |
2766 | +@api_custom_endpoint.route('custom/live') |
2767 | +@requires_auth |
2768 | +def custom_live_api(request): |
2769 | + """ |
2770 | + Handles requests for making a song live |
2771 | + |
2772 | + :param request: The http request object. |
2773 | + """ |
2774 | + return live(request, 'custom', log) |
2775 | + |
2776 | + |
2777 | +@api_custom_endpoint.route('custom/add') |
2778 | +@requires_auth |
2779 | +def custom_service_api(request): |
2780 | + """ |
2781 | + Handles requests for adding a song to the service |
2782 | + |
2783 | + :param request: The http request object. |
2784 | + """ |
2785 | + try: |
2786 | + search(request, 'custom', log) |
2787 | + except NotFound: |
2788 | + return {'results': {'items': []}} |
2789 | |
2790 | === added file 'openlp/plugins/images/endpoint.py' |
2791 | --- openlp/plugins/images/endpoint.py 1970-01-01 00:00:00 +0000 |
2792 | +++ openlp/plugins/images/endpoint.py 2017-08-13 07:11:15 +0000 |
2793 | @@ -0,0 +1,113 @@ |
2794 | +# -*- coding: utf-8 -*- |
2795 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2796 | + |
2797 | +############################################################################### |
2798 | +# OpenLP - Open Source Lyrics Projection # |
2799 | +# --------------------------------------------------------------------------- # |
2800 | +# Copyright (c) 2008-2017 OpenLP Developers # |
2801 | +# --------------------------------------------------------------------------- # |
2802 | +# This program is free software; you can redistribute it and/or modify it # |
2803 | +# under the terms of the GNU General Public License as published by the Free # |
2804 | +# Software Foundation; version 2 of the License. # |
2805 | +# # |
2806 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
2807 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2808 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2809 | +# more details. # |
2810 | +# # |
2811 | +# You should have received a copy of the GNU General Public License along # |
2812 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2813 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2814 | +############################################################################### |
2815 | +import logging |
2816 | + |
2817 | +from openlp.core.api.http.endpoint import Endpoint |
2818 | +from openlp.core.api.http.errors import NotFound |
2819 | +from openlp.core.api.endpoint.pluginhelpers import search, live, service, display_thumbnails |
2820 | +from openlp.core.api.http import requires_auth |
2821 | + |
2822 | + |
2823 | +log = logging.getLogger(__name__) |
2824 | + |
2825 | +images_endpoint = Endpoint('images') |
2826 | +api_images_endpoint = Endpoint('api') |
2827 | + |
2828 | + |
2829 | +# images/thumbnails/320x240/1.jpg |
2830 | +@images_endpoint.route('thumbnails/{dimensions}/{file_name}') |
2831 | +def images_thumbnails(request, dimensions, file_name): |
2832 | + """ |
2833 | + Return an image to a web page based on a URL |
2834 | + :param request: Request object |
2835 | + :param dimensions: the image size eg 88x88 |
2836 | + :param file_name: the individual image name |
2837 | + :return: |
2838 | + """ |
2839 | + return display_thumbnails(request, 'images', log, dimensions, file_name) |
2840 | + |
2841 | + |
2842 | +@images_endpoint.route('search') |
2843 | +def images_search(request): |
2844 | + """ |
2845 | + Handles requests for searching the images plugin |
2846 | + |
2847 | + :param request: The http request object. |
2848 | + """ |
2849 | + return search(request, 'images', log) |
2850 | + |
2851 | + |
2852 | +@images_endpoint.route('live') |
2853 | +@requires_auth |
2854 | +def images_live(request): |
2855 | + """ |
2856 | + Handles requests for making a song live |
2857 | + |
2858 | + :param request: The http request object. |
2859 | + """ |
2860 | + return live(request, 'images', log) |
2861 | + |
2862 | + |
2863 | +@images_endpoint.route('add') |
2864 | +@requires_auth |
2865 | +def images_service(request): |
2866 | + """ |
2867 | + Handles requests for adding a song to the service |
2868 | + |
2869 | + :param request: The http request object. |
2870 | + """ |
2871 | + service(request, 'images', log) |
2872 | + |
2873 | + |
2874 | +@api_images_endpoint.route('images/search') |
2875 | +def images_search_api(request): |
2876 | + """ |
2877 | + Handles requests for searching the images plugin |
2878 | + |
2879 | + :param request: The http request object. |
2880 | + """ |
2881 | + return search(request, 'images', log) |
2882 | + |
2883 | + |
2884 | +@api_images_endpoint.route('images/live') |
2885 | +@requires_auth |
2886 | +def images_live_api(request): |
2887 | + """ |
2888 | + Handles requests for making a song live |
2889 | + |
2890 | + :param request: The http request object. |
2891 | + """ |
2892 | + return live(request, 'images', log) |
2893 | + |
2894 | + |
2895 | +@api_images_endpoint.route('images/add') |
2896 | +@requires_auth |
2897 | +def images_service_api(request): |
2898 | + """ |
2899 | + Handles requests for adding a song to the service |
2900 | + |
2901 | + :param request: The http request object. |
2902 | + """ |
2903 | + try: |
2904 | + search(request, 'images', log) |
2905 | + except NotFound: |
2906 | + return {'results': {'items': []}} |
2907 | |
2908 | === modified file 'openlp/plugins/images/imageplugin.py' |
2909 | --- openlp/plugins/images/imageplugin.py 2016-12-31 11:01:36 +0000 |
2910 | +++ openlp/plugins/images/imageplugin.py 2017-08-13 07:11:15 +0000 |
2911 | @@ -24,9 +24,11 @@ |
2912 | |
2913 | import logging |
2914 | |
2915 | +from openlp.core.api.http import register_endpoint |
2916 | from openlp.core.common import Settings, translate |
2917 | from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon |
2918 | from openlp.core.lib.db import Manager |
2919 | +from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint |
2920 | from openlp.plugins.images.lib import ImageMediaItem, ImageTab |
2921 | from openlp.plugins.images.lib.db import init_schema |
2922 | |
2923 | @@ -51,6 +53,8 @@ |
2924 | self.weight = -7 |
2925 | self.icon_path = ':/plugins/plugin_images.png' |
2926 | self.icon = build_icon(self.icon_path) |
2927 | + register_endpoint(images_endpoint) |
2928 | + register_endpoint(api_images_endpoint) |
2929 | |
2930 | @staticmethod |
2931 | def about(): |
2932 | |
2933 | === added file 'openlp/plugins/media/endpoint.py' |
2934 | --- openlp/plugins/media/endpoint.py 1970-01-01 00:00:00 +0000 |
2935 | +++ openlp/plugins/media/endpoint.py 2017-08-13 07:11:15 +0000 |
2936 | @@ -0,0 +1,100 @@ |
2937 | +# -*- coding: utf-8 -*- |
2938 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2939 | + |
2940 | +############################################################################### |
2941 | +# OpenLP - Open Source Lyrics Projection # |
2942 | +# --------------------------------------------------------------------------- # |
2943 | +# Copyright (c) 2008-2017 OpenLP Developers # |
2944 | +# --------------------------------------------------------------------------- # |
2945 | +# This program is free software; you can redistribute it and/or modify it # |
2946 | +# under the terms of the GNU General Public License as published by the Free # |
2947 | +# Software Foundation; version 2 of the License. # |
2948 | +# # |
2949 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
2950 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2951 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2952 | +# more details. # |
2953 | +# # |
2954 | +# You should have received a copy of the GNU General Public License along # |
2955 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2956 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2957 | +############################################################################### |
2958 | +import logging |
2959 | + |
2960 | +from openlp.core.api.http.endpoint import Endpoint |
2961 | +from openlp.core.api.http.errors import NotFound |
2962 | +from openlp.core.api.endpoint.pluginhelpers import search, live, service |
2963 | +from openlp.core.api.http import requires_auth |
2964 | + |
2965 | + |
2966 | +log = logging.getLogger(__name__) |
2967 | + |
2968 | +media_endpoint = Endpoint('media') |
2969 | +api_media_endpoint = Endpoint('api') |
2970 | + |
2971 | + |
2972 | +@media_endpoint.route('search') |
2973 | +def media_search(request): |
2974 | + """ |
2975 | + Handles requests for searching the media plugin |
2976 | + |
2977 | + :param request: The http request object. |
2978 | + """ |
2979 | + return search(request, 'media', log) |
2980 | + |
2981 | + |
2982 | +@media_endpoint.route('live') |
2983 | +@requires_auth |
2984 | +def media_live(request): |
2985 | + """ |
2986 | + Handles requests for making a song live |
2987 | + |
2988 | + :param request: The http request object. |
2989 | + """ |
2990 | + return live(request, 'media', log) |
2991 | + |
2992 | + |
2993 | +@media_endpoint.route('add') |
2994 | +@requires_auth |
2995 | +def media_service(request): |
2996 | + """ |
2997 | + Handles requests for adding a song to the service |
2998 | + |
2999 | + :param request: The http request object. |
3000 | + """ |
3001 | + service(request, 'media', log) |
3002 | + |
3003 | + |
3004 | +@api_media_endpoint.route('media/search') |
3005 | +def media_search_api(request): |
3006 | + """ |
3007 | + Handles requests for searching the media plugin |
3008 | + |
3009 | + :param request: The http request object. |
3010 | + """ |
3011 | + return search(request, 'media', log) |
3012 | + |
3013 | + |
3014 | +@api_media_endpoint.route('media/live') |
3015 | +@requires_auth |
3016 | +def media_live_api(request): |
3017 | + """ |
3018 | + Handles requests for making a song live |
3019 | + |
3020 | + :param request: The http request object. |
3021 | + """ |
3022 | + return live(request, 'media', log) |
3023 | + |
3024 | + |
3025 | +@api_media_endpoint.route('media/add') |
3026 | +@requires_auth |
3027 | +def media_service_api(request): |
3028 | + """ |
3029 | + Handles requests for adding a song to the service |
3030 | + |
3031 | + :param request: The http request object. |
3032 | + """ |
3033 | + try: |
3034 | + search(request, 'media', log) |
3035 | + except NotFound: |
3036 | + return {'results': {'items': []}} |
3037 | |
3038 | === modified file 'openlp/plugins/media/mediaplugin.py' |
3039 | --- openlp/plugins/media/mediaplugin.py 2017-08-01 20:59:41 +0000 |
3040 | +++ openlp/plugins/media/mediaplugin.py 2017-08-13 07:11:15 +0000 |
3041 | @@ -26,12 +26,13 @@ |
3042 | import logging |
3043 | import os |
3044 | import re |
3045 | -from shutil import which |
3046 | |
3047 | from PyQt5 import QtCore |
3048 | |
3049 | -from openlp.core.common import AppLocation, Settings, translate, check_binary_exists, is_win |
3050 | +from openlp.core.api.http import register_endpoint |
3051 | +from openlp.core.common import AppLocation, translate, check_binary_exists |
3052 | from openlp.core.lib import Plugin, StringContent, build_icon |
3053 | +from openlp.plugins.media.endpoint import api_media_endpoint, media_endpoint |
3054 | from openlp.plugins.media.lib import MediaMediaItem, MediaTab |
3055 | |
3056 | |
3057 | @@ -58,6 +59,8 @@ |
3058 | self.icon = build_icon(self.icon_path) |
3059 | # passed with drag and drop messages |
3060 | self.dnd_id = 'Media' |
3061 | + register_endpoint(media_endpoint) |
3062 | + register_endpoint(api_media_endpoint) |
3063 | |
3064 | def initialise(self): |
3065 | """ |
3066 | |
3067 | === added file 'openlp/plugins/presentations/endpoint.py' |
3068 | --- openlp/plugins/presentations/endpoint.py 1970-01-01 00:00:00 +0000 |
3069 | +++ openlp/plugins/presentations/endpoint.py 2017-08-13 07:11:15 +0000 |
3070 | @@ -0,0 +1,114 @@ |
3071 | +# -*- coding: utf-8 -*- |
3072 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
3073 | + |
3074 | +############################################################################### |
3075 | +# OpenLP - Open Source Lyrics Projection # |
3076 | +# --------------------------------------------------------------------------- # |
3077 | +# Copyright (c) 2008-2017 OpenLP Developers # |
3078 | +# --------------------------------------------------------------------------- # |
3079 | +# This program is free software; you can redistribute it and/or modify it # |
3080 | +# under the terms of the GNU General Public License as published by the Free # |
3081 | +# Software Foundation; version 2 of the License. # |
3082 | +# # |
3083 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
3084 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
3085 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
3086 | +# more details. # |
3087 | +# # |
3088 | +# You should have received a copy of the GNU General Public License along # |
3089 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
3090 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
3091 | +############################################################################### |
3092 | +import logging |
3093 | + |
3094 | +from openlp.core.api.http.endpoint import Endpoint |
3095 | +from openlp.core.api.http.errors import NotFound |
3096 | +from openlp.core.api.endpoint.pluginhelpers import search, live, service, display_thumbnails |
3097 | +from openlp.core.api.http import requires_auth |
3098 | + |
3099 | + |
3100 | +log = logging.getLogger(__name__) |
3101 | + |
3102 | +presentations_endpoint = Endpoint('presentations') |
3103 | +api_presentations_endpoint = Endpoint('api') |
3104 | + |
3105 | + |
3106 | +# /presentations/thumbnails88x88/PA%20Rota.pdf/slide5.png |
3107 | +@presentations_endpoint.route('thumbnails/{dimensions}/{file_name}/{slide}') |
3108 | +def presentations_thumbnails(request, dimensions, file_name, slide): |
3109 | + """ |
3110 | + Return a presentation to a web page based on a URL |
3111 | + :param request: Request object |
3112 | + :param dimensions: the image size eg 88x88 |
3113 | + :param file_name: the file name of the image |
3114 | + :param slide: the individual image name |
3115 | + :return: |
3116 | + """ |
3117 | + return display_thumbnails(request, 'presentations', log, dimensions, file_name, slide) |
3118 | + |
3119 | + |
3120 | +@presentations_endpoint.route('search') |
3121 | +def presentations_search(request): |
3122 | + """ |
3123 | + Handles requests for searching the presentations plugin |
3124 | + |
3125 | + :param request: The http request object. |
3126 | + """ |
3127 | + return search(request, 'presentations', log) |
3128 | + |
3129 | + |
3130 | +@presentations_endpoint.route('live') |
3131 | +@requires_auth |
3132 | +def presentations_live(request): |
3133 | + """ |
3134 | + Handles requests for making a song live |
3135 | + |
3136 | + :param request: The http request object. |
3137 | + """ |
3138 | + return live(request, 'presentations', log) |
3139 | + |
3140 | + |
3141 | +@presentations_endpoint.route('add') |
3142 | +@requires_auth |
3143 | +def presentations_service(request): |
3144 | + """ |
3145 | + Handles requests for adding a song to the service |
3146 | + |
3147 | + :param request: The http request object. |
3148 | + """ |
3149 | + service(request, 'presentations', log) |
3150 | + |
3151 | + |
3152 | +@api_presentations_endpoint.route('presentations/search') |
3153 | +def presentations_search_api(request): |
3154 | + """ |
3155 | + Handles requests for searching the presentations plugin |
3156 | + |
3157 | + :param request: The http request object. |
3158 | + """ |
3159 | + return search(request, 'presentations', log) |
3160 | + |
3161 | + |
3162 | +@api_presentations_endpoint.route('presentations/live') |
3163 | +@requires_auth |
3164 | +def presentations_live_api(request): |
3165 | + """ |
3166 | + Handles requests for making a song live |
3167 | + |
3168 | + :param request: The http request object. |
3169 | + """ |
3170 | + return live(request, 'presentations', log) |
3171 | + |
3172 | + |
3173 | +@api_presentations_endpoint.route('presentations/add') |
3174 | +@requires_auth |
3175 | +def presentations_service_api(request): |
3176 | + """ |
3177 | + Handles requests for adding a song to the service |
3178 | + |
3179 | + :param request: The http request object. |
3180 | + """ |
3181 | + try: |
3182 | + search(request, 'presentations', log) |
3183 | + except NotFound: |
3184 | + return {'results': {'items': []}} |
3185 | |
3186 | === modified file 'openlp/plugins/presentations/presentationplugin.py' |
3187 | --- openlp/plugins/presentations/presentationplugin.py 2017-06-09 06:06:49 +0000 |
3188 | +++ openlp/plugins/presentations/presentationplugin.py 2017-08-13 07:11:15 +0000 |
3189 | @@ -28,8 +28,10 @@ |
3190 | |
3191 | from PyQt5 import QtCore |
3192 | |
3193 | -from openlp.core.common import AppLocation, extension_loader, translate |
3194 | +from openlp.core.api.http import register_endpoint |
3195 | +from openlp.core.common import extension_loader, translate |
3196 | from openlp.core.lib import Plugin, StringContent, build_icon |
3197 | +from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint |
3198 | from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab |
3199 | |
3200 | log = logging.getLogger(__name__) |
3201 | @@ -66,6 +68,8 @@ |
3202 | self.weight = -8 |
3203 | self.icon_path = ':/plugins/plugin_presentations.png' |
3204 | self.icon = build_icon(self.icon_path) |
3205 | + register_endpoint(presentations_endpoint) |
3206 | + register_endpoint(api_presentations_endpoint) |
3207 | |
3208 | def create_settings_tab(self, parent): |
3209 | """ |
3210 | |
3211 | === modified file 'openlp/plugins/remotes/__init__.py' |
3212 | --- openlp/plugins/remotes/__init__.py 2016-12-31 11:01:36 +0000 |
3213 | +++ openlp/plugins/remotes/__init__.py 2017-08-13 07:11:15 +0000 |
3214 | @@ -19,71 +19,3 @@ |
3215 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # |
3216 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
3217 | ############################################################################### |
3218 | -""" |
3219 | -The :mod:`remotes` plugin allows OpenLP to be controlled from another machine |
3220 | -over a network connection. |
3221 | - |
3222 | -Routes: |
3223 | - |
3224 | -``/`` |
3225 | - Go to the web interface. |
3226 | - |
3227 | -``/files/{filename}`` |
3228 | - Serve a static file. |
3229 | - |
3230 | -``/api/poll`` |
3231 | - Poll to see if there are any changes. Returns a JSON-encoded dict of |
3232 | - any changes that occurred:: |
3233 | - |
3234 | - {"results": {"type": "controller"}} |
3235 | - |
3236 | - Or, if there were no results, False:: |
3237 | - |
3238 | - {"results": False} |
3239 | - |
3240 | -``/api/controller/{live|preview}/{action}`` |
3241 | - Perform ``{action}`` on the live or preview controller. Valid actions |
3242 | - are: |
3243 | - |
3244 | - ``next`` |
3245 | - Load the next slide. |
3246 | - |
3247 | - ``previous`` |
3248 | - Load the previous slide. |
3249 | - |
3250 | - ``jump`` |
3251 | - Jump to a specific slide. Requires an id return in a JSON-encoded |
3252 | - dict like so:: |
3253 | - |
3254 | - {"request": {"id": 1}} |
3255 | - |
3256 | - ``first`` |
3257 | - Load the first slide. |
3258 | - |
3259 | - ``last`` |
3260 | - Load the last slide. |
3261 | - |
3262 | - ``text`` |
3263 | - Request the text of the current slide. |
3264 | - |
3265 | -``/api/service/{action}`` |
3266 | - Perform ``{action}`` on the service manager (e.g. go live). Data is |
3267 | - passed as a json-encoded ``data`` parameter. Valid actions are: |
3268 | - |
3269 | - ``next`` |
3270 | - Load the next item in the service. |
3271 | - |
3272 | - ``previous`` |
3273 | - Load the previews item in the service. |
3274 | - |
3275 | - ``jump`` |
3276 | - Jump to a specific item in the service. Requires an id returned in |
3277 | - a JSON-encoded dict like so:: |
3278 | - |
3279 | - {"request": {"id": 1}} |
3280 | - |
3281 | - ``list`` |
3282 | - Request a list of items in the service. |
3283 | - |
3284 | - |
3285 | -""" |
3286 | |
3287 | === added file 'openlp/plugins/remotes/deploy.py' |
3288 | --- openlp/plugins/remotes/deploy.py 1970-01-01 00:00:00 +0000 |
3289 | +++ openlp/plugins/remotes/deploy.py 2017-08-13 07:11:15 +0000 |
3290 | @@ -0,0 +1,69 @@ |
3291 | +# -*- coding: utf-8 -*- |
3292 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
3293 | + |
3294 | +############################################################################### |
3295 | +# OpenLP - Open Source Lyrics Projection # |
3296 | +# --------------------------------------------------------------------------- # |
3297 | +# Copyright (c) 2008-2017 OpenLP Developers # |
3298 | +# --------------------------------------------------------------------------- # |
3299 | +# This program is free software; you can redistribute it and/or modify it # |
3300 | +# under the terms of the GNU General Public License as published by the Free # |
3301 | +# Software Foundation; version 2 of the License. # |
3302 | +# # |
3303 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
3304 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
3305 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
3306 | +# more details. # |
3307 | +# # |
3308 | +# You should have received a copy of the GNU General Public License along # |
3309 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
3310 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
3311 | +############################################################################### |
3312 | + |
3313 | +import os |
3314 | +import zipfile |
3315 | +import urllib.error |
3316 | + |
3317 | +from openlp.core.common import AppLocation, Registry |
3318 | +from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size |
3319 | + |
3320 | + |
3321 | +def deploy_zipfile(app_root, zip_name): |
3322 | + """ |
3323 | + Process the downloaded zip file and add to the correct directory |
3324 | + |
3325 | + :param zip_name: the zip file to be processed |
3326 | + :param app_root: the directory where the zip get expanded to |
3327 | + |
3328 | + :return: None |
3329 | + """ |
3330 | + zip_file = os.path.join(app_root, zip_name) |
3331 | + web_zip = zipfile.ZipFile(zip_file) |
3332 | + web_zip.extractall(app_root) |
3333 | + |
3334 | + |
3335 | +def download_sha256(): |
3336 | + """ |
3337 | + Download the config file to extract the sha256 and version number |
3338 | + """ |
3339 | + user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() |
3340 | + try: |
3341 | + web_config = get_web_page('{host}{name}'.format(host='https://get.openlp.org/webclient/', name='download.cfg'), |
3342 | + header=('User-Agent', user_agent)) |
3343 | + except (urllib.error.URLError, ConnectionError) as err: |
3344 | + return False |
3345 | + file_bits = web_config.read().decode('utf-8').split() |
3346 | + return file_bits[0], file_bits[2] |
3347 | + |
3348 | + |
3349 | +def download_and_check(callback=None): |
3350 | + """ |
3351 | + Download the web site and deploy it. |
3352 | + """ |
3353 | + sha256, version = download_sha256() |
3354 | + file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip') |
3355 | + callback.setRange(0, file_size) |
3356 | + if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'), |
3357 | + os.path.join(str(AppLocation.get_section_data_path('remotes')), 'site.zip'), |
3358 | + sha256=sha256): |
3359 | + deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip') |
3360 | |
3361 | === added file 'openlp/plugins/remotes/endpoint.py' |
3362 | --- openlp/plugins/remotes/endpoint.py 1970-01-01 00:00:00 +0000 |
3363 | +++ openlp/plugins/remotes/endpoint.py 2017-08-13 07:11:15 +0000 |
3364 | @@ -0,0 +1,46 @@ |
3365 | +# -*- coding: utf-8 -*- |
3366 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
3367 | + |
3368 | +############################################################################### |
3369 | +# OpenLP - Open Source Lyrics Projection # |
3370 | +# --------------------------------------------------------------------------- # |
3371 | +# Copyright (c) 2008-2017 OpenLP Developers # |
3372 | +# --------------------------------------------------------------------------- # |
3373 | +# This program is free software; you can redistribute it and/or modify it # |
3374 | +# under the terms of the GNU General Public License as published by the Free # |
3375 | +# Software Foundation; version 2 of the License. # |
3376 | +# # |
3377 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
3378 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
3379 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
3380 | +# more details. # |
3381 | +# # |
3382 | +# You should have received a copy of the GNU General Public License along # |
3383 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
3384 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
3385 | +############################################################################### |
3386 | +import logging |
3387 | + |
3388 | +import os |
3389 | + |
3390 | +from openlp.core.api.http.endpoint import Endpoint |
3391 | +from openlp.core.api.endpoint.core import TRANSLATED_STRINGS |
3392 | +from openlp.core.common import AppLocation |
3393 | + |
3394 | + |
3395 | +static_dir = os.path.join(str(AppLocation.get_section_data_path('remotes'))) |
3396 | + |
3397 | +log = logging.getLogger(__name__) |
3398 | + |
3399 | +remote_endpoint = Endpoint('remote', template_dir=static_dir, static_dir=static_dir) |
3400 | + |
3401 | + |
3402 | +@remote_endpoint.route('{view}') |
3403 | +def index(request, view): |
3404 | + """ |
3405 | + Handles requests for /remotes url |
3406 | + |
3407 | + :param request: The http request object. |
3408 | + :param view: The view name to be servered. |
3409 | + """ |
3410 | + return remote_endpoint.render_template('{view}.mako'.format(view=view), **TRANSLATED_STRINGS) |
3411 | |
3412 | === removed directory 'openlp/plugins/remotes/html' |
3413 | === removed directory 'openlp/plugins/remotes/html/assets' |
3414 | === removed file 'openlp/plugins/remotes/html/assets/jquery.js' |
3415 | --- openlp/plugins/remotes/html/assets/jquery.js 2016-03-19 18:49:55 +0000 |
3416 | +++ openlp/plugins/remotes/html/assets/jquery.js 1970-01-01 00:00:00 +0000 |
3417 | @@ -1,9404 +0,0 @@ |
3418 | -/*! |
3419 | - * jQuery JavaScript Library v1.7.2 |
3420 | - * http://jquery.com/ |
3421 | - * |
3422 | - * Copyright 2011, John Resig |
3423 | - * Dual licensed under the MIT or GPL Version 2 licenses. |
3424 | - * http://jquery.org/license |
3425 | - * |
3426 | - * Includes Sizzle.js |
3427 | - * http://sizzlejs.com/ |
3428 | - * Copyright 2011, The Dojo Foundation |
3429 | - * Released under the MIT, BSD, and GPL Licenses. |
3430 | - * |
3431 | - * Date: Wed Mar 21 12:46:34 2012 -0700 |
3432 | - */ |
3433 | -(function( window, undefined ) { |
3434 | - |
3435 | -// Use the correct document accordingly with window argument (sandbox) |
3436 | -var document = window.document, |
3437 | - navigator = window.navigator, |
3438 | - location = window.location; |
3439 | -var jQuery = (function() { |
3440 | - |
3441 | -// Define a local copy of jQuery |
3442 | -var jQuery = function( selector, context ) { |
3443 | - // The jQuery object is actually just the init constructor 'enhanced' |
3444 | - return new jQuery.fn.init( selector, context, rootjQuery ); |
3445 | - }, |
3446 | - |
3447 | - // Map over jQuery in case of overwrite |
3448 | - _jQuery = window.jQuery, |
3449 | - |
3450 | - // Map over the $ in case of overwrite |
3451 | - _$ = window.$, |
3452 | - |
3453 | - // A central reference to the root jQuery(document) |
3454 | - rootjQuery, |
3455 | - |
3456 | - // A simple way to check for HTML strings or ID strings |
3457 | - // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) |
3458 | - quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, |
3459 | - |
3460 | - // Check if a string has a non-whitespace character in it |
3461 | - rnotwhite = /\S/, |
3462 | - |
3463 | - // Used for trimming whitespace |
3464 | - trimLeft = /^\s+/, |
3465 | - trimRight = /\s+$/, |
3466 | - |
3467 | - // Match a standalone tag |
3468 | - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, |
3469 | - |
3470 | - // JSON RegExp |
3471 | - rvalidchars = /^[\],:{}\s]*$/, |
3472 | - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, |
3473 | - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, |
3474 | - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, |
3475 | - |
3476 | - // Useragent RegExp |
3477 | - rwebkit = /(webkit)[ \/]([\w.]+)/, |
3478 | - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, |
3479 | - rmsie = /(msie) ([\w.]+)/, |
3480 | - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, |
3481 | - |
3482 | - // Matches dashed string for camelizing |
3483 | - rdashAlpha = /-([a-z]|[0-9])/ig, |
3484 | - rmsPrefix = /^-ms-/, |
3485 | - |
3486 | - // Used by jQuery.camelCase as callback to replace() |
3487 | - fcamelCase = function( all, letter ) { |
3488 | - return ( letter + "" ).toUpperCase(); |
3489 | - }, |
3490 | - |
3491 | - // Keep a UserAgent string for use with jQuery.browser |
3492 | - userAgent = navigator.userAgent, |
3493 | - |
3494 | - // For matching the engine and version of the browser |
3495 | - browserMatch, |
3496 | - |
3497 | - // The deferred used on DOM ready |
3498 | - readyList, |
3499 | - |
3500 | - // The ready event handler |
3501 | - DOMContentLoaded, |
3502 | - |
3503 | - // Save a reference to some core methods |
3504 | - toString = Object.prototype.toString, |
3505 | - hasOwn = Object.prototype.hasOwnProperty, |
3506 | - push = Array.prototype.push, |
3507 | - slice = Array.prototype.slice, |
3508 | - trim = String.prototype.trim, |
3509 | - indexOf = Array.prototype.indexOf, |
3510 | - |
3511 | - // [[Class]] -> type pairs |
3512 | - class2type = {}; |
3513 | - |
3514 | -jQuery.fn = jQuery.prototype = { |
3515 | - constructor: jQuery, |
3516 | - init: function( selector, context, rootjQuery ) { |
3517 | - var match, elem, ret, doc; |
3518 | - |
3519 | - // Handle $(""), $(null), or $(undefined) |
3520 | - if ( !selector ) { |
3521 | - return this; |
3522 | - } |
3523 | - |
3524 | - // Handle $(DOMElement) |
3525 | - if ( selector.nodeType ) { |
3526 | - this.context = this[0] = selector; |
3527 | - this.length = 1; |
3528 | - return this; |
3529 | - } |
3530 | - |
3531 | - // The body element only exists once, optimize finding it |
3532 | - if ( selector === "body" && !context && document.body ) { |
3533 | - this.context = document; |
3534 | - this[0] = document.body; |
3535 | - this.selector = selector; |
3536 | - this.length = 1; |
3537 | - return this; |
3538 | - } |
3539 | - |
3540 | - // Handle HTML strings |
3541 | - if ( typeof selector === "string" ) { |
3542 | - // Are we dealing with HTML string or an ID? |
3543 | - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { |
3544 | - // Assume that strings that start and end with <> are HTML and skip the regex check |
3545 | - match = [ null, selector, null ]; |
3546 | - |
3547 | - } else { |
3548 | - match = quickExpr.exec( selector ); |
3549 | - } |
3550 | - |
3551 | - // Verify a match, and that no context was specified for #id |
3552 | - if ( match && (match[1] || !context) ) { |
3553 | - |
3554 | - // HANDLE: $(html) -> $(array) |
3555 | - if ( match[1] ) { |
3556 | - context = context instanceof jQuery ? context[0] : context; |
3557 | - doc = ( context ? context.ownerDocument || context : document ); |
3558 | - |
3559 | - // If a single string is passed in and it's a single tag |
3560 | - // just do a createElement and skip the rest |
3561 | - ret = rsingleTag.exec( selector ); |
3562 | - |
3563 | - if ( ret ) { |
3564 | - if ( jQuery.isPlainObject( context ) ) { |
3565 | - selector = [ document.createElement( ret[1] ) ]; |
3566 | - jQuery.fn.attr.call( selector, context, true ); |
3567 | - |
3568 | - } else { |
3569 | - selector = [ doc.createElement( ret[1] ) ]; |
3570 | - } |
3571 | - |
3572 | - } else { |
3573 | - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); |
3574 | - selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; |
3575 | - } |
3576 | - |
3577 | - return jQuery.merge( this, selector ); |
3578 | - |
3579 | - // HANDLE: $("#id") |
3580 | - } else { |
3581 | - elem = document.getElementById( match[2] ); |
3582 | - |
3583 | - // Check parentNode to catch when Blackberry 4.6 returns |
3584 | - // nodes that are no longer in the document #6963 |
3585 | - if ( elem && elem.parentNode ) { |
3586 | - // Handle the case where IE and Opera return items |
3587 | - // by name instead of ID |
3588 | - if ( elem.id !== match[2] ) { |
3589 | - return rootjQuery.find( selector ); |
3590 | - } |
3591 | - |
3592 | - // Otherwise, we inject the element directly into the jQuery object |
3593 | - this.length = 1; |
3594 | - this[0] = elem; |
3595 | - } |
3596 | - |
3597 | - this.context = document; |
3598 | - this.selector = selector; |
3599 | - return this; |
3600 | - } |
3601 | - |
3602 | - // HANDLE: $(expr, $(...)) |
3603 | - } else if ( !context || context.jquery ) { |
3604 | - return ( context || rootjQuery ).find( selector ); |
3605 | - |
3606 | - // HANDLE: $(expr, context) |
3607 | - // (which is just equivalent to: $(context).find(expr) |
3608 | - } else { |
3609 | - return this.constructor( context ).find( selector ); |
3610 | - } |
3611 | - |
3612 | - // HANDLE: $(function) |
3613 | - // Shortcut for document ready |
3614 | - } else if ( jQuery.isFunction( selector ) ) { |
3615 | - return rootjQuery.ready( selector ); |
3616 | - } |
3617 | - |
3618 | - if ( selector.selector !== undefined ) { |
3619 | - this.selector = selector.selector; |
3620 | - this.context = selector.context; |
3621 | - } |
3622 | - |
3623 | - return jQuery.makeArray( selector, this ); |
3624 | - }, |
3625 | - |
3626 | - // Start with an empty selector |
3627 | - selector: "", |
3628 | - |
3629 | - // The current version of jQuery being used |
3630 | - jquery: "1.7.2", |
3631 | - |
3632 | - // The default length of a jQuery object is 0 |
3633 | - length: 0, |
3634 | - |
3635 | - // The number of elements contained in the matched element set |
3636 | - size: function() { |
3637 | - return this.length; |
3638 | - }, |
3639 | - |
3640 | - toArray: function() { |
3641 | - return slice.call( this, 0 ); |
3642 | - }, |
3643 | - |
3644 | - // Get the Nth element in the matched element set OR |
3645 | - // Get the whole matched element set as a clean array |
3646 | - get: function( num ) { |
3647 | - return num == null ? |
3648 | - |
3649 | - // Return a 'clean' array |
3650 | - this.toArray() : |
3651 | - |
3652 | - // Return just the object |
3653 | - ( num < 0 ? this[ this.length + num ] : this[ num ] ); |
3654 | - }, |
3655 | - |
3656 | - // Take an array of elements and push it onto the stack |
3657 | - // (returning the new matched element set) |
3658 | - pushStack: function( elems, name, selector ) { |
3659 | - // Build a new jQuery matched element set |
3660 | - var ret = this.constructor(); |
3661 | - |
3662 | - if ( jQuery.isArray( elems ) ) { |
3663 | - push.apply( ret, elems ); |
3664 | - |
3665 | - } else { |
3666 | - jQuery.merge( ret, elems ); |
3667 | - } |
3668 | - |
3669 | - // Add the old object onto the stack (as a reference) |
3670 | - ret.prevObject = this; |
3671 | - |
3672 | - ret.context = this.context; |
3673 | - |
3674 | - if ( name === "find" ) { |
3675 | - ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; |
3676 | - } else if ( name ) { |
3677 | - ret.selector = this.selector + "." + name + "(" + selector + ")"; |
3678 | - } |
3679 | - |
3680 | - // Return the newly-formed element set |
3681 | - return ret; |
3682 | - }, |
3683 | - |
3684 | - // Execute a callback for every element in the matched set. |
3685 | - // (You can seed the arguments with an array of args, but this is |
3686 | - // only used internally.) |
3687 | - each: function( callback, args ) { |
3688 | - return jQuery.each( this, callback, args ); |
3689 | - }, |
3690 | - |
3691 | - ready: function( fn ) { |
3692 | - // Attach the listeners |
3693 | - jQuery.bindReady(); |
3694 | - |
3695 | - // Add the callback |
3696 | - readyList.add( fn ); |
3697 | - |
3698 | - return this; |
3699 | - }, |
3700 | - |
3701 | - eq: function( i ) { |
3702 | - i = +i; |
3703 | - return i === -1 ? |
3704 | - this.slice( i ) : |
3705 | - this.slice( i, i + 1 ); |
3706 | - }, |
3707 | - |
3708 | - first: function() { |
3709 | - return this.eq( 0 ); |
3710 | - }, |
3711 | - |
3712 | - last: function() { |
3713 | - return this.eq( -1 ); |
3714 | - }, |
3715 | - |
3716 | - slice: function() { |
3717 | - return this.pushStack( slice.apply( this, arguments ), |
3718 | - "slice", slice.call(arguments).join(",") ); |
3719 | - }, |
3720 | - |
3721 | - map: function( callback ) { |
3722 | - return this.pushStack( jQuery.map(this, function( elem, i ) { |
3723 | - return callback.call( elem, i, elem ); |
3724 | - })); |
3725 | - }, |
3726 | - |
3727 | - end: function() { |
3728 | - return this.prevObject || this.constructor(null); |
3729 | - }, |
3730 | - |
3731 | - // For internal use only. |
3732 | - // Behaves like an Array's method, not like a jQuery method. |
3733 | - push: push, |
3734 | - sort: [].sort, |
3735 | - splice: [].splice |
3736 | -}; |
3737 | - |
3738 | -// Give the init function the jQuery prototype for later instantiation |
3739 | -jQuery.fn.init.prototype = jQuery.fn; |
3740 | - |
3741 | -jQuery.extend = jQuery.fn.extend = function() { |
3742 | - var options, name, src, copy, copyIsArray, clone, |
3743 | - target = arguments[0] || {}, |
3744 | - i = 1, |
3745 | - length = arguments.length, |
3746 | - deep = false; |
3747 | - |
3748 | - // Handle a deep copy situation |
3749 | - if ( typeof target === "boolean" ) { |
3750 | - deep = target; |
3751 | - target = arguments[1] || {}; |
3752 | - // skip the boolean and the target |
3753 | - i = 2; |
3754 | - } |
3755 | - |
3756 | - // Handle case when target is a string or something (possible in deep copy) |
3757 | - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { |
3758 | - target = {}; |
3759 | - } |
3760 | - |
3761 | - // extend jQuery itself if only one argument is passed |
3762 | - if ( length === i ) { |
3763 | - target = this; |
3764 | - --i; |
3765 | - } |
3766 | - |
3767 | - for ( ; i < length; i++ ) { |
3768 | - // Only deal with non-null/undefined values |
3769 | - if ( (options = arguments[ i ]) != null ) { |
3770 | - // Extend the base object |
3771 | - for ( name in options ) { |
3772 | - src = target[ name ]; |
3773 | - copy = options[ name ]; |
3774 | - |
3775 | - // Prevent never-ending loop |
3776 | - if ( target === copy ) { |
3777 | - continue; |
3778 | - } |
3779 | - |
3780 | - // Recurse if we're merging plain objects or arrays |
3781 | - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { |
3782 | - if ( copyIsArray ) { |
3783 | - copyIsArray = false; |
3784 | - clone = src && jQuery.isArray(src) ? src : []; |
3785 | - |
3786 | - } else { |
3787 | - clone = src && jQuery.isPlainObject(src) ? src : {}; |
3788 | - } |
3789 | - |
3790 | - // Never move original objects, clone them |
3791 | - target[ name ] = jQuery.extend( deep, clone, copy ); |
3792 | - |
3793 | - // Don't bring in undefined values |
3794 | - } else if ( copy !== undefined ) { |
3795 | - target[ name ] = copy; |
3796 | - } |
3797 | - } |
3798 | - } |
3799 | - } |
3800 | - |
3801 | - // Return the modified object |
3802 | - return target; |
3803 | -}; |
3804 | - |
3805 | -jQuery.extend({ |
3806 | - noConflict: function( deep ) { |
3807 | - if ( window.$ === jQuery ) { |
3808 | - window.$ = _$; |
3809 | - } |
3810 | - |
3811 | - if ( deep && window.jQuery === jQuery ) { |
3812 | - window.jQuery = _jQuery; |
3813 | - } |
3814 | - |
3815 | - return jQuery; |
3816 | - }, |
3817 | - |
3818 | - // Is the DOM ready to be used? Set to true once it occurs. |
3819 | - isReady: false, |
3820 | - |
3821 | - // A counter to track how many items to wait for before |
3822 | - // the ready event fires. See #6781 |
3823 | - readyWait: 1, |
3824 | - |
3825 | - // Hold (or release) the ready event |
3826 | - holdReady: function( hold ) { |
3827 | - if ( hold ) { |
3828 | - jQuery.readyWait++; |
3829 | - } else { |
3830 | - jQuery.ready( true ); |
3831 | - } |
3832 | - }, |
3833 | - |
3834 | - // Handle when the DOM is ready |
3835 | - ready: function( wait ) { |
3836 | - // Either a released hold or an DOMready/load event and not yet ready |
3837 | - if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { |
3838 | - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). |
3839 | - if ( !document.body ) { |
3840 | - return setTimeout( jQuery.ready, 1 ); |
3841 | - } |
3842 | - |
3843 | - // Remember that the DOM is ready |
3844 | - jQuery.isReady = true; |
3845 | - |
3846 | - // If a normal DOM Ready event fired, decrement, and wait if need be |
3847 | - if ( wait !== true && --jQuery.readyWait > 0 ) { |
3848 | - return; |
3849 | - } |
3850 | - |
3851 | - // If there are functions bound, to execute |
3852 | - readyList.fireWith( document, [ jQuery ] ); |
3853 | - |
3854 | - // Trigger any bound ready events |
3855 | - if ( jQuery.fn.trigger ) { |
3856 | - jQuery( document ).trigger( "ready" ).off( "ready" ); |
3857 | - } |
3858 | - } |
3859 | - }, |
3860 | - |
3861 | - bindReady: function() { |
3862 | - if ( readyList ) { |
3863 | - return; |
3864 | - } |
3865 | - |
3866 | - readyList = jQuery.Callbacks( "once memory" ); |
3867 | - |
3868 | - // Catch cases where $(document).ready() is called after the |
3869 | - // browser event has already occurred. |
3870 | - if ( document.readyState === "complete" ) { |
3871 | - // Handle it asynchronously to allow scripts the opportunity to delay ready |
3872 | - return setTimeout( jQuery.ready, 1 ); |
3873 | - } |
3874 | - |
3875 | - // Mozilla, Opera and webkit nightlies currently support this event |
3876 | - if ( document.addEventListener ) { |
3877 | - // Use the handy event callback |
3878 | - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); |
3879 | - |
3880 | - // A fallback to window.onload, that will always work |
3881 | - window.addEventListener( "load", jQuery.ready, false ); |
3882 | - |
3883 | - // If IE event model is used |
3884 | - } else if ( document.attachEvent ) { |
3885 | - // ensure firing before onload, |
3886 | - // maybe late but safe also for iframes |
3887 | - document.attachEvent( "onreadystatechange", DOMContentLoaded ); |
3888 | - |
3889 | - // A fallback to window.onload, that will always work |
3890 | - window.attachEvent( "onload", jQuery.ready ); |
3891 | - |
3892 | - // If IE and not a frame |
3893 | - // continually check to see if the document is ready |
3894 | - var toplevel = false; |
3895 | - |
3896 | - try { |
3897 | - toplevel = window.frameElement == null; |
3898 | - } catch(e) {} |
3899 | - |
3900 | - if ( document.documentElement.doScroll && toplevel ) { |
3901 | - doScrollCheck(); |
3902 | - } |
3903 | - } |
3904 | - }, |
3905 | - |
3906 | - // See test/unit/core.js for details concerning isFunction. |
3907 | - // Since version 1.3, DOM methods and functions like alert |
3908 | - // aren't supported. They return false on IE (#2968). |
3909 | - isFunction: function( obj ) { |
3910 | - return jQuery.type(obj) === "function"; |
3911 | - }, |
3912 | - |
3913 | - isArray: Array.isArray || function( obj ) { |
3914 | - return jQuery.type(obj) === "array"; |
3915 | - }, |
3916 | - |
3917 | - isWindow: function( obj ) { |
3918 | - return obj != null && obj == obj.window; |
3919 | - }, |
3920 | - |
3921 | - isNumeric: function( obj ) { |
3922 | - return !isNaN( parseFloat(obj) ) && isFinite( obj ); |
3923 | - }, |
3924 | - |
3925 | - type: function( obj ) { |
3926 | - return obj == null ? |
3927 | - String( obj ) : |
3928 | - class2type[ toString.call(obj) ] || "object"; |
3929 | - }, |
3930 | - |
3931 | - isPlainObject: function( obj ) { |
3932 | - // Must be an Object. |
3933 | - // Because of IE, we also have to check the presence of the constructor property. |
3934 | - // Make sure that DOM nodes and window objects don't pass through, as well |
3935 | - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { |
3936 | - return false; |
3937 | - } |
3938 | - |
3939 | - try { |
3940 | - // Not own constructor property must be Object |
3941 | - if ( obj.constructor && |
3942 | - !hasOwn.call(obj, "constructor") && |
3943 | - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { |
3944 | - return false; |
3945 | - } |
3946 | - } catch ( e ) { |
3947 | - // IE8,9 Will throw exceptions on certain host objects #9897 |
3948 | - return false; |
3949 | - } |
3950 | - |
3951 | - // Own properties are enumerated firstly, so to speed up, |
3952 | - // if last one is own, then all properties are own. |
3953 | - |
3954 | - var key; |
3955 | - for ( key in obj ) {} |
3956 | - |
3957 | - return key === undefined || hasOwn.call( obj, key ); |
3958 | - }, |
3959 | - |
3960 | - isEmptyObject: function( obj ) { |
3961 | - for ( var name in obj ) { |
3962 | - return false; |
3963 | - } |
3964 | - return true; |
3965 | - }, |
3966 | - |
3967 | - error: function( msg ) { |
3968 | - throw new Error( msg ); |
3969 | - }, |
3970 | - |
3971 | - parseJSON: function( data ) { |
3972 | - if ( typeof data !== "string" || !data ) { |
3973 | - return null; |
3974 | - } |
3975 | - |
3976 | - // Make sure leading/trailing whitespace is removed (IE can't handle it) |
3977 | - data = jQuery.trim( data ); |
3978 | - |
3979 | - // Attempt to parse using the native JSON parser first |
3980 | - if ( window.JSON && window.JSON.parse ) { |
3981 | - return window.JSON.parse( data ); |
3982 | - } |
3983 | - |
3984 | - // Make sure the incoming data is actual JSON |
3985 | - // Logic borrowed from http://json.org/json2.js |
3986 | - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) |
3987 | - .replace( rvalidtokens, "]" ) |
3988 | - .replace( rvalidbraces, "")) ) { |
3989 | - |
3990 | - return ( new Function( "return " + data ) )(); |
3991 | - |
3992 | - } |
3993 | - jQuery.error( "Invalid JSON: " + data ); |
3994 | - }, |
3995 | - |
3996 | - // Cross-browser xml parsing |
3997 | - parseXML: function( data ) { |
3998 | - if ( typeof data !== "string" || !data ) { |
3999 | - return null; |
4000 | - } |
4001 | - var xml, tmp; |
4002 | - try { |
4003 | - if ( window.DOMParser ) { // Standard |
4004 | - tmp = new DOMParser(); |
4005 | - xml = tmp.parseFromString( data , "text/xml" ); |
4006 | - } else { // IE |
4007 | - xml = new ActiveXObject( "Microsoft.XMLDOM" ); |
4008 | - xml.async = "false"; |
4009 | - xml.loadXML( data ); |
4010 | - } |
4011 | - } catch( e ) { |
4012 | - xml = undefined; |
4013 | - } |
4014 | - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { |
4015 | - jQuery.error( "Invalid XML: " + data ); |
4016 | - } |
4017 | - return xml; |
4018 | - }, |
4019 | - |
4020 | - noop: function() {}, |
4021 | - |
4022 | - // Evaluates a script in a global context |
4023 | - // Workarounds based on findings by Jim Driscoll |
4024 | - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context |
4025 | - globalEval: function( data ) { |
4026 | - if ( data && rnotwhite.test( data ) ) { |
4027 | - // We use execScript on Internet Explorer |
4028 | - // We use an anonymous function so that context is window |
4029 | - // rather than jQuery in Firefox |
4030 | - ( window.execScript || function( data ) { |
4031 | - window[ "eval" ].call( window, data ); |
4032 | - } )( data ); |
4033 | - } |
4034 | - }, |
4035 | - |
4036 | - // Convert dashed to camelCase; used by the css and data modules |
4037 | - // Microsoft forgot to hump their vendor prefix (#9572) |
4038 | - camelCase: function( string ) { |
4039 | - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); |
4040 | - }, |
4041 | - |
4042 | - nodeName: function( elem, name ) { |
4043 | - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); |
4044 | - }, |
4045 | - |
4046 | - // args is for internal usage only |
4047 | - each: function( object, callback, args ) { |
4048 | - var name, i = 0, |
4049 | - length = object.length, |
4050 | - isObj = length === undefined || jQuery.isFunction( object ); |
4051 | - |
4052 | - if ( args ) { |
4053 | - if ( isObj ) { |
4054 | - for ( name in object ) { |
4055 | - if ( callback.apply( object[ name ], args ) === false ) { |
4056 | - break; |
4057 | - } |
4058 | - } |
4059 | - } else { |
4060 | - for ( ; i < length; ) { |
4061 | - if ( callback.apply( object[ i++ ], args ) === false ) { |
4062 | - break; |
4063 | - } |
4064 | - } |
4065 | - } |
4066 | - |
4067 | - // A special, fast, case for the most common use of each |
4068 | - } else { |
4069 | - if ( isObj ) { |
4070 | - for ( name in object ) { |
4071 | - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { |
4072 | - break; |
4073 | - } |
4074 | - } |
4075 | - } else { |
4076 | - for ( ; i < length; ) { |
4077 | - if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { |
4078 | - break; |
4079 | - } |
4080 | - } |
4081 | - } |
4082 | - } |
4083 | - |
4084 | - return object; |
4085 | - }, |
4086 | - |
4087 | - // Use native String.trim function wherever possible |
4088 | - trim: trim ? |
4089 | - function( text ) { |
4090 | - return text == null ? |
4091 | - "" : |
4092 | - trim.call( text ); |
4093 | - } : |
4094 | - |
4095 | - // Otherwise use our own trimming functionality |
4096 | - function( text ) { |
4097 | - return text == null ? |
4098 | - "" : |
4099 | - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); |
4100 | - }, |
4101 | - |
4102 | - // results is for internal usage only |
4103 | - makeArray: function( array, results ) { |
4104 | - var ret = results || []; |
4105 | - |
4106 | - if ( array != null ) { |
4107 | - // The window, strings (and functions) also have 'length' |
4108 | - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 |
4109 | - var type = jQuery.type( array ); |
4110 | - |
4111 | - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { |
4112 | - push.call( ret, array ); |
4113 | - } else { |
4114 | - jQuery.merge( ret, array ); |
4115 | - } |
4116 | - } |
4117 | - |
4118 | - return ret; |
4119 | - }, |
4120 | - |
4121 | - inArray: function( elem, array, i ) { |
4122 | - var len; |
4123 | - |
4124 | - if ( array ) { |
4125 | - if ( indexOf ) { |
4126 | - return indexOf.call( array, elem, i ); |
4127 | - } |
4128 | - |
4129 | - len = array.length; |
4130 | - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; |
4131 | - |
4132 | - for ( ; i < len; i++ ) { |
4133 | - // Skip accessing in sparse arrays |
4134 | - if ( i in array && array[ i ] === elem ) { |
4135 | - return i; |
4136 | - } |
4137 | - } |
4138 | - } |
4139 | - |
4140 | - return -1; |
4141 | - }, |
4142 | - |
4143 | - merge: function( first, second ) { |
4144 | - var i = first.length, |
4145 | - j = 0; |
4146 | - |
4147 | - if ( typeof second.length === "number" ) { |
4148 | - for ( var l = second.length; j < l; j++ ) { |
4149 | - first[ i++ ] = second[ j ]; |
4150 | - } |
4151 | - |
4152 | - } else { |
4153 | - while ( second[j] !== undefined ) { |
4154 | - first[ i++ ] = second[ j++ ]; |
4155 | - } |
4156 | - } |
4157 | - |
4158 | - first.length = i; |
4159 | - |
4160 | - return first; |
4161 | - }, |
4162 | - |
4163 | - grep: function( elems, callback, inv ) { |
4164 | - var ret = [], retVal; |
4165 | - inv = !!inv; |
4166 | - |
4167 | - // Go through the array, only saving the items |
4168 | - // that pass the validator function |
4169 | - for ( var i = 0, length = elems.length; i < length; i++ ) { |
4170 | - retVal = !!callback( elems[ i ], i ); |
4171 | - if ( inv !== retVal ) { |
4172 | - ret.push( elems[ i ] ); |
4173 | - } |
4174 | - } |
4175 | - |
4176 | - return ret; |
4177 | - }, |
4178 | - |
4179 | - // arg is for internal usage only |
4180 | - map: function( elems, callback, arg ) { |
4181 | - var value, key, ret = [], |
4182 | - i = 0, |
4183 | - length = elems.length, |
4184 | - // jquery objects are treated as arrays |
4185 | - isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; |
4186 | - |
4187 | - // Go through the array, translating each of the items to their |
4188 | - if ( isArray ) { |
4189 | - for ( ; i < length; i++ ) { |
4190 | - value = callback( elems[ i ], i, arg ); |
4191 | - |
4192 | - if ( value != null ) { |
4193 | - ret[ ret.length ] = value; |
4194 | - } |
4195 | - } |
4196 | - |
4197 | - // Go through every key on the object, |
4198 | - } else { |
4199 | - for ( key in elems ) { |
4200 | - value = callback( elems[ key ], key, arg ); |
4201 | - |
4202 | - if ( value != null ) { |
4203 | - ret[ ret.length ] = value; |
4204 | - } |
4205 | - } |
4206 | - } |
4207 | - |
4208 | - // Flatten any nested arrays |
4209 | - return ret.concat.apply( [], ret ); |
4210 | - }, |
4211 | - |
4212 | - // A global GUID counter for objects |
4213 | - guid: 1, |
4214 | - |
4215 | - // Bind a function to a context, optionally partially applying any |
4216 | - // arguments. |
4217 | - proxy: function( fn, context ) { |
4218 | - if ( typeof context === "string" ) { |
4219 | - var tmp = fn[ context ]; |
4220 | - context = fn; |
4221 | - fn = tmp; |
4222 | - } |
4223 | - |
4224 | - // Quick check to determine if target is callable, in the spec |
4225 | - // this throws a TypeError, but we will just return undefined. |
4226 | - if ( !jQuery.isFunction( fn ) ) { |
4227 | - return undefined; |
4228 | - } |
4229 | - |
4230 | - // Simulated bind |
4231 | - var args = slice.call( arguments, 2 ), |
4232 | - proxy = function() { |
4233 | - return fn.apply( context, args.concat( slice.call( arguments ) ) ); |
4234 | - }; |
4235 | - |
4236 | - // Set the guid of unique handler to the same of original handler, so it can be removed |
4237 | - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; |
4238 | - |
4239 | - return proxy; |
4240 | - }, |
4241 | - |
4242 | - // Mutifunctional method to get and set values to a collection |
4243 | - // The value/s can optionally be executed if it's a function |
4244 | - access: function( elems, fn, key, value, chainable, emptyGet, pass ) { |
4245 | - var exec, |
4246 | - bulk = key == null, |
4247 | - i = 0, |
4248 | - length = elems.length; |
4249 | - |
4250 | - // Sets many values |
4251 | - if ( key && typeof key === "object" ) { |
4252 | - for ( i in key ) { |
4253 | - jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); |
4254 | - } |
4255 | - chainable = 1; |
4256 | - |
4257 | - // Sets one value |
4258 | - } else if ( value !== undefined ) { |
4259 | - // Optionally, function values get executed if exec is true |
4260 | - exec = pass === undefined && jQuery.isFunction( value ); |
4261 | - |
4262 | - if ( bulk ) { |
4263 | - // Bulk operations only iterate when executing function values |
4264 | - if ( exec ) { |
4265 | - exec = fn; |
4266 | - fn = function( elem, key, value ) { |
4267 | - return exec.call( jQuery( elem ), value ); |
4268 | - }; |
4269 | - |
4270 | - // Otherwise they run against the entire set |
4271 | - } else { |
4272 | - fn.call( elems, value ); |
4273 | - fn = null; |
4274 | - } |
4275 | - } |
4276 | - |
4277 | - if ( fn ) { |
4278 | - for (; i < length; i++ ) { |
4279 | - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); |
4280 | - } |
4281 | - } |
4282 | - |
4283 | - chainable = 1; |
4284 | - } |
4285 | - |
4286 | - return chainable ? |
4287 | - elems : |
4288 | - |
4289 | - // Gets |
4290 | - bulk ? |
4291 | - fn.call( elems ) : |
4292 | - length ? fn( elems[0], key ) : emptyGet; |
4293 | - }, |
4294 | - |
4295 | - now: function() { |
4296 | - return ( new Date() ).getTime(); |
4297 | - }, |
4298 | - |
4299 | - // Use of jQuery.browser is frowned upon. |
4300 | - // More details: http://docs.jquery.com/Utilities/jQuery.browser |
4301 | - uaMatch: function( ua ) { |
4302 | - ua = ua.toLowerCase(); |
4303 | - |
4304 | - var match = rwebkit.exec( ua ) || |
4305 | - ropera.exec( ua ) || |
4306 | - rmsie.exec( ua ) || |
4307 | - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || |
4308 | - []; |
4309 | - |
4310 | - return { browser: match[1] || "", version: match[2] || "0" }; |
4311 | - }, |
4312 | - |
4313 | - sub: function() { |
4314 | - function jQuerySub( selector, context ) { |
4315 | - return new jQuerySub.fn.init( selector, context ); |
4316 | - } |
4317 | - jQuery.extend( true, jQuerySub, this ); |
4318 | - jQuerySub.superclass = this; |
4319 | - jQuerySub.fn = jQuerySub.prototype = this(); |
4320 | - jQuerySub.fn.constructor = jQuerySub; |
4321 | - jQuerySub.sub = this.sub; |
4322 | - jQuerySub.fn.init = function init( selector, context ) { |
4323 | - if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { |
4324 | - context = jQuerySub( context ); |
4325 | - } |
4326 | - |
4327 | - return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); |
4328 | - }; |
4329 | - jQuerySub.fn.init.prototype = jQuerySub.fn; |
4330 | - var rootjQuerySub = jQuerySub(document); |
4331 | - return jQuerySub; |
4332 | - }, |
4333 | - |
4334 | - browser: {} |
4335 | -}); |
4336 | - |
4337 | -// Populate the class2type map |
4338 | -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { |
4339 | - class2type[ "[object " + name + "]" ] = name.toLowerCase(); |
4340 | -}); |
4341 | - |
4342 | -browserMatch = jQuery.uaMatch( userAgent ); |
4343 | -if ( browserMatch.browser ) { |
4344 | - jQuery.browser[ browserMatch.browser ] = true; |
4345 | - jQuery.browser.version = browserMatch.version; |
4346 | -} |
4347 | - |
4348 | -// Deprecated, use jQuery.browser.webkit instead |
4349 | -if ( jQuery.browser.webkit ) { |
4350 | - jQuery.browser.safari = true; |
4351 | -} |
4352 | - |
4353 | -// IE doesn't match non-breaking spaces with \s |
4354 | -if ( rnotwhite.test( "\xA0" ) ) { |
4355 | - trimLeft = /^[\s\xA0]+/; |
4356 | - trimRight = /[\s\xA0]+$/; |
4357 | -} |
4358 | - |
4359 | -// All jQuery objects should point back to these |
4360 | -rootjQuery = jQuery(document); |
4361 | - |
4362 | -// Cleanup functions for the document ready method |
4363 | -if ( document.addEventListener ) { |
4364 | - DOMContentLoaded = function() { |
4365 | - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); |
4366 | - jQuery.ready(); |
4367 | - }; |
4368 | - |
4369 | -} else if ( document.attachEvent ) { |
4370 | - DOMContentLoaded = function() { |
4371 | - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). |
4372 | - if ( document.readyState === "complete" ) { |
4373 | - document.detachEvent( "onreadystatechange", DOMContentLoaded ); |
4374 | - jQuery.ready(); |
4375 | - } |
4376 | - }; |
4377 | -} |
4378 | - |
4379 | -// The DOM ready check for Internet Explorer |
4380 | -function doScrollCheck() { |
4381 | - if ( jQuery.isReady ) { |
4382 | - return; |
4383 | - } |
4384 | - |
4385 | - try { |
4386 | - // If IE is used, use the trick by Diego Perini |
4387 | - // http://javascript.nwbox.com/IEContentLoaded/ |
4388 | - document.documentElement.doScroll("left"); |
4389 | - } catch(e) { |
4390 | - setTimeout( doScrollCheck, 1 ); |
4391 | - return; |
4392 | - } |
4393 | - |
4394 | - // and execute any waiting functions |
4395 | - jQuery.ready(); |
4396 | -} |
4397 | - |
4398 | -return jQuery; |
4399 | - |
4400 | -})(); |
4401 | - |
4402 | - |
4403 | -// String to Object flags format cache |
4404 | -var flagsCache = {}; |
4405 | - |
4406 | -// Convert String-formatted flags into Object-formatted ones and store in cache |
4407 | -function createFlags( flags ) { |
4408 | - var object = flagsCache[ flags ] = {}, |
4409 | - i, length; |
4410 | - flags = flags.split( /\s+/ ); |
4411 | - for ( i = 0, length = flags.length; i < length; i++ ) { |
4412 | - object[ flags[i] ] = true; |
4413 | - } |
4414 | - return object; |
4415 | -} |
4416 | - |
4417 | -/* |
4418 | - * Create a callback list using the following parameters: |
4419 | - * |
4420 | - * flags: an optional list of space-separated flags that will change how |
4421 | - * the callback list behaves |
4422 | - * |
4423 | - * By default a callback list will act like an event callback list and can be |
4424 | - * "fired" multiple times. |
4425 | - * |
4426 | - * Possible flags: |
4427 | - * |
4428 | - * once: will ensure the callback list can only be fired once (like a Deferred) |
4429 | - * |
4430 | - * memory: will keep track of previous values and will call any callback added |
4431 | - * after the list has been fired right away with the latest "memorized" |
4432 | - * values (like a Deferred) |
4433 | - * |
4434 | - * unique: will ensure a callback can only be added once (no duplicate in the list) |
4435 | - * |
4436 | - * stopOnFalse: interrupt callings when a callback returns false |
4437 | - * |
4438 | - */ |
4439 | -jQuery.Callbacks = function( flags ) { |
4440 | - |
4441 | - // Convert flags from String-formatted to Object-formatted |
4442 | - // (we check in cache first) |
4443 | - flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; |
4444 | - |
4445 | - var // Actual callback list |
4446 | - list = [], |
4447 | - // Stack of fire calls for repeatable lists |
4448 | - stack = [], |
4449 | - // Last fire value (for non-forgettable lists) |
4450 | - memory, |
4451 | - // Flag to know if list was already fired |
4452 | - fired, |
4453 | - // Flag to know if list is currently firing |
4454 | - firing, |
4455 | - // First callback to fire (used internally by add and fireWith) |
4456 | - firingStart, |
4457 | - // End of the loop when firing |
4458 | - firingLength, |
4459 | - // Index of currently firing callback (modified by remove if needed) |
4460 | - firingIndex, |
4461 | - // Add one or several callbacks to the list |
4462 | - add = function( args ) { |
4463 | - var i, |
4464 | - length, |
4465 | - elem, |
4466 | - type, |
4467 | - actual; |
4468 | - for ( i = 0, length = args.length; i < length; i++ ) { |
4469 | - elem = args[ i ]; |
4470 | - type = jQuery.type( elem ); |
4471 | - if ( type === "array" ) { |
4472 | - // Inspect recursively |
4473 | - add( elem ); |
4474 | - } else if ( type === "function" ) { |
4475 | - // Add if not in unique mode and callback is not in |
4476 | - if ( !flags.unique || !self.has( elem ) ) { |
4477 | - list.push( elem ); |
4478 | - } |
4479 | - } |
4480 | - } |
4481 | - }, |
4482 | - // Fire callbacks |
4483 | - fire = function( context, args ) { |
4484 | - args = args || []; |
4485 | - memory = !flags.memory || [ context, args ]; |
4486 | - fired = true; |
4487 | - firing = true; |
4488 | - firingIndex = firingStart || 0; |
4489 | - firingStart = 0; |
4490 | - firingLength = list.length; |
4491 | - for ( ; list && firingIndex < firingLength; firingIndex++ ) { |
4492 | - if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { |
4493 | - memory = true; // Mark as halted |
4494 | - break; |
4495 | - } |
4496 | - } |
4497 | - firing = false; |
4498 | - if ( list ) { |
4499 | - if ( !flags.once ) { |
4500 | - if ( stack && stack.length ) { |
4501 | - memory = stack.shift(); |
4502 | - self.fireWith( memory[ 0 ], memory[ 1 ] ); |
4503 | - } |
4504 | - } else if ( memory === true ) { |
4505 | - self.disable(); |
4506 | - } else { |
4507 | - list = []; |
4508 | - } |
4509 | - } |
4510 | - }, |
4511 | - // Actual Callbacks object |
4512 | - self = { |
4513 | - // Add a callback or a collection of callbacks to the list |
4514 | - add: function() { |
4515 | - if ( list ) { |
4516 | - var length = list.length; |
4517 | - add( arguments ); |
4518 | - // Do we need to add the callbacks to the |
4519 | - // current firing batch? |
4520 | - if ( firing ) { |
4521 | - firingLength = list.length; |
4522 | - // With memory, if we're not firing then |
4523 | - // we should call right away, unless previous |
4524 | - // firing was halted (stopOnFalse) |
4525 | - } else if ( memory && memory !== true ) { |
4526 | - firingStart = length; |
4527 | - fire( memory[ 0 ], memory[ 1 ] ); |
4528 | - } |
4529 | - } |
4530 | - return this; |
4531 | - }, |
4532 | - // Remove a callback from the list |
4533 | - remove: function() { |
4534 | - if ( list ) { |
4535 | - var args = arguments, |
4536 | - argIndex = 0, |
4537 | - argLength = args.length; |
4538 | - for ( ; argIndex < argLength ; argIndex++ ) { |
4539 | - for ( var i = 0; i < list.length; i++ ) { |
4540 | - if ( args[ argIndex ] === list[ i ] ) { |
4541 | - // Handle firingIndex and firingLength |
4542 | - if ( firing ) { |
4543 | - if ( i <= firingLength ) { |
4544 | - firingLength--; |
4545 | - if ( i <= firingIndex ) { |
4546 | - firingIndex--; |
4547 | - } |
4548 | - } |
4549 | - } |
4550 | - // Remove the element |
4551 | - list.splice( i--, 1 ); |
4552 | - // If we have some unicity property then |
4553 | - // we only need to do this once |
4554 | - if ( flags.unique ) { |
4555 | - break; |
4556 | - } |
4557 | - } |
4558 | - } |
4559 | - } |
4560 | - } |
4561 | - return this; |
4562 | - }, |
4563 | - // Control if a given callback is in the list |
4564 | - has: function( fn ) { |
4565 | - if ( list ) { |
4566 | - var i = 0, |
4567 | - length = list.length; |
4568 | - for ( ; i < length; i++ ) { |
4569 | - if ( fn === list[ i ] ) { |
4570 | - return true; |
4571 | - } |
4572 | - } |
4573 | - } |
4574 | - return false; |
4575 | - }, |
4576 | - // Remove all callbacks from the list |
4577 | - empty: function() { |
4578 | - list = []; |
4579 | - return this; |
4580 | - }, |
4581 | - // Have the list do nothing anymore |
4582 | - disable: function() { |
4583 | - list = stack = memory = undefined; |
4584 | - return this; |
4585 | - }, |
4586 | - // Is it disabled? |
4587 | - disabled: function() { |
4588 | - return !list; |
4589 | - }, |
4590 | - // Lock the list in its current state |
4591 | - lock: function() { |
4592 | - stack = undefined; |
4593 | - if ( !memory || memory === true ) { |
4594 | - self.disable(); |
4595 | - } |
4596 | - return this; |
4597 | - }, |
4598 | - // Is it locked? |
4599 | - locked: function() { |
4600 | - return !stack; |
4601 | - }, |
4602 | - // Call all callbacks with the given context and arguments |
4603 | - fireWith: function( context, args ) { |
4604 | - if ( stack ) { |
4605 | - if ( firing ) { |
4606 | - if ( !flags.once ) { |
4607 | - stack.push( [ context, args ] ); |
4608 | - } |
4609 | - } else if ( !( flags.once && memory ) ) { |
4610 | - fire( context, args ); |
4611 | - } |
4612 | - } |
4613 | - return this; |
4614 | - }, |
4615 | - // Call all the callbacks with the given arguments |
4616 | - fire: function() { |
4617 | - self.fireWith( this, arguments ); |
4618 | - return this; |
4619 | - }, |
4620 | - // To know if the callbacks have already been called at least once |
4621 | - fired: function() { |
4622 | - return !!fired; |
4623 | - } |
4624 | - }; |
4625 | - |
4626 | - return self; |
4627 | -}; |
4628 | - |
4629 | - |
4630 | - |
4631 | - |
4632 | -var // Static reference to slice |
4633 | - sliceDeferred = [].slice; |
4634 | - |
4635 | -jQuery.extend({ |
4636 | - |
4637 | - Deferred: function( func ) { |
4638 | - var doneList = jQuery.Callbacks( "once memory" ), |
4639 | - failList = jQuery.Callbacks( "once memory" ), |
4640 | - progressList = jQuery.Callbacks( "memory" ), |
4641 | - state = "pending", |
4642 | - lists = { |
4643 | - resolve: doneList, |
4644 | - reject: failList, |
4645 | - notify: progressList |
4646 | - }, |
4647 | - promise = { |
4648 | - done: doneList.add, |
4649 | - fail: failList.add, |
4650 | - progress: progressList.add, |
4651 | - |
4652 | - state: function() { |
4653 | - return state; |
4654 | - }, |
4655 | - |
4656 | - // Deprecated |
4657 | - isResolved: doneList.fired, |
4658 | - isRejected: failList.fired, |
4659 | - |
4660 | - then: function( doneCallbacks, failCallbacks, progressCallbacks ) { |
4661 | - deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); |
4662 | - return this; |
4663 | - }, |
4664 | - always: function() { |
4665 | - deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); |
4666 | - return this; |
4667 | - }, |
4668 | - pipe: function( fnDone, fnFail, fnProgress ) { |
4669 | - return jQuery.Deferred(function( newDefer ) { |
4670 | - jQuery.each( { |
4671 | - done: [ fnDone, "resolve" ], |
4672 | - fail: [ fnFail, "reject" ], |
4673 | - progress: [ fnProgress, "notify" ] |
4674 | - }, function( handler, data ) { |
4675 | - var fn = data[ 0 ], |
4676 | - action = data[ 1 ], |
4677 | - returned; |
4678 | - if ( jQuery.isFunction( fn ) ) { |
4679 | - deferred[ handler ](function() { |
4680 | - returned = fn.apply( this, arguments ); |
4681 | - if ( returned && jQuery.isFunction( returned.promise ) ) { |
4682 | - returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); |
4683 | - } else { |
4684 | - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); |
4685 | - } |
4686 | - }); |
4687 | - } else { |
4688 | - deferred[ handler ]( newDefer[ action ] ); |
4689 | - } |
4690 | - }); |
4691 | - }).promise(); |
4692 | - }, |
4693 | - // Get a promise for this deferred |
4694 | - // If obj is provided, the promise aspect is added to the object |
4695 | - promise: function( obj ) { |
4696 | - if ( obj == null ) { |
4697 | - obj = promise; |
4698 | - } else { |
4699 | - for ( var key in promise ) { |
4700 | - obj[ key ] = promise[ key ]; |
4701 | - } |
4702 | - } |
4703 | - return obj; |
4704 | - } |
4705 | - }, |
4706 | - deferred = promise.promise({}), |
4707 | - key; |
4708 | - |
4709 | - for ( key in lists ) { |
4710 | - deferred[ key ] = lists[ key ].fire; |
4711 | - deferred[ key + "With" ] = lists[ key ].fireWith; |
4712 | - } |
4713 | - |
4714 | - // Handle state |
4715 | - deferred.done( function() { |
4716 | - state = "resolved"; |
4717 | - }, failList.disable, progressList.lock ).fail( function() { |
4718 | - state = "rejected"; |
4719 | - }, doneList.disable, progressList.lock ); |
4720 | - |
4721 | - // Call given func if any |
4722 | - if ( func ) { |
4723 | - func.call( deferred, deferred ); |
4724 | - } |
4725 | - |
4726 | - // All done! |
4727 | - return deferred; |
4728 | - }, |
4729 | - |
4730 | - // Deferred helper |
4731 | - when: function( firstParam ) { |
4732 | - var args = sliceDeferred.call( arguments, 0 ), |
4733 | - i = 0, |
4734 | - length = args.length, |
4735 | - pValues = new Array( length ), |
4736 | - count = length, |
4737 | - pCount = length, |
4738 | - deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? |
4739 | - firstParam : |
4740 | - jQuery.Deferred(), |
4741 | - promise = deferred.promise(); |
4742 | - function resolveFunc( i ) { |
4743 | - return function( value ) { |
4744 | - args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; |
4745 | - if ( !( --count ) ) { |
4746 | - deferred.resolveWith( deferred, args ); |
4747 | - } |
4748 | - }; |
4749 | - } |
4750 | - function progressFunc( i ) { |
4751 | - return function( value ) { |
4752 | - pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; |
4753 | - deferred.notifyWith( promise, pValues ); |
4754 | - }; |
4755 | - } |
4756 | - if ( length > 1 ) { |
4757 | - for ( ; i < length; i++ ) { |
4758 | - if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { |
4759 | - args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); |
4760 | - } else { |
4761 | - --count; |
4762 | - } |
4763 | - } |
4764 | - if ( !count ) { |
4765 | - deferred.resolveWith( deferred, args ); |
4766 | - } |
4767 | - } else if ( deferred !== firstParam ) { |
4768 | - deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); |
4769 | - } |
4770 | - return promise; |
4771 | - } |
4772 | -}); |
4773 | - |
4774 | - |
4775 | - |
4776 | - |
4777 | -jQuery.support = (function() { |
4778 | - |
4779 | - var support, |
4780 | - all, |
4781 | - a, |
4782 | - select, |
4783 | - opt, |
4784 | - input, |
4785 | - fragment, |
4786 | - tds, |
4787 | - events, |
4788 | - eventName, |
4789 | - i, |
4790 | - isSupported, |
4791 | - div = document.createElement( "div" ), |
4792 | - documentElement = document.documentElement; |
4793 | - |
4794 | - // Preliminary tests |
4795 | - div.setAttribute("className", "t"); |
4796 | - div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; |
4797 | - |
4798 | - all = div.getElementsByTagName( "*" ); |
4799 | - a = div.getElementsByTagName( "a" )[ 0 ]; |
4800 | - |
4801 | - // Can't get basic test support |
4802 | - if ( !all || !all.length || !a ) { |
4803 | - return {}; |
4804 | - } |
4805 | - |
4806 | - // First batch of supports tests |
4807 | - select = document.createElement( "select" ); |
4808 | - opt = select.appendChild( document.createElement("option") ); |
4809 | - input = div.getElementsByTagName( "input" )[ 0 ]; |
4810 | - |
4811 | - support = { |
4812 | - // IE strips leading whitespace when .innerHTML is used |
4813 | - leadingWhitespace: ( div.firstChild.nodeType === 3 ), |
4814 | - |
4815 | - // Make sure that tbody elements aren't automatically inserted |
4816 | - // IE will insert them into empty tables |
4817 | - tbody: !div.getElementsByTagName("tbody").length, |
4818 | - |
4819 | - // Make sure that link elements get serialized correctly by innerHTML |
4820 | - // This requires a wrapper element in IE |
4821 | - htmlSerialize: !!div.getElementsByTagName("link").length, |
4822 | - |
4823 | - // Get the style information from getAttribute |
4824 | - // (IE uses .cssText instead) |
4825 | - style: /top/.test( a.getAttribute("style") ), |
4826 | - |
4827 | - // Make sure that URLs aren't manipulated |
4828 | - // (IE normalizes it by default) |
4829 | - hrefNormalized: ( a.getAttribute("href") === "/a" ), |
4830 | - |
4831 | - // Make sure that element opacity exists |
4832 | - // (IE uses filter instead) |
4833 | - // Use a regex to work around a WebKit issue. See #5145 |
4834 | - opacity: /^0.55/.test( a.style.opacity ), |
4835 | - |
4836 | - // Verify style float existence |
4837 | - // (IE uses styleFloat instead of cssFloat) |
4838 | - cssFloat: !!a.style.cssFloat, |
4839 | - |
4840 | - // Make sure that if no value is specified for a checkbox |
4841 | - // that it defaults to "on". |
4842 | - // (WebKit defaults to "" instead) |
4843 | - checkOn: ( input.value === "on" ), |
4844 | - |
4845 | - // Make sure that a selected-by-default option has a working selected property. |
4846 | - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) |
4847 | - optSelected: opt.selected, |
4848 | - |
4849 | - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) |
4850 | - getSetAttribute: div.className !== "t", |
4851 | - |
4852 | - // Tests for enctype support on a form(#6743) |
4853 | - enctype: !!document.createElement("form").enctype, |
4854 | - |
4855 | - // Makes sure cloning an html5 element does not cause problems |
4856 | - // Where outerHTML is undefined, this still works |
4857 | - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", |
4858 | - |
4859 | - // Will be defined later |
4860 | - submitBubbles: true, |
4861 | - changeBubbles: true, |
4862 | - focusinBubbles: false, |
4863 | - deleteExpando: true, |
4864 | - noCloneEvent: true, |
4865 | - inlineBlockNeedsLayout: false, |
4866 | - shrinkWrapBlocks: false, |
4867 | - reliableMarginRight: true, |
4868 | - pixelMargin: true |
4869 | - }; |
4870 | - |
4871 | - // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead |
4872 | - jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); |
4873 | - |
4874 | - // Make sure checked status is properly cloned |
4875 | - input.checked = true; |
4876 | - support.noCloneChecked = input.cloneNode( true ).checked; |
4877 | - |
4878 | - // Make sure that the options inside disabled selects aren't marked as disabled |
4879 | - // (WebKit marks them as disabled) |
4880 | - select.disabled = true; |
4881 | - support.optDisabled = !opt.disabled; |
4882 | - |
4883 | - // Test to see if it's possible to delete an expando from an element |
4884 | - // Fails in Internet Explorer |
4885 | - try { |
4886 | - delete div.test; |
4887 | - } catch( e ) { |
4888 | - support.deleteExpando = false; |
4889 | - } |
4890 | - |
4891 | - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { |
4892 | - div.attachEvent( "onclick", function() { |
4893 | - // Cloning a node shouldn't copy over any |
4894 | - // bound event handlers (IE does this) |
4895 | - support.noCloneEvent = false; |
4896 | - }); |
4897 | - div.cloneNode( true ).fireEvent( "onclick" ); |
4898 | - } |
4899 | - |
4900 | - // Check if a radio maintains its value |
4901 | - // after being appended to the DOM |
4902 | - input = document.createElement("input"); |
4903 | - input.value = "t"; |
4904 | - input.setAttribute("type", "radio"); |
4905 | - support.radioValue = input.value === "t"; |
4906 | - |
4907 | - input.setAttribute("checked", "checked"); |
4908 | - |
4909 | - // #11217 - WebKit loses check when the name is after the checked attribute |
4910 | - input.setAttribute( "name", "t" ); |
4911 | - |
4912 | - div.appendChild( input ); |
4913 | - fragment = document.createDocumentFragment(); |
4914 | - fragment.appendChild( div.lastChild ); |
4915 | - |
4916 | - // WebKit doesn't clone checked state correctly in fragments |
4917 | - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; |
4918 | - |
4919 | - // Check if a disconnected checkbox will retain its checked |
4920 | - // value of true after appended to the DOM (IE6/7) |
4921 | - support.appendChecked = input.checked; |
4922 | - |
4923 | - fragment.removeChild( input ); |
4924 | - fragment.appendChild( div ); |
4925 | - |
4926 | - // Technique from Juriy Zaytsev |
4927 | - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ |
4928 | - // We only care about the case where non-standard event systems |
4929 | - // are used, namely in IE. Short-circuiting here helps us to |
4930 | - // avoid an eval call (in setAttribute) which can cause CSP |
4931 | - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP |
4932 | - if ( div.attachEvent ) { |
4933 | - for ( i in { |
4934 | - submit: 1, |
4935 | - change: 1, |
4936 | - focusin: 1 |
4937 | - }) { |
4938 | - eventName = "on" + i; |
4939 | - isSupported = ( eventName in div ); |
4940 | - if ( !isSupported ) { |
4941 | - div.setAttribute( eventName, "return;" ); |
4942 | - isSupported = ( typeof div[ eventName ] === "function" ); |
4943 | - } |
4944 | - support[ i + "Bubbles" ] = isSupported; |
4945 | - } |
4946 | - } |
4947 | - |
4948 | - fragment.removeChild( div ); |
4949 | - |
4950 | - // Null elements to avoid leaks in IE |
4951 | - fragment = select = opt = div = input = null; |
4952 | - |
4953 | - // Run tests that need a body at doc ready |
4954 | - jQuery(function() { |
4955 | - var container, outer, inner, table, td, offsetSupport, |
4956 | - marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, |
4957 | - paddingMarginBorderVisibility, paddingMarginBorder, |
4958 | - body = document.getElementsByTagName("body")[0]; |
4959 | - |
4960 | - if ( !body ) { |
4961 | - // Return for frameset docs that don't have a body |
4962 | - return; |
4963 | - } |
4964 | - |
4965 | - conMarginTop = 1; |
4966 | - paddingMarginBorder = "padding:0;margin:0;border:"; |
4967 | - positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; |
4968 | - paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; |
4969 | - style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; |
4970 | - html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" + |
4971 | - "<table " + style + "' cellpadding='0' cellspacing='0'>" + |
4972 | - "<tr><td></td></tr></table>"; |
4973 | - |
4974 | - container = document.createElement("div"); |
4975 | - container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; |
4976 | - body.insertBefore( container, body.firstChild ); |
4977 | - |
4978 | - // Construct the test element |
4979 | - div = document.createElement("div"); |
4980 | - container.appendChild( div ); |
4981 | - |
4982 | - // Check if table cells still have offsetWidth/Height when they are set |
4983 | - // to display:none and there are still other visible table cells in a |
4984 | - // table row; if so, offsetWidth/Height are not reliable for use when |
4985 | - // determining if an element has been hidden directly using |
4986 | - // display:none (it is still safe to use offsets if a parent element is |
4987 | - // hidden; don safety goggles and see bug #4512 for more information). |
4988 | - // (only IE 8 fails this test) |
4989 | - div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>"; |
4990 | - tds = div.getElementsByTagName( "td" ); |
4991 | - isSupported = ( tds[ 0 ].offsetHeight === 0 ); |
4992 | - |
4993 | - tds[ 0 ].style.display = ""; |
4994 | - tds[ 1 ].style.display = "none"; |
4995 | - |
4996 | - // Check if empty table cells still have offsetWidth/Height |
4997 | - // (IE <= 8 fail this test) |
4998 | - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); |
4999 | - |
5000 | - // Check if div with explicit width and no margin-right incorrectly |
So, some thoughts around removing the web remote from OpenLP:
- The remote plugin should handle the download and install of the web remote
- The downloadable web remote should be versioned
- OpenLP needs to be aware of the versions
- Users should be able to check if there's a new version of the web remote
- Users should be able to download an updated version of the web remote
- Technically, the remote plugin should implement the old API
- We should really make the new API RESTful, which means returning 400,
500 and 200 status codes and not {"result": {"success": true}}
I'm going to write up more on the wiki about how I think the new API methods should look.
Also, see inline comments.