|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the JSON based server base class. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import json |
|
13 |
|
14 from PyQt5.QtCore import pyqtSlot, QProcess |
|
15 from PyQt5.QtNetwork import QTcpServer, QHostAddress |
|
16 |
|
17 from E5Gui import E5MessageBox |
|
18 |
|
19 import Preferences |
|
20 import Utilities |
|
21 |
|
22 |
|
23 class JsonServer(QTcpServer): |
|
24 """ |
|
25 Class implementing the JSON based server base class. |
|
26 """ |
|
27 def __init__(self, parent=None): |
|
28 """ |
|
29 Constructor |
|
30 |
|
31 @param parent parent object |
|
32 @type QObject |
|
33 """ |
|
34 super(JsonServer, self).__init__(parent) |
|
35 |
|
36 self.__clientProcess = None |
|
37 self.__connection = None |
|
38 |
|
39 # setup the network interface |
|
40 networkInterface = Preferences.getDebugger("NetworkInterface") |
|
41 if networkInterface == "all" or '.' in networkInterface: |
|
42 # IPv4 |
|
43 self.__hostAddress = '127.0.0.1' |
|
44 else: |
|
45 # IPv6 |
|
46 self.__hostAddress = '::1' |
|
47 self.listen(QHostAddress(self.__hostAddress)) |
|
48 |
|
49 self.newConnection.connect(self.__handleNewConnection) |
|
50 |
|
51 port = self.serverPort() |
|
52 ## Note: Need the port if started external in debugger: |
|
53 print('Refactoring server listening on: {0:d}'.format(port)) |
|
54 # __IGNORE_WARNING__ |
|
55 |
|
56 @pyqtSlot() |
|
57 def __handleNewConnection(self): |
|
58 """ |
|
59 Private slot for new incomming connections from the refactoring client. |
|
60 """ |
|
61 if self.__connection is not None: |
|
62 self.__connection.close() |
|
63 self.__connection = None |
|
64 |
|
65 connection = self.nextPendingConnection() |
|
66 if not connection.isValid(): |
|
67 return |
|
68 |
|
69 self.__connection = connection |
|
70 connection.readyRead.connect(self.__receiveJson) |
|
71 connection.disconnected.connect(self.__handleDisconnect) |
|
72 |
|
73 self.sendJson("ping", {}) |
|
74 |
|
75 @pyqtSlot() |
|
76 def __handleDisconnect(self): |
|
77 """ |
|
78 Private slot handling a disconnect of the refactoring client. |
|
79 """ |
|
80 if self.__connection is not None: |
|
81 self.__connection.close() |
|
82 |
|
83 self.__connection = None |
|
84 |
|
85 @pyqtSlot() |
|
86 def __receiveJson(self): |
|
87 """ |
|
88 Private slot handling received data from the client. |
|
89 """ |
|
90 while self.__connection and self.__connection.canReadLine(): |
|
91 data = self.__connection.readLine() |
|
92 jsonLine = bytes(data).decode("utf-8", 'backslashreplace') |
|
93 |
|
94 print("JSON Server: ", jsonLine) ##debug |
|
95 |
|
96 try: |
|
97 clientDict = json.loads(jsonLine.strip()) |
|
98 except (TypeError, ValueError) as err: |
|
99 E5MessageBox.critical( |
|
100 None, |
|
101 self.tr("JSON Protocol Error"), |
|
102 self.tr("""<p>The response received from the client""" |
|
103 """ could not be decoded. Please report""" |
|
104 """ this issue with the received data to the""" |
|
105 """ eric bugs email address.</p>""" |
|
106 """<p>Error: {0}</p>""" |
|
107 """<p>Data:<br/>{0}</p>""").format( |
|
108 str(err), Utilities.html_encode(jsonLine.strip())), |
|
109 E5MessageBox.StandardButtons( |
|
110 E5MessageBox.Ok)) |
|
111 return |
|
112 |
|
113 method = clientDict["method"] |
|
114 params = clientDict["params"] |
|
115 |
|
116 # TODO: remove these once done |
|
117 print("Method:", method) |
|
118 print("Params:", params) |
|
119 |
|
120 self.handleCall(method, params) |
|
121 |
|
122 def handleCall(self, method, params): |
|
123 """ |
|
124 Public method to handle a method call from the client. |
|
125 |
|
126 Note: This is an empty implementation that must be overridden in |
|
127 derived classes. |
|
128 |
|
129 @param method requested method name |
|
130 @type str |
|
131 @param params dictionary with method specific parameters |
|
132 @type dict |
|
133 """ |
|
134 pass |
|
135 |
|
136 def sendJson(self, command, params): |
|
137 """ |
|
138 Public method to send a single refactoring command to the client. |
|
139 |
|
140 @param command command name to be sent |
|
141 @type str |
|
142 @param params dictionary of named parameters for the command |
|
143 @type dict |
|
144 """ |
|
145 commandDict = { |
|
146 "jsonrpc": "2.0", |
|
147 "method": command, |
|
148 "params": params, |
|
149 } |
|
150 cmd = json.dumps(commandDict) + '\n' |
|
151 |
|
152 if self.__connection is not None: |
|
153 self.__connection.write(cmd.encode('utf8', 'backslashreplace')) |
|
154 |
|
155 def startClient(self, interpreter, clientScript, clientArgs): |
|
156 """ |
|
157 Public method to start the client process. |
|
158 |
|
159 @param interpreter interpreter to be used for the client |
|
160 @type str |
|
161 @param clientScript path to the client script |
|
162 @type str |
|
163 @param clientArgs list of arguments for the client |
|
164 @return flag indicating a successful client start |
|
165 @rtype bool |
|
166 """ |
|
167 if interpreter == "" or not Utilities.isinpath(interpreter): |
|
168 return False |
|
169 |
|
170 proc = QProcess() |
|
171 proc.setProcessChannelMode(QProcess.ForwardedChannels) |
|
172 args = [clientScript, self.__hostAddress, str(self.serverPort())] |
|
173 args.extend(clientArgs) |
|
174 proc.start(interpreter, args) |
|
175 if not proc.waitForStarted(10000): |
|
176 proc = None |
|
177 |
|
178 self.__clientProcess = proc |
|
179 |
|
180 return proc is not None |
|
181 |
|
182 def stopClient(self): |
|
183 """ |
|
184 Public method to stop the client process. |
|
185 """ |
|
186 self.__clientProcess.close() |
|
187 self.__clientProcess = None |
|
188 |
|
189 # |
|
190 # eflag: noqa = M801 |