BackendPlugin.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. # Copyright (c) 2023 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import subprocess
  4. from typing import Optional, List
  5. from UM.Logger import Logger
  6. from UM.Message import Message
  7. from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender
  8. from UM.PluginObject import PluginObject
  9. from UM.i18n import i18nCatalog
  10. from UM.Platform import Platform
  11. class BackendPlugin(AdditionalSettingDefinitionsAppender, PluginObject):
  12. catalog = i18nCatalog("cura")
  13. def __init__(self) -> None:
  14. super().__init__()
  15. self.__port: int = 0
  16. self._plugin_address: str = "127.0.0.1"
  17. self._plugin_command: Optional[List[str]] = None
  18. self._process = None
  19. self._is_running = False
  20. self._supported_slots: List[int] = []
  21. def getSupportedSlots(self) -> List[int]:
  22. return self._supported_slots
  23. def isRunning(self):
  24. return self._is_running
  25. def setPort(self, port: int) -> None:
  26. self.__port = port
  27. def getPort(self) -> int:
  28. return self.__port
  29. def getAddress(self) -> str:
  30. return self._plugin_address
  31. def _validatePluginCommand(self) -> list[str]:
  32. """
  33. Validate the plugin command and add the port parameter if it is missing.
  34. :return: A list of strings containing the validated plugin command.
  35. """
  36. if not self._plugin_command or "--port" in self._plugin_command:
  37. return self._plugin_command or []
  38. return self._plugin_command + ["--address", self.getAddress(), "--port", str(self.__port)]
  39. def start(self) -> bool:
  40. """
  41. Starts the backend_plugin process.
  42. :return: True if the plugin process started successfully, False otherwise.
  43. """
  44. try:
  45. # STDIN needs to be None because we provide no input, but communicate via a local socket instead.
  46. # The NUL device sometimes doesn't exist on some computers.
  47. Logger.info(f"Starting backend_plugin [{self._plugin_id}] with command: {self._validatePluginCommand()}")
  48. popen_kwargs = {"stdin": None}
  49. if Platform.isWindows():
  50. popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
  51. self._process = subprocess.Popen(self._validatePluginCommand(), **popen_kwargs)
  52. self._is_running = True
  53. return True
  54. except PermissionError:
  55. Logger.log("e", f"Couldn't start EnginePlugin: {self._plugin_id} No permission to execute process.")
  56. self._showMessage(self.catalog.i18nc("@info:plugin_failed",
  57. f"Couldn't start EnginePlugin: {self._plugin_id}\nNo permission to execute process."),
  58. message_type = Message.MessageType.ERROR)
  59. except FileNotFoundError:
  60. Logger.logException("e", f"Unable to find local EnginePlugin server executable for: {self._plugin_id}")
  61. self._showMessage(self.catalog.i18nc("@info:plugin_failed",
  62. f"Unable to find local EnginePlugin server executable for: {self._plugin_id}"),
  63. message_type = Message.MessageType.ERROR)
  64. except BlockingIOError:
  65. Logger.logException("e", f"Couldn't start EnginePlugin: {self._plugin_id} Resource is temporarily unavailable")
  66. self._showMessage(self.catalog.i18nc("@info:plugin_failed",
  67. f"Couldn't start EnginePlugin: {self._plugin_id}\nResource is temporarily unavailable"),
  68. message_type = Message.MessageType.ERROR)
  69. except OSError as e:
  70. Logger.logException("e", f"Couldn't start EnginePlugin {self._plugin_id} Operating system is blocking it (antivirus?)")
  71. self._showMessage(self.catalog.i18nc("@info:plugin_failed",
  72. f"Couldn't start EnginePlugin: {self._plugin_id}\nOperating system is blocking it (antivirus?)"),
  73. message_type = Message.MessageType.ERROR)
  74. return False
  75. def stop(self) -> bool:
  76. if not self._process:
  77. self._is_running = False
  78. return True # Nothing to stop
  79. try:
  80. self._process.terminate()
  81. return_code = self._process.wait()
  82. self._is_running = False
  83. Logger.log("d", f"EnginePlugin: {self._plugin_id} was killed. Received return code {return_code}")
  84. return True
  85. except PermissionError:
  86. Logger.log("e", f"Unable to kill running EnginePlugin: {self._plugin_id} Access is denied.")
  87. self._showMessage(self.catalog.i18nc("@info:plugin_failed",
  88. f"Unable to kill running EnginePlugin: {self._plugin_id}\nAccess is denied."),
  89. message_type = Message.MessageType.ERROR)
  90. return False
  91. def _showMessage(self, message: str, message_type: Message.MessageType = Message.MessageType.ERROR) -> None:
  92. Message(message, title=self.catalog.i18nc("@info:title", "EnginePlugin"), message_type = message_type).show()