Exemple #1
0
    def _createSocket(self, protocol_file):
        if self._socket:
            Logger.log("d", "Previous socket existed. Closing that first."
                       )  # temp debug logging
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # Hack for (at least) Linux. If the socket is connecting, the close will deadlock.
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            # If the error occurred due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            protocol_file = protocol_file.replace("\\", "/")

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Uranium protocol messages: %s",
                       self._socket.getLastError())

        if Application.getInstance().getCommandLineOption(
                "external-backend", False):
            Logger.log("i", "Listening for backend connections on %s",
                       self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #2
0
    def _createSocket(self, protocol_file):
        if self._socket:
            Logger.log("d", "Previous socket existed. Closing that first."
                       )  # temp debug logging
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # Hack for (at least) Linux. If the socket is connecting, the close will deadlock.
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            sleep(0.1)
            # If the error occurred due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()
            sleep(0.1)
            Logger.log("d", "SOCKET CLOSED !!!")

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            # Using sys.getfilesystemencoding() avoid the application crashing if it is
            # installed on a path with non-ascii characters GitHub issue #3907
            protocol_file = protocol_file.replace("\\", "/").encode(
                sys.getfilesystemencoding())
        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Uranium protocol messages: %s",
                       self._socket.getLastError())
        if UM.Application.Application.getInstance().getUseExternalBackend():
            Logger.log("i", "Listening for backend connections on %s",
                       self._port)
        self._socket.listen("127.0.0.1", self._port)
Exemple #3
0
    def _createSocket(self, protocol_file):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # If the error occured due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            protocol_file = protocol_file.replace("\\", "/")

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError())

        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #4
0
    def _createSocket(self):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        self._socket.listen("127.0.0.1", self._port)
        
        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)
Exemple #5
0
    def _createSocket(self, protocol_file):
        if self._socket:
            Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # Hack for (at least) Linux. If the socket is connecting, the close will deadlock.
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            # If the error occurred due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            # Using sys.getfilesystemencoding() avoid the application crashing if it is
            # installed on a path with non-ascii characters GitHub issue #3907
            protocol_file = protocol_file.replace("\\", "/").encode(sys.getfilesystemencoding())

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError())

        if UM.Application.Application.getInstance().getUseExternalBackend():
            Logger.log("i", "Listening for backend connections on %s", self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #6
0
    def _createSocket(self, protocol_file):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # If the error occured due to parsing, both connections believe that connection is okay. So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            protocol_file = protocol_file.replace("\\", "/")

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError())

        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #7
0
    def _createSocket(self):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        self._socket.listen("127.0.0.1", self._port)
Exemple #8
0
    def _createSocket(self):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        self._socket.listen("127.0.0.1", self._port)
        
        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)
