E5Network

Sun, 09 May 2021 14:53:27 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 09 May 2021 14:53:27 +0200
changeset 8300
72ba9635ec5c
parent 8299
58cf45497dc0
child 8301
952a05857e81

E5Network
- added base classes for a JSON based server and client (examples see rope and jedi plug-in)

eric6.epj file | annotate | diff | comparison | revisions
eric6/APIs/Python3/eric6.api file | annotate | diff | comparison | revisions
eric6/APIs/Python3/eric6.bas file | annotate | diff | comparison | revisions
eric6/Documentation/Help/source.qch file | annotate | diff | comparison | revisions
eric6/Documentation/Help/source.qhp file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.E5Network.E5JsonClient.html file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.E5Network.E5JsonServer.html file | annotate | diff | comparison | revisions
eric6/Documentation/Source/index-eric6.E5Network.html file | annotate | diff | comparison | revisions
eric6/E5Network/E5JsonClient.py file | annotate | diff | comparison | revisions
eric6/E5Network/E5JsonServer.py file | annotate | diff | comparison | revisions
--- a/eric6.epj	Sun May 09 14:34:26 2021 +0200
+++ b/eric6.epj	Sun May 09 14:53:27 2021 +0200
@@ -2275,7 +2275,9 @@
       "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyNodeVisitor.py",
       "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsFunctionVisitor.py",
       "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsEnums.py",
-      "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsCheckerDefaults.py"
+      "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsCheckerDefaults.py",
+      "eric6/E5Network/E5JsonClient.py",
+      "eric6/E5Network/E5JsonServer.py"
     ],
     "SPELLEXCLUDES": "Dictionaries/excludes.dic",
     "SPELLLANGUAGE": "en_US",
--- a/eric6/APIs/Python3/eric6.api	Sun May 09 14:34:26 2021 +0200
+++ b/eric6/APIs/Python3/eric6.api	Sun May 09 14:53:27 2021 +0200
@@ -1680,6 +1680,18 @@
 eric6.E5Network.E5GoogleMailHelpers.TOKEN_FILE?7
 eric6.E5Network.E5GoogleMailHelpers.getInstallCommand?4()
 eric6.E5Network.E5GoogleMailHelpers.isClientSecretFileAvailable?4()
+eric6.E5Network.E5JsonClient.E5JsonClient.handleCall?4(method, params)
+eric6.E5Network.E5JsonClient.E5JsonClient.run?4()
+eric6.E5Network.E5JsonClient.E5JsonClient.sendJson?4(command, params)
+eric6.E5Network.E5JsonClient.E5JsonClient?1(host, port, idString="")
+eric6.E5Network.E5JsonServer.E5JsonServer.connectionNames?4()
+eric6.E5Network.E5JsonServer.E5JsonServer.handleCall?4(method, params)
+eric6.E5Network.E5JsonServer.E5JsonServer.handleNewConnection?4()
+eric6.E5Network.E5JsonServer.E5JsonServer.sendJson?4(command, params, flush=False, idString="")
+eric6.E5Network.E5JsonServer.E5JsonServer.startClient?4(interpreter, clientScript, clientArgs, idString="", environment=None)
+eric6.E5Network.E5JsonServer.E5JsonServer.stopAllClients?4()
+eric6.E5Network.E5JsonServer.E5JsonServer.stopClient?4(idString="")
+eric6.E5Network.E5JsonServer.E5JsonServer?1(name="", multiplex=False, parent=None)
 eric6.E5Network.E5NetworkHeaderDetailsDialog.E5NetworkHeaderDetailsDialog.setData?4(name, value)
 eric6.E5Network.E5NetworkHeaderDetailsDialog.E5NetworkHeaderDetailsDialog?1(parent=None)
 eric6.E5Network.E5NetworkProxyFactory.E5NetworkProxyFactory.queryProxy?4(query)
--- a/eric6/APIs/Python3/eric6.bas	Sun May 09 14:34:26 2021 +0200
+++ b/eric6/APIs/Python3/eric6.bas	Sun May 09 14:53:27 2021 +0200
@@ -193,6 +193,7 @@
 E5GoogleMailAuthBrowser QDialog
 E5GraphicsView QGraphicsView
 E5HorizontalToolBox E5TabWidget
