Sat, 22 May 2021 16:52:45 +0200
Renamed 'E5Utilities' to 'EricUtilities' and 'E5Network' to 'EricNetwork'.
8300 | 1 | # -*- coding: utf-8 -*- |
2 | ||
3 | # Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> | |
4 | # | |
5 | ||
6 | """ | |
7 | Module implementing the JSON based client base class. | |
8 | """ | |
9 | ||
10 | import io | |
11 | import sys | |
12 | import socket | |
13 | import select | |
14 | import traceback | |
15 | import json | |
16 | import contextlib | |
17 | ||
18 | ||
8354
12ebd3934fef
Renamed 'E5Utilities' to 'EricUtilities' and 'E5Network' to 'EricNetwork'.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8327
diff
changeset
|
19 | class EricJsonClient: |
8300 | 20 | """ |
21 | Class implementing a JSON based client base class. | |
22 | """ | |
23 | def __init__(self, host, port, idString=""): | |
24 | """ | |
25 | Constructor | |
26 | ||
27 | @param host ip address the background service is listening | |
28 | @type str | |
29 | @param port port of the background service | |
30 | @type int | |
31 | @param idString assigned client id to be sent back to the server in | |
32 | order to identify the connection | |
33 | @type str | |
34 | """ | |
35 | self.__connection = socket.create_connection((host, port)) | |
36 | if idString: | |
37 | reply = idString + '\n' | |
38 | self.__connection.sendall(reply.encode('utf8', 'backslashreplace')) | |
39 | ||
40 | def sendJson(self, command, params): | |
41 | """ | |
42 | Public method to send a single refactoring command to the server. | |
43 | ||
44 | @param command command name to be sent | |
45 | @type str | |
46 | @param params dictionary of named parameters for the command | |
47 | @type dict | |
48 | """ | |
49 | commandDict = { | |
50 | "jsonrpc": "2.0", | |
51 | "method": command, | |
52 | "params": params, | |
53 | } | |
54 | cmd = json.dumps(commandDict) + '\n' | |
55 | self.__connection.sendall(cmd.encode('utf8', 'backslashreplace')) | |
56 | ||
57 | def __receiveJson(self): | |
58 | """ | |
59 | Private method to receive a JSON encode command and data from the | |
60 | server. | |
61 | ||
62 | @return tuple containing the received command and a dictionary | |
63 | containing the associated data | |
64 | @rtype tuple of (str, dict) | |
65 | """ | |
66 | # step 1: receive the data | |
67 | # The JSON RPC string is prefixed by a 9 character long length field. | |
68 | length = self.__connection.recv(9) | |
69 | if len(length) < 9: | |
70 | # invalid length string received | |
71 | return None, None | |
72 | ||
73 | length = int(length) | |
74 | data = b'' | |
75 | while len(data) < length: | |
76 | newData = self.__connection.recv(length - len(data)) | |
77 | if not newData: | |
78 | return None, None | |
79 | ||
80 | data += newData | |
81 | ||
82 | # step 2: decode and convert the data | |
83 | line = data.decode( | |
84 | 'utf8', 'backslashreplace') | |
85 | try: | |
86 | commandDict = json.loads(line.strip()) | |
87 | except (TypeError, ValueError) as err: | |
88 | self.sendJson("ClientException", { | |
89 | "ExceptionType": "ProtocolError", | |
90 | "ExceptionValue": str(err), | |
91 | "ProtocolData": line.strip(), | |
92 | }) | |
93 | return None, None | |
94 | ||
95 | method = commandDict["method"] | |
96 | params = commandDict["params"] | |
97 | ||
98 | return method, params | |
99 | ||
100 | def handleCall(self, method, params): | |
101 | """ | |
102 | Public method to handle a method call from the server. | |
103 | ||
104 | Note: This is an empty implementation that must be overridden in | |
105 | derived classes. | |
106 | ||
107 | @param method requested method name | |
108 | @type str | |
109 | @param params dictionary with method specific parameters | |
110 | @type dict | |
111 | """ | |
112 | pass | |
113 | ||
114 | def run(self): | |
115 | """ | |
116 | Public method implementing the main loop of the client. | |
117 | """ | |
118 | try: | |
119 | selectErrors = 0 | |
120 | while selectErrors <= 10: # selected arbitrarily | |
121 | try: | |
122 | rrdy, wrdy, xrdy = select.select( | |
123 | [self.__connection], [], []) | |
124 | ||
125 | # Just waiting for self.__connection. Therefor no check | |
126 | # needed. | |
127 | method, params = self.__receiveJson() | |
128 | if method is None: | |
129 | selectErrors += 1 | |
130 | elif method == "Exit": | |
131 | break | |
132 | else: | |
133 | self.handleCall(method, params) | |
134 | ||
135 | # reset select errors | |
136 | selectErrors = 0 | |
137 | ||
138 | except (select.error, KeyboardInterrupt, socket.error): | |
139 | selectErrors += 1 | |
140 | ||
141 | except Exception: | |
142 | exctype, excval, exctb = sys.exc_info() | |
143 | tbinfofile = io.StringIO() | |
144 | traceback.print_tb(exctb, None, tbinfofile) | |
145 | tbinfofile.seek(0) | |
146 | tbinfo = tbinfofile.read() | |
147 | del exctb | |
148 | self.sendJson("ClientException", { | |
149 | "ExceptionType": str(exctype), | |
150 | "ExceptionValue": str(excval), | |
151 | "Traceback": tbinfo, | |
152 | }) | |
153 | ||
154 | # Give time to process latest response on server side | |
155 | with contextlib.suppress(socket.error, OSError): | |
156 | self.__connection.shutdown(socket.SHUT_RDWR) | |
157 | self.__connection.close() | |
8327
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
158 | |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
159 | def poll(self, waitMethod=""): |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
160 | """ |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
161 | Public method to check and receive one message (if available). |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
162 | |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
163 | @param waitMethod name of a method to wait for |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
164 | @type str |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
165 | @return dictionary containing the data of the waited for method |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
166 | @rtype dict |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
167 | """ |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
168 | try: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
169 | if waitMethod: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
170 | rrdy, wrdy, xrdy = select.select( |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
171 | [self.__connection], [], []) |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
172 | else: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
173 | rrdy, wrdy, xrdy = select.select( |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
174 | [self.__connection], [], [], 0) |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
175 | |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
176 | if self.__connection in rrdy: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
177 | method, params = self.__receiveJson() |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
178 | if method is not None: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
179 | if method == "Exit": |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
180 | self.__exitClient = True |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
181 | elif method == waitMethod: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
182 | return params |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
183 | else: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
184 | self.handleCall(method, params) |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
185 | |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
186 | except (select.error, KeyboardInterrupt, socket.error): |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
187 | # just ignore these |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
188 | pass |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
189 | |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
190 | except Exception: |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
191 | exctype, excval, exctb = sys.exc_info() |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
192 | tbinfofile = io.StringIO() |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
193 | traceback.print_tb(exctb, None, tbinfofile) |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
194 | tbinfofile.seek(0) |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
195 | tbinfo = tbinfofile.read() |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
196 | del exctb |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
197 | self.sendJson("ClientException", { |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
198 | "ExceptionType": str(exctype), |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
199 | "ExceptionValue": str(excval), |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
200 | "Traceback": tbinfo, |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
201 | }) |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
202 | |
666c2b81cbb7
Continued porting eric to PyQt6.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
8312
diff
changeset
|
203 | return None |