Exemple #9
0
class Backend(PluginObject):
    def __init__(self):
        super().__init__()  # Call super to make multiple inheritance work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._process = None  # type: Optional[subprocess.Popen]
        self._backend_log = []
        self._backend_log_max_lines = None

        self._backend_state = BackendState.NotStarted

        UM.Application.Application.getInstance().callLater(self._createSocket)

    processingProgress = Signal()
    backendStateChange = Signal()
    backendConnected = Signal()
    backendQuit = Signal()

    def setState(self, new_state):
        if new_state != self._backend_state:
            self._backend_state = new_state
            self.backendStateChange.emit(self._backend_state)

    ##   \brief Start the backend / engine.
    #   Runs the engine, this is only called when the socket is fully opened & ready to accept connections
    def startEngine(self):
        command = self.getEngineCommand()
        if not command:
            self._createSocket()
            return

        if not self._backend_log_max_lines:
            self._backend_log = []

        # Double check that the old process is indeed killed.
        if self._process is not None:
            self._process.terminate()
            Logger.log("d",
                       "Engine process is killed. Received return code %s",
                       self._process.wait())

        self._process = self._runEngineProcess(command)
        if self._process is None:  # Failed to start engine.
            return
        Logger.log("i", "Started engine process: %s",
                   self.getEngineCommand()[0])
        self._backendLog(
            bytes("Calling engine with: %s\n" % self.getEngineCommand(),
                  "utf-8"))
        t = threading.Thread(target=self._storeOutputToLogThread,
                             args=(self._process.stdout, ))
        t.daemon = True
        t.start()
        t = threading.Thread(target=self._storeStderrToLogThread,
                             args=(self._process.stderr, ))
        t.daemon = True
        t.start()

    def close(self):
        if self._socket:
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            self._socket.close()

    def _backendLog(self, line):
        try:
            line_str = line.decode("utf-8")
        except UnicodeDecodeError:
            line_str = line.decode(
                "latin1"
            )  #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte.
        Logger.log("d", "[Backend] " + line_str.strip())
        self._backend_log.append(line)

    ##  Get the logging messages of the backend connection.
    #   \returns
    def getLog(self):
        if self._backend_log_max_lines and type(
                self._backend_log_max_lines) == int:
            while len(self._backend_log) >= self._backend_log_max_lines:
                del (self._backend_log[0])
        return self._backend_log

    ##  \brief Convert byte array containing 3 floats per vertex
    def convertBytesToVerticeList(self, data):
        result = []
        if not (len(data) % 12):
            if data is not None:
                for index in range(0, int(len(data) /
                                          12)):  # For each 12 bits (3 floats)
                    result.append(
                        struct.unpack("fff", data[index * 12:index * 12 + 12]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None

    ##  \brief Convert byte array containing 6 floats per vertex
    def convertBytesToVerticeWithNormalsList(self, data):
        result = []
        if not (len(data) % 24):
            if data is not None:
                for index in range(0, int(len(data) /
                                          24)):  # For each 24 bits (6 floats)
                    result.append(
                        struct.unpack("ffffff",
                                      data[index * 24:index * 24 + 24]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None

    ##  Get the command used to start the backend executable
    def getEngineCommand(self):
        return [
            UM.Application.Application.getInstance().getPreferences().getValue(
                "backend/location"), "--port",
            str(self._socket.getPort())
        ]

    ##  Start the (external) backend process.
    def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]:
        kwargs = {}  #type: Dict[str, Any]
        if sys.platform == "win32":
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000  # BELOW_NORMAL_PRIORITY_CLASS
        try:
            return subprocess.Popen(command_list,
                                    stdin=subprocess.DEVNULL,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE,
                                    **kwargs)
        except PermissionError:
            Logger.log(
                "e",
                "Couldn't start back-end: No permission to execute process.")
        except FileNotFoundError:
            Logger.logException("e", "Unable to find backend executable: %s",
                                command_list[0])
        return None

    def _storeOutputToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                self.backendQuit.emit()
                break
            self._backendLog(line)

    def _storeStderrToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                break
            self._backendLog(line)

    ##  Private socket state changed handler.
    def _onSocketStateChanged(self, state):
        self._logSocketState(state)
        if state == Arcus.SocketState.Listening:
            if not UM.Application.Application.getInstance(
            ).getUseExternalBackend():
                self.startEngine()
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()

    ## Debug function created to provide more info for CURA-2127
    def _logSocketState(self, state):
        if state == Arcus.SocketState.Listening:
            Logger.log("d", "Socket state changed to Listening")
        elif state == Arcus.SocketState.Connecting:
            Logger.log("d", "Socket state changed to Connecting")
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Socket state changed to Connected")
        elif state == Arcus.SocketState.Error:
            Logger.log("d", "Socket state changed to Error")
        elif state == Arcus.SocketState.Closing:
            Logger.log("d", "Socket state changed to Closing")
        elif state == Arcus.SocketState.Closed:
            Logger.log("d", "Socket state changed to Closed")

    ##  Private message handler
    def _onMessageReceived(self):
        message = self._socket.takeNextMessage()

        if message.getTypeName() not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s",
                       message.getTypeName())
            return

        self._message_handlers[message.getTypeName()](message)

    ##  Private socket error handler
    def _onSocketError(self, error):
        if error.getErrorCode() == Arcus.ErrorCode.BindFailedError:
            self._port += 1
            Logger.log(
                "d",
                "Socket was unable to bind to port, increasing port number to %s",
                self._port)
        elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError:
            Logger.log("i", "Backend crashed or closed.")
        elif error.getErrorCode() == Arcus.ErrorCode.Debug:
            Logger.log("d", "Socket debug: %s", str(error))
            return
        else:
            Logger.log("w", "Unhandled socket error %s", str(error))

        self._createSocket()

    ##  Creates a socket and attaches listeners.
    def _createSocket(self, protocol_file):
        if self._socket:
            Logger.log("d", "Previous socket existed. Closing that first."
                       )  # temp debug logging
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # Hack for (at least) Linux. If the socket is connecting, the close will deadlock.
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            # If the error occurred due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            # Using sys.getfilesystemencoding() avoid the application crashing if it is
            # installed on a path with non-ascii characters GitHub issue #3907
            protocol_file = protocol_file.replace("\\", "/").encode(
                sys.getfilesystemencoding())

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Uranium protocol messages: %s",
                       self._socket.getLastError())

        if UM.Application.Application.getInstance().getUseExternalBackend():
            Logger.log("i", "Listening for backend connections on %s",
                       self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #10
0
class Backend(PluginObject, SignalEmitter):
    def __init__(self):
        super().__init__() # Call super to make multiple inheritence work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._createSocket()
        self._process = None
        self._backend_log = []

    processingProgress = Signal()
    backendConnected = Signal()

    ##   \brief Start the backend / engine.
    #   Runs the engine, this is only called when the socket is fully opend & ready to accept connections
    def startEngine(self):
        try:
            self._backend_log = []
            self._process = self._runEngineProcess(self.getEngineCommand())
            Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0]))
            self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), 'utf-8'))
            t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout,))
            t.daemon = True
            t.start()
            t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stderr,))
            t.daemon = True
            t.start()
        except FileNotFoundError as e:
            Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0]))

    def close(self):
        if self._socket:
            self._socket.close()
    
    ##  Get the logging messages of the backend connection.
    #   \returns  
    def getLog(self):
        return self._backend_log

    ##  \brief Convert byte array containing 3 floats per vertex
    def convertBytesToVerticeList(self, data):
        result = []
        if not (len(data) % 12):
            if data is not None:
                for index in range(0,int(len(data)/12)): #For each 12 bits (3 floats)
                    result.append(struct.unpack("fff",data[index*12:index*12+12]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  \brief Convert byte array containing 6 floats per vertex
    def convertBytesToVerticeWithNormalsList(self,data):
        result = []
        if not (len(data) % 24):
            if data is not None:
                for index in range(0,int(len(data)/24)): #For each 24 bits (6 floats)
                    result.append(struct.unpack("ffffff",data[index*24:index*24+24]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  Get the command used to start the backend executable 
    def getEngineCommand(self):
        return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket_thread.getPort())]

    ##  Start the (external) backend process.
    def _runEngineProcess(self, command_list):
        kwargs = {}
        if subprocess.mswindows:
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
        return subprocess.Popen(command_list, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)

    def _storeOutputToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                break
            self._backend_log.append(line)

    ##  Private socket state changed handler.
    def _onSocketStateChanged(self, state):
        if state == SignalSocket.ListeningState:
            if not Application.getInstance().getCommandLineOption("external-backend", False):
                self.startEngine()
        elif state == SignalSocket.ConnectedState:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()
    
    ##  Private message handler
    def _onMessageReceived(self):
        message = self._socket.takeNextMessage()

        if type(message) not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s", type(message))
            return

        self._message_handlers[type(message)](message)
    
    ##  Private socket error handler   
    def _onSocketError(self, error):
        if error.errno == 98 or error.errno == 48:# Socked in use error
            self._port += 1
            self._createSocket()
        elif error.errno == 104 or error.errno == 32 or error.errno == 54 or error.errno == 41:
            # 104 is connection reset by peer. 32 is broken pipe. 54 is also connection reset by peer.
            # 41 is specific for MacOSX and happens when closing a socket.
            # All these imply the connection to the backend was broken and we need to restart it.
            Logger.log("i", "Backend crashed or closed. Restarting...")
            self._createSocket()
        elif platform.system() == "Windows":
            if error.winerror == 10048:# Socked in use error
                self._port += 1
                self._createSocket()
            elif error.winerror == 10054:
                Logger.log("i", "Backend crashed or closed. Restarting...")
                self._createSocket()
        else:
            Logger.log("e", str(error))
    
    ##  Creates a socket and attaches listeners.
    def _createSocket(self):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        self._socket.listen("127.0.0.1", self._port)
        
        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)
Exemple #11
0
class Backend(PluginObject):
    def __init__(self):
        super().__init__()  # Call super to make multiple inheritance work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._process = None # type: Optional[subprocess.Popen]
        self._backend_log = []
        self._backend_log_max_lines = None

        self._backend_state = BackendState.NotStarted

        UM.Application.Application.getInstance().callLater(self._createSocket)

    processingProgress = Signal()
    backendStateChange = Signal()
    backendConnected = Signal()
    backendQuit = Signal()

    def setState(self, new_state):
        if new_state != self._backend_state:
            self._backend_state = new_state
            self.backendStateChange.emit(self._backend_state)

    ##   \brief Start the backend / engine.
    #   Runs the engine, this is only called when the socket is fully opened & ready to accept connections
    def startEngine(self):
        command = self.getEngineCommand()
        if not command:
            self._createSocket()
            return

        if not self._backend_log_max_lines:
            self._backend_log = []

        # Double check that the old process is indeed killed.
        if self._process is not None:
            self._process.terminate()
            Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait())

        self._process = self._runEngineProcess(command)
        if self._process is None:  # Failed to start engine.
            return
        Logger.log("i", "Started engine process: %s", self.getEngineCommand()[0])
        self._backendLog(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8"))
        t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,))
        t.daemon = True
        t.start()
        t = threading.Thread(target = self._storeStderrToLogThread, args = (self._process.stderr,))
        t.daemon = True
        t.start()

    def close(self):
        if self._socket:
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            self._socket.close()

    def _backendLog(self, line):
        try:
            line_str = line.decode("utf-8")
        except UnicodeDecodeError:
            line_str = line.decode("latin1") #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte.
        Logger.log("d", "[Backend] " + line_str.strip())
        self._backend_log.append(line)

    ##  Get the logging messages of the backend connection.
    #   \returns  
    def getLog(self):
        if self._backend_log_max_lines and type(self._backend_log_max_lines) == int:
            while len(self._backend_log) >= self._backend_log_max_lines:
                del(self._backend_log[0])
        return self._backend_log
    
    ##  Get the command used to start the backend executable 
    def getEngineCommand(self):
        return [UM.Application.Application.getInstance().getPreferences().getValue("backend/location"), "--port", str(self._socket.getPort())]

    ##  Start the (external) backend process.
    def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]:
        kwargs = {} #type: Dict[str, Any]
        if sys.platform == "win32":
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000  # BELOW_NORMAL_PRIORITY_CLASS
        try:
            return subprocess.Popen(command_list, stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs)
        except PermissionError:
            Logger.log("e", "Couldn't start back-end: No permission to execute process.")
        except FileNotFoundError:
            Logger.logException("e", "Unable to find backend executable: %s", command_list[0])
        except BlockingIOError:
            Logger.log("e", "Couldn't start back-end: Resource is temporarily unavailable")
        return None

    def _storeOutputToLogThread(self, handle):
        while True:
            try:
                line = handle.readline()
            except OSError:
                Logger.logException("w", "Exception handling stdout log from backend.")
                continue
            if line == b"":
                self.backendQuit.emit()
                break
            self._backendLog(line)

    def _storeStderrToLogThread(self, handle):
        while True:
            try:
                line = handle.readline()
            except OSError:
                Logger.logException("w", "Exception handling stderr log from backend.")
                continue
            if line == b"":
                break
            self._backendLog(line)

    ##  Private socket state changed handler.
    def _onSocketStateChanged(self, state):
        self._logSocketState(state)
        if state == Arcus.SocketState.Listening:
            if not UM.Application.Application.getInstance().getUseExternalBackend():
                self.startEngine()
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()

    ## Debug function created to provide more info for CURA-2127
    def _logSocketState(self, state):
        if state == Arcus.SocketState.Listening:
            Logger.log("d", "Socket state changed to Listening")
        elif state == Arcus.SocketState.Connecting:
            Logger.log("d", "Socket state changed to Connecting")
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Socket state changed to Connected")
        elif state == Arcus.SocketState.Error:
            Logger.log("d", "Socket state changed to Error")
        elif state == Arcus.SocketState.Closing:
            Logger.log("d", "Socket state changed to Closing")
        elif state == Arcus.SocketState.Closed:
            Logger.log("d", "Socket state changed to Closed")

    ##  Private message handler
    def _onMessageReceived(self):
        message = self._socket.takeNextMessage()

        if message.getTypeName() not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s", message.getTypeName())
            return

        self._message_handlers[message.getTypeName()](message)
    
    ##  Private socket error handler   
    def _onSocketError(self, error):
        if error.getErrorCode() == Arcus.ErrorCode.BindFailedError:
            self._port += 1
            Logger.log("d", "Socket was unable to bind to port, increasing port number to %s", self._port)
        elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError:
            Logger.log("i", "Backend crashed or closed.")
        elif error.getErrorCode() == Arcus.ErrorCode.Debug:
            Logger.log("d", "Socket debug: %s", str(error))
            return
        else:
            Logger.log("w", "Unhandled socket error %s", str(error))

        self._createSocket()

    ##  Creates a socket and attaches listeners.
    def _createSocket(self, protocol_file):
        if self._socket:
            Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # Hack for (at least) Linux. If the socket is connecting, the close will deadlock.
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            # If the error occurred due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            # Using sys.getfilesystemencoding() avoid the application crashing if it is
            # installed on a path with non-ascii characters GitHub issue #3907
            protocol_file = protocol_file.replace("\\", "/").encode(sys.getfilesystemencoding())

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError())

        if UM.Application.Application.getInstance().getUseExternalBackend():
            Logger.log("i", "Listening for backend connections on %s", self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #12
0
class Backend(PluginObject, SignalEmitter):
    def __init__(self):
        super().__init__() # Call super to make multiple inheritence work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._process = None
        self._backend_log = []

        Application.getInstance().callLater(self._createSocket)

    processingProgress = Signal()
    backendStateChange = Signal()
    backendConnected = Signal()
    backendQuit = Signal()

    ##   \brief Start the backend / engine.
    #   Runs the engine, this is only called when the socket is fully opend & ready to accept connections
    def startEngine(self):
        try:
            command = self.getEngineCommand()
            if not command:
                self._createSocket()
                return

            self._backend_log = []
            self._process = self._runEngineProcess(command)
            Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0]))
            self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8"))
            t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,))
            t.daemon = True
            t.start()
            t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stderr,))
            t.daemon = True
            t.start()
        except FileNotFoundError as e:
            Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0]))

    def close(self):
        if self._socket:
            self._socket.close()
    
    ##  Get the logging messages of the backend connection.
    #   \returns  
    def getLog(self):
        return self._backend_log

    ##  \brief Convert byte array containing 3 floats per vertex
    def convertBytesToVerticeList(self, data):
        result = []
        if not (len(data) % 12):
            if data is not None:
                for index in range(0,int(len(data)/12)): #For each 12 bits (3 floats)
                    result.append(struct.unpack("fff", data[index * 12: index * 12 + 12]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  \brief Convert byte array containing 6 floats per vertex
    def convertBytesToVerticeWithNormalsList(self,data):
        result = []
        if not (len(data) % 24):
            if data is not None:
                for index in range(0,int(len(data)/24)): #For each 24 bits (6 floats)
                    result.append(struct.unpack("ffffff",data[index * 24: index * 24 + 24]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  Get the command used to start the backend executable 
    def getEngineCommand(self):
        return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket.getPort())]

    ##  Start the (external) backend process.
    def _runEngineProcess(self, command_list):
        kwargs = {}
        if sys.platform == "win32":
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
        return subprocess.Popen(command_list, stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs)

    def _storeOutputToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                self.backendQuit.emit()
                break
            self._backend_log.append(line)

    ##  Private socket state changed handler.
    def _onSocketStateChanged(self, state):
        if state == Arcus.SocketState.Listening:
            if not Application.getInstance().getCommandLineOption("external-backend", False):
                self.startEngine()
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()
    
    ##  Private message handler
    def _onMessageReceived(self):
        message = self._socket.takeNextMessage()

        if message.getTypeName() not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s", type(message))
            return

        self._message_handlers[message.getTypeName()](message)
    
    ##  Private socket error handler   
    def _onSocketError(self, error):
        if error.getErrorCode() == Arcus.ErrorCode.BindFailedError:
            self._port += 1
        elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError:
            Logger.log("i", "Backend crashed or closed. Restarting...")
        elif error.getErrorCode() == Arcus.ErrorCode.Debug:
            Logger.log("d", str(error))
            return
        else:
            Logger.log("w", str(error))

        sleep(0.1) #Hack: Withouth a sleep this can deadlock the application spamming error messages.
        self._createSocket()

    
    ##  Creates a socket and attaches listeners.
    def _createSocket(self, protocol_file):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # If the error occured due to parsing, both connections believe that connection is okay. So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            protocol_file = protocol_file.replace("\\", "/")

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError())

        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #13
0
class Backend(PluginObject, SignalEmitter):
    def __init__(self):
        super().__init__() # Call super to make multiple inheritence work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._createSocket()
        self._process = None
        self._backend_log = []

    processingProgress = Signal()
    backendConnected = Signal()

    ##   \brief Start the backend / engine.
    #   Runs the engine, this is only called when the socket is fully opend & ready to accept connections
    def startEngine(self):
        try:
            command = self.getEngineCommand()
            if not command:
                self._createSocket()
                return

            self._backend_log = []
            self._process = self._runEngineProcess(command)
            Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0]))
            self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), 'utf-8'))
            t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout,))
            t.daemon = True
            t.start()
            t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stderr,))
            t.daemon = True
            t.start()
        except FileNotFoundError as e:
            Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0]))

    def close(self):
        if self._socket:
            self._socket.close()
    
    ##  Get the logging messages of the backend connection.
    #   \returns  
    def getLog(self):
        return self._backend_log

    ##  \brief Convert byte array containing 3 floats per vertex
    def convertBytesToVerticeList(self, data):
        result = []
        if not (len(data) % 12):
            if data is not None:
                for index in range(0,int(len(data)/12)): #For each 12 bits (3 floats)
                    result.append(struct.unpack("fff",data[index*12:index*12+12]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  \brief Convert byte array containing 6 floats per vertex
    def convertBytesToVerticeWithNormalsList(self,data):
        result = []
        if not (len(data) % 24):
            if data is not None:
                for index in range(0,int(len(data)/24)): #For each 24 bits (6 floats)
                    result.append(struct.unpack("ffffff",data[index*24:index*24+24]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  Get the command used to start the backend executable 
    def getEngineCommand(self):
        return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket.getPort())]

    ##  Start the (external) backend process.
    def _runEngineProcess(self, command_list):
        kwargs = {}
        if sys.platform == "win32":
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
        return subprocess.Popen(command_list, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)

    def _storeOutputToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                break
            self._backend_log.append(line)

    ##  Private socket state changed handler.
    def _onSocketStateChanged(self, state):
        if state == SignalSocket.ListeningState:
            if not Application.getInstance().getCommandLineOption("external-backend", False):
                self.startEngine()
        elif state == SignalSocket.ConnectedState:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()
    
    ##  Private message handler
    def _onMessageReceived(self):
        message = self._socket.takeNextMessage()

        if type(message) not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s", type(message))
            return

        self._message_handlers[type(message)](message)
    
    ##  Private socket error handler   
    def _onSocketError(self, error):
        try: 
            if error.errno == 98 or error.errno == 48:# Socked in use error
                self._port += 1
            elif error.errno == 104 or error.errno == 32 or error.errno == 54 or error.errno == 41:
                # 104 is connection reset by peer. 32 is broken pipe. 54 is also connection reset by peer.
                # 41 is specific for MacOSX and happens when closing a socket.
                # All these imply the connection to the backend was broken and we need to restart it.
                Logger.log("i", "Backend crashed or closed. Restarting...")
            elif platform.system() == "Windows":
                if error.winerror == 10048:# Socked in use error
                    self._port += 1
                elif error.winerror == 10054:
                    Logger.log("i", "Backend crashed or closed. Restarting...")
            else:
                Logger.log("e", str(error))
        except Exception as e:
            Logger.log("e", "Failed to parse socket error")

        self._createSocket()
    
    ##  Creates a socket and attaches listeners.
    def _createSocket(self):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        self._socket.listen("127.0.0.1", self._port)
        
        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)
Exemple #14
0
class Backend(PluginObject):
    """Base class for any backend communication (separate piece of software).
    It makes use of the Socket class from libArcus for the actual communication bits.
    The message_handlers dict should be filled with string (full name of proto message), function pairs.
    """
    def __init__(self):
        super().__init__()  # Call super to make multiple inheritance work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._process = None  # type: Optional[subprocess.Popen]
        self._backend_log = []
        self._backend_log_max_lines = None

        self._backend_state = BackendState.NotStarted

        UM.Application.Application.getInstance().callLater(self._createSocket)

    processingProgress = Signal()
    backendStateChange = Signal()
    backendConnected = Signal()
    backendQuit = Signal()

    def setState(self, new_state):
        if new_state != self._backend_state:
            self._backend_state = new_state
            self.backendStateChange.emit(self._backend_state)

    def startEngine(self):
        """:brief Start the backend / engine.
        Runs the engine, this is only called when the socket is fully opened & ready to accept connections
        """

        command = self.getEngineCommand()
        if not command:
            self._createSocket()
            return

        if not self._backend_log_max_lines:
            self._backend_log = []

        # Double check that the old process is indeed killed.
        if self._process is not None:
            try:
                self._process.terminate()
            except PermissionError:
                Logger.log("e",
                           "Unable to kill running engine. Access is denied.")
                return
            Logger.log("d",
                       "Engine process is killed. Received return code %s",
                       self._process.wait())

        self._process = self._runEngineProcess(command)
        if self._process is None:  # Failed to start engine.
            return
        Logger.log("i", "Started engine process: %s",
                   self.getEngineCommand()[0])
        self._backendLog(
            bytes("Calling engine with: %s\n" % self.getEngineCommand(),
                  "utf-8"))
        t = threading.Thread(target=self._storeOutputToLogThread,
                             args=(self._process.stdout, ),
                             name="EngineOutputThread")
        t.daemon = True
        t.start()
        t = threading.Thread(target=self._storeStderrToLogThread,
                             args=(self._process.stderr, ),
                             name="EngineErrorThread")
        t.daemon = True
        t.start()

    def close(self):
        if self._socket:
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            self._socket.close()

    def _backendLog(self, line):
        try:
            line_str = line.decode("utf-8")
        except UnicodeDecodeError:
            line_str = line.decode(
                "latin1"
            )  #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte.
        Logger.log("d", "[Backend] " + line_str.strip())
        self._backend_log.append(line)

    def getLog(self):
        """Get the logging messages of the backend connection."""

        if self._backend_log_max_lines and type(
                self._backend_log_max_lines) == int:
            while len(self._backend_log) >= self._backend_log_max_lines:
                del (self._backend_log[0])
        return self._backend_log

    def getEngineCommand(self):
        """Get the command used to start the backend executable """

        return [
            UM.Application.Application.getInstance().getPreferences().getValue(
                "backend/location"), "--port",
            str(self._socket.getPort())
        ]

    def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]:
        """Start the (external) backend process."""

        kwargs = {}  #type: Dict[str, Any]
        if sys.platform == "win32":
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000  # BELOW_NORMAL_PRIORITY_CLASS
        try:
            # STDIN needs to be None because we provide no input, but communicate via a local socket instead. The NUL device sometimes doesn't exist on some computers.
            # STDOUT and STDERR need to be pipes because we'd like to log the output on those channels into the application log.
            return subprocess.Popen(command_list,
                                    stdin=None,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE,
                                    **kwargs)
        except PermissionError:
            Logger.log(
                "e",
                "Couldn't start back-end: No permission to execute process.")
        except FileNotFoundError:
            Logger.logException("e", "Unable to find backend executable: %s",
                                command_list[0])
        except BlockingIOError:
            Logger.log(
                "e",
                "Couldn't start back-end: Resource is temporarily unavailable")
        except OSError as e:
            Logger.log(
                "e",
                "Couldn't start back-end: Operating system is blocking it (antivirus?): {err}"
                .format(err=str(e)))
        return None

    def _storeOutputToLogThread(self, handle):
        while True:
            try:
                line = handle.readline()
            except OSError:
                Logger.logException(
                    "w", "Exception handling stdout log from backend.")
                continue
            if line == b"":
                self.backendQuit.emit()
                break
            self._backendLog(line)

    def _storeStderrToLogThread(self, handle):
        while True:
            try:
                line = handle.readline()
            except OSError:
                Logger.logException(
                    "w", "Exception handling stderr log from backend.")
                continue
            if line == b"":
                break
            self._backendLog(line)

    def _onSocketStateChanged(self, state):
        """Private socket state changed handler."""

        self._logSocketState(state)
        if state == Arcus.SocketState.Listening:
            if not UM.Application.Application.getInstance(
            ).getUseExternalBackend():
                self.startEngine()
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()

    def _logSocketState(self, state):
        """Debug function created to provide more info for CURA-2127"""

        if state == Arcus.SocketState.Listening:
            Logger.log("d", "Socket state changed to Listening")
        elif state == Arcus.SocketState.Connecting:
            Logger.log("d", "Socket state changed to Connecting")
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Socket state changed to Connected")
        elif state == Arcus.SocketState.Error:
            Logger.log("d", "Socket state changed to Error")
        elif state == Arcus.SocketState.Closing:
            Logger.log("d", "Socket state changed to Closing")
        elif state == Arcus.SocketState.Closed:
            Logger.log("d", "Socket state changed to Closed")

    def _onMessageReceived(self):
        """Private message handler"""

        message = self._socket.takeNextMessage()

        if message.getTypeName() not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s",
                       message.getTypeName())
            return

        self._message_handlers[message.getTypeName()](message)

    def _onSocketError(self, error):
        """Private socket error handler"""

        if error.getErrorCode() == Arcus.ErrorCode.BindFailedError:
            self._port += 1
            Logger.log(
                "d",
                "Socket was unable to bind to port, increasing port number to %s",
                self._port)
        elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError:
            Logger.log("i", "Backend crashed or closed.")
        elif error.getErrorCode() == Arcus.ErrorCode.Debug:
            Logger.log("d", "Socket debug: %s", str(error))
            return
        else:
            Logger.log("w", "Unhandled socket error %s", str(error))

        self._createSocket()

    def _createSocket(self, protocol_file):
        """Creates a socket and attaches listeners."""

        if self._socket:
            Logger.log("d", "Previous socket existed. Closing that first."
                       )  # temp debug logging
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # Hack for (at least) Linux. If the socket is connecting, the close will deadlock.
            while self._socket.getState() == Arcus.SocketState.Opening:
                sleep(0.1)
            # If the error occurred due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)

        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            # Using sys.getfilesystemencoding() avoid the application crashing if it is
            # installed on a path with non-ascii characters GitHub issue #3907
            protocol_file = protocol_file.replace("\\", "/").encode(
                sys.getfilesystemencoding())

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Uranium protocol messages: %s",
                       self._socket.getLastError())

        if UM.Application.Application.getInstance().getUseExternalBackend():
            Logger.log("i", "Listening for backend connections on %s",
                       self._port)

        self._socket.listen("127.0.0.1", self._port)