+E5JsonServer QTcpServer
 E5Led QWidget
 E5LedType enum.Enum
 E5LineEdit QLineEdit
Binary file eric6/Documentation/Help/source.qch has changed
--- a/eric6/Documentation/Help/source.qhp	Sun May 09 14:34:26 2021 +0200
+++ b/eric6/Documentation/Help/source.qhp	Sun May 09 14:53:27 2021 +0200
@@ -152,6 +152,8 @@
             <section title="eric6.E5Network.E5Ftp" ref="eric6.E5Network.E5Ftp.html" />
             <section title="eric6.E5Network.E5GoogleMail" ref="eric6.E5Network.E5GoogleMail.html" />
             <section title="eric6.E5Network.E5GoogleMailHelpers" ref="eric6.E5Network.E5GoogleMailHelpers.html" />
+            <section title="eric6.E5Network.E5JsonClient" ref="eric6.E5Network.E5JsonClient.html" />
+            <section title="eric6.E5Network.E5JsonServer" ref="eric6.E5Network.E5JsonServer.html" />
             <section title="eric6.E5Network.E5NetworkHeaderDetailsDialog" ref="eric6.E5Network.E5NetworkHeaderDetailsDialog.html" />
             <section title="eric6.E5Network.E5NetworkProxyFactory" ref="eric6.E5Network.E5NetworkProxyFactory.html" />
             <section title="eric6.E5Network.E5RFC6266" ref="eric6.E5Network.E5RFC6266.html" />
@@ -4165,6 +4167,25 @@
       <keyword name="E5HorizontalToolBox.removeItem" id="E5HorizontalToolBox.removeItem" ref="eric6.E5Gui.E5ToolBox.html#E5HorizontalToolBox.removeItem" />
       <keyword name="E5HorizontalToolBox.setItemEnabled" id="E5HorizontalToolBox.setItemEnabled" ref="eric6.E5Gui.E5ToolBox.html#E5HorizontalToolBox.setItemEnabled" />
       <keyword name="E5HorizontalToolBox.setItemToolTip" id="E5HorizontalToolBox.setItemToolTip" ref="eric6.E5Gui.E5ToolBox.html#E5HorizontalToolBox.setItemToolTip" />
+      <keyword name="E5JsonClient" id="E5JsonClient" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient" />
+      <keyword name="E5JsonClient (Constructor)" id="E5JsonClient (Constructor)" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.__init__" />
+      <keyword name="E5JsonClient (Module)" id="E5JsonClient (Module)" ref="eric6.E5Network.E5JsonClient.html" />
+      <keyword name="E5JsonClient.__receiveJson" id="E5JsonClient.__receiveJson" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.__receiveJson" />
+      <keyword name="E5JsonClient.handleCall" id="E5JsonClient.handleCall" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.handleCall" />
+      <keyword name="E5JsonClient.run" id="E5JsonClient.run" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.run" />
+      <keyword name="E5JsonClient.sendJson" id="E5JsonClient.sendJson" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.sendJson" />
+      <keyword name="E5JsonServer" id="E5JsonServer" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer" />
+      <keyword name="E5JsonServer (Constructor)" id="E5JsonServer (Constructor)" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.__init__" />
+      <keyword name="E5JsonServer (Module)" id="E5JsonServer (Module)" ref="eric6.E5Network.E5JsonServer.html" />
+      <keyword name="E5JsonServer.__handleDisconnect" id="E5JsonServer.__handleDisconnect" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.__handleDisconnect" />
+      <keyword name="E5JsonServer.__receiveJson" id="E5JsonServer.__receiveJson" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.__receiveJson" />
+      <keyword name="E5JsonServer.connectionNames" id="E5JsonServer.connectionNames" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.connectionNames" />
+      <keyword name="E5JsonServer.handleCall" id="E5JsonServer.handleCall" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.handleCall" />
+      <keyword name="E5JsonServer.handleNewConnection" id="E5JsonServer.handleNewConnection" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.handleNewConnection" />
+      <keyword name="E5JsonServer.sendJson" id="E5JsonServer.sendJson" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.sendJson" />
+      <keyword name="E5JsonServer.startClient" id="E5JsonServer.startClient" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.startClient" />
+      <keyword name="E5JsonServer.stopAllClients" id="E5JsonServer.stopAllClients" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.stopAllClients" />
+      <keyword name="E5JsonServer.stopClient" id="E5JsonServer.stopClient" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.stopClient" />
       <keyword name="E5Led" id="E5Led" ref="eric6.E5Gui.E5Led.html#E5Led" />
       <keyword name="E5Led (Constructor)" id="E5Led (Constructor)" ref="eric6.E5Gui.E5Led.html#E5Led.__init__" />
       <keyword name="E5Led (Module)" id="E5Led (Module)" ref="eric6.E5Gui.E5Led.html" />
