Merge lp:~trb143/openlp/allow_local_stage_views into lp:openlp

Proposed by Tim Bentley
Status: Merged
Merged at revision: 2572
Proposed branch: lp:~trb143/openlp/allow_local_stage_views
Merge into: lp:openlp
Diff against target: 193 lines (+92/-14)
3 files modified
openlp/plugins/remotes/lib/httprouter.py (+55/-13)
openlp/plugins/remotes/lib/httpserver.py (+1/-1)
tests/functional/openlp_plugins/remotes/test_router.py (+36/-0)
To merge this branch: bzr merge lp:~trb143/openlp/allow_local_stage_views
Reviewer Review Type Date Requested Status
Raoul Snyman Approve
Tomas Groth Pending
Review via email: mp+279678@code.launchpad.net

This proposal supersedes a proposal from 2015-11-08.

Description of the change

Allow Custom stage views.

Under the config directory create a stages directory.
Within that create user directories i.e trb143 then stage/trb143 will server the stage.html file from within that directory.
If you wish to change the css file amend the files/stage.css in the html file to stages/trb143/yourcss.css.
Create a yourcss.css file and it will get loaded.

lp:~trb143/openlp/allow_local_stage_views (revision 2575)
[SUCCESS] https//ci.openlp.io/job/Branch-01-Pull/1187/
[SUCCESS] https//ci.openlp.io/job/Branch-02-Functional-Tests/1110/
[SUCCESS] https//ci.openlp.io/job/Branch-03-Interface-Tests/1051/
[SUCCESS] https//ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/898/
[SUCCESS] https//ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/494/
[SUCCESS] https//ci.openlp.io/job/Branch-05a-Code_Analysis/610/
[SUCCESS] https//ci.openlp.io/job/Branch-05b-Test_Coverage/481/

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Why not just have a single file called "remote/stage/custom.css" or "remote/stage.css" ?

review: Needs Information
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Do you mean we have

remotes
---Certificate files
---trb143
------stage,html
------stage
-----------stage.css
---superfly
------stage,html
------stage
-----------stage.css

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

I don't understand why we need the username files. This is already in the users home directory. Why not just use stage.css?

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

This is to allow custom stage views per user. All the current files in inside core and we have requests to allow them to be changeable.

This would allow the html and the css to be customised for a user and have any number of customisations.

The current files are served from within the plugin so cannot be changed (without a hack).

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

I'm convinced :)

review: Approve
Revision history for this message
Charles Crossan (crossan007) wrote : Posted in a previous version of this proposal

The sound of distant cheering is heard by the whole group.
On Nov 29, 2015 8:21 AM, "Tomas Groth" <email address hidden> wrote:

> Review: Approve
>
> I'm convinced :)
> --
>
> https://code.launchpad.net/~trb143/openlp/allow_local_stage_views/+merge/276940
> You are subscribed to branch lp:openlp.
>

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

But there's only ever one user, so it makes no sense.

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

I understand custom stylesheets, and I'm totally for it, but there's only ever one user, so why the username?

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

It's an example it could be

remotes
---Certificate files
---drums
------stage,html
------stage
-----------stage.css
---bass
------stage,html
------stage
-----------stage.css
---worshipleader
------stage,html
------stage
-----------stage.css

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

So when would each custom style be used? We only support one at a time at the moment.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

We support two in core.
/Stage is a stage view
/Main is a picture view.

These can be used by any number of people.
This change allows customisations of view also we have people who want to change the look and feel and this is not possible with the current set up.

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

OK, I kinda get it. I still don't really see the point of multiple directories. I don't think the majority of people will ever use more than just one.

You have a method called "temp_path" (ugh, bad name) in your method signature, but you never actually use it in your method?

    def stages(self, temp_path, file_name)