Exemple #15
0
class Backend(PluginObject):
    def __init__(self):
        super().__init__()  # Call super to make multiple inheritance work.
        self._supported_commands = {}

        self._message_handlers = {}

        self._socket = None
        self._port = 49674
        self._process = None
        self._backend_log = []

        Application.getInstance().callLater(self._createSocket)

    processingProgress = Signal()
    backendStateChange = Signal()
    backendConnected = Signal()
    backendQuit = Signal()

    ##   \brief Start the backend / engine.
    #   Runs the engine, this is only called when the socket is fully opened & ready to accept connections
    def startEngine(self):
        try:
            command = self.getEngineCommand()
            if not command:
                self._createSocket()
                return

            self._backend_log = []
            self._process = self._runEngineProcess(command)
            Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0]))
            self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8"))
            t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,))
            t.daemon = True
            t.start()
            t = threading.Thread(target = self._storeStderrToLogThread, args = (self._process.stderr,))
            t.daemon = True
            t.start()
        except FileNotFoundError as e:
            Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0]))

    def close(self):
        if self._socket:
            self._socket.close()
    
    ##  Get the logging messages of the backend connection.
    #   \returns  
    def getLog(self):
        return self._backend_log

    ##  \brief Convert byte array containing 3 floats per vertex
    def convertBytesToVerticeList(self, data):
        result = []
        if not (len(data) % 12):
            if data is not None:
                for index in range(0, int(len(data) / 12)):  # For each 12 bits (3 floats)
                    result.append(struct.unpack("fff", data[index * 12: index * 12 + 12]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  \brief Convert byte array containing 6 floats per vertex
    def convertBytesToVerticeWithNormalsList(self,data):
        result = []
        if not (len(data) % 24):
            if data is not None:
                for index in range(0,int(len(data)/24)):  # For each 24 bits (6 floats)
                    result.append(struct.unpack("ffffff", data[index * 24: index * 24 + 24]))
                return result
        else:
            Logger.log("e", "Data length was incorrect for requested type")
            return None
    
    ##  Get the command used to start the backend executable 
    def getEngineCommand(self):
        return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket.getPort())]

    ##  Start the (external) backend process.
    def _runEngineProcess(self, command_list):
        kwargs = {}
        if sys.platform == "win32":
            su = subprocess.STARTUPINFO()
            su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            su.wShowWindow = subprocess.SW_HIDE
            kwargs["startupinfo"] = su
            kwargs["creationflags"] = 0x00004000  # BELOW_NORMAL_PRIORITY_CLASS
        return subprocess.Popen(command_list, stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs)

    def _storeOutputToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                self.backendQuit.emit()
                break
            self._backend_log.append(line)

    def _storeStderrToLogThread(self, handle):
        while True:
            line = handle.readline()
            if line == b"":
                break
            self._backend_log.append(line)

    ##  Private socket state changed handler.
    def _onSocketStateChanged(self, state):
        if state == Arcus.SocketState.Listening:
            if not Application.getInstance().getCommandLineOption("external-backend", False):
                self.startEngine()
        elif state == Arcus.SocketState.Connected:
            Logger.log("d", "Backend connected on port %s", self._port)
            self.backendConnected.emit()
    
    ##  Private message handler
    def _onMessageReceived(self):
        message = self._socket.takeNextMessage()

        if message.getTypeName() not in self._message_handlers:
            Logger.log("e", "No handler defined for message of type %s", message.getTypeName())
            return

        self._message_handlers[message.getTypeName()](message)
    
    ##  Private socket error handler   
    def _onSocketError(self, error):
        if error.getErrorCode() == Arcus.ErrorCode.BindFailedError:
            self._port += 1
        elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError:
            Logger.log("i", "Backend crashed or closed. Restarting...")
        elif error.getErrorCode() == Arcus.ErrorCode.Debug:
            Logger.log("d", str(error))
            return
        else:
            Logger.log("w", str(error))

        sleep(0.1)  # Hack: Without a sleep this can deadlock the application spamming error messages.
        self._createSocket()

    ##  Creates a socket and attaches listeners.
    def _createSocket(self, protocol_file):
        if self._socket:
            self._socket.stateChanged.disconnect(self._onSocketStateChanged)
            self._socket.messageReceived.disconnect(self._onMessageReceived)
            self._socket.error.disconnect(self._onSocketError)
            # If the error occured due to parsing, both connections believe that connection is okay.
            # So we need to force a close.
            self._socket.close()

        self._socket = SignalSocket()
        self._socket.stateChanged.connect(self._onSocketStateChanged)
        self._socket.messageReceived.connect(self._onMessageReceived)
        self._socket.error.connect(self._onSocketError)
        
        if Platform.isWindows():
            # On Windows, the Protobuf DiskSourceTree does stupid things with paths.
            # So convert to forward slashes here so it finds the proto file properly.
            protocol_file = protocol_file.replace("\\", "/")

        if not self._socket.registerAllMessageTypes(protocol_file):
            Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError())

        if Application.getInstance().getCommandLineOption("external-backend", False):
            Logger.log("i", "Listening for backend connections on %s", self._port)

        self._socket.listen("127.0.0.1", self._port)