@@ -18732,6 +18753,8 @@
       <file>eric6.E5Network.E5Ftp.html</file>
       <file>eric6.E5Network.E5GoogleMail.html</file>
       <file>eric6.E5Network.E5GoogleMailHelpers.html</file>
+      <file>eric6.E5Network.E5JsonClient.html</file>
+      <file>eric6.E5Network.E5JsonServer.html</file>
       <file>eric6.E5Network.E5NetworkHeaderDetailsDialog.html</file>
       <file>eric6.E5Network.E5NetworkProxyFactory.html</file>
       <file>eric6.E5Network.E5RFC6266.html</file>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Documentation/Source/eric6.E5Network.E5JsonClient.html	Sun May 09 14:53:27 2021 +0200
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric6.E5Network.E5JsonClient</title>
+<meta charset="UTF-8">
+<style>
+body {
+    background: #EDECE6;
+    margin: 0em 1em 10em 1em;
+    color: black;
+}
+
+h1 { color: white; background: #85774A; }
+h2 { color: white; background: #85774A; }
+h3 { color: white; background: #9D936E; }
+h4 { color: white; background: #9D936E; }
+    
+a { color: #BA6D36; }
+
+</style>
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric6.E5Network.E5JsonClient</h1>
+
+<p>
+Module implementing the JSON based client base class.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#E5JsonClient">E5JsonClient</a></td>
+<td>Class implementing a JSON based client base class.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="E5JsonClient" ID="E5JsonClient"></a>
+<h2>E5JsonClient</h2>
+
+<p>
+    Class implementing a JSON based client base class.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#E5JsonClient.__init__">E5JsonClient</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#E5JsonClient.__receiveJson">__receiveJson</a></td>
+<td>Private method to receive a JSON encode command and data from the server.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonClient.handleCall">handleCall</a></td>
+<td>Public method to handle a method call from the server.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonClient.run">run</a></td>
+<td>Public method implementing the main loop of the client.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonClient.sendJson">sendJson</a></td>
+<td>Public method to send a single refactoring command to the server.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="E5JsonClient.__init__" ID="E5JsonClient.__init__"></a>
+<h4>E5JsonClient (Constructor)</h4>
+<b>E5JsonClient</b>(<i>host, port, idString=""</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>host</i> (str)</dt>
+<dd>
+ip address the background service is listening
+</dd>
+<dt><i>port</i> (int)</dt>
+<dd>
+port of the background service
+</dd>
+<dt><i>idString</i> (str)</dt>
+<dd>
+assigned client id to be sent back to the server in
+            order to identify the connection
+</dd>
+</dl>
+<a NAME="E5JsonClient.__receiveJson" ID="E5JsonClient.__receiveJson"></a>
+<h4>E5JsonClient.__receiveJson</h4>
+<b>__receiveJson</b>(<i></i>)
+
+<p>
+        Private method to receive a JSON encode command and data from the
+        server.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing the received command and a dictionary
+            containing the associated data
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (str, dict)
+</dd>
+</dl>
+<a NAME="E5JsonClient.handleCall" ID="E5JsonClient.handleCall"></a>
+<h4>E5JsonClient.handleCall</h4>
+<b>handleCall</b>(<i>method, params</i>)
+
+<p>
+        Public method to handle a method call from the server.
+</p>
+<p>
+        Note: This is an empty implementation that must be overridden in
+        derived classes.
+</p>
+<dl>
+
+<dt><i>method</i> (str)</dt>
+<dd>
+requested method name
+</dd>
+<dt><i>params</i> (dict)</dt>
+<dd>
+dictionary with method specific parameters
+</dd>
+</dl>
+<a NAME="E5JsonClient.run" ID="E5JsonClient.run"></a>
+<h4>E5JsonClient.run</h4>
+<b>run</b>(<i></i>)
+
+<p>
+        Public method implementing the main loop of the client.
+</p>
+<a NAME="E5JsonClient.sendJson" ID="E5JsonClient.sendJson"></a>
+<h4>E5JsonClient.sendJson</h4>
+<b>sendJson</b>(<i>command, params</i>)
+
+<p>
+        Public method to send a single refactoring command to the server.
+</p>
+<dl>
+
+<dt><i>command</i> (str)</dt>
+<dd>
+command name to be sent
+</dd>
+<dt><i>params</i> (dict)</dt>
+<dd>
+dictionary of named parameters for the command
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Documentation/Source/eric6.E5Network.E5JsonServer.html	Sun May 09 14:53:27 2021 +0200
@@ -0,0 +1,310 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric6.E5Network.E5JsonServer</title>
+<meta charset="UTF-8">
+<style>
+body {
+    background: #EDECE6;
+    margin: 0em 1em 10em 1em;
+    color: black;
+}
+
+h1 { color: white; background: #85774A; }
+h2 { color: white; background: #85774A; }
+h3 { color: white; background: #9D936E; }
+h4 { color: white; background: #9D936E; }
+    
+a { color: #BA6D36; }
+
+</style>
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric6.E5Network.E5JsonServer</h1>
+
+<p>
+Module implementing the JSON based server base class.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#E5JsonServer">E5JsonServer</a></td>
+<td>Class implementing a JSON based server base class.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="E5JsonServer" ID="E5JsonServer"></a>
+<h2>E5JsonServer</h2>
+
+<p>
+    Class implementing a JSON based server base class.
+</p>
+<h3>Derived from</h3>
+QTcpServer
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#E5JsonServer.__init__">E5JsonServer</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.__handleDisconnect">__handleDisconnect</a></td>
+<td>Private slot handling a disconnect of the client.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.__receiveJson">__receiveJson</a></td>
+<td>Private slot handling received data from the client.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.connectionNames">connectionNames</a></td>
+<td>Public method to get the list of active connection names.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.handleCall">handleCall</a></td>
+<td>Public method to handle a method call from the client.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.handleNewConnection">handleNewConnection</a></td>
+<td>Public slot for new incoming connections from a client.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.sendJson">sendJson</a></td>
+<td>Public method to send a single command to a client.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.startClient">startClient</a></td>
+<td>Public method to start a client process.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.stopAllClients">stopAllClients</a></td>
+<td>Public method to stop all clients.</td>
+</tr>
+<tr>
+<td><a href="#E5JsonServer.stopClient">stopClient</a></td>
+<td>Public method to stop a client process.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="E5JsonServer.__init__" ID="E5JsonServer.__init__"></a>
+<h4>E5JsonServer (Constructor)</h4>
+<b>E5JsonServer</b>(<i>name="", multiplex=False, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>name</i> (str)</dt>
+<dd>
+name of the server (used for output only)
+</dd>
+<dt><i>multiplex</i> (bool)</dt>
+<dd>
+flag indicating a multiplexing server
+</dd>
+<dt><i>parent</i> (QObject)</dt>
+<dd>
+parent object
+</dd>
+</dl>
+<a NAME="E5JsonServer.__handleDisconnect" ID="E5JsonServer.__handleDisconnect"></a>
+<h4>E5JsonServer.__handleDisconnect</h4>
+<b>__handleDisconnect</b>(<i>idString</i>)
+
+<p>
+        Private slot handling a disconnect of the client.
+</p>
+<dl>
+
+<dt><i>idString</i> (str)</dt>
+<dd>
+id of the connection been disconnected
+</dd>
+</dl>
+<a NAME="E5JsonServer.__receiveJson" ID="E5JsonServer.__receiveJson"></a>
+<h4>E5JsonServer.__receiveJson</h4>
+<b>__receiveJson</b>(<i>idString</i>)
+
+<p>
+        Private slot handling received data from the client.
+</p>
+<dl>
+
+<dt><i>idString</i> (str)</dt>
+<dd>
+id of the connection been disconnected
+</dd>
+</dl>
+<a NAME="E5JsonServer.connectionNames" ID="E5JsonServer.connectionNames"></a>
+<h4>E5JsonServer.connectionNames</h4>
+<b>connectionNames</b>(<i></i>)
+
+<p>
+        Public method to get the list of active connection names.
+</p>
+<p>
+        If this is not a multiplexing server, an empty list is returned.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of active connection names
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<a NAME="E5JsonServer.handleCall" ID="E5JsonServer.handleCall"></a>
+<h4>E5JsonServer.handleCall</h4>
+<b>handleCall</b>(<i>method, params</i>)
+
+<p>
+        Public method to handle a method call from the client.
+</p>
+<p>
+        Note: This is an empty implementation that must be overridden in
+        derived classes.
+</p>
+<dl>
+
+<dt><i>method</i> (str)</dt>
+<dd>
+requested method name
+</dd>
+<dt><i>params</i> (dict)</dt>
+<dd>
+dictionary with method specific parameters
+</dd>
+</dl>
+<a NAME="E5JsonServer.handleNewConnection" ID="E5JsonServer.handleNewConnection"></a>
+<h4>E5JsonServer.handleNewConnection</h4>
+<b>handleNewConnection</b>(<i></i>)
+
+<p>
+        Public slot for new incoming connections from a client.
+</p>
+<a NAME="E5JsonServer.sendJson" ID="E5JsonServer.sendJson"></a>
+<h4>E5JsonServer.sendJson</h4>
+<b>sendJson</b>(<i>command, params, flush=False, idString=""</i>)
+
+<p>
+        Public method to send a single command to a client.
+</p>
+<dl>
+
+<dt><i>command</i> (str)</dt>
+<dd>
+command name to be sent
+</dd>
+<dt><i>params</i> (dict)</dt>
+<dd>
+dictionary of named parameters for the command
+</dd>
+<dt><i>flush</i> (bool)</dt>
+<dd>
+flag indicating to flush the data to the socket
+</dd>
+<dt><i>idString</i> (str)</dt>
+<dd>
+id of the connection to send data to
+</dd>
+</dl>
+<a NAME="E5JsonServer.startClient" ID="E5JsonServer.startClient"></a>
+<h4>E5JsonServer.startClient</h4>
+<b>startClient</b>(<i>interpreter, clientScript, clientArgs, idString="", environment=None</i>)
+
+<p>
+        Public method to start a client process.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the client
+</dd>
+<dt><i>clientScript</i> (str)</dt>
+<dd>
+path to the client script
+</dd>
+<dt><i>clientArgs</i></dt>
+<dd>
+list of arguments for the client
+</dd>
+<dt><i>idString</i> (str)</dt>
+<dd>
+id of the client to be started
+</dd>
+<dt><i>environment</i> (dict)</dt>
+<dd>
+dictionary of environment settings to pass
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating a successful client start
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="E5JsonServer.stopAllClients" ID="E5JsonServer.stopAllClients"></a>
+<h4>E5JsonServer.stopAllClients</h4>
+<b>stopAllClients</b>(<i></i>)
+
+<p>
+        Public method to stop all clients.
+</p>
+<a NAME="E5JsonServer.stopClient" ID="E5JsonServer.stopClient"></a>
+<h4>E5JsonServer.stopClient</h4>
+<b>stopClient</b>(<i>idString=""</i>)
+
+<p>
+        Public method to stop a client process.
+</p>
+<dl>
+
+<dt><i>idString</i> (str)</dt>
+<dd>
+id of the client to be stopped
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- a/eric6/Documentation/Source/index-eric6.E5Network.html	Sun May 09 14:34:26 2021 +0200
+++ b/eric6/Documentation/Source/index-eric6.E5Network.html	Sun May 09 14:53:27 2021 +0200
@@ -50,6 +50,14 @@
 <td>Module implementing some helpers for Google mail.</td>
 </tr>
 <tr>
+<td><a href="eric6.E5Network.E5JsonClient.html">E5JsonClient</a></td>
+<td>Module implementing the JSON based client base class.</td>
+</tr>
+<tr>
+<td><a href="eric6.E5Network.E5JsonServer.html">E5JsonServer</a></td>
+<td>Module implementing the JSON based server base class.</td>
+</tr>
+<tr>
 <td><a href="eric6.E5Network.E5NetworkHeaderDetailsDialog.html">E5NetworkHeaderDetailsDialog</a></td>
 <td>Module implementing a dialog to show the data of a response or reply header.</td>
 </tr>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Network/E5JsonClient.py	Sun May 09 14:53:27 2021 +0200
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the JSON based client base class.
+"""
+
+import io
+import sys
+import socket
+import select
+import traceback
+import json
+import contextlib
+
+
+class E5JsonClient:
+    """
+    Class implementing a JSON based client base class.
+    """
+    def __init__(self, host, port, idString=""):
+        """
+        Constructor
+        
+        @param host ip address the background service is listening
+        @type str
+        @param port port of the background service
+        @type int
+        @param idString assigned client id to be sent back to the server in
+            order to identify the connection
+        @type str
+        """
+        self.__connection = socket.create_connection((host, port))
+        if idString:
+            reply = idString + '\n'
+            self.__connection.sendall(reply.encode('utf8', 'backslashreplace'))
+    
+    def sendJson(self, command, params):
+        """
+        Public method to send a single refactoring command to the server.
+        
+        @param command command name to be sent
+        @type str
+        @param params dictionary of named parameters for the command
+        @type dict
+        """
+        commandDict = {
+            "jsonrpc": "2.0",
+            "method": command,
+            "params": params,
+        }
+        cmd = json.dumps(commandDict) + '\n'
+        self.__connection.sendall(cmd.encode('utf8', 'backslashreplace'))
+    
+    def __receiveJson(self):
+        """
+        Private method to receive a JSON encode command and data from the
+        server.
+        
+        @return tuple containing the received command and a dictionary
+            containing the associated data
+        @rtype tuple of (str, dict)
+        """
+        # step 1: receive the data
+        # The JSON RPC string is prefixed by a 9 character long length field.
+        length = self.__connection.recv(9)
+        if len(length) < 9:
+            # invalid length string received
+            return None, None
+        
+        length = int(length)
+        data = b''
+        while len(data) < length:
+            newData = self.__connection.recv(length - len(data))
+            if not newData:
+                return None, None
+            
+            data += newData
+        
+        # step 2: decode and convert the data
+        line = data.decode(
+            'utf8', 'backslashreplace')
+        try:
+            commandDict = json.loads(line.strip())
+        except (TypeError, ValueError) as err:
+            self.sendJson("ClientException", {
+                "ExceptionType": "ProtocolError",
+                "ExceptionValue": str(err),
+                "ProtocolData": line.strip(),
+            })
+            return None, None
+        
+        method = commandDict["method"]
+        params = commandDict["params"]
+        
+        return method, params
+    
+    def handleCall(self, method, params):
+        """
+        Public method to handle a method call from the server.
+        
+        Note: This is an empty implementation that must be overridden in
+        derived classes.
+        
+        @param method requested method name
+        @type str
+        @param params dictionary with method specific parameters
+        @type dict
+        """
+        pass
+    
+    def run(self):
+        """
+        Public method implementing the main loop of the client.
+        """
+        try:
+            selectErrors = 0
+            while selectErrors <= 10:  # selected arbitrarily
+                try:
+                    rrdy, wrdy, xrdy = select.select(
+                        [self.__connection], [], [])
+                    
+                    # Just waiting for self.__connection. Therefor no check
+                    # needed.
+                    method, params = self.__receiveJson()
+                    if method is None:
+                        selectErrors += 1
+                    elif method == "Exit":
+                        break
+                    else:
+                        self.handleCall(method, params)
+                        
+                        # reset select errors
+                        selectErrors = 0
+                
+                except (select.error, KeyboardInterrupt, socket.error):
+                    selectErrors += 1
+        
+        except Exception:
+            exctype, excval, exctb = sys.exc_info()
+            tbinfofile = io.StringIO()
+            traceback.print_tb(exctb, None, tbinfofile)
+            tbinfofile.seek(0)
+            tbinfo = tbinfofile.read()
+            del exctb
+            self.sendJson("ClientException", {
+                "ExceptionType": str(exctype),
+                "ExceptionValue": str(excval),
+                "Traceback": tbinfo,
+            })
+
+        # Give time to process latest response on server side
+        with contextlib.suppress(socket.error, OSError):
+            self.__connection.shutdown(socket.SHUT_RDWR)
+            self.__connection.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Network/E5JsonServer.py	Sun May 09 14:53:27 2021 +0200
@@ -0,0 +1,313 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the JSON based server base class.
+"""
+
+import contextlib
+import json
+
+from PyQt5.QtCore import (
+    pyqtSlot, QProcess, QProcessEnvironment, QCoreApplication, QEventLoop,
+    QTimer
+)
+from PyQt5.QtNetwork import QTcpServer, QHostAddress
+
+from E5Gui import E5MessageBox
+
+import Preferences
+import Utilities
+
+
+class E5JsonServer(QTcpServer):
+    """
+    Class implementing a JSON based server base class.
+    """
+    def __init__(self, name="", multiplex=False, parent=None):
+        """
+        Constructor
+        
+        @param name name of the server (used for output only)
+        @type str
+        @param multiplex flag indicating a multiplexing server
+        @type bool
+        @param parent parent object
+        @type QObject
+        """
+        super().__init__(parent)
+        
+        self.__name = name
+        self.__multiplex = multiplex
+        if self.__multiplex:
+            self.__clientProcesses = {}
+            self.__connections = {}
+        else:
+            self.__clientProcess = None
+            self.__connection = None
+        
+        # setup the network interface
+        networkInterface = Preferences.getDebugger("NetworkInterface")
+        if networkInterface == "all" or '.' in networkInterface:
+            # IPv4
+            self.__hostAddress = '127.0.0.1'
+        else:
+            # IPv6
+            self.__hostAddress = '::1'
+        self.listen(QHostAddress(self.__hostAddress))
+
+        self.newConnection.connect(self.handleNewConnection)
+        
+        port = self.serverPort()
+        ## Note: Need the port if client is started external in debugger.
+        print('JSON server ({1}) listening on: {0:d}'   # __IGNORE_WARNING__
+              .format(port, self.__name))
+    
+    @pyqtSlot()
+    def handleNewConnection(self):
+        """
+        Public slot for new incoming connections from a client.
+        """
+        connection = self.nextPendingConnection()
+        if not connection.isValid():
+            return
+        
+        if self.__multiplex:
+            if not connection.waitForReadyRead(3000):
+                return
+            idString = bytes(connection.readLine()).decode(
+                "utf-8", 'backslashreplace').strip()
+            if idString in self.__connections:
+                self.__connections[idString].close()
+            self.__connections[idString] = connection
+        else:
+            idString = ""
+            if self.__connection is not None:
+                self.__connection.close()
+            
+            self.__connection = connection
+        
+        connection.readyRead.connect(
+            lambda: self.__receiveJson(idString))
+        connection.disconnected.connect(
+            lambda: self.__handleDisconnect(idString))
+    
+    @pyqtSlot()
+    def __handleDisconnect(self, idString):
+        """
+        Private slot handling a disconnect of the client.
+        
+        @param idString id of the connection been disconnected
+        @type str
+        """
+        if idString:
+            if idString in self.__connections:
+                self.__connections[idString].close()
+                del self.__connections[idString]
+        else:
+            if self.__connection is not None:
+                self.__connection.close()
+            
+            self.__connection = None
+    
+    def connectionNames(self):
+        """
+        Public method to get the list of active connection names.
+        
+        If this is not a multiplexing server, an empty list is returned.
+        
+        @return list of active connection names
+        @rtype list of str
+        """
+        if self.__multiplex:
+            return list(self.__connections.keys())
+        else:
+            return []
+    
+    @pyqtSlot()
+    def __receiveJson(self, idString):
+        """
+        Private slot handling received data from the client.
+        
+        @param idString id of the connection been disconnected
+        @type str
+        """
+        if idString:
+            try:
+                connection = self.__connections[idString]
+            except KeyError:
+                connection = None
+        else:
+            connection = self.__connection
+        
+        while connection and connection.canReadLine():
+            data = connection.readLine()
+            jsonLine = bytes(data).decode("utf-8", 'backslashreplace')
+            
+            #- print("JSON Server ({0}): {1}".format(self.__name, jsonLine))
+            #- this is for debugging only
+            
+            try:
+                clientDict = json.loads(jsonLine.strip())
+            except (TypeError, ValueError) as err:
+                E5MessageBox.critical(
+                    None,
+                    self.tr("JSON Protocol Error"),
+                    self.tr("""<p>The response received from the client"""
+                            """ could not be decoded. Please report"""
+                            """ this issue with the received data to the"""
+                            """ eric bugs email address.</p>"""
+                            """<p>Error: {0}</p>"""
+                            """<p>Data:<br/>{1}</p>""").format(
+                        str(err), Utilities.html_encode(jsonLine.strip())),
+                    E5MessageBox.StandardButtons(
+                        E5MessageBox.Ok))
+                return
+            
+            self.handleCall(clientDict["method"], clientDict["params"])
+    
+    def sendJson(self, command, params, flush=False, idString=""):
+        """
+        Public method to send a single command to a client.
+        
+        @param command command name to be sent
+        @type str
+        @param params dictionary of named parameters for the command
+        @type dict
+        @param flush flag indicating to flush the data to the socket
+        @type bool
+        @param idString id of the connection to send data to
+        @type str
+        """
+        commandDict = {
+            "jsonrpc": "2.0",
+            "method": command,
+            "params": params,
+        }
+        cmd = json.dumps(commandDict) + '\n'
+        
+        if idString:
+            try:
+                connection = self.__connections[idString]
+            except KeyError:
+                connection = None
+        else:
+            connection = self.__connection
+        
+        if connection is not None:
+            data = cmd.encode('utf8', 'backslashreplace')
+            length = "{0:09d}".format(len(data))
+            connection.write(length.encode() + data)
+            if flush:
+                connection.flush()
+    
+    def startClient(self, interpreter, clientScript, clientArgs, idString="",
+                    environment=None):
+        """
+        Public method to start a client process.
+        
+        @param interpreter interpreter to be used for the client
+        @type str
+        @param clientScript path to the client script
+        @type str
+        @param clientArgs list of arguments for the client
+        @param idString id of the client to be started
+        @type str
+        @param environment dictionary of environment settings to pass
+        @type dict
+        @return flag indicating a successful client start
+        @rtype bool
+        """
+        if interpreter == "" or not Utilities.isinpath(interpreter):
+            return False
+        
+        proc = QProcess()
+        proc.setProcessChannelMode(
+            QProcess.ProcessChannelMode.ForwardedChannels)
+        if environment is not None:
+            env = QProcessEnvironment()
+            for key, value in list(environment.items()):
+                env.insert(key, value)
+            proc.setProcessEnvironment(env)
+        args = [clientScript, self.__hostAddress, str(self.serverPort())]
+        if idString:
+            args.append(idString)
+        args.extend(clientArgs)
+        proc.start(interpreter, args)
+        if not proc.waitForStarted(10000):
+            proc = None
+        
+        if idString:
+            self.__clientProcesses[idString] = proc
+            if proc:
+                timer = QTimer()
+                timer.setSingleShot(True)
+                timer.start(30000)           # 30s timeout
+                while (
+                    idString not in self.connectionNames() and
+                    timer.isActive()
+                ):
+                    # Give the event loop the chance to process the new
+                    # connection of the client (= slow start).
+                    QCoreApplication.processEvents(
+                        QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents)
+        else:
+            self.__clientProcess = proc
+        
+        return proc is not None
+    
+    def stopClient(self, idString=""):
+        """
+        Public method to stop a client process.
+        
+        @param idString id of the client to be stopped
+        @type str
+        """
+        self.sendJson("Exit", {}, flush=True, idString=idString)
+        
+        if idString:
+            try:
+                connection = self.__connections[idString]
+            except KeyError:
+                connection = None
+        else:
+            connection = self.__connection
+        if connection is not None:
+            connection.waitForDisconnected()
+        
+        if idString:
+            with contextlib.suppress(KeyError):
+                self .__clientProcesses[idString].close()
+                del self.__clientProcesses[idString]
+        else:
+            if self.__clientProcess is not None:
+                self.__clientProcess.close()
+                self.__clientProcess = None
+    
+    def stopAllClients(self):
+        """
+        Public method to stop all clients.
+        """
+        clientNames = self.connectionNames()[:]
+        for clientName in clientNames:
+            self.stopClient(clientName)
+    
+    #######################################################################
+    ## The following methods should be overridden by derived classes
+    #######################################################################
+    
+    def handleCall(self, method, params):
+        """
+        Public method to handle a method call from the client.
+        
+        Note: This is an empty implementation that must be overridden in
+        derived classes.
+        
+        @param method requested method name
+        @type str
+        @param params dictionary with method specific parameters
+        @type dict
+        """
+        pass

eric ide

mercurial