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