eric7/Toolbox/SingleApplication.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
diff -r 4e8b98454baa -r 800c432b34c8 eric7/Toolbox/SingleApplication.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Toolbox/SingleApplication.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2004 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the single application server and client.
+"""
+
+import json
+
+from PyQt5.QtCore import QByteArray
+from PyQt5.QtNetwork import QLocalServer, QLocalSocket
+
+from E5Gui import E5MessageBox
+
+import Utilities
+
+
+class SingleApplicationServer(QLocalServer):
+    """
+    Class implementing the single application server base class.
+    """
+    def __init__(self, name):
+        """
+        Constructor
+        
+        @param name name this server is listening to (string)
+        """
+        super().__init__()
+        
+        res = self.listen(name)
+        if not res:
+            # maybe it crashed last time
+            self.removeServer(name)
+            self.listen(name)
+        
+        self.newConnection.connect(self.__newConnection)
+
+        self.qsock = None
+
+    def __newConnection(self):
+        """
+        Private slot to handle a new connection.
+        """
+        sock = self.nextPendingConnection()
+
+        # If we already have a connection, refuse this one. It will be closed
+        # automatically.
+        if self.qsock is not None:
+            return
+
+        self.qsock = sock
+
+        self.qsock.readyRead.connect(self.__receiveJson)
+        self.qsock.disconnected.connect(self.__disconnected)
+
+    def __receiveJson(self):
+        """
+        Private method to receive the data from the client.
+        """
+        while self.qsock and self.qsock.canReadLine():
+            line = bytes(self.qsock.readLine()).decode()
+            
+##            print(line)          ## debug         # __IGNORE_WARNING_M891__
+            
+            try:
+                commandDict = json.loads(line.strip())
+            except (TypeError, ValueError) as err:
+                E5MessageBox.critical(
+                    None,
+                    self.tr("Single Application Protocol Error"),
+                    self.tr("""<p>The response received from the single"""
+                            """ application 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(line.strip())),
+                    E5MessageBox.StandardButtons(
+                        E5MessageBox.Ok))
+                return
+            
+            command = commandDict["command"]
+            arguments = commandDict["arguments"]
+            
+            self.handleCommand(command, arguments)
+    
+    def __disconnected(self):
+        """
+        Private method to handle the closure of the socket.
+        """
+        self.qsock = None
+    
+    def shutdown(self):
+        """
+        Public method used to shut down the server.
+        """
+        if self.qsock is not None:
+            self.qsock.readyRead.disconnect(self.__parseLine)
+            self.qsock.disconnected.disconnect(self.__disconnected)
+        
+        self.qsock = None
+        
+        self.close()
+
+    def handleCommand(self, command, arguments):
+        """
+        Public slot to handle the command sent by the client.
+        
+        <b>Note</b>: This method must be overridden by subclasses.
+        
+        @param command command sent by the client
+        @type str
+        @param arguments list of command arguments
+        @type list of str
+        @exception RuntimeError raised to indicate that this method must be
+            implemented by a subclass
+        """
+        raise RuntimeError("'handleCommand' must be overridden")
+
+
+class SingleApplicationClient:
+    """
+    Class implementing the single application client base class.
+    """
+    def __init__(self, name):
+        """
+        Constructor
+        
+        @param name name of the local server to connect to (string)
+        """
+        self.name = name
+        self.connected = False
+        
+    def connect(self, timeout=10000):
+        """
+        Public method to connect the single application client to its server.
+        
+        @param timeout connection timeout value in milliseconds
+        @type int
+        @return value indicating success or an error number. Value is one of:
+            <table>
+                <tr><td>0</td><td>No application is running</td></tr>
+                <tr><td>1</td><td>Application is already running</td></tr>
+            </table>
+        """
+        self.sock = QLocalSocket()
+        self.sock.connectToServer(self.name)
+        if self.sock.waitForConnected(timeout):
+            self.connected = True
+            return 1
+        else:
+            err = self.sock.error()
+            if err == QLocalSocket.LocalSocketError.ServerNotFoundError:
+                return 0
+            else:
+                return -err
+        
+    def disconnect(self):
+        """
+        Public method to disconnect from the Single Appliocation server.
+        """
+        self.sock.disconnectFromServer()
+        self.connected = False
+    
+    def processArgs(self, args):
+        """
+        Public method to process the command line args passed to the UI.
+        
+        <b>Note</b>: This method must be overridden by subclasses.
+        
+        @param args command line args (list of strings)
+        @exception RuntimeError raised to indicate that this method must be
+            implemented by a subclass
+        """
+        raise RuntimeError("'processArgs' must be overridden")
+    
+    def sendCommand(self, command, arguments):
+        """
+        Public method to send the command to the application server.
+        
+        @param command command to be sent to the server
+        @type str
+        @param arguments list of command arguments
+        @type list of str
+        """
+        if self.connected:
+            commandDict = {
+                "command": command,
+                "arguments": arguments,
+            }
+            self.sock.write(QByteArray(
+                "{0}\n".format(json.dumps(commandDict)).encode()
+            ))
+            self.sock.flush()
+        
+    def errstr(self):
+        """
+        Public method to return a meaningful error string for the last error.
+        
+        @return error string for the last error (string)
+        """
+        return self.sock.errorString()

eric ide

mercurial