Also, no print statements!

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/plugins/remotes/lib/httprouter.py'
2--- openlp/plugins/remotes/lib/httprouter.py 2015-10-22 16:23:40 +0000
3+++ openlp/plugins/remotes/lib/httprouter.py 2015-12-05 12:49:02 +0000
4@@ -150,6 +150,7 @@
5 self.routes = [
6 ('^/$', {'function': self.serve_file, 'secure': False}),
7 ('^/(stage)$', {'function': self.serve_file, 'secure': False}),
8+ ('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}),
9 ('^/(main)$', {'function': self.serve_file, 'secure': False}),
10 (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
11 (r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
12@@ -170,6 +171,7 @@
13 self.settings_section = 'remotes'
14 self.translate()
15 self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
16+ self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
17
18 def do_post_processor(self):
19 """
20@@ -340,24 +342,32 @@
21 'settings': translate('RemotePlugin.Mobile', 'Settings'),
22 }
23
24- def serve_file(self, file_name=None):
25+ def stages(self, url_path, file_name):
26 """
27- Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
28- If subfolders requested return 404, easier for security for the present.
29+ Allow Stage view to be delivered with custom views.
30
31- Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
32- where xx is the language, e.g. 'en'
33+ :param url_path: base path of the URL. Not used but passed by caller
34+ :param file_name: file name with path
35+ :return:
36 """
37 log.debug('serve file request %s' % file_name)
38- if not file_name:
39- file_name = 'index.html'
40- elif file_name == 'stage':
41- file_name = 'stage.html'
42- elif file_name == 'main':
43- file_name = 'main.html'
44- path = os.path.normpath(os.path.join(self.html_dir, file_name))
45- if not path.startswith(self.html_dir):
46+ parts = file_name.split('/')
47+ if len(parts) == 1:
48+ file_name = os.path.join(parts[0], 'stage.html')
49+ elif len(parts) == 3:
50+ file_name = os.path.join(parts[1], parts[2])
51+ path = os.path.normpath(os.path.join(self.config_dir, file_name))
52+ if not path.startswith(self.config_dir):
53 return self.do_not_found()
54+ return self._process_file(path)
55+
56+ def _process_file(self, path):
57+ """
58+ Common file processing code
59+
60+ :param path: path to file to be loaded
61+ :return: web resource to be loaded
62+ """
63 content = None
64 ext, content_type = self.get_content_type(path)
65 file_handle = None
66@@ -380,10 +390,32 @@
67 self.end_headers()
68 return content
69
70+ def serve_file(self, file_name=None):
71+ """
72+ Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
73+ If subfolders requested return 404, easier for security for the present.
74+
75+ Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
76+ where xx is the language, e.g. 'en'
77+ """
78+ log.debug('serve file request %s' % file_name)
79+ if not file_name:
80+ file_name = 'index.html'
81+ elif file_name == 'stage':
82+ file_name = 'stage.html'
83+ elif file_name == 'main':
84+ file_name = 'main.html'
85+ path = os.path.normpath(os.path.join(self.html_dir, file_name))
86+ if not path.startswith(self.html_dir):
87+ return self.do_not_found()
88+ return self._process_file(path)
89+
90 def get_content_type(self, file_name):
91 """
92 Examines the extension of the file and determines what the content_type should be, defaults to text/plain
93 Returns the extension and the content_type
94+
95+ :param file_name: name of file
96 """
97 ext = os.path.splitext(file_name)[1]
98 content_type = FILE_TYPES.get(ext, 'text/plain')
99@@ -392,6 +424,10 @@
100 def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
101 """
102 Serve an image file. If not found return 404.
103+
104+ :param file_name: file name to be served
105+ :param dimensions: image size
106+ :param controller_name: controller to be called
107 """
108 log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name))
109 supported_controllers = ['presentations', 'images']
110@@ -496,6 +532,8 @@
111 def controller_text(self, var):
112 """
113 Perform an action on the slide controller.
114+
115+ :param var: variable - not used
116 """
117 log.debug("controller_text var = %s" % var)
118 current_item = self.live_controller.service_item
119@@ -629,6 +667,8 @@
120 def go_live(self, plugin_name):
121 """
122 Go live on an item of type ``plugin``.
123+
124+ :param plugin_name: name of plugin
125 """
126 try:
127 request_id = json.loads(self.request_data)['request']['id']
128@@ -642,6 +682,8 @@
129 def add_to_service(self, plugin_name):
130 """
131 Add item of type ``plugin_name`` to the end of the service.
132+
133+ :param plugin_name: name of plugin to be called
134 """
135 try:
136 request_id = json.loads(self.request_data)['request']['id']
137
138=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
139--- openlp/plugins/remotes/lib/httpserver.py 2015-01-18 13:39:21 +0000
140+++ openlp/plugins/remotes/lib/httpserver.py 2015-12-05 12:49:02 +0000
141@@ -167,7 +167,7 @@
142 local_data = AppLocation.get_directory(AppLocation.DataDir)
143 self.socket = ssl.SSLSocket(
144 sock=socket.socket(self.address_family, self.socket_type),
145- ssl_version=ssl.PROTOCOL_TLSv1,
146+ ssl_version=ssl.PROTOCOL_TLSv1_2,
147 certfile=os.path.join(local_data, 'remotes', 'openlp.crt'),
148 keyfile=os.path.join(local_data, 'remotes', 'openlp.key'),
149 server_side=True)
150
151=== modified file 'tests/functional/openlp_plugins/remotes/test_router.py'
152--- tests/functional/openlp_plugins/remotes/test_router.py 2015-11-25 21:47:56 +0000
153+++ tests/functional/openlp_plugins/remotes/test_router.py 2015-12-05 12:49:02 +0000
154@@ -342,3 +342,39 @@
155
156 # THEN: service_manager.next_item() should have been called
157 self.assertTrue(mocked_previous_item.called, 'previous_item() should have been called in service_manager')
158+
159+ def remote_stage_personal_html_test(self):
160+ """
161+ Test the stage url with a parameter after loaded a url/stage.html file
162+ """
163+ # GIVEN: initial route
164+ self.router.config_dir = ''
165+ self.router.send_response = MagicMock()
166+ self.router.send_header = MagicMock()
167+ self.router.end_headers = MagicMock()
168+ self.router.wfile = MagicMock()
169+ self.router._process_file = MagicMock()
170+
171+ # WHEN: I call stage with a suffix
172+ self.router.stages('stages', 'trb')
173+
174+ # THEN: we should use the specific stage file instance
175+ self.router._process_file.assert_called_with(os.path.join('trb', 'stage.html'))
176+
177+ def remote_stage_personal_css_test(self):
178+ """
179+ Test the html with reference stages/trb/trb.css then loaded a stages/trb/trb.css file
180+ """
181+ # GIVEN: initial route
182+ self.router.config_dir = ''
183+ self.router.send_response = MagicMock()
184+ self.router.send_header = MagicMock()
185+ self.router.end_headers = MagicMock()
186+ self.router.wfile = MagicMock()
187+ self.router._process_file = MagicMock()
188+
189+ # WHEN: I call stage with a suffix
190+ self.router.stages('stages', 'stages/trb/trb.css')
191+
192+ # THEN: we should use the specific stage file instance
193+ self.router._process_file.assert_called_with(os.path.join('trb', 'trb.css'))