Plugins/VcsPlugins/vcsMercurial/HgClient.py

changeset 1240
4d5fc346bd3b
child 1241
09c6155ee612
diff -r 697757468865 -r 4d5fc346bd3b Plugins/VcsPlugins/vcsMercurial/HgClient.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/HgClient.py	Sun Aug 28 18:53:13 2011 +0200
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an interface to the Mercurial command server.
+"""
+
+import struct
+import io
+
+from PyQt4.QtCore import QProcess, QProcessEnvironment, QObject, QByteArray, \
+    QCoreApplication
+
+import Preferences
+
+
+class HgClient(QObject):
+    """
+    Class implementing the Mercurial command server interface.
+    """
+    InputFormat = ">I"
+    OutputFormat = ">cI"
+    OutputFormatSize = struct.calcsize(OutputFormat)
+    ReturnFormat = ">i"
+    
+    def __init__(self, repoPath, encoding, parent=None):
+        """
+        Constructor
+        
+        @param repoPath root directory of the repository (string)
+        @param encoding encoding to be used by the command server (string)
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__server = QProcess()
+        self.__started = False
+        self.__version = None
+        self.__encoding = Preferences.getSystem("IOEncoding")
+        
+        # connect signals
+        self.__server.finished.connect(self.__serverFinished)
+        
+        # generate command line and environment
+        self.__serverArgs = []
+        self.__serverArgs.append("serve")
+        self.__serverArgs.append("--cmdserver")
+        self.__serverArgs.append("pipe")
+        self.__serverArgs.append("--config")
+        self.__serverArgs.append("ui.interactive=True")
+        if repoPath:
+            self.__serverArgs.append("--repository")
+            self.__serverArgs.append(repoPath)
+        
+        if encoding:
+            env = QProcessEnvironment.systemEnvironment()
+            env.insert("HGENCODING", encoding)
+            self.__server.setProcessEnvironment(env)
+            self.__encoding = encoding
+    
+    def startServer(self):
+        """
+        Public method to start the command server.
+        
+        @return tuple of flag indicating a successful start (boolean) and
+            an error message (string) in case of failure
+        """
+        self.__server.start('hg', self.__serverArgs)
+        serverStarted = self.__server.waitForStarted()
+        if not serverStarted:
+            return False, self.trUtf8(
+                    'The process {0} could not be started. '
+                    'Ensure, that it is in the search path.'
+                ).format('hg')
+        
+        self.__server.setReadChannel(QProcess.StandardOutput)
+        ok, error = self.__readHello()
+        self.__started = ok
+        return ok, error
+    
+    def stopServer(self):
+        """
+        Public method to stop the command server.
+        """
+        self.__server.closeWriteChannel()
+        self.__server.waitForFinished()
+    
+    def restartServer(self):
+        """
+        Public method to restart the command server.
+        
+        @return tuple of flag indicating a successful start (boolean) and
+            an error message (string) in case of failure
+        """
+        self.stopServer()
+        return self.startServer()
+    
+    def __readHello(self):
+        """
+        Private method to read the hello message sent by the command server.
+        
+        @return tuple of flag indicating success (boolean) and an error message
+            in case of failure (string)
+        """
+        ch, msg = self.__readChannel()
+        if not ch:
+            return False, self.trUtf8("Did not receive the 'hello' message.")
+        elif ch != "o":
+            return False, self.trUtf8("Received data on unexpected channel.")
+        
+        msg = msg.split("\n")
+        
+        if not msg[0].startswith("capabilities: "):
+            return False, self.trUtf8("Bad 'hello' message, expected 'capabilities: '"
+                                      " but got '{0}'.").format(msg[0])
+        self.__capabilities = msg[0][len('capabilities: '):]
+        if not self.__capabilities:
+            return False, self.trUtf8("'capabilities' message did not contain"
+                                      " any capability.")
+        
+        self.__capabilities = set(self.__capabilities.split())
+        if "runcommand" not in self.__capabilities:
+            return False, "'capabilities' did not contain 'runcommand'."
+        
+        if not msg[1].startswith("encoding: "):
+            return False, self.trUtf8("Bad 'hello' message, expected 'encoding: '"
+                                      " but got '{0}'.").format(msg[1])
+        encoding = msg[1][len('encoding: '):]
+        if not encoding:
+            return False, self.trUtf8("'encoding' message did not contain"
+                                      " any encoding.")
+        self.__encoding = encoding
+        
+        return True, ""
+    
+    def __serverFinished(self, exitCode, exitStatus):
+        """
+        Private slot connected to the finished signal.
+        
+        @param exitCode exit code of the process (integer)
+        @param exitStatus exit status of the process (QProcess.ExitStatus)
+        """
+        self.__started = False
+    
+    def __readChannel(self):
+        """
+        Private method to read data from the command server.
+        
+        @return tuple of channel designator and channel data (string, integer or string)
+        """
+        if self.__server.bytesAvailable() > 0 or \
+           self.__server.waitForReadyRead(10000):
+            data = bytes(self.__server.read(HgClient.OutputFormatSize))
+            if not data:
+                return "", ""
+            
+            channel, length = struct.unpack(HgClient.OutputFormat, data)
+            channel = channel.decode(self.__encoding)
+            if channel in "IL":
+                return channel, length
+            else:
+                return (channel,
+                        str(self.__server.read(length), self.__encoding, "replace"))
+        else:
+            return "", ""
+    
+    def __writeDataBlock(self, data):
+        """
+        Private slot to write some data to the command server.
+        
+        @param data data to be sent (string)
+        """
+        if not isinstance(data, bytes):
+            data = data.encode(self.__encoding)
+        self.__server.write(QByteArray(struct.pack(HgClient.InputFormat, len(data))))
+        self.__server.write(QByteArray(data))
+        self.__server.waitForBytesWritten()
+    
+    def __runcommand(self, args, inputChannels, outputChannels):
+        """
+        Private method to run a command in the server (low level).
+        
+        @param args list of arguments for the command (list of string)
+        @param inputChannels dictionary of input channels. The dictionary must
+            have the keys 'I' and 'L' and each entry must be a function receiving
+            the number of bytes to write.
+        @param outputChannels dictionary of output channels. The dictionary must
+            have the keys 'o' and 'e' and each entry must be a function receiving
+            the data.
+        """
+        if not self.__started:
+            return -1
+        
+        self.__server.write(QByteArray(b'runcommand\n'))
+        self.__writeDataBlock('\0'.join(args))
+        
+        while True:
+            QCoreApplication.processEvents()
+            if self.__server.bytesAvailable() == 0:
+                continue
+            channel, data = self.__readChannel()
+            
+            # input channels
+            if channel in inputChannels:
+                self.__writeDataBlock(inputChannels[channel](data))
+            
+            # output channels
+            elif channel in outputChannels:
+                outputChannels[channel](data)
+            
+            # result channel, command is finished
+            elif channel == "r":
+                return struct.unpack(HgClient.ReturnFormat, 
+                                     data.encode(self.__encoding))[0]
+            
+            # unexpected but required channel
+            elif channel.isupper():
+                raise RuntimeError(
+                    "Unexpected but required channel '{0}'.".format(channel))
+            
+            # optional channels
+            else:
+                pass
+    
+    def runcommand(self, args, prompt=None, input=None):
+        """
+        Public method to execute a command via the command server.
+        
+        @param args list of arguments for the command (list of string)
+        @param prompt function to reply to prompts by the server. It
+            receives the max number of bytes to return and the contents
+            of the output channel received so far.
+        @param input function to reply to bulk data requests by the server.
+            It receives the max number of bytes to return.
+        @return output and errors of the command server (string)
+        """
+        output = io.StringIO()
+        error = io.StringIO()
+        outputChannels = {
+            "o": output.write,
+            "e": error.write
+        }
+        
+        inputChannels = {}
+        if prompt is not None:
+            def func(size):
+                reply = prompt(size, output.getvalue())
+                return reply
+            inputChannels["L"] = func
+        if input is not None:
+            inputChannels["I"] = input
+        
+        self.__runcommand(args, inputChannels, outputChannels)
+        out = output.getvalue()
+        err = error.getvalue()
+        
+        return out, err
+        

eric ide

mercurial