1 # -*- coding: utf-8 -*- |
1 # -*- coding: utf-8 -*- |
2 |
2 |
3 # Copyright (c) 2004 - 2018 Detlev Offenbach <detlev@die-offenbachs.de> |
3 # Copyright (c) 2004 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
4 # |
4 # |
5 |
5 |
6 """ |
6 """ |
7 Module implementing the single application server and client. |
7 Module implementing the single application server and client. |
8 """ |
8 """ |
9 |
9 |
10 from __future__ import unicode_literals |
10 from __future__ import unicode_literals |
11 |
11 |
|
12 import json |
|
13 |
12 from PyQt5.QtCore import QByteArray |
14 from PyQt5.QtCore import QByteArray |
13 from PyQt5.QtNetwork import QLocalServer, QLocalSocket |
15 from PyQt5.QtNetwork import QLocalServer, QLocalSocket |
|
16 |
|
17 from E5Gui import E5MessageBox |
|
18 |
|
19 import Utilities |
14 |
20 |
15 |
21 |
16 class SingleApplicationServer(QLocalServer): |
22 class SingleApplicationServer(QLocalServer): |
17 """ |
23 """ |
18 Class implementing the single application server base class. |
24 Class implementing the single application server base class. |
39 """ |
45 """ |
40 Private slot to handle a new connection. |
46 Private slot to handle a new connection. |
41 """ |
47 """ |
42 sock = self.nextPendingConnection() |
48 sock = self.nextPendingConnection() |
43 |
49 |
44 # If we already have a connection, refuse this one. It will be closed |
50 # If we already have a connection, refuse this one. It will be closed |
45 # automatically. |
51 # automatically. |
46 if self.qsock is not None: |
52 if self.qsock is not None: |
47 return |
53 return |
48 |
54 |
49 self.qsock = sock |
55 self.qsock = sock |
50 |
56 |
51 self.qsock.readyRead.connect(self.__parseLine) |
57 self.qsock.readyRead.connect(self.__receiveJson) |
52 self.qsock.disconnected.connect(self.__disconnected) |
58 self.qsock.disconnected.connect(self.__disconnected) |
53 |
59 |
54 def __parseLine(self): |
60 def __receiveJson(self): |
55 """ |
61 """ |
56 Private method to handle data from the client. |
62 Private method to receive the data from the client. |
57 """ |
63 """ |
58 while self.qsock and self.qsock.canReadLine(): |
64 while self.qsock and self.qsock.canReadLine(): |
59 line = bytes(self.qsock.readLine()).decode() |
65 line = bytes(self.qsock.readLine()).decode() |
60 |
66 |
61 ## print(line) ##debug |
67 ## print(line) ##debug |
62 |
68 |
63 eoc = line.find('<') + 1 |
69 try: |
64 |
70 commandDict = json.loads(line.strip()) |
65 boc = line.find('>') |
71 except (TypeError, ValueError) as err: |
66 if boc >= 0 and eoc > boc: |
72 E5MessageBox.critical( |
67 # handle the command sent by the client. |
73 None, |
68 cmd = line[boc:eoc] |
74 self.tr("Single Application Protocol Error"), |
69 params = line[eoc:-1] |
75 self.tr("""<p>The response received from the single""" |
70 |
76 """ application client could not be decoded.""" |
71 self.handleCommand(cmd, params) |
77 """ Please report this issue with the received""" |
|
78 """ data to the eric bugs email address.</p>""" |
|
79 """<p>Error: {0}</p>""" |
|
80 """<p>Data:<br/>{1}</p>""").format( |
|
81 str(err), Utilities.html_encode(line.strip())), |
|
82 E5MessageBox.StandardButtons( |
|
83 E5MessageBox.Ok)) |
|
84 return |
|
85 |
|
86 command = commandDict["command"] |
|
87 arguments = commandDict["arguments"] |
|
88 |
|
89 self.handleCommand(command, arguments) |
72 |
90 |
73 def __disconnected(self): |
91 def __disconnected(self): |
74 """ |
92 """ |
75 Private method to handle the closure of the socket. |
93 Private method to handle the closure of the socket. |
76 """ |
94 """ |
86 |
104 |
87 self.qsock = None |
105 self.qsock = None |
88 |
106 |
89 self.close() |
107 self.close() |
90 |
108 |
91 def handleCommand(self, cmd, params): |
109 def handleCommand(self, command, arguments): |
92 """ |
110 """ |
93 Public slot to handle the command sent by the client. |
111 Public slot to handle the command sent by the client. |
94 |
112 |
95 <b>Note</b>: This method must be overridden by subclasses. |
113 <b>Note</b>: This method must be overridden by subclasses. |
96 |
114 |
97 @param cmd commandstring (string) |
115 @param command command sent by the client |
98 @param params parameterstring (string) |
116 @type str |
|
117 @param arguments list of command arguments |
|
118 @type list of str |
99 @exception RuntimeError raised to indicate that this method must be |
119 @exception RuntimeError raised to indicate that this method must be |
100 implemented by a subclass |
120 implemented by a subclass |
101 """ |
121 """ |
102 raise RuntimeError("'handleCommand' must be overridden") |
122 raise RuntimeError("'handleCommand' must be overridden") |
103 |
123 |
113 @param name name of the local server to connect to (string) |
133 @param name name of the local server to connect to (string) |
114 """ |
134 """ |
115 self.name = name |
135 self.name = name |
116 self.connected = False |
136 self.connected = False |
117 |
137 |
118 def connect(self): |
138 def connect(self, timeout=10000): |
119 """ |
139 """ |
120 Public method to connect the single application client to its server. |
140 Public method to connect the single application client to its server. |
121 |
141 |
|
142 @param timeout connection timeout value in milliseconds |
|
143 @type int |
122 @return value indicating success or an error number. Value is one of: |
144 @return value indicating success or an error number. Value is one of: |
123 <table> |
145 <table> |
124 <tr><td>0</td><td>No application is running</td></tr> |
146 <tr><td>0</td><td>No application is running</td></tr> |
125 <tr><td>1</td><td>Application is already running</td></tr> |
147 <tr><td>1</td><td>Application is already running</td></tr> |
126 </table> |
148 </table> |
127 """ |
149 """ |
128 self.sock = QLocalSocket() |
150 self.sock = QLocalSocket() |
129 self.sock.connectToServer(self.name) |
151 self.sock.connectToServer(self.name) |
130 if self.sock.waitForConnected(10000): |
152 if self.sock.waitForConnected(timeout): |
131 self.connected = True |
153 self.connected = True |
132 return 1 |
154 return 1 |
133 else: |
155 else: |
134 err = self.sock.error() |
156 err = self.sock.error() |
135 if err == QLocalSocket.ServerNotFoundError: |
157 if err == QLocalSocket.ServerNotFoundError: |
154 @exception RuntimeError raised to indicate that this method must be |
176 @exception RuntimeError raised to indicate that this method must be |
155 implemented by a subclass |
177 implemented by a subclass |
156 """ |
178 """ |
157 raise RuntimeError("'processArgs' must be overridden") |
179 raise RuntimeError("'processArgs' must be overridden") |
158 |
180 |
159 def sendCommand(self, cmd): |
181 def sendCommand(self, command, arguments): |
160 """ |
182 """ |
161 Public method to send the command to the application server. |
183 Public method to send the command to the application server. |
162 |
184 |
163 @param cmd command to be sent (string) |
185 @param command command to be sent to the server |
|
186 @type str |
|
187 @param arguments list of command arguments |
|
188 @type list of str |
164 """ |
189 """ |
165 if self.connected: |
190 if self.connected: |
166 self.sock.write(QByteArray(cmd.encode())) |
191 commandDict = { |
|
192 "command": command, |
|
193 "arguments": arguments, |
|
194 } |
|
195 self.sock.write(QByteArray( |
|
196 "{0}\n".format(json.dumps(commandDict)).encode() |
|
197 )) |
167 self.sock.flush() |
198 self.sock.flush() |
168 |
199 |
169 def errstr(self): |
200 def errstr(self): |
170 """ |
201 """ |
171 Public method to return a meaningful error string for the last error. |
202 Public method to return a meaningful error string for the last error. |