Merge lp:~raoul-snyman/openlp/ssl into lp:openlp

Proposed by Raoul Snyman
Status: Superseded
Proposed branch: lp:~raoul-snyman/openlp/ssl
Merge into: lp:openlp
Diff against target: 658 lines (+302/-96)
3 files modified
openlp/core/utils/__init__.py (+7/-3)
openlp/plugins/remotes/lib/httpserver.py (+164/-43)
openlp/plugins/remotes/lib/remotetab.py (+131/-50)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/ssl
Reviewer Review Type Date Requested Status
Tim Bentley Pending
Review via email: mp+106535@code.launchpad.net

This proposal supersedes a proposal from 2012-05-18.

Description of the change

Implement an HTTPS server for the web remote and the Android remote.

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

264 commented out code.

Tried to run ssl with not certificate and got this.

Traceback (most recent call last):
  File "/home/timali/dev/projects/openlp/ssl/openlp/plugins/remotes/lib/httpserver.py", line 165, in incomingConnection
    open(os.path.join(cert_path, u'openlp.crt'), u'rb').read())
IOError: [Errno 2] No such file or directory: u'/usr/share/openlp/openlp.crt'

How do I get a certificate?

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

The code should not generate a certificate. You must generate your own (we'll put the generation of certs into installers and/or build scripts).

Here's how to generate a self-signed certificate:

    http://doc.qt.nokia.com/solutions/4/qtsslsocket/sslguide.html#how-to-create-a-private-key-and-a-certificate

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

Thanks but till a bug as it should not crash if no certificate.

Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

It shouldn't crash if no certificate exists though, either disable the service and also prevent SSL being turned on in the first place if no certificate exists

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

Generated certificate and key file Ok.
Now Stage view gives me the web page source
The normal page gives my the html but no css or js

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

The missing-cert-crash "bug" has not been looked at yet from a code perspective. We will generate the cert when the package is installed (or when built, in the case of the OS X and Windows builds).

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

i.e. I'll still look at it, this is just a sanity-check for the other stuff.

lp:~raoul-snyman/openlp/ssl updated
1964. By Raoul Snyman

Check to see if there is an SSL certificate available, and if not disable the HTTPS server.

Unmerged revisions

1964. By Raoul Snyman

Check to see if there is an SSL certificate available, and if not disable the HTTPS server.

1963. By Raoul Snyman

HTTPS server is now optional.
Both HTTP and HTTPS servers are restarted if the configuration changes.

1962. By Raoul Snyman

HEAD

1961. By Raoul Snyman

Add the entry on the settings tab for the HTTPS server.

1960. By Raoul Snyman

Fixed an unnecessary import.
Downgraded the log.error in sslErrors to log.warn.

1959. By Raoul Snyman

Added a new "SharedData" enumeration for /usr/share/openlp on Linux/BSD, defaults to the same place as the LanguageDir on Windows and OS X.
Added fetching an SSL certificate from the aforementioned "SharedData" enumeration.
Some code didn't need to be there.
Some code needed to be tidied up.

1958. By Raoul Snyman

Last fix to make HTTPS work. YAHOO!

1957. By Raoul Snyman

Trying various things to get the SSL socket to actually return data.

1956. By Raoul Snyman

Implementing an HTTPS server. So close and yet so far.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/utils/__init__.py'
2--- openlp/core/utils/__init__.py 2012-05-01 12:58:22 +0000
3+++ openlp/core/utils/__init__.py 2012-05-20 16:30:25 +0000
4@@ -88,6 +88,7 @@
5 VersionDir = 5
6 CacheDir = 6
7 LanguageDir = 7
8+ SharedData = 8
9
10 # Base path where data/config/cache dir is located
11 BaseDir = None
12@@ -151,7 +152,8 @@
13 if dir_type == AppLocation.DataDir:
14 return os.path.join(unicode(os.getenv(u'APPDATA'), encoding),
15 u'openlp', u'data')
16- elif dir_type == AppLocation.LanguageDir:
17+ elif dir_type == AppLocation.LanguageDir or \
18+ dir_type == AppLocation.SharedData:
19 return os.path.split(openlp.__file__)[0]
20 return os.path.join(unicode(os.getenv(u'APPDATA'), encoding),
21 u'openlp')
22@@ -159,12 +161,14 @@
23 if dir_type == AppLocation.DataDir:
24 return os.path.join(unicode(os.getenv(u'HOME'), encoding),
25 u'Library', u'Application Support', u'openlp', u'Data')
26- elif dir_type == AppLocation.LanguageDir:
27+ elif dir_type == AppLocation.LanguageDir or \
28+ dir_type == AppLocation.SharedData:
29 return os.path.split(openlp.__file__)[0]
30 return os.path.join(unicode(os.getenv(u'HOME'), encoding),
31 u'Library', u'Application Support', u'openlp')
32 else:
33- if dir_type == AppLocation.LanguageDir:
34+ if dir_type == AppLocation.LanguageDir or \
35+ dir_type == AppLocation.SharedData:
36 return os.path.join(u'/usr', u'share', u'openlp')
37 if XDG_BASE_AVAILABLE:
38 if dir_type == AppLocation.ConfigDir:
39
40=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
41--- openlp/plugins/remotes/lib/httpserver.py 2012-04-20 19:36:10 +0000
42+++ openlp/plugins/remotes/lib/httpserver.py 2012-05-20 16:30:25 +0000
43@@ -27,8 +27,8 @@
44
45 """
46 The :mod:`http` module contains the API web server. This is a lightweight web
47-server used by remotes to interact with OpenLP. It uses JSON to communicate with
48-the remotes.
49+server used by remotes to interact with OpenLP. It uses JSON to communicate
50+with the remotes.
51
52 *Routes:*
53
54@@ -126,6 +126,7 @@
55
56 log = logging.getLogger(__name__)
57
58+
59 class HttpResponse(object):
60 """
61 A simple object to encapsulate a pseudo-http response.
62@@ -144,14 +145,53 @@
63 self.code = code
64
65
66-class HttpServer(object):
67+class SslServer(QtNetwork.QTcpServer):
68+ """
69+ SslServer is a class that implements an HTTPS server.
70+ """
71+ sslCertificate = None
72+ sslPrivateKey = None
73+ connections = []
74+
75+ def incomingConnection(self, socket_descriptor):
76+ """
77+ This method overrides the default one in :method:`incomingConnection`
78+ to provide the SSL socket support needed for HTTPS.
79+ """
80+ log.debug(u'Incoming HTTPS connection')
81+ cert_path = AppLocation.get_directory(AppLocation.SharedData)
82+ if not SslServer.sslCertificate:
83+ ssl_cert_data = QtCore.QByteArray(
84+ open(os.path.join(cert_path, u'openlp.crt'), u'rb').read())
85+ SslServer.sslCertificate = QtNetwork.QSslCertificate(ssl_cert_data)
86+ if not SslServer.sslPrivateKey:
87+ ssl_key_data = QtCore.QByteArray(
88+ open(os.path.join(cert_path, u'openlp.key'), u'rb').read())
89+ SslServer.sslPrivateKey = QtNetwork.QSslKey(ssl_key_data,
90+ QtNetwork.QSsl.Rsa)
91+ server_socket = QtNetwork.QSslSocket()
92+ if server_socket.setSocketDescriptor(socket_descriptor):
93+ server_socket.setPrivateKey(SslServer.sslPrivateKey)
94+ server_socket.setLocalCertificate(SslServer.sslCertificate)
95+ server_socket.setPeerVerifyMode(QtNetwork.QSslSocket.VerifyNone)
96+ server_socket.startServerEncryption()
97+ self.connections.append(server_socket)
98+ self.addPendingConnection(server_socket)
99+
100+
101+class HttpServer(QtCore.QObject):
102 """
103 Ability to control OpenLP via a web browser.
104 """
105+
106+ http_server = None
107+ https_server = None
108+
109 def __init__(self, plugin):
110 """
111 Initialise the httpserver, and start the server.
112 """
113+ QtCore.QObject.__init__(self)
114 log.debug(u'Initialise httpserver')
115 self.plugin = plugin
116 self.html_dir = os.path.join(
117@@ -160,33 +200,81 @@
118 self.connections = []
119 self.current_item = None
120 self.current_slide = None
121- self.start_tcp()
122-
123- def start_tcp(self):
124- """
125- Start the http server, use the port in the settings default to 4316.
126- Listen out for slide and song changes so they can be broadcast to
127- clients. Listen out for socket connections.
128- """
129- log.debug(u'Start TCP server')
130- port = QtCore.QSettings().value(
131- self.plugin.settingsSection + u'/port',
132- QtCore.QVariant(4316)).toInt()[0]
133- address = QtCore.QSettings().value(
134- self.plugin.settingsSection + u'/ip address',
135- QtCore.QVariant(u'0.0.0.0')).toString()
136- self.server = QtNetwork.QTcpServer()
137- self.server.listen(QtNetwork.QHostAddress(address), port)
138 QtCore.QObject.connect(Receiver.get_receiver(),
139 QtCore.SIGNAL(u'slidecontroller_live_changed'),
140 self.slide_change)
141 QtCore.QObject.connect(Receiver.get_receiver(),
142 QtCore.SIGNAL(u'slidecontroller_live_started'),
143 self.item_change)
144- QtCore.QObject.connect(self.server,
145+ QtCore.QObject.connect(Receiver.get_receiver(),
146+ QtCore.SIGNAL(u'remotes_config_updated'),
147+ self.update_servers)
148+ self.start_tcp()
149+ if QtCore.QSettings().value(self.plugin.settingsSection + \
150+ u'/https enabled', QtCore.QVariant(False)).toBool():
151+ self.start_ssl()
152+
153+ def update_servers(self):
154+ """
155+ Restart the server(s) if the configuration changes.
156+ """
157+ log.debug(u'Config changed, updating servers')
158+ https_enabled = QtCore.QSettings().value(
159+ self.plugin.settingsSection + u'/https enabled',
160+ QtCore.QVariant(False)).toBool()
161+ # Restart the HTTP server
162+ self.http_server.close()
163+ self.http_server = None
164+ self.start_tcp()
165+ # Check the status of the HTTPS server and restart it
166+ if not self.https_server and https_enabled:
167+ self.start_ssl()
168+ elif https_enabled and self.https_server:
169+ self.https_server.close()
170+ self.https_server = None
171+ self.start_ssl()
172+ elif self.https_server and not https_enabled:
173+ self.https_server.close()
174+ self.https_server = None
175+
176+ def start_tcp(self):
177+ """
178+ Start the HTTP server, use the port in the settings default to 4316.
179+ Listen out for slide and song changes so they can be broadcast to
180+ clients. Listen out for socket connections.
181+ """
182+ log.debug(u'Start TCP server')
183+ settings = QtCore.QSettings()
184+ settings.beginGroup(self.plugin.settingsSection)
185+ port = settings.value(u'port', QtCore.QVariant(4316)).toInt()[0]
186+ address = settings.value(
187+ u'ip address', QtCore.QVariant(u'0.0.0.0')).toString()
188+ settings.endGroup()
189+ self.http_server = QtNetwork.QTcpServer()
190+ self.http_server.listen(QtNetwork.QHostAddress(address), port)
191+ QtCore.QObject.connect(self.http_server,
192 QtCore.SIGNAL(u'newConnection()'), self.new_connection)
193 log.debug(u'TCP listening on port %d' % port)
194
195+ def start_ssl(self):
196+ """
197+ Start the HTTPS server, use the port in the settings default to 4317.
198+ Listen out for slide and song changes so they can be broadcast to
199+ clients. Listen out for socket connections.
200+ """
201+ log.debug(u'Start SSL server')
202+ settings = QtCore.QSettings()
203+ settings.beginGroup(self.plugin.settingsSection)
204+ port = settings.value(u'ssl port', QtCore.QVariant(4317)).toInt()[0]
205+ address = settings.value(
206+ u'ip address', QtCore.QVariant(u'0.0.0.0')).toString()
207+ settings.endGroup()
208+ self.https_server = SslServer()
209+ self.https_server.listen(QtNetwork.QHostAddress(address), port)
210+ QtCore.QObject.connect(self.https_server,
211+ QtCore.SIGNAL(u'newConnection()'), self.new_connection)
212+ log.debug(u'SSL listening on port %d' % port)
213+
214 def slide_change(self, row):
215 """
216 Slide change listener. Store the item and tell the clients.
217@@ -205,7 +293,8 @@
218 communication.
219 """
220 log.debug(u'new http connection')
221- socket = self.server.nextPendingConnection()
222+ server = self.sender()
223+ socket = server.nextPendingConnection()
224 if socket:
225 self.connections.append(HttpConnection(self, socket))
226
227@@ -213,7 +302,7 @@
228 """
229 The connection has been closed. Clean up
230 """
231- log.debug(u'close http connection')
232+ log.debug(u'close connection')
233 if connection in self.connections:
234 self.connections.remove(connection)
235
236@@ -222,7 +311,9 @@
237 Close down the http server.
238 """
239 log.debug(u'close http server')
240- self.server.close()
241+ self.http_server.close()
242+ if self.https_server:
243+ self.https_server.close()
244
245
246 class HttpConnection(object):
247@@ -252,10 +343,17 @@
248 (r'^/api/(.*)/live$', self.go_live),
249 (r'^/api/(.*)/add$', self.add_to_service)
250 ]
251- QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
252- self.ready_read)
253- QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
254- self.disconnected)
255+ if isinstance(socket, QtNetwork.QSslSocket):
256+ QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'encrypted()'),
257+ self.encrypted)
258+ QtCore.QObject.connect(self.socket,
259+ QtCore.SIGNAL(u'sslErrors(const QList<QSslError> &)'),
260+ self.sslErrors)
261+ else:
262+ QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
263+ self.ready_read)
264+ QtCore.QObject.connect(self.socket,
265+ QtCore.SIGNAL(u'disconnected()'), self.disconnected)
266 self.translate()
267
268 def _get_service_items(self):
269@@ -309,17 +407,32 @@
270 'options': translate('RemotePlugin.Mobile', 'Options')
271 }
272
273+ def encrypted(self):
274+ """
275+ Only setup these slots when the data is encrypted.
276+ """
277+ QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
278+ self.ready_read)
279+ QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
280+ self.disconnected)
281+
282+ def sslErrors(self, errors):
283+ for error in errors:
284+ log.warn(unicode(error.errorString()))
285+ self.socket.ignoreSslErrors()
286+
287 def ready_read(self):
288 """
289 Data has been sent from the client. Respond to it
290 """
291- log.debug(u'ready to read socket')
292+ log.debug(u'Ready to read socket')
293 if self.socket.canReadLine():
294 data = str(self.socket.readLine())
295 try:
296- log.debug(u'received: ' + data)
297+ log.debug(u'Received: ' + data)
298 except UnicodeDecodeError:
299 # Malicious request containing non-ASCII characters.
300+ self.send_response(HttpResponse(code='400 Bad Request'))
301 self.close()
302 return
303 words = data.split(' ')
304@@ -342,23 +455,28 @@
305 else:
306 self.send_response(HttpResponse(code='404 Not Found'))
307 self.close()
308+ else:
309+ return
310
311 def serve_file(self, filename=None):
312 """
313- Send a file to the socket. For now, just a subset of file types
314- and must be top level inside the html folder.
315- If subfolders requested return 404, easier for security for the present.
316+ Send a file to the socket. For now, only a subset of file types will
317+ be send, and all files must be at the top level inside the html folder.
318+ If sub-folders are requested return 404, easier for security for the
319+ present.
320
321 Ultimately for i18n, this could first look for xx/file.html before
322 falling back to file.html... where xx is the language, e.g. 'en'
323 """
324- log.debug(u'serve file request %s' % filename)
325+ log.debug(u'serve file request (original) %s' % filename)
326 if not filename:
327 filename = u'index.html'
328 elif filename == u'stage':
329 filename = u'stage.html'
330+ log.debug(u'serve file request (updated) %s' % filename)
331 path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
332 if not path.startswith(self.parent.html_dir):
333+ log.debug(u'File not found, returning 404')
334 return HttpResponse(code=u'404 Not Found')
335 ext = os.path.splitext(filename)[1]
336 html = None
337@@ -404,8 +522,8 @@
338 u'slide': self.parent.current_slide or 0,
339 u'item': self.parent.current_item._uuid \
340 if self.parent.current_item else u'',
341- u'twelve':QtCore.QSettings().value(
342- u'remotes/twelve hour', QtCore.QVariant(True)).toBool(),
343+ u'twelve': QtCore.QSettings().value(
344+ u'remotes/twelve hour', QtCore.QVariant(True)).toBool(),
345 u'blank': self.parent.plugin.liveController.blankScreen.\
346 isChecked(),
347 u'theme': self.parent.plugin.liveController.themeScreen.\
348@@ -436,7 +554,7 @@
349 try:
350 text = json.loads(
351 self.url_params[u'data'][0])[u'request'][u'text']
352- except KeyError, ValueError:
353+ except (KeyError, ValueError):
354 return HttpResponse(code=u'400 Bad Request')
355 text = urllib.unquote(text)
356 Receiver.send_message(u'alerts_text', [text])
357@@ -484,7 +602,7 @@
358 if self.url_params and self.url_params.get(u'data'):
359 try:
360 data = json.loads(self.url_params[u'data'][0])
361- except KeyError, ValueError:
362+ except (KeyError, ValueError):
363 return HttpResponse(code=u'400 Bad Request')
364 log.info(data)
365 # This slot expects an int within a list.
366@@ -500,14 +618,16 @@
367 event = u'servicemanager_%s' % action
368 if action == u'list':
369 return HttpResponse(
370- json.dumps({u'results': {u'items': self._get_service_items()}}),
371+ json.dumps({
372+ u'results': {u'items': self._get_service_items()}
373+ }),
374 {u'Content-Type': u'application/json'})
375 else:
376 event += u'_item'
377 if self.url_params and self.url_params.get(u'data'):
378 try:
379 data = json.loads(self.url_params[u'data'][0])
380- except KeyError, ValueError:
381+ except (KeyError, ValueError):
382 return HttpResponse(code=u'400 Bad Request')
383 Receiver.send_message(event, data[u'request'][u'id'])
384 else:
385@@ -543,7 +663,7 @@
386 """
387 try:
388 text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
389- except KeyError, ValueError:
390+ except (KeyError, ValueError):
391 return HttpResponse(code=u'400 Bad Request')
392 text = urllib.unquote(text)
393 plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
394@@ -562,7 +682,7 @@
395 """
396 try:
397 id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
398- except KeyError, ValueError:
399+ except (KeyError, ValueError):
400 return HttpResponse(code=u'400 Bad Request')
401 plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
402 if plugin.status == PluginStatus.Active and plugin.mediaItem:
403@@ -575,7 +695,7 @@
404 """
405 try:
406 id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
407- except KeyError, ValueError:
408+ except (KeyError, ValueError):
409 return HttpResponse(code=u'400 Bad Request')
410 plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
411 if plugin.status == PluginStatus.Active and plugin.mediaItem:
412@@ -590,6 +710,7 @@
413 http += '\r\n'
414 self.socket.write(http)
415 self.socket.write(response.content)
416+ self.socket.flush()
417
418 def disconnected(self):
419 """
420
421=== modified file 'openlp/plugins/remotes/lib/remotetab.py'
422--- openlp/plugins/remotes/lib/remotetab.py 2012-03-18 09:33:05 +0000
423+++ openlp/plugins/remotes/lib/remotetab.py 2012-05-20 16:30:25 +0000
424@@ -41,7 +41,9 @@
425 def setupUi(self):
426 self.setObjectName(u'RemoteTab')
427 SettingsTab.setupUi(self)
428+ # Main server settings
429 self.serverSettingsGroupBox = QtGui.QGroupBox(self.leftColumn)
430+ self.serverSettingsGroupBox.setContentsMargins(8, 8, 8, 8)
431 self.serverSettingsGroupBox.setObjectName(u'serverSettingsGroupBox')
432 self.serverSettingsLayout = QtGui.QFormLayout(
433 self.serverSettingsGroupBox)
434@@ -60,27 +62,63 @@
435 self.twelveHourCheckBox = QtGui.QCheckBox(self.serverSettingsGroupBox)
436 self.twelveHourCheckBox.setObjectName(u'twelveHourCheckBox')
437 self.serverSettingsLayout.addRow(self.twelveHourCheckBox)
438- self.portLabel = QtGui.QLabel(self.serverSettingsGroupBox)
439- self.portLabel.setObjectName(u'portLabel')
440- self.portSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox)
441- self.portSpinBox.setMaximum(32767)
442- self.portSpinBox.setObjectName(u'portSpinBox')
443- QtCore.QObject.connect(self.portSpinBox,
444- QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
445- self.serverSettingsLayout.addRow(self.portLabel, self.portSpinBox)
446- self.remoteUrlLabel = QtGui.QLabel(self.serverSettingsGroupBox)
447- self.remoteUrlLabel.setObjectName(u'remoteUrlLabel')
448- self.remoteUrl = QtGui.QLabel(self.serverSettingsGroupBox)
449- self.remoteUrl.setObjectName(u'remoteUrl')
450- self.remoteUrl.setOpenExternalLinks(True)
451- self.serverSettingsLayout.addRow(self.remoteUrlLabel, self.remoteUrl)
452- self.stageUrlLabel = QtGui.QLabel(self.serverSettingsGroupBox)
453- self.stageUrlLabel.setObjectName(u'stageUrlLabel')
454- self.stageUrl = QtGui.QLabel(self.serverSettingsGroupBox)
455- self.stageUrl.setObjectName(u'stageUrl')
456- self.stageUrl.setOpenExternalLinks(True)
457- self.serverSettingsLayout.addRow(self.stageUrlLabel, self.stageUrl)
458 self.leftLayout.addWidget(self.serverSettingsGroupBox)
459+ self.httpSettingsGroupBox = QtGui.QGroupBox(self.leftColumn)
460+ self.httpSettingsGroupBox.setObjectName(u'httpSettingsGroupBox')
461+ self.httpSettingsLayout = QtGui.QFormLayout(
462+ self.httpSettingsGroupBox)
463+ self.httpSettingsLayout.setObjectName(u'httpSettingsLayout')
464+ self.httpPortLabel = QtGui.QLabel(self.httpSettingsGroupBox)
465+ self.httpPortLabel.setObjectName(u'httpPortLabel')
466+ self.httpPortSpinBox = QtGui.QSpinBox(self.httpSettingsGroupBox)
467+ self.httpPortSpinBox.setMaximum(32767)
468+ self.httpPortSpinBox.setObjectName(u'httpPortSpinBox')
469+ self.httpSettingsLayout.addRow(
470+ self.httpPortLabel, self.httpPortSpinBox)
471+ self.remoteHttpUrlLabel = QtGui.QLabel(self.httpSettingsGroupBox)
472+ self.remoteHttpUrlLabel.setObjectName(u'remoteHttpUrlLabel')
473+ self.remoteHttpUrl = QtGui.QLabel(self.httpSettingsGroupBox)
474+ self.remoteHttpUrl.setObjectName(u'remoteHttpUrl')
475+ self.remoteHttpUrl.setOpenExternalLinks(True)
476+ self.httpSettingsLayout.addRow(
477+ self.remoteHttpUrlLabel, self.remoteHttpUrl)
478+ self.stageHttpUrlLabel = QtGui.QLabel(self.httpSettingsGroupBox)
479+ self.stageHttpUrlLabel.setObjectName(u'stageHttpUrlLabel')
480+ self.stageHttpUrl = QtGui.QLabel(self.httpSettingsGroupBox)
481+ self.stageHttpUrl.setObjectName(u'stageHttpUrl')
482+ self.stageHttpUrl.setOpenExternalLinks(True)
483+ self.httpSettingsLayout.addRow(
484+ self.stageHttpUrlLabel, self.stageHttpUrl)
485+ self.leftLayout.addWidget(self.httpSettingsGroupBox)
486+ self.httpsSettingsGroupBox = QtGui.QGroupBox(self.leftColumn)
487+ self.httpsSettingsGroupBox.setCheckable(True)
488+ self.httpsSettingsGroupBox.setChecked(False)
489+ self.httpsSettingsGroupBox.setObjectName(u'httpsSettingsGroupBox')
490+ self.httpsSettingsLayout = QtGui.QFormLayout(
491+ self.httpsSettingsGroupBox)
492+ self.httpsSettingsLayout.setObjectName(u'httpsSettingsLayout')
493+ self.httpsPortLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
494+ self.httpsPortLabel.setObjectName(u'httpsPortLabel')
495+ self.httpsPortSpinBox = QtGui.QSpinBox(self.httpsSettingsGroupBox)
496+ self.httpsPortSpinBox.setMaximum(32767)
497+ self.httpsPortSpinBox.setObjectName(u'httpsPortSpinBox')
498+ self.httpsSettingsLayout.addRow(
499+ self.httpsPortLabel, self.httpsPortSpinBox)
500+ self.remoteHttpsUrl = QtGui.QLabel(self.httpsSettingsGroupBox)
501+ self.remoteHttpsUrl.setObjectName(u'remoteHttpsUrl')
502+ self.remoteHttpsUrl.setOpenExternalLinks(True)
503+ self.remoteHttpsUrlLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
504+ self.remoteHttpsUrlLabel.setObjectName(u'remoteHttpsUrlLabel')
505+ self.httpsSettingsLayout.addRow(
506+ self.remoteHttpsUrlLabel, self.remoteHttpsUrl)
507+ self.stageHttpsUrlLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
508+ self.stageHttpsUrlLabel.setObjectName(u'stageHttpsUrlLabel')
509+ self.stageHttpsUrl = QtGui.QLabel(self.httpsSettingsGroupBox)
510+ self.stageHttpsUrl.setObjectName(u'stageHttpsUrl')
511+ self.stageHttpsUrl.setOpenExternalLinks(True)
512+ self.httpsSettingsLayout.addRow(
513+ self.stageHttpsUrlLabel, self.stageHttpsUrl)
514+ self.leftLayout.addWidget(self.httpsSettingsGroupBox)
515 self.androidAppGroupBox = QtGui.QGroupBox(self.rightColumn)
516 self.androidAppGroupBox.setObjectName(u'androidAppGroupBox')
517 self.rightLayout.addWidget(self.androidAppGroupBox)
518@@ -99,6 +137,10 @@
519 self.qrLayout.addWidget(self.qrDescriptionLabel)
520 self.leftLayout.addStretch()
521 self.rightLayout.addStretch()
522+ QtCore.QObject.connect(self.httpPortSpinBox,
523+ QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
524+ QtCore.QObject.connect(self.httpsPortSpinBox,
525+ QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
526 QtCore.QObject.connect(self.twelveHourCheckBox,
527 QtCore.SIGNAL(u'stateChanged(int)'),
528 self.onTwelveHourCheckBoxChanged)
529@@ -108,11 +150,21 @@
530 translate('RemotePlugin.RemoteTab', 'Server Settings'))
531 self.addressLabel.setText(translate('RemotePlugin.RemoteTab',
532 'Serve on IP address:'))
533- self.portLabel.setText(translate('RemotePlugin.RemoteTab',
534- 'Port number:'))
535- self.remoteUrlLabel.setText(translate('RemotePlugin.RemoteTab',
536- 'Remote URL:'))
537- self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab',
538+ self.httpSettingsGroupBox.setTitle(
539+ translate('RemotePlugin.RemoteTab', 'HTTP Server'))
540+ self.httpPortLabel.setText(translate('RemotePlugin.RemoteTab',
541+ 'Port number:'))
542+ self.remoteHttpUrlLabel.setText(translate('RemotePlugin.RemoteTab',
543+ 'Remote URL:'))
544+ self.stageHttpUrlLabel.setText(translate('RemotePlugin.RemoteTab',
545+ 'Stage view URL:'))
546+ self.httpsSettingsGroupBox.setTitle(
547+ translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
548+ self.httpsPortLabel.setText(translate('RemotePlugin.RemoteTab',
549+ 'Port number:'))
550+ self.remoteHttpsUrlLabel.setText(translate('RemotePlugin.RemoteTab',
551+ 'Remote URL:'))
552+ self.stageHttpsUrlLabel.setText(translate('RemotePlugin.RemoteTab',
553 'Stage view URL:'))
554 self.twelveHourCheckBox.setText(
555 translate('RemotePlugin.RemoteTab',
556@@ -125,7 +177,7 @@
557 'download</a> to install the Android app from the Market.'))
558
559 def setUrls(self):
560- ipAddress = u'localhost'
561+ ip_address = u'localhost'
562 if self.addressEdit.text() == ZERO_URL:
563 ifaces = QtNetwork.QNetworkInterface.allInterfaces()
564 for iface in ifaces:
565@@ -138,41 +190,70 @@
566 ip = addr.ip()
567 if ip.protocol() == 0 and \
568 ip != QtNetwork.QHostAddress.LocalHost:
569- ipAddress = ip.toString()
570+ ip_address = ip.toString()
571 break
572 else:
573- ipAddress = self.addressEdit.text()
574- url = u'http://%s:%s/' % (ipAddress, self.portSpinBox.value())
575- self.remoteUrl.setText(u'<a href="%s">%s</a>' % (url, url))
576- url = url + u'stage'
577- self.stageUrl.setText(u'<a href="%s">%s</a>' % (url, url))
578+ ip_address = self.addressEdit.text()
579+ http_url = u'http://%s:%s/' % \
580+ (ip_address, self.httpPortSpinBox.value())
581+ https_url = u'https://%s:%s/' % \
582+ (ip_address, self.httpsPortSpinBox.value())
583+ self.remoteHttpUrl.setText(u'<a href="%s">%s</a>' % \
584+ (http_url, http_url))
585+ self.remoteHttpsUrl.setText(u'<a href="%s">%s</a>' % \
586+ (https_url, https_url))
587+ http_url += u'stage'
588+ https_url += u'stage'
589+ self.stageHttpUrl.setText(u'<a href="%s">%s</a>' % \
590+ (http_url, http_url))
591+ self.stageHttpsUrl.setText(u'<a href="%s">%s</a>' % \
592+ (https_url, https_url))
593
594 def load(self):
595- self.portSpinBox.setValue(
596- QtCore.QSettings().value(self.settingsSection + u'/port',
597- QtCore.QVariant(4316)).toInt()[0])
598+ settings = QtCore.QSettings()
599+ settings.beginGroup(self.settingsSection)
600+ self.httpPortSpinBox.setValue(
601+ settings.value(u'port', QtCore.QVariant(4316)).toInt()[0])
602+ self.httpsPortSpinBox.setValue(
603+ settings.value(u'ssl port', QtCore.QVariant(4317)).toInt()[0])
604 self.addressEdit.setText(
605- QtCore.QSettings().value(self.settingsSection + u'/ip address',
606- QtCore.QVariant(ZERO_URL)).toString())
607- self.twelveHour = QtCore.QSettings().value(
608- self.settingsSection + u'/twelve hour',
609- QtCore.QVariant(True)).toBool()
610+ settings.value(
611+ u'ip address', QtCore.QVariant(ZERO_URL)).toString())
612+ self.twelveHour = settings.value(
613+ u'twelve hour', QtCore.QVariant(True)).toBool()
614+ self.httpsSettingsGroupBox.setChecked(settings.value(u'https enabled',
615+ QtCore.QVariant(False)).toBool())
616+ settings.endGroup()
617 self.twelveHourCheckBox.setChecked(self.twelveHour)
618 self.setUrls()
619
620 def save(self):
621 changed = False
622- if QtCore.QSettings().value(self.settingsSection + u'/ip address',
623- QtCore.QVariant(ZERO_URL).toString() != self.addressEdit.text() or
624- QtCore.QSettings().value(self.settingsSection + u'/port',
625- QtCore.QVariant(4316).toInt()[0]) != self.portSpinBox.value()):
626+ settings = QtCore.QSettings()
627+ settings.beginGroup(self.settingsSection)
628+ ip_address = settings.value(
629+ u'ip address', QtCore.QVariant(ZERO_URL).toString())
630+ http_port = settings.value(
631+ u'port', QtCore.QVariant(4316).toInt()[0])
632+ https_port = settings.value(
633+ u'ssl port', QtCore.QVariant(4317).toInt()[0])
634+ https_enabled = settings.value(
635+ u'https enabled', QtCore.QVariant(False)).toBool()
636+ if ip_address != self.addressEdit.text() or \
637+ http_port != self.httpPortSpinBox.value() or\
638+ https_port != self.httpsPortSpinBox.value() or \
639+ https_enabled != self.httpsSettingsGroupBox.isChecked():
640 changed = True
641- QtCore.QSettings().setValue(self.settingsSection + u'/port',
642- QtCore.QVariant(self.portSpinBox.value()))
643- QtCore.QSettings().setValue(self.settingsSection + u'/ip address',
644- QtCore.QVariant(self.addressEdit.text()))
645- QtCore.QSettings().setValue(self.settingsSection + u'/twelve hour',
646- QtCore.QVariant(self.twelveHour))
647+ settings.setValue(
648+ u'port', QtCore.QVariant(self.httpPortSpinBox.value()))
649+ settings.setValue(
650+ u'ssl port', QtCore.QVariant(self.httpsPortSpinBox.value()))
651+ settings.setValue(
652+ u'ip address', QtCore.QVariant(self.addressEdit.text()))
653+ settings.setValue(u'twelve hour', QtCore.QVariant(self.twelveHour))
654+ settings.setValue(u'https enabled',
655+ QtCore.QVariant(self.httpsSettingsGroupBox.isChecked()))
656+ settings.endGroup()
657 if changed:
658 Receiver.send_message(u'remotes_config_updated